我也说说Emacs吧(5) - 基本编辑操作

简介: 剪切、复制、粘贴等基本编辑操作

基本编辑操作

进入编辑模式

标准的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 "<R><x>")
  (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 "<R><x><y>")
  (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 "<R><x><y>")
  (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<x>")
  (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 .
目录
相关文章
|
5月前
|
小程序 Shell Linux
VI编辑器配置代码高亮
Linux下的VI编辑器和VIM编辑器最大的区别就是VIM编辑器有代码高亮显示。 VI编辑器没有代码高亮显示。 但是习惯了代码高亮显示的我,突然看到了一片一个色儿的代码,让我有点不适应。
86 0
|
7月前
|
机器学习/深度学习 开发工具
帅气的代码编辑方式——Vim编辑器
帅气的代码编辑方式——Vim编辑器
|
7月前
|
开发工具
|
9月前
|
Unix Shell Linux
第九章、vim程序编辑器
第九章、vim程序编辑器
49 0
|
11月前
|
Java Unix Linux
文本编辑工具vi的使用
vi 是Linux/Unix上的一个文本编辑器,vim(Vi IMproved)是它的增强版。
|
Linux 程序员 开发工具
LINUX常用操作命令和命令行编辑快捷键
LINUX常用操作命令和命令行编辑快捷键
171 0
|
开发工具 C++
5.8 Vim多窗口编辑模式
在编辑文件时,有时需要参考另一个文件,如果在两个文件之间进行切换则比较麻烦。可以使用 Vim 同时打开两个文件,每个文件分别占用一个窗口。
178 0
5.8 Vim多窗口编辑模式
|
开发工具
编辑神器VIM命令总结(一)
编辑神器VIM命令总结(一)一直想学VIM很久了,可是都没有系统的学习过VIM。这次就系统地总结一下VIM命令,每一行都是本人敲过的命令,感觉都挺实用的。 编辑神器VIM命令总结(二)编辑神器VIM命令总结(三)小白都能看懂的超详细ideavim自定义快捷键讲解 移动1.
582 0
|
开发工具 Perl
教程 | Vim 教程【命令-操作-快捷键】
命令历史以:和/开头的命令都有历史纪录,可以首先键入:或/然后按上下箭头来选择某个历史命令。 启动vim在命令行窗口中输入以下命令即可 vim 直接启动vim vim filename 打开vim并创建名为filename的文件 文件命令打开单个文件 vim file 同时打开多个文件 vim file1 file2 file3 ... 在vim窗口中打开一个新文件 :open file 在新窗口中打开文件 :split file 切换到下一个文件 :bn 切换到上一个文件 :bp 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来。
4543 0