写一个VIM简化版本的Emacs的Minor-Mode

创建文件me-mode.el,放在.emacs.d里的elisp目录。

(define-minor-mode me-local-mode
  "simplified vim minor mode"
  :init-value nil
  :global 1
  :lighter " me"
  :keymap me-local-mode-map)

定义minor mode

(defvar me-local-mode-map
  (make-keymap))

定义keymap

(defun me-mode-disable ()
  "Disable my evil mode on special occation"
  (interactive)
  (me-local-mode -1))

定义disable函数

(defun me-mode-enable ()
  "auto enable my evil mode when evil mode"
  (interactive)  
  (if (and (not (bound-and-true-p me-local-mode))
           (/= (point) (line-beginning-position)))
      (backward-char))
  (me-local-mode 1))

定义enable函数,当前行不为空时,回退一个字符

(add-hook 'minibuffer-setup-hook 'me-mode-disable)
(add-hook 'minibuffer-exit-hook 'me-mode-enable)

minibuffer对应处理

(provide 'me-mode)

在文件最后写上provide

(require 'me-mode)
(global-set-key [escape] 'me-mode-enable)

在init.el里require,并绑定ESC

(defun me-dummy-bind ()
  (interactive)
  (message "dummy key"))

定义函数,屏蔽所有不需要的按键

(define-key me-local-mode-map (kbd "t") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "T") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "U") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "X") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "Y") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "z") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "Z") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "DEL") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "RET") 'me-dummy-bind)
(define-key me-local-mode-map (kbd "SPC") 'me-dummy-bind)

所有不需要响应的按键,都屏蔽

(defun me-move-beginning ()
  "move beginning and tab"
  (interactive)
  (move-beginning-of-line nil)
  (indent-for-tab-command))
(define-key me-local-mode-map (kbd "0") 'me-move-beginning)

当按下0时移动到行首,并tab一下

(define-key me-local-mode-map (kbd "<") 'beginning-of-buffer)
(define-key me-local-mode-map (kbd ">") 'end-of-buffer)
(define-key me-local-mode-map (kbd "$") 'move-end-of-line)

< 转到文件头 ,> 转到文件尾,$ 转到行尾

(define-key me-local-mode-map (kbd "u") 'undo)

u 代替C-x u

(define-key me-local-mode-map (kbd "e") 'move-end-of-line)
(define-key me-local-mode-map (kbd "h") 'backward-char)
(define-key me-local-mode-map (kbd "i") 'me-mode-disable)
(define-key me-local-mode-map (kbd "j") 'next-line)
(define-key me-local-mode-map (kbd "k") 'previous-line)
(define-key me-local-mode-map (kbd "l") 'forward-char)

e 与 $ 一样,光标移至行尾,kjhl 上下左右,i 关闭 me-mode

(defvar-local me-pre-keystrokes '()
  "record pre keystrokes to perform next operation")

有一些需要多个按键,比如 dw, dd, cw等等,需要把暂时用不上的放入这个list

(defvar-local me-line-selection-overlay nil
  "the overlay of just line selection")

使用overlay技术,主要为大V命令用的

(defvar-local me-visual-begin-pos nil
  "begin pos of the visual line")

配合大V命令使用,标记行选取的开始,需要计算

(defvar-local me-visual-end-pos nil
  "end pos of the visual line")

配合大V命令使用,标记行选取的结束,需要计算

(defun me-clear-keystrokes ()
  "clear saved keystrokes"
  (setq me-pre-keystrokes '()))

多个按键触发了命令,需要清空保存的按键

(defun me-advice-clear-everything ()
  (me-clear-keystrokes)
  ;;(message "clear keystrokes")
  (if me-line-selection-overlay
      (delete-overlay me-line-selection-overlay))
  (setq me-line-selection-overlay nil)
  (setq me-visual-begin-pos nil)
  (setq me-visual-end-pos nil))

清除所有自定义变量,保存的按键,大V用的overlay,起始结束位置

(advice-add #'keyboard-quit :before #'me-advice-clear-everything)

使用advice,加入C-g的后续函数列表,保证按下C-g,不污染任何后续操作

(advice-add #'next-line :after #'me-advice-next-line)
(advice-add #'previous-line :after #'me-advice-previous-line)

在大V按下的前提下,在上下行移动时,需要重新计算begin-pos和end-pos的位置

(defun me-advice-next-line (&rest r)
  "move next line check visual selection"
  (if me-line-selection-overlay
      (progn
        (me-recalc-visual-pos-next-line)
        (move-overlay me-line-selection-overlay me-visual-begin-pos me-visual-end-pos)
        (overlay-put me-line-selection-overlay 'face 'region))))
(defun me-advice-previous-line (&rest r)
  "move previous line check visual selection"
  (if me-line-selection-overlay
      (progn
        (me-recalc-visual-pos-previous-line)
        (move-overlay me-line-selection-overlay me-visual-begin-pos me-visual-end-pos)
        (overlay-put me-line-selection-overlay 'face 'region))))

在有大Voverlay的前提下,先计算一下位置,在重新标记overlay

(defun me-recalc-visual-pos-next-line ()
  "only under visual line selection"
  ;;(message "%s %s %s" (line-beginning-position) me-visual-begin-pos me-visual-end-pos)
  (if (and (> (line-beginning-position) me-visual-begin-pos)
           (< (line-beginning-position) me-visual-end-pos))
      (setq me-visual-begin-pos (line-beginning-position))
    (setq me-visual-end-pos (line-end-position))))
(defun me-recalc-visual-pos-previous-line ()
  "only under visual line selection"
  (if (and (> (line-end-position) me-visual-begin-pos)
           (< (line-end-position) me-visual-end-pos))
      (setq me-visual-end-pos (line-end-position))
    (setq me-visual-begin-pos (line-beginning-position))))

重新计算位置,分开往上和往下两个函数分开调用

(defun me-make-line-visual-selection ()
  "start line visual selection"
  (interactive)
  (me-advice-clear-everything)
  (me-refresh-visual-pos)
  (if me-line-selection-overlay
      (move-overlay me-line-selection-overlay me-visual-begin-pos me-visual-end-pos)
    (setq me-line-selection-overlay
          (make-overlay me-visual-begin-pos me-visual-end-pos)))
  (overlay-put me-line-selection-overlay 'face 'region))
(define-key me-local-mode-map (kbd "V") 'me-make-line-visual-selection)

绑定到大V上,先清空所有自定义变量,在初始化一下起始和结束位置,之后构造overlay

(defun me-refresh-visual-pos ()
  "update the var's value"
  (if me-visual-begin-pos
      (let ((line-begin-pos-now (line-beginning-position)))
        (setq me-visual-begin-pos (if (< line-begin-pos-now me-visual-begin-pos)
                                   line-begin-pos-now
                                 me-visual-begin-pos)))
    (setq me-visual-begin-pos (line-beginning-position)))
  (if me-visual-end-pos
      (let ((line-end-pos-now (line-end-position)))
        (setq me-visual-end-pos (if (> line-end-pos-now me-visual-end-pos)
                                   line-end-pos-now
                                 me-visual-end-pos)))
    (setq me-visual-end-pos (line-end-position))))

计算overlay的起始和结束位置

(define-key me-local-mode-map (kbd "d") 'me-d-bind)

绑定d按键

(defun me-d-bind ()
  "when press d"
  (interactive)
  (if (null (car me-pre-keystrokes)) ;; no pre keys
      (cond  (mark-active ;; by set-mark-command
             (kill-region (region-beginning) (region-end)))
            (me-line-selection-overlay ;; by big V
             (progn
               (kill-region me-visual-begin-pos me-visual-end-pos)
               (me-advice-clear-everything)))
            (t
             (push 'd me-pre-keystrokes)))
    (if (eq 'd (car me-pre-keystrokes)) ;; double d
        (progn
          (me-trigger-dd)
          (me-advice-clear-everything)))))

如果没有前置按键比如dd这样的,则清除标记区域或者大V标记的overlay区域,要不然就push 到 list中,以备后续使用

如果有前置d按键,那么就触发dd,删除当前行操作,再清除所有自定义变量

(defun me-trigger-dd ()
  "check if can trigger dd operation"
  (let ((empty-line? (eq (line-beginning-position)
                         (line-end-position))))
    (ignore-errors
      (move-beginning-of-line nil)
      (kill-line))
    (if (not empty-line?)
        (delete-char 1))))

删除当前行,如果还剩一个字符,把那个字符也删了

(define-key me-local-mode-map (kbd "w") 'me-w-bind)

绑定w按键

(defun me-w-bind ()
  "when press w"
  (interactive)
  (cond ((null (car me-pre-keystrokes))
         (forward-word))
        ((eq 'd (car me-pre-keystrokes))
         (me-trigger-dw)
         (me-advice-clear-everything))
        ((eq 'c (car me-pre-keystrokes))
         (me-trigger-dw)
         (me-advice-clear-everything)
         (me-mode-disable))))

如果没有前置按键,只是往前移动一个单词。

如果前置按键是d,那么触发dw,删除一个字符,并把所有变量清除

如果前置按键是c,那么触发cw,删除一个字符,清空所有两边,并disable掉me-mode

(defun me-trigger-dw ()
  "kill one word"
  (ignore-errors
    (kill-region (point)
                 (progn
                   (forward-word)
                   (point)))))

删除一个word,并忽略所有错误,防止影响后续函数调用

其他支持命令后续持续添加中,目前暂时够用

(define-key me-local-mode-map [escape] 'me-esc-bind)
(define-key me-local-mode-map (kbd "0") 'move-beginning-of-line)
(define-key me-local-mode-map (kbd "a") 'me-forward-char-insert)
(define-key me-local-mode-map (kbd "A") 'me-line-end-insert)
(define-key me-local-mode-map (kbd "b") 'backward-word)
(define-key me-local-mode-map (kbd "c") 'me-c-bind)
(define-key me-local-mode-map (kbd "d") 'me-d-bind)
(define-key me-local-mode-map (kbd "e") 'move-end-of-line)
(define-key me-local-mode-map (kbd "h") 'backward-char)
(define-key me-local-mode-map (kbd "i") 'me-mode-disable)
(define-key me-local-mode-map (kbd "j") 'next-line)
(define-key me-local-mode-map (kbd "k") 'previous-line)
(define-key me-local-mode-map (kbd "l") 'forward-char)
(define-key me-local-mode-map (kbd "o") 'me-next-line-insert)
(define-key me-local-mode-map (kbd "O") 'me-previous-line-insert)
(define-key me-local-mode-map (kbd "p") 'me-p-bind)
(define-key me-local-mode-map (kbd "P") 'me-upper-p-operation)
(define-key me-local-mode-map (kbd "v") 'me-v-bind)
(define-key me-local-mode-map (kbd "V") 'me-make-line-visual-selection)
(define-key me-local-mode-map (kbd "w") 'me-w-bind)
(define-key me-local-mode-map (kbd "W") 'forward-word)
(define-key me-local-mode-map (kbd "x") 'me-x-bind)
(define-key me-local-mode-map (kbd "u") 'undo)
(define-key me-local-mode-map (kbd "y") 'me-y-bind)
(define-key me-local-mode-map (kbd "<") 'beginning-of-buffer)
(define-key me-local-mode-map (kbd ">") 'end-of-buffer)
(define-key me-local-mode-map (kbd "$") 'move-end-of-line)

当前支持的所有命令

github me-mode