做异端中的异端 -- 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
    )
)

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

相关推荐
JobDocLS17 小时前
VScode使用方法
ide·vscode·编辑器
@小博的博客1 天前
Linux的工具第一篇:vim编辑器的使用详解
linux·编辑器·vim
陈橘又青1 天前
开创性的初创企业利用 Amazon SageMaker孵化器释放企业价值
人工智能·网络协议·学习·ai·编辑器
恶猫1 天前
EditPlus v6.1 Build 780 烈火汉化版
windows·编辑器·文本编辑器·editplus
Crkylin1 天前
使用vscode搭建cmake工程
ide·vscode·编辑器
Lovely Ruby1 天前
Cursor 迁移到 Zed 编辑器
java·缓存·编辑器
云声风语1 天前
buuCTF练习题misc大白记一次vim配合xxd使用
linux·编辑器·vim
猫头虎2 天前
GoLand 2025.3 最新变化:值得更新吗?
ide·windows·macos·pycharm·编辑器·intellij-idea·idea
☆七年2 天前
Beyond Compare 过滤干扰项设置_android项目文件过滤设置
编辑器
恶猫2 天前
ImHex 十六进制编辑器 v1.38.0 多语便携版
编辑器·逆向·十六进制·模式匹配·imhex