Logo
Published on

Vim 編輯基礎:輸入、刪除和複製貼上

Vim 系列文章

  1. Vim 生存指南:模式與移動
  2. Vim 編輯基礎:輸入、刪除和複製貼上 (本篇)
  3. Vim 選取模式:三種 Visual Mode
  4. Vim 語法系統:動詞 + 量詞 + 名詞
  5. Vim 搜尋技巧:快速定位與取代
  6. Vim 效率倍增:重複與自動化
  7. Vim 客製化:設定與 IDE 整合

上一篇我們學會了在 Vim 中移動。但光會移動還不夠,今天要學的是編輯:怎麼輸入文字、怎麼刪除、怎麼複製貼上

先搞懂 Vim 的游標

在一般編輯器裡,游標是一條細線,停在字元之間

h e l l o
 ^           ← 游標在 h 和 e 之間

但 Vim 不一樣。在 Normal Mode 中,游標是一個方塊,停在字元上面

h e l l o
  ^(游標「在」e 上面)

這個設計是有原因的:Vim 的很多指令是「對游標所在的字元做操作」。例如 x 會刪除游標下的字元,r 會取代游標下的字元

理解這點很重要,因為等下學 ia 時,你會發現它們的差別就在於:

  • i 在游標所在字元的前面插入
  • a 在游標所在字元的後面插入

進入 Insert Mode 的方式

在第一篇我們提到 Vim 有不同的模式,其中 Insert Mode 是用來輸入文字的。要怎麼進入 Insert Mode?有很多種方式,每種都有它的用途

基本方式

指令動作記憶
i在游標插入insert
a在游標插入append
I在行首(非空白)插入大寫 = 行
A在行尾插入大寫 = 行
o在下方新增一行open line below
O在上方新增一行大寫 = 上方

i vs a

hello
  ^(游標在 l 上)

按 i 後輸入 X
結果:heXllo    ← 在游標「前」插入

按 a 後輸入 X
結果:helXlo    ← 在游標「後」插入

I vs A

IAia 的「整行版」

    hello world
          ^(游標在 w 上)

I
    hello world
   ^              ← 跳到第一個非空白字元,會在 h 前面,進入 Insert Mode

A
    hello world
               ^   ← 跳到行尾,會在 d 後面,進入 Insert Mode

換句話說:

  • I = ^ + i(跳到行首非空白 + 插入)
  • A = $ + a(跳到行尾 + 插入)

A 超常用,因為「在行尾加東西」是很常見的需求

o vs O

1 current line
          ^(在 l 上)

按 o:在「下方」開新行
1 current line
2
  ^(游標在第 2 行對齊 c 的位置)

O:在「上方」開新行
1
  ^(游標在第 1 行對齊 c 的位置)
2 current line

刪除 (Delete)

刪除操作完成後,你還是留在 Normal Mode,不會進入 Insert Mode。如果你想「刪掉然後馬上打新的字」,請看下面的「修改 (Change)」

基本刪除

指令動作說明
x刪除游標下的字元像 Del 鍵
X刪除游標前的字元像 Backspace
dd刪除整行d 重複兩次 = 整行
D刪除到行尾d$

dd 刪除整行後,游標在哪?

刪除整行後,游標會移到下一行的第一個非空白字元

下一行有縮排

1 function foo() {
2     var a = 1
      ^(游標在第 2 行的 v)
3     return 2
4 }

按 dd 刪除整行

1 function foo() {
2     return 2
      ^游標在下一行的第一個非空白字元(r)
3 }

下一行沒有縮排

1 function foo() {
2     return 1
      ^(游標在第 2 行的 r)
3 }

按 dd 刪除整行

1 function foo() {
2 }
  ^游標在下一行的第一個非空白字元(})

修改 (Change)

c 的作用是:刪除 + 進入 Insert Mode

可以想成:c = d + i

指令動作
cc修改整行
C修改到行尾(同 c$)
s刪除一個字元並進入 Insert(同 cl)
S刪除整行並進入 Insert(同 cc)

cc / S 修改整行後,游標在哪?

ccS 的效果一樣,都是刪除整行內容然後進入 Insert Mode

重點:縮排會保留

1 function foo() {
2     return 1
      ^(游標在第 2 行的 r)
3 }

按 cc 或 S

1 function foo() {
2
      ^(游標會停在原本 r 的位置)
3 }

這個設計很貼心,因為你通常想保留縮排。對比 dd + O(刪除整行再往上開新行),cc 省事多了

沒有縮排的情況

1 function foo() {
2 return 1         ← 這行沒有縮排
  ^(游標在第 2 行的 r)
3 }

按 cc 或 S

1 function foo() {
2                 ← 整行清空,游標在行首
  ^(游標會停在原本 r 的位置)
3 }

取代單一字元:r

r 可以取代游標下的字元,不需要進入 Insert Mode

原文:hello warld
游標:       ^(游標在 a 上)

ro(r 後按 o)
結果:hello world    ← a 被取代成 o,仍在 Normal Mode

超適合用來修正 typo

Undo / Redo

指令動作
uUndo(復原)
Ctrl + rRedo(重做)

注意:Vim 的 undo 是以「一次 Insert Mode」為單位。如果你一直在 Insert Mode 中打字,按一次 u 會把全部都復原

複製貼上 (Yank & Paste)

在 Vim 中,複製叫做 yank(不是 copy),但概念是一樣的

複製 (Yank)

指令動作
yy複製整行
Y複製整行(同 yy)

貼上 (Paste)

指令動作
p貼在游標後面
P貼在游標前面

p vs P 的差異

1 first line
  ^(游標在 f 上)
2 second line

yy(複製第 1),然後

按 p
1 first line
2 first line    ← 貼在「下一行」
  ^(游標在新貼上那行的 f 上)
3 second line

P
1 first line    ← 貼在「上一行」
  ^(游標在新貼上那行的 f 上)
2 first line
3 second line

小技巧:複製整行時,p 貼在下一行,P 貼在上一行,游標都會停在新貼上那行的第一個非空白字元

剪下在哪裡?

Vim 沒有獨立的「剪下」指令。因為 d 刪除的內容會自動存起來,所以 d 就等於剪下

hello world
^

dd(刪除 hello world 一整行)
按 p

hello world ← 剛刪掉的被貼回來了

所以在 Vim 中

  • 剪下一行 = dd
  • 剪下到行尾 = D

連續貼上的陷阱

這是新手一定會踩到的坑

假設你想把某行複製到好幾個地方

1. yy 複製一行
2. 移動到目標位置,按 p 貼上
3. 移動到另一個位置,中間順手 dd 刪了一行
4. 按 p ,結果貼出來的是剛刪的那行!

為什麼? 因為 Vim 的 dy 都會存到同一個地方(預設寄存器)。後面的操作會覆蓋前面的

解法一:先貼再刪

調整操作順序,先把要貼的都貼完,再做刪除

解法二:用指定寄存器

"ayy   → 複製到 a 寄存器
"ap    → 從 a 寄存器貼上

這樣不管中間做什麼操作,a 寄存器的內容都不會被覆蓋

寄存器是進階主題,後續文章會詳細介紹。現在只要知道有這個解法就好

其他實用操作

合併行:J

J 可以把下一行接到當前行後面

1  first line
2  second line

在第一行任何位置按 J
1  first line second line

大小寫轉換

按鍵動作
~切換游標下字元的大小寫
gUU整行轉大寫
guu整行轉小寫
hello world
^

~Hello world(h 變 H,游標移到下一個字元)
連按 ~~~~~HELLO world

縮排

按鍵動作
>>當前行增加縮排
<<當前行減少縮排
function hello() {
console.log("hi");    ← 游標在這行
}

>>
function hello() {
    console.log("hi");    ← 增加一層縮排
}

可以搭配數字使用:3>> 會把當前行和下面兩行一起增加縮排

本篇重點整理

進入 Insert
  i/a → 游標前/  I/A → 行首/行尾
  o/O → 下方/上方新行

刪除
  x → 刪一個字元
  dd → 刪整行
  D → 刪到行尾

修改
  c = d + 進入 Insert Mode
  cc/C/s/S
  r → 取代單一字元(不進 Insert)

複製貼上
  yy → 複製整行
  p/P → 貼在後/  d 就是剪下(刪除的內容會自動存起來)

大小寫
  ~ → 切換大小寫
  gUU/guu → 整行轉大寫/小寫

縮排
  >> → 增加縮排
  << → 減少縮排