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

    xiaoxiao2023-12-23  25

    基本编辑操作

    进入编辑模式

    标准的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-appendA绑定evil-append-lineI绑定evil-insert-lineo绑定evil-open-belowO绑定evil-open-aboves绑定evil-substituteS绑定evil-change-whole-lineR绑定的是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-charx无删除光标至行尾kill-lineC-k无传递负参数negative-argumentC--, A--, C-A--无设置文本块开始标记set-mark-commandC-@无交换光标与文本标记exchange-point-and-markC-x C-x无剪切文本块到删除环kill-regionShift-Delete无evil-deleted无复制文本块到删除环kill-ring-saveA-w无evil-yanky无从删除环中弹出,即粘贴yankShift-Insert无evil-paste-afterp无evil-paste-beforeP无从删除环中弹出更早的结果yank-pop无无移至行首第一个非空白符evil-first-non-blank^无匹配下一个可配对的括号evil-jump-item%无再次执行上一次执行的命令evil-repeat.无
    最新回复(0)