基本编辑操作
进入编辑模式
标准的emacs用户是遇不到这一节的,因为默认就可以编辑。但是spacemacs用户需要先学习一下强大的vi的模式切换功能了。
vi的一个重要特点就是命令特别多,所以一旦学会了效率就非常高。
我们以进入编辑模式为说明一下,大家就清楚了。
有同学说了,切换到编辑模式,只用一条命令,比如i命令,切过去就是了么。从功能上说,是这样的。但是,这样的效率还不够高。
vi中是这样的定义的:
- i:在当前光标之前插入文本
- a: 在当前光标之后插入文本
- A: 在当前行的结尾处插入文本
- I: 在当前行的开始处插入文本
- o: 在光标位置的下一行加一个新行插入文本
- O: 在光标位置的上一行加一个新行插入文本
- s: 删除光标所在位置的字符,再进行插入
- S: 删除光标所在行,再进行插入
- R: 用新的字符替换现有的字符
在evil的实现中,i对应的是evil-insert函数:
(defun evil-insert (count &optional vcount skip-empty-lines)
(interactive
(list (prefix-numeric-value current-prefix-arg)
(and (evil-visual-state-p)
(memq (evil-visual-type) '(line block))
(save-excursion
(let ((m (mark)))
;; go to upper-left corner temporarily so
;; `count-lines' yields accurate results
(evil-visual-rotate 'upper-left)
(prog1 (count-lines evil-visual-beginning evil-visual-end)
(set-mark m)))))
(evil-visual-state-p)))
(if (and (called-interactively-p 'any)
(evil-visual-state-p))
(cond
((eq (evil-visual-type) 'line)
(evil-visual-rotate 'upper-left)
(evil-insert-line count vcount))
((eq (evil-visual-type) 'block)
(let ((column (min (evil-column evil-visual-beginning)
(evil-column evil-visual-end))))
(evil-visual-rotate 'upper-left)
(move-to-column column t)
(evil-insert count vcount skip-empty-lines)))
(t
(evil-visual-rotate 'upper-left)
(evil-insert count vcount skip-empty-lines)))
(setq evil-insert-count count
evil-insert-lines nil
evil-insert-vcount (and vcount
(> vcount 1)
(list (line-number-at-pos)
(current-column)
vcount))
evil-insert-skip-empty-lines skip-empty-lines)
(evil-insert-state 1)))
a绑定的是evil-append
A绑定evil-append-line
I绑定evil-insert-line
o绑定evil-open-below
O绑定evil-open-above
s绑定evil-substitute
S绑定evil-change-whole-line
R绑定的是evil-replace-state
从编辑模式退回普通模式的命令的Esc.
标准emacs下的编辑命令
删除一个字符
Del 删除上一个字符,对应delete-forward-char函数。
C-d (delete-char) 删除光标位置字符,不过这个绑定在spacemacs中不支持。
在spacemacs中的正常模式下,可以使用vi的x命令来删除当前字符,函数是evil-delete-char.
(evil-define-operator evil-delete-char (beg end type register)
:motion evil-forward-char
(interactive "")
(evil-delete beg end type register))
删除一行
下面我们学习emacs中非常有趣的一个功能。
在vi中,删除一行非常容易,一条命令dd就可以了。
但是emacs不会这样的,要想删除一行,需要三个命令C-a C-k C-k,哈哈
C-k绑定的命令是kill-line,它的作用是,删除光标至行尾的所有内容,但是不包括换行符。删除掉这个空行需要再执行一次kill-line.
如果要删除从光标开始到行首的命令的话,我们可以使用kill-line的负命令,就是把负号做为参数传给kill-line. 方法是调用negative-argument函数,它绑定的键特别多,有C--, A--, C-A--,反正跟减号的组合都是它就是了。
(defun negative-argument (arg)
"Begin a negative numeric argument for the next command.
\\[universal-argument] following digits or minus sign ends the argument."
(interactive "P")
(prefix-command-preserve-state)
(setq prefix-arg (cond ((integerp arg) (- arg))
((eq arg '-) nil)
(t '-)))
(universal-argument--mode))
剪切,复制和粘贴
文本区域的选择
要想做剪切和复制,第一步要先做文本区域的选择。
在想要选择的文本区域的头部,使用set-mark-command函数来设置标记:
(defun set-mark-command (arg)
(interactive "P")
(cond ((eq transient-mark-mode 'lambda)
(kill-local-variable 'transient-mark-mode))
((eq (car-safe transient-mark-mode) 'only)
(deactivate-mark)))
(cond
((and (consp arg) (> (prefix-numeric-value arg) 4))
(push-mark-command nil))
((not (eq this-command 'set-mark-command))
(if arg
(pop-to-mark-command)
(push-mark-command t)))
((and set-mark-command-repeat-pop
(eq last-command 'pop-global-mark)
(not arg))
(setq this-command 'pop-global-mark)
(pop-global-mark))
((or (and set-mark-command-repeat-pop
(eq last-command 'pop-to-mark-command))
arg)
(setq this-command 'pop-to-mark-command)
(pop-to-mark-command))
((eq last-command 'set-mark-command)
(if (region-active-p)
(progn
(deactivate-mark)
(message "Mark deactivated"))
(activate-mark)
(message "Mark activated")))
(t
(push-mark-command nil))))
set-mark-command绑定在Ctrl-@和Ctrl-空格上。不管是Windows还是MacOS,Ctrl-空格都已被操作系统征用了,可以用C-@,不过这个键确实是不好用,需要按Ctrl-Shift-2。
设置了标记之后,将光标移至要选择的结尾就可以了。
如果想确认下标记的位置,可以用exchange-point-and-mark函数,快捷键C-x C-x.
剪切
选中文本块之后,就可以将其删除kill-region,然后再到新的位置通过yank命令粘贴出来就好。删除之后,删除的结果会存到删除环中。
在标准emacs下,kill-region绑定在C-w上,但是spacemacs下不支持。可以使用Shift-Delete组合。yank在标准emacs上绑定在C-y上,spacemacs下也不支持,但是可以使用Shift-Insert.
spacemacs是个土洋结合的系统,使用emacs的方式选中文本区域之后,可以使用vi的d命令达到同样的剪切效果,函数是evil-delete。
不选中文本块,只删除一行的话,命令是dd。
(evil-define-operator evil-delete (beg end type register yank-handler)
(interactive "")
(unless register
(let ((text (filter-buffer-substring beg end)))
(unless (string-match-p "\n" text)
;; set the small delete register
(evil-set-register ?- text))))
(let ((evil-was-yanked-without-register nil))
(evil-yank beg end type register yank-handler))
(cond
((eq type 'block)
(evil-apply-on-block #'delete-region beg end nil))
((and (eq type 'line)
(= end (point-max))
(or (= beg end)
(/= (char-before end) ?\n))
(/= beg (point-min))
(= (char-before beg) ?\n))
(delete-region (1- beg) end))
(t
(delete-region beg end)))
;; place cursor on beginning of line
(when (and (called-interactively-p 'any)
(eq type 'line))
(evil-first-non-blank)))
复制
复制也是先选中文本区域,然后使用kill-ring-save函数将其复制到删除环中,最后还是通过yank命令粘贴。它绑定的键是A-w,在spacemacs中仍然有效。
也可以使用vi中的y命令,达到同样的效果,函数是evil-yank. 有没有觉得用y键比A-w键要舒服一些?
(evil-define-operator evil-yank (beg end type register yank-handler)
"Saves the characters in motion into the kill-ring."
:move-point nil
:repeat nil
(interactive "")
(let ((evil-was-yanked-without-register
(and evil-was-yanked-without-register (not register))))
(cond
((and (fboundp 'cua--global-mark-active)
(fboundp 'cua-copy-region-to-global-mark)
(cua--global-mark-active))
(cua-copy-region-to-global-mark beg end))
((eq type 'block)
(evil-yank-rectangle beg end register yank-handler))
((eq type 'line)
(evil-yank-lines beg end register yank-handler))
(t
(evil-yank-characters beg end register yank-handler)))))
如果要复制一行的话,命令是yy.
vi中的粘贴命令是p,函数是evil-paste-after
(evil-define-command evil-paste-after
(count &optional register yank-handler)
"Pastes the latest yanked text behind point.
The return value is the yanked text."
:suppress-operator t
(interactive "P")
(if (evil-visual-state-p)
(evil-visual-paste count register)
(evil-with-undo
(let* ((text (if register
(evil-get-register register)
(current-kill 0)))
(yank-handler (or yank-handler
(when (stringp text)
(car-safe (get-text-property
0 'yank-handler text)))))
(opoint (point)))
(when text
(if (functionp yank-handler)
(let ((evil-paste-count count)
;; for non-interactive use
(this-command #'evil-paste-after))
(insert-for-yank text))
;; no yank-handler, default
(when (vectorp text)
(setq text (evil-vector-to-string text)))
(set-text-properties 0 (length text) nil text)
(unless (eolp) (forward-char))
(push-mark (point) t)
;; TODO: Perhaps it is better to collect a list of all
;; (point . mark) pairs to undo the yanking for COUNT > 1.
;; The reason is that this yanking could very well use
;; `yank-handler'.
(let ((beg (point)))
(dotimes (i (or count 1))
(insert-for-yank text))
(setq evil-last-paste
(list #'evil-paste-after
count
opoint
beg ; beg
(point))) ; end
(evil-set-marker ?\[ beg)
(evil-set-marker ?\] (1- (point)))
(when (evil-normal-state-p)
(evil-move-cursor-back)))))
(when register
(setq evil-last-paste nil))
(and (> (length text) 0) text)))))
p是粘贴到当前光标后,如果要粘贴到当前光标前的话,命令是P,函数是evil-paste-before.
从删除环中获取更早的结果
删除环不只会保存上一次的结果,如果想要更早的结果,可以通过yank-pop函数来获取。
vi移动光标方式的补充
上一讲讲过,可以在正常模式下通过使用0来移动到行首。但是,有时候我们希望跳过行首第一个不是空白符的位置,这时候,就要用"^"命令,函数是evil-first-non-blank.
(evil-define-motion evil-first-non-blank ()
"Move the cursor to the first non-blank character of the current line."
:type exclusive
(evil-narrow-to-line (back-to-indentation)))
vi括号匹配
vi中的%命令,可以匹配跟当前括号配对的另一半括号,函数是evil-jump-item.
vi重复做上一条命令
"."可以执行上一条命令,函数是evil-repeat.
(evil-define-command evil-repeat (count &optional save-point)
"Repeat the last editing command with count replaced by COUNT.
If SAVE-POINT is non-nil, do not move point."
:repeat ignore
:suppress-operator t
(interactive (list current-prefix-arg
(not evil-repeat-move-cursor)))
(cond
((null evil-repeat-ring)
(error "Already executing repeat"))
(save-point
(save-excursion
(evil-repeat count)))
(t
(unwind-protect
(let ((confirm-kill-emacs t)
(kill-buffer-hook
(cons #'(lambda ()
(user-error "Cannot delete buffer in repeat command"))
kill-buffer-hook))
(undo-pointer buffer-undo-list))
(evil-with-single-undo
(setq evil-last-repeat (list (point) count undo-pointer))
(evil-execute-repeat-info-with-count
count (ring-ref evil-repeat-ring 0))))
(evil-normal-state)))))
小结
功能 | 函数 | 键绑定 | leader key |
---|---|---|---|
删除光标处字符 | delete-char | 无 | 无 |
evil-delete-char | x | 无 | |
删除光标至行尾 | kill-line | C-k | 无 |
传递负参数 | negative-argument | C--, A--, C-A-- | 无 |
设置文本块开始标记 | set-mark-command | C-@ | 无 |
交换光标与文本标记 | exchange-point-and-mark | C-x C-x | 无 |
剪切文本块到删除环 | kill-region | Shift-Delete | 无 |
evil-delete | d | 无 | |
复制文本块到删除环 | kill-ring-save | A-w | 无 |
evil-yank | y | 无 | |
从删除环中弹出,即粘贴 | yank | Shift-Insert | 无 |
evil-paste-after | p | 无 | |
evil-paste-before | P | 无 | |
从删除环中弹出更早的结果 | yank-pop | 无 | 无 |
移至行首第一个非空白符 | evil-first-non-blank | ^ | 无 |
匹配下一个可配对的括号 | evil-jump-item | % | 无 |
再次执行上一次执行的命令 | evil-repeat | . | 无 |