做异端中的异端 -- Emacs裸奔之路5: 条件反射式移动

移动命令使用频率非常之高,只要方法多一个小小的弯路,对使用体验影响都很大。

克服移动上的难度,离掌握Emacs就不远了。

在不安装其它包的情况下,Emacs就可以:

  • 以行为单位移动: C-n/C-p
  • 以段落为单位移动:C-Down/C-Up

但能快速移动到某一行,这种方式太低效了,就算是使用C-r/C-s搜索字符,也非常的麻烦。

本文以上下移动命令为例,利用人的本能条件反射,实现高效的移动方式。

基本思想

  • 利用本能反应,即目标在哪就往哪里移动
  • 对不精确的操作再修正

具体步骤是:

  1. 目标在光标上方,就向上移动,反之向下
  2. 移动的幅度为当前位置到边界的一半
  3. 再重复1,2步骤,直到满意为止

整个过程利用二分法定位。

动图演示,当前光标在文件底部,目标位置是NOTE那行注释:

avy之类的第三方包有类似的功能,但思想完全不一样。

它些包基本是为每行生成一些标记,再通过输入标记从而定位到对应的位置。

这些方法你需要观察标记,再输入标记。但读取标记是要经过大脑思考的,并不是人的本能反应,所以使用起来并不流畅。

类比于开车转弯:

当想向右拐弯的时候,向右打方向盘是不用思考的,只是打多打少的问题。如果车辆打过了,再向左打以校正,重复这个过程直到车辆方向正确。

这个过程是非常自然的,人脑不是擅长这种精密操作,有一个八九不离十再修正就可以了。

具体实现

Margin处的颜色与字符是用来预测下两步操作的,选择JK是因为这个思想我先在Vim上实现,JK是Vim的移动方式。

Margin上的标识比Vim中的Sign方便,不像Vim最多两个字符,而且在Bash上也可以正常渲染。

关键代码并不多,大部分是细节优化,比如到达边界自动翻页、跳转到上次位置、跳过折叠的内容等。

  (global-set-key (kbd (concat custom-user-prefix-key "k")) (lambda () (interactive) (custom/bisect-jump-2 t)))
  (global-set-key (kbd (concat custom-user-prefix-key "j")) 'custom/bisect-jump-2)

(defun custom/bisect-jump-2 ( &optional is-up )
  "Bisect jump."
  (interactive)

  (setq old-hl-line-flag nil)
  (if (boundp 'hl-line-mode)
      (setq old-hl-line-flag hl-line-mode)
    )

  (create-all-bisect-jump-marks)
  (set-window-margins nil 2)
  (setq hit-radus 0)

  (setq current-position (count-screen-lines (window-start) (point)))
  (setq top-line 1)
  (setq bottom-line (- (count-screen-lines (window-start) (window-end)) 1))

  (setq done nil)
  (setq old-position (point))
  (setq reach-end nil)
  (setq move-down nil)
  (setq move-up nil)
  (setq is-quick nil)

  (while (not done)

    (setq old-position (point))

    (if reach-end
        (progn
          (setq top-line 1)
          (setq bottom-line (- (count-screen-lines (window-start) (window-end)) 1))          
          (setq reach-end nil)))

    ;; (if (<= (1+ (* 2 hit-radus)) (- bottom-line top-line))
    (if (and (> bottom-line top-line) (not move-down) (not move-up))
        (if is-up
            (progn
              (setq bottom-line (- (- current-position hit-radus) 1) )
              (if is-quick
                  (setq top-line (/ (+ top-line bottom-line) 2))))

            (progn
              (setq top-line (+ current-position hit-radus 1))
              (if is-quick
                  (setq bottom-line (/ (+ top-line bottom-line) 2))))))


    (if (>= top-line bottom-line)
        (progn
          (if is-up            
              (setq bottom-line top-line)
            (setq top-line bottom-line))
          (setq reach-end t)))

    (setq flags (generate-flags (1+ (- bottom-line top-line)) (+ 1 (* 2 hit-radus))))

    (if (and (not move-down) (not move-up))
        (setq current-position (/ (+ top-line bottom-line) 2))

      (progn
        (if move-down
            (setq current-position (1+ current-position))
          (setq current-position (1- current-position))
          )
        )
      )

    (goto-char (window-start))
    (vertical-motion (- current-position 1))

    ;; (global-hl-line-highlight)
    (hl-line-mode)

    (clear-all-bisect-jump-marks)

    ;; render the position
    (setq i 0)
    (while (< i (length flags))
           (setq item (nth i flags))
           (setq line (+ i top-line))
           (setq position (custom/pos-at-screen-line-col line 0))

           (if (or (= 0 (nth 0 item)) (> 2 (length item)))
             (mark-in-margin i position (nth 0 item) 0)
             (progn
               (setq first-element (nth 0 item))
               (setq second-element (nth 1 item))
               (mark-in-margin i position first-element second-element)
               )
             )
           (setq i (1+ i)))

    (if (and (= old-position (point)) reach-end)
        (progn
          (setq top-line 1)
          (setq bottom-line (- (count-screen-lines (window-start) (window-end)) 1))

          (if is-up                      
              (recenter (- bottom-line 2) t)
              (recenter 1 t)
            )

          (setq current-position (/ (+ top-line bottom-line) 2))
          (setq top-line current-position)
          (setq bottom-line current-position)
          )
      )

    (if reach-end
        (message "Reach the finish line, there's no room to jump yet!")
      (message "Bisect jump. Type j/k or M-j/k to jump up or down, ' to jump back and 'q' to quit."))

    (setq move-down nil)
    (setq move-up nil)
    (setq is-quick nil)
    (setq key 0)

    ;; https://stackoverflow.com/questions/884498/how-do-i-intercept-ctrl-g-in-emacs    
    (let ((inhibit-quit t))

      (unless (with-local-quit
                (setq key (read-key "read-key"))
                ;; read-event is affected by message statement, quite weird.
                ;; (setq key (read-event "todo")) 
                t)
        (progn
          (setq key ?q)
          (message "C-g hit")
          (setq quit-flag nil)
          )))

    (cond
     ( (equal key ?j)
      (setq is-up nil))
     ( (equal key ?k)
       (setq is-up t))
     ( (equal key ?n)
       (setq is-quick t)
       (setq is-up nil))
     ( (equal key ?p)
       (setq is-quick t)
       (setq is-up t))          
     ( (equal key ?q)
       (setq done t))
     ( (or (equal key ?') (equal key ?\;) )
       (goto-char old-position)
       (setq done t))
     (t
      (setq done t)

      ;; apply the current key press 
      (setq unread-command-events
        (append (listify-key-sequence (vector key))
            unread-command-events))

      )
     )

    (if done
        (progn
          (clear-all-bisect-jump-marks)
          (message "Quit bisect-jump.")
          ))

    )

  (unless old-hl-line-flag
    (hl-line-mode -1)
    )
  )

其中generate-flags是用于计算预测位置的:

(defun generate-flags (num  width)

  (let
    ((flags '()) 
     (flags-1 '()) 
     (pos1 0)
     (pos2 0)
     (i 0))

    (if (<= num width) 
      (setq flags (make-list num '(0)))
      (progn

        (setq pos1 (/ (- num width) 2))
        (setq pos2 (+ pos1 width))

        ; first part [0, pos1)
        (setq flags (make-list pos1 '(1)))
        (if (> pos1 width) 
          (progn
            (setq flags-1 (generate-flags pos1 width))
            (setq i 0)
            (while (< i pos1)
                   (setf (elt flags i) (append (nth i flags) (nth i flags-1)) )
                   (setq i (1+ i))
                   )
            )
          )

        ; middle part [pos1, pos2)
        (setq flags (append flags (make-list (- pos2 pos1) '(0))))

        ; last part [pos2, num)
        (setq flags (append flags 
                            (make-list (- num pos2) '(2))
                            ))
        (if (> (- num pos2) width) 
          (progn
            (setq flags-1 (generate-flags (- num pos2) width))
            (setq i 0)
            (while (< i (- num pos2))
                   (setf (elt flags (+ pos2 i)) (append (nth (+ pos2 i) flags) (nth i flags-1)) )
                   (setq i (1+ i))
                   )
            )
          )


        )
      )

    flags
    )
)

至此,你不需要安装额外的包就解决了光标移动的效率问题。

相关推荐
万维组态16 小时前
web组态可视化编辑器
前端·物联网·低代码·编辑器·人机交互·iot
Tensorrrrrr17 小时前
【已解决】如何在vscode对项目路径以外的文件打断点
ide·vscode·编辑器
医学影像处理1 天前
解决vscode中的不能安装VS code 服务(XHR Failed)【已解决】
ide·vscode·编辑器
Jam-Young1 天前
PyQt的窗口,按钮,行编辑器,标签
数据库·编辑器·pyqt
veminhe2 天前
Visual Studio code中编写和运行C语言
ide·vscode·编辑器
new出一个对象2 天前
VSCode的json2ts插件的使用
ide·vscode·编辑器
kingbal2 天前
VSCode:代码格式化插件
ide·vscode·编辑器
CCSBRIDGE2 天前
Nano 和 Vim 基础操作教程
linux·编辑器·vim
孙克旭_2 天前
vscode的markdown扩展问题
ide·vscode·编辑器