閱讀604 返回首頁    go 阿裏雲 go 技術社區[雲棲]


我也說說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 .

最後更新:2017-06-05 15:01:38

  上一篇:go  8 個構建容器應用的最佳實踐
  下一篇:go  《JavaScript開發框架權威指南》——2.7 小結