效率至上--一文带你真正走进vim

vim一直是程序员之间比较有争议的一个话题。有人认为她是编辑器之神,有人则认为她古老过时,远远不如IDE,或是以当红小生vscode为代表的图形化文本编辑器。无论爱恨,我们的开发工作,大到远程登录服务器coding,修改config文件,小到git commit message,或多或少总要接触她。

为什么要写这篇blog呢,是因为我发现各大平台充斥的vim教程类blog其实很不友好,大多数是命令的堆砌,很少有对思想的解读。由此造成的结果,很多工程师对vim总是敬畏三分,或者就算部分人可以使用vim,也只是以自己的固有思维,结合vim的命令操作,并没有真正掌握vim的精髓。所以我尝试用自己的方式,试图帮助大家系统地建立起vim的知识系统。

文章主要结构如下:

  • 首先介绍vim编辑器最简单、基本的操作,让你快速入门,在遇到vim时,可以不至于惊慌,从容完成任务。如果想到某些操作,比如常用的复制、粘贴之类,可以到第二部分查找对应高阶操作,循序渐进使用vim
  • 接下来,将会介绍vim一系列高级操作,将我们的效率最大化。注:这部分信息量较大,建议您在阅读部分内容后,快速浏览不熟悉的命令,做到心中有数;随后可前进到第三部分;回过头来,再循序渐进,边学边练。
  • 最后,试图讲述vim思想的精髓,既让我们真正对vim的操作融会贯通,又让我们可以在使用其他IDE/编辑器时应用这些思想,甚至在我们自己设计、实现功能、组件时,都能进行应用。这才算真正掌握了神器vim

模式

vim有三个模式,分别为普通(正常)模式、插入模式以及命令模式。

  • 普通模式:一般用于浏览文件,也包括一些复制、粘贴、除等操作。
  • 插入模式:主要用来输入、修改、删除字符,此时的操作,除了不能用鼠标外,与我们日常在编辑器中操作无异。普通模式下,通过i等命令进入插入模式。
  • 命令模式:用以执行一些输入并执行一些vim或插件提供的指令。在普通模式下通过输入:后,可以发现,屏幕的右下角会出现:,此时便进入了命令模式。本文中使用开头的命令,便可视为输入:进入命令模式后,输入后面的字符,执行命令。

很多人对于vim的第一点疑惑,便来源于此。我们习惯了图形化编辑器下,始终处于插入状态。然而在vim中,大多数情况下,我们会处于正常模式。只有当需要输入字符时,进入插入模式;当需要使用命令时,进入到命令模式。在插入和命令模式下,输入Esc便可返回正常模式。一张图概括如下:

image

注:后文讲解,如无特殊说明,均为普通模式下操作。

基本操作

如果不追求效率,只想完成修改文件的任务,并保存退出,只需要掌握以下三个命令:

  1. 移动:h j k l 最简单的移动,相当于键盘上面的方向键,分别对应左下上右。
  2. 进入插入模式:i
  3. 保存退出:ZZ(注意区分大小写)

流程如下:

  1. 普通模式下,通过h j k l 移动到想要修改的位置
  2. 输入i进入插入模式,此时通过输入(字母、数字、符号),删除(Backspace)等,完成基本修改操作
  3. Esc回到普通模式,ZZ,保存修改并退出。

进阶:命令形式

普通模式下,vim的命令主要分为以下三种:

  1. 动作,用以移动光标,或者定义操作的范围;比如:

    1. h:定义操作范围为一格,单独使用时,向左移动光标一格。
    2. w:定义操作范围为一个单词,移动光标到下一个单词首部。
  2. 操作,这种命令需要在后面接表示操作范围的指令;

    1. d,删除,后接表示一个单词操作范围的w,即dw时,表示删除到当前词尾。
    2. c,修改,后接表示一个单词操作范围的w,即cw时,表示修改当前单词。(编辑器行为表现为,删除到当前词尾,同时进入插入模式)。
  3. 命令,直接执行的命令,其中一部分,在执行命令后,直接进入编辑模式;比如:

    1. D,删除至行末。
    2. I,到行首进入插入模式。
      我们的使用方式主要也是三种:命令、动作、操作+动作。
      此外,在动作类的命令前,加上number为可选项,可实现重复n次的效果:
  4. [number] + h/j/k/l左/下/上/右移动number个字符。比如,'2j',向下移动光标2个字符。

  5. 依旧使用dw来举例,d是删除,w是单词,dw代表删除一个单词,d2w代表删除两个单词。后面的命令,大多都可应用此种形式组合使用,大家多注意,养成这种操作 + [次数] + 范围的思维模式,举一反三,便可发挥最大功效。

移动进阶

单词级别的移动

这里有仅大小写不同的两组命令,两组命令的功能,是相同的:跳转光标到对应位置。但是对应的单位不同,分别为wordstring。具体区别是:

  • string仅以空格分开;
  • word字母数字以外的字符分开。

以这个字符串为例:hello world-hehe111 abcde

  • word有5个,分别为hello world, -, hehe111abcde
  • string有3个,分别为hello world-hehe111abcde

两组命令如下:(跳转光标至)

  • w 下一个单词开头
  • e 当前或下一个单词结尾
  • b 当前或上一个单词开头
  • ge 上一个单词的结尾
  • W 下一个字符串的开头
  • E 当前或下一个字符串结尾
  • B 当前或上一个字符串的开头
  • GE 上一个字符串的结尾

举个例子,当光标位于hehe111的第一个字符h时,前后的单词/字符串信息如下:

前一个 当前 后一个
单词 - hehe111 abcde
字符串 hello world-hehe111 abcde

那么以上各个敲击以上各个命令的结果,便一目了然(加粗字表示命令运行后光标位置):

image

句子,段落级别的移动

  • 0 移动到当前行行首
  • ^ 移动到当前行的第一个非空字符
  • $ 移动到当前行尾
  • ( 跳转到当前或前一个句子的开头
  • ) 跳转到当前或下一个句子的结尾
  • { 跳转到当前或前一个段落的开头
  • } 跳转到当前或下一个段落的结尾
  • 这里段落很容易理解,是以空行分隔开的。句子麻烦些,是按照句号来算的。
  • 记得在这些命令前添加 d试一下效果吧,掌握操作+范围这种命令形式吧。

页面级别的移动

按行移动光标
  • gg 移动到文本第一行行首
  • G 移动到文本末行行首
  • [n] + %:按百分比近似定位到某行,该行位于整个文件的n%
  • [n] + gg/G 跳转到第n行,常用。

要想用好上述几个命令,有两个简单的建议:

  1. 结合命令:ctrl-g。该命令的作用是显示当前行的位置信息(第几行,相对整个文本行数的百分比)。
  2. 在命令模式下输入以下命令,或在~/.vimrc中添加如下代码片段
set nu " 显示行号
set cursorline " 高亮光标所在行</pre>
显示页面内移动光标
  • H:屏幕顶部行首
  • M:屏幕中央行首
  • L:屏幕底部行首
滚动与翻页
  • ctrl-d/u:前进/后退半页
  • ctrl-f/b:前进/后退整页
  • ctrl+e:上滚一行
  • ctrl+y:下滚一行
  • zt:使光标所在位置移动到屏幕的顶部(所有内容做位移)
  • zz:使光标所在位置移动到屏幕的中央(所有内容做位移)
  • zb:使光标所在位置移动到屏幕的底部(所有内容做位移)

匹配

  • f+单个字符:在本行内向右移动到指定字符
  • F+单个字符:在本行内向左移动到指定字符
  • t+单个字符:在本行内向右移动到指定字符的前一个字符
  • T+单个字符:在本行内向左移动到指定字符的前一个字符
  • %: 在“( )”、“[ ]”、“{ }”类符号的首尾间切换
  • *#: 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词(*是下一个,#是上一个)。

Mark

  • m+[a~z] :在当前光标做标记,如ma
  • '+[mark]:光标返回指定标记所在的行,如'a,则光标返回到标记a所在行首
  • "`"+[mark]:光标返回指定标记
  • ctrl+o:跳转回光标前一个位置
  • ctrl+i:跳转回较新的光标位置
  • 建议结合命令模式下如下两个命令,可获得更好体验:
    • :marks:显示全部mark
    • :delmarks [mark]:删除指定mark

编辑进阶

进入插入模式

在不同位置进入插入模式
  • i:在光标前插入字符
  • I:在行首插入字符
  • a:在光标后插入字符
  • A:在行尾插入字符
  • o:在光标下发插入空行
  • O:在光标上方插入空行
使用修改命令进入插入模式
  • c:修改,后面需要接范围
  • c+w:删除光标位置单词,并进入插入模式
  • c+l / s:删除光标位置字符,并进入插入模式
  • c+c / S:删除光标所在行,并进入插入模式
  • c+$ / C:删除光标位置到行尾的字符,并进入插入模式
  • r: 替换当前字符。
  • R:(进入replace模式)持续替换光标所在字符,直到使用ESC退出替换模式。

删除

  • x: 删除当前位置或下一个位置的字符。
  • d:删除,属于动作指令,后面需要加操作类指令。比如如下命令:
    • de:删除到当前单词结尾。
    • dw:删除到下一个单词开始。
    • 注意,此处与de的区别在于,dw会删除两个单词之间的空格。
    • daw:删除一个单词,包含单词的边界(空格)。
    • d0:删除至行首。
    • d$ / D:删除至行尾。
  • da[:删除[ ]整个块,包含符号本身;
  • di[:删除[ ]块,不包含符号本身;
  • da/di + ' " { ( 等,也与接[类似,删除整个区块。唯一需要注意的,"和'仅仅在行内。
  • dt[x]:在本行,删除到[x]。比如,dt"删除到双引号,dtf,删除到字母fd/foo:在全文, 删除到 “foo” 。

剪切

剪切操作其实就是我们之前讲的删除。也就是d。删除的内容,默认会存放到剪切板中。也就相当于进行了剪切。

进阶操作符

从这里大家可以看出,ia的作用比较特殊,代表与区块相关的某种操作。区别就在于,i不包含区块边界符号。a包含。这两个操作符很重要,在后面的复制操作中还会用到。此外还有t/。此外,ia还可以接t,此时t表示一对xml标签。i:区块,不包含边界。a:区块,包含边界。t:"to",本行到哪里。/:接匹配,全文到哪里。

粘贴

  • p:粘贴到光标后,或下一行。
  • P:粘贴到光标前,或前一行。

为什么会有光标前后或上下一行两种情况呢?是因为我们复制或剪切的内容有可能是字符串或者整行:

  • 当复制内容为字符串时,粘贴到光标前/后。
  • 当复制内容为整行时,粘贴到上/下一行。

复制

  • y,复制,属于操作,后面需要接动作来标识复制的范围。比如:
    • yw:复制到当前单词结尾。
    • ye:从当前位置复制到本单词的最后一个字符。
    • y$:复制到当前行尾。
    • yyY:复制当前行。
    • nyy:复制从光标所在行起的n行,注意n在最前面。

0y$: 命令意味着:

  • 0 → 先到行头
  • y → 从这里开始拷贝
  • $ → 拷贝到本行最后一个字符

当然也可以结合我们刚刚介绍的进阶操作符来进行操作:

  • yi":复制两个引号之间
  • yit:复制两个xml标签之间
  • y/[x]:复制到x。

剪切板

vim 有 12 个剪切板,分别是 0、1、2、...、9、a、“、+。:reg:查看各个剪切板里的内容。yp默认使用 "剪切板中的内容。
"[n]y:复制到剪切板n中。"[n]p:粘贴剪切板n中的内容。

查看是否支持系统剪切板:

vim --version | grep "clipboard"

观看输出中,clipboard前面是+还是-。若是-,则说明不支持系统剪切板。
+号剪切板比较特殊,是系统剪切板,用于与系统其他应用互动:

  • "+y,将内容复制到系统剪切板,ctrl+v将其粘贴到其他应用中,比如vs code
  • "+p,将其他应用中复制的内容,粘贴到vim中。

可视模式

v:进入可视模式。V:进入行选择模式。Crtl + v:进入块选择模式。

进入可视模式后,可以通过之前的移动操作,来进行选择。比如:hjkl:前后左右选择。$:选择到行尾。i":选择两个引号之间。

选择后,可以使用

  • d进行删除/剪切,
  • y进行复制。
  • 还可以使用以下很有意思的命令:
    • gU:变大写。
    • gu:变小写。
    • J:把所有的行连接起来(变成一行)。
    • <>:左右缩进。
    • = :自动缩进 。

格式化

=:调整格式化缩进。gg=G:全文代码格式化。

  • gg,到文章开头
  • =,调整格式
  • G,到文章结尾。

自动补全

编辑模式下Ctrl + n/p出现提示,此时会出现补全的选项。按住Ctrl不放,用np来遍历提示选项,到达期待的选项后,无需其他操作,继续输入即可。

撤销

  • u:撤销前一个动作
  • U:撤销当前行的一系列动作
  • CTRL-R:Redo,意思就是我又不想撤销了。

查找替换

  • /: 查找,此时Terminal左下角会出现/,在后面输入想要查找的内容,回车即可。
  • ?:反向查找,同样道理,左下角会出现?
  • /[search]\c:忽略大小写。比如:/test\c,查找test,忽略大小写
  • n: 下一个匹配
  • N: 前一个匹配

命令模式下:

  • s/old/new/:用new替换old
  • s/old/new/g:全局替换
  • set hlsearch高亮搜索结果

宏录制

qa 操作序列 q, @a, @@

  • qa 把你的操作记录在寄存器 a。
  • 于是 @a 会replay被录制的宏。
  • @@ 是一个快捷键用来replay最新录制的宏。

命令

  • :w:保存修改
  • :wq:保存修改并退出
  • ZZ:保存修改并退出
  • q!:不保存修改,强制退出
  • e!:不保存修改,强制重新打开当前文件

大家可以看到,!的作用便在于,强制。除此以外,他还有另一个很强势的功能,就是执行shell命令。具体信息,大家可以详细阅读下一节。

  • .:重复执行前一个命令。这个命令很灵活、实用,建议多多尝试。
  • :help [command]:查看某命令的help此外,在命令行中执行如下命令,便可进入vim的教程。
vimtutor

外部命令

这是vim的一个很神奇的功能,在编辑的时候可以与外部文本互动,甚至执行一些shell命令。

  • :w [file-name]:将当前内容输出到指定文件中
  • :r [file-name]:将另外一个文件的内容输出到当前位置
  • :e filename:vim下打开指定文本
  • ctrl+w, s:水平拆分窗口
  • ctrl+w, v:垂直拆分窗口
  • ctrl+w, ARROW(h,j,k,l或方向键):在窗口间切换光标。
  • ctrl+w, w:在窗口间切换光标。
  • :qa:关闭所有窗口。
  • :saveas:另存为。
  • :n/bn/bp:在打开的多个文件间切换。
  • :![command]:vim下执行某shell命令。
  • 比如,:!ls,便会暂时切换到shell下,输出当前目录的文件名。此时输入回车,便可退回当前vim编辑的文件中。

如果你觉得这种输入命令的方式还不够过瘾,vim还提供了保留当前工作现场,直接进入shell的方式。这种命令一个典型的工作场景是,如我们编辑了一个文件,但是发现无法保存(没有写权限),此时可以先进入到shell下,执行类似chmod u+w [filename],的命令,为当前用户获取该文件的写权限,然后再回到 vim 保存刚刚的修改。 有如下两种方法:

  1. :shell:sh,当退出当前 shell 时(比如exit),就会回到 vim。
  2. ctr-z 进入 shell,fg 退回 vim。

Config

这部分主要是一些vim的config。可以直接命令模式输入,也可以保存到~/.vimrc中,便可每次打开vim自动应用。(其中一些命令是互相冲突的,请自行选择有用的命令)。

syntax on # 开启语法高亮

set nu[mber] # 显示行号
set nonu[mber] # 隐藏行号

set cursorline # 高亮当前行
set ruler # 显示光标位置信息
set noruler # 隐藏光标位置信息

set hlsearch # 高亮匹配
set nohlsearch # 取消高亮匹配
nohlsearch # 临时取消高亮(只取消一次查询的高亮)
set incsearch # 在输入字符串过程中显示匹配点
set nowrapscan # 找到文尾后停止查找
set wrapscan # 恢复为到文尾后自动从头开始
set ic/ignorecase) # 忽略大小写
set noic/noignorecase # 区分大小写</pre>

VIM思想

这部分主要是一些我在使用vim过程中的一些思考和感悟,试图尽力阐述出来。如果大家能有一些思考和收获,说明我的思考是有意义的。如果大家有不同见解,十分欢迎拍砖交流。

Why Normal

  • 为什么vim下,要放弃人们习惯的插入模式,使用命令模式呢?仔细想一想,其实原因很简单:在没有鼠标的年代,人们只能依靠键盘来移动光标,修改文本。
  • 为什么现在有了鼠标,我们还要用正常模式呢?
    1. 工作内容覆盖。我们每个人都认为,工程师的工作,是写代码。然而,其实我们主要的工作,是读,或者说,理解代码。经调查,工程师日常工作中,读:写代码的比例,为10:1(参考《Clean Code》一书)。所以默认的普通模式,主要满足占比重更大的”读“;遇到需要修改的时候,再进入编辑模式。
    2. 大量快捷键。相信每个人,都最起码知道一组快捷键:ctrl-c/v,也就是我们熟悉的copy & paste. 如果你平时注重效率,养成了快捷键的习惯,还可能知道一些诸如ctrl-a/x/s/w等。不知你注意到没有,如刚刚列举的很多快捷键,都由 特殊的命令符+字母构成。因为在此时,键盘上的大多数按键,都是可以输入到文本中的字符。而在vim的正常模式下面,无法直接向文件中输入这些字符,相当于不用按ctrl等特殊的命令符,直接可以把这些按键,用作命令的快捷键。

合理的快捷键

  • vim中的快捷键,布局非常合理。根据使用频繁程度,调整距离手边的距离。比如,最基础的移动操作,放在手边的HJKL。虽然移动将手移动到键盘上的方向键,并未真正的浪费多少时间,但是其对思维的打断,其实非常影响效率。
  • 快捷键的设置,也是非常合理,结合了单词的意义、读音,非常便于记忆。比如:
    • d delete
    • c change
    • w word
    • e end
    • b back
    • I edit
    • f find
    • r replace

精细化,多维度命令

快捷键应有尽有,各个维度移动,都切合使用者思维,几乎可以做到”指哪儿打哪儿“。
比如,移动、删除、复制、等等操作,都可以结合精细化的位置,根据符合人类思维的不同维度,进行操作。
比如,字符,单词,行,文章,屏幕,匹配(位置、文字、符号),以及类似书签的Mark等。

原子、组合命令

vim的大部分快捷键,都是原子操作,并通过与范围结合,排列组合,灵活多变,完成各种强大的功能。这也与unix的主要思想契合:每个命令做好,且只做好同一件事。
与此同时,通过用数字和宏,代替无意义的重复。
此外,对一些常用操作,提供了现成的宏,方便操作。比如,dd,是删除整行,同时也可以直接用D来完成。IA等,也是类似道理。

外部命令

类似栈的思路,可以放下当前操作,保存现场,然后进入另一个操作。当操作完成后,回到当前现场。

思维模式

vim的快捷键,或者说命令,不仅很符合我们的思维,而且还能在很大程度上扩展我们的思维。
拿编辑代码时最多的操作,移动光标来说。原来我们的移动,基本就是通过键盘的方向键,上下左右,或者通过鼠标,移动到想要去的位置。而在vim中,你会发现,光标除了上下左右,还可以移动到词首,词尾,句首,句尾,行首,行尾,页面首部,页面中部,页面尾部,文档首部,文档尾部,文档任意一行,甚至还可以移动到某个指定字母,某个tag,匹配大、中、小括号。度过最初的不适应后,通过刻意练习和日常使用,肌肉形成记忆,便无需刻意回想是用什么命令,而是潜意识完成操作。掌握了这些命令后,当你使用原来的编辑器时,也会去寻找这些快捷键。这就不仅仅是使用vim时候提供效率了,而是通过提高编辑操作的意识、思想,提高了整体的工作效率。

使用vim一段时间后,我在其他工具中进行编辑时,编再也无法忍受,一个一个自读地移动光标。于是也会主动去找单词、行级别移动的快捷键。

  • Mac系统
    • cmd + ←/→ 移动到:当前行首/尾部
    • alt + ←/→ 移动到:当前单词首/尾部
  • iterm:
    • ctrl + f/b 前进/后退一个字符
    • Esc + f/b 前进/后退一个单词
    • ctrl + a/e 行首/行尾
    • ctrl + h/d 删除光标前/后一个字符
    • ctrl + w 删除光标前一个单词
    • ctrl + k/u 删除光标前/后所有内容
    • ctrl + y 粘贴之前删除的内容

后记

这篇文章到这里也就结束了,洋洋洒洒写了这么多,一次读下来就接受,很难;仅仅通过阅读就掌握,更难。想要真正用熟vim,掌握思想,需要后续更多思考、实践。但是相信我,这些付出,一定是值得的。因为它不仅能让你掌握一个开发利器,更能带给你很有价值的思想。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容