让AI帮忙对expreg做了一点小改造,各位有想法的自取改造

@casouri 大佬有个包expreg 非常不错,我看到有人提了一个Accept numeric prefix arg for commands? · Issue #4 · casouri/expreg · GitHub 的请求,真是个好主意。于是让AI帮忙做了个小改造,效果还不错,有 flash.nvim 那味了,还不错,于是分享一下。

效果是这样的:

代码如下:

;;; expreg-numeric-selection.el

(defface expreg-number-face
  '((t :foreground "#ffff00" :weight bold))
  "Face for expreg numeric overlays.")

(defvar-local expreg--region-overlays nil
  "List of overlays showing region numbers.")

;; ---------------------------------------------------------------------------
;;  主要交互命令
;; ---------------------------------------------------------------------------
(defun expreg-expand-with-numbers ()
  "Show numbered expansion options and allow direct selection."
  (interactive)
  (let ((regions (expreg--collect-expansion-sequence)))
    (if (null regions)
        (message "No expansion regions found")
      (unwind-protect
          (progn
            (expreg--show-region-numbers regions)
            (let ((choice (expreg--read-region-choice (length regions))))
             (when choice
                (expreg--apply-region-selection choice regions))))
        (expreg--cleanup-region-overlays)))))

;; ---------------------------------------------------------------------------
;;  收集连续扩张区域
;; ---------------------------------------------------------------------------
(defun expreg--collect-expansion-sequence ()
  "Collect expansion regions by repeatedly calling expreg-expand."
  (let ((regions '())
        (original-point (point))
        (original-mark (when (region-active-p) (mark)))
        (max-expansions 9))
    (save-excursion
      (save-restriction
        (goto-char original-point)
        (deactivate-mark)
        (dotimes (i max-expansions)
          (condition-case nil
              (let ((before-beg (if (region-active-p) (region-beginning) (point)))
                    (before-end (if (region-active-p) (region-end) (point))))
                (if (fboundp 'expreg-expand)
                    (call-interactively 'expreg-expand)
                  (error "expreg-expand function not found"))
                (when (region-active-p)
                  (let ((new-beg (region-beginning))
                        (new-end (region-end)))
                    (unless (and (= new-beg before-beg) (= new-end before-end))
                      (let ((new-region (cons new-beg new-end)))
                        (unless (member new-region regions)
                          (push new-region regions)))))))
            (error (cl-return))))))
    (goto-char original-point)
    (if original-mark
        (progn (set-mark original-mark) (activate-mark))
      (deactivate-mark))
    (reverse regions)))

;; ---------------------------------------------------------------------------
;;  显示数字标签
;; ---------------------------------------------------------------------------
(defun expreg--show-region-numbers (regions)
  "Display number labels for each expansion region."
  (setq expreg--region-overlays nil)
  (cl-loop for region in regions
           for i from 1 to (min (length regions) 9)
           do (setq expreg--region-overlays
                    (append (expreg--create-number-overlay (car region) (cdr region) i)
                            expreg--region-overlays))))

;; ---------------------------------------------------------------------------
;;  创建纯数字标签(无括号,无背景)
;; ---------------------------------------------------------------------------
(defun expreg--create-number-overlay (beg end number)
  "Left: N    Right: N    Bright yellow."
  (let ((ov-left  (make-overlay beg beg))
        (ov-right (make-overlay end end))
        (str (number-to-string number)))
    (overlay-put ov-left  'before-string
                 (propertize str 'face 'expreg-number-face 'display '(raise 0.2)))
    (overlay-put ov-right 'after-string
                 (propertize str 'face 'expreg-number-face 'display '(raise 0.2)))
    (list ov-left ov-right)))

;; ---------------------------------------------------------------------------
;;  读取选择
;; ---------------------------------------------------------------------------
(defun expreg--read-region-choice (max-regions)
  (let ((prompt (format "Choose region (1–%d, or q to quit): " (min max-regions 9))))
    (let ((char (read-char prompt)))
      (cond
       ((and (>= char ?1) (<= char ?9))
        (let ((choice (- char ?0)))
          (if (<= choice max-regions) choice
            (progn (message "Invalid choice: %d" choice) nil))))
       ((or (= char ?q) (= char ?\C-g)) (message "Selection cancelled") nil)
       (t (message "Invalid input: %c" char) nil)))))

;; ---------------------------------------------------------------------------
;;  应用选择
;; ---------------------------------------------------------------------------
(defun expreg--apply-region-selection (choice regions)
  (let ((selected-region (nth (1- choice) regions)))
    (when selected-region
      (goto-char (car selected-region))
      (set-mark (cdr selected-region))
      (activate-mark)
      (message "Selected region %d: %d–%d"
               choice (car selected-region) (cdr selected-region)))))

;; ---------------------------------------------------------------------------
;;  清理
;; ---------------------------------------------------------------------------
(defun expreg--cleanup-region-overlays ()
  (when expreg--region-overlays
    (mapc #'delete-overlay expreg--region-overlays)
    (setq expreg--region-overlays nil)))

;; ---------------------------------------------------------------------------
;;  前缀命令
;; ---------------------------------------------------------------------------
(defun expreg-expand-region-interactive (&optional arg)
  (interactive "P")
  (cond
   ((and (numberp arg) (> arg 0))
    (dotimes (_ arg)
      (condition-case nil
          (call-interactively 'expreg-expand)
        (error (message "Cannot expand further") (cl-return)))))
   ((equal arg '(4)) (expreg-expand-with-numbers))
   (t (if (fboundp 'expreg-expand)
          (call-interactively 'expreg-expand)
        (message "expreg-expand function not available")))))

;; ---------------------------------------------------------------------------
;;  键绑定
;; ---------------------------------------------------------------------------
(defun expreg-setup-numeric-bindings ()
  (global-set-key (kbd "M-=") 'expreg-expand-region-interactive)
  (global-set-key (kbd "C-M-=") 'expreg-expand-with-numbers))

;; ---------------------------------------------------------------------------
;;  一次性初始化
;; ---------------------------------------------------------------------------
(defun expreg-numeric-selection-init ()
  (expreg-setup-numeric-bindings)
  (message "Expreg numeric selection initialized"))

(provide 'expreg-numeric-selection)
;;; expreg-numeric-selection.el ends here

5 个赞

或许做成 a~z 会更好, 因为数字离手指太远了..

因为我自己用meow,它就用数字扩展。你可以让AI帮你改一版用字母的。 :joy:

expreg 好,之前用它换掉了 expand-region

由单数字改成字母了,可选范围更大了,离手指也近。

;;; expreg-numeric-selection.el

(defface expreg-letter-face
  '((t :foreground "yellow" :weight bold))
  "Face for single-letter region labels.")

(defvar-local expreg--region-overlays nil
  "List of overlays showing region letters.")

(defconst expreg--letter-keys
  '(?h ?j ?k ?l ?\; ?g ?f ?d ?s ?a        ; home-row 10
    ?y ?u ?i ?o ?p ?r ?t ?v ?b ?n ?m ?e ?w ?q ?c ?x ?z)
  "按人体工学排序的 26 个字母键。")

(defconst expreg--letter-chars
  (mapcar #'char-to-string expreg--letter-keys)
  "对应的字符串列表。")

;; ---------------------------------------------------------------------------
;;  主命令
;; ---------------------------------------------------------------------------
(defun expreg-expand-with-numbers ()
  "Show *single-letter* (a-z) expansion options and allow direct selection."
  (interactive)
  (let ((regions (expreg--collect-expansion-sequence)))
    (if (null regions)
        (message "No expansion regions found")
      (unwind-protect
          (progn
            (expreg--show-region-letters regions)
            (let ((choice (expreg--read-letter-choice (length regions))))
              (when choice
                (expreg--apply-region-selection choice regions))))
        (expreg--cleanup-region-overlays)))))

;; ---------------------------------------------------------------------------
;;  收集扩张序列(上限 26)
;; ---------------------------------------------------------------------------
(defun expreg--collect-expansion-sequence ()
  "Collect up to 26 expansion regions by repeatedly calling expreg-expand."
  (let ((regions '())
        (original-point (point))
        (original-mark (when (region-active-p) (mark)))
        (max-expansions 26))
    (save-excursion
      (save-restriction
        (goto-char original-point)
        (deactivate-mark)
        (dotimes (i max-expansions)
          (condition-case nil
              (let ((before-beg (if (region-active-p) (region-beginning) (point)))
                    (before-end (if (region-active-p) (region-end) (point))))
                (if (fboundp 'expreg-expand)
                    (call-interactively 'expreg-expand)
                  (error "expreg-expand function not found"))
                (when (region-active-p)
                  (let ((new-beg (region-beginning))
                        (new-end (region-end)))
                    (unless (and (= new-beg before-beg) (= new-end before-end))
                      (let ((new-region (cons new-beg new-end)))
                        (unless (member new-region regions)
                          (push new-region regions)))))))
            (error (cl-return))))))
    (goto-char original-point)
    (if original-mark
        (progn (set-mark original-mark) (activate-mark))
      (deactivate-mark))
    (reverse regions)))

;; ---------------------------------------------------------------------------
;;  显示字母标签
;; ---------------------------------------------------------------------------
(defun expreg--show-region-letters (regions)
  (setq expreg--region-overlays nil)
  (cl-loop for region in regions
           for i from 0
           do (setq expreg--region-overlays
                    (append (expreg--create-letter-overlay
                            (car region) (cdr region) i)
                            expreg--region-overlays))))

;; ---------------------------------------------------------------------------
;;  创建单字母 overlay(无括号,无背景)
;; ---------------------------------------------------------------------------
(defun expreg--create-letter-overlay (beg end idx)
  "Left: letter   Right: same letter."
  (let* ((ov-left  (make-overlay beg beg))
         (ov-right (make-overlay end end))
         (char-str (nth idx expreg--letter-chars)))
    (overlay-put ov-left  'before-string
                 (propertize char-str 'face 'expreg-letter-face
                             'display '(raise 0.2)))
    (overlay-put ov-right 'after-string
                 (propertize char-str 'face 'expreg-letter-face
                             'display '(raise 0.2)))
    (list ov-left ov-right)))

;; ---------------------------------------------------------------------------
;;  读字母键(单字符,无需回车)
;; ---------------------------------------------------------------------------
(defun expreg--read-letter-choice (max-regions)
  "Read single letter a-z to select region."
  (let* ((avail (cl-subseq expreg--letter-keys 0 max-regions))
         (prompt (format "Choose region (%s): "
                         (string-join (cl-subseq expreg--letter-chars 0 max-regions) "")))
         (char (read-char prompt)))
    (cond
     ((memq char avail)               ; 命中有效字母
      (+ 1 (cl-position char avail))) ; 返回 1-based 索引
     ((or (= char ?q) (= char ?\C-g))
      (message "Selection cancelled") nil)
     (t (message "Invalid input: %c" char) nil))))

;; ---------------------------------------------------------------------------
;;  应用选择
;; ---------------------------------------------------------------------------
(defun expreg--apply-region-selection (choice regions)
  (let ((selected-region (nth (1- choice) regions)))
    (when selected-region
      (goto-char (car selected-region))
      (set-mark (cdr selected-region))
      (activate-mark)
      (message "Selected region %c: %d–%d"
               (nth (1- choice) expreg--letter-keys)
               (car selected-region) (cdr selected-region)))))

;; ---------------------------------------------------------------------------
;;  清理
;; ---------------------------------------------------------------------------
(defun expreg--cleanup-region-overlays ()
  (when expreg--region-overlays
    (mapc #'delete-overlay expreg--region-overlays)
    (setq expreg--region-overlays nil)))

;; ---------------------------------------------------------------------------
;;  前缀命令(C-u / 数字前缀)
;; ---------------------------------------------------------------------------
(defun expreg-expand-region-interactive (&optional arg)
  (interactive "P")
  (cond
   ((and (numberp arg) (> arg 0))
    (dotimes (_ arg)
      (condition-case nil
          (call-interactively 'expreg-expand)
        (error (message "Cannot expand further") (cl-return)))))
   ((equal arg '(4)) (expreg-expand-with-numbers))
   (t (if (fboundp 'expreg-expand)
          (call-interactively 'expreg-expand)
        (message "expreg-expand function not available")))))

;; ---------------------------------------------------------------------------
;;  键绑定
;; ---------------------------------------------------------------------------
(defun expreg-setup-numeric-bindings ()
  (global-set-key (kbd "M-=") 'expreg-expand-region-interactive)
  (global-set-key (kbd "C-M-=") 'expreg-expand-with-numbers))

;; ---------------------------------------------------------------------------
;;  一次性初始化
;; ---------------------------------------------------------------------------
(defun expreg-numeric-selection-init ()
  (expreg-setup-numeric-bindings)
  (message "Expreg single-letter selection initialized"))

(provide 'expreg-numeric-selection)
;;; expreg-numeric-selection.el ends here
2 个赞

感觉可以提 pr 了, 换一个符合的名称.

这个包不错,收了哈哈

又麻烦AI帮我改了下。各位嫌麻烦可以到 home-row-expreg 复制配置,直接安装就好了。

效果大概是这样的,最快按两次就能选中。

1 个赞

会merge到上游吗?

话说没人用smart-region吗?这是我保留expand-region的最大动力。如果都迁移过来就好了

那你关于类似er/mark-email这样的功能还用吗?expreg那边应该没有。

写了一个pr给expreg,如果接受了,使用起来就更方便了。不过我还是觉得你部分功能可能会缺失,如果你也用不到那些功能,且觉得这边的功能满足,那可以考虑切过来用expreg了。

2 个赞

这个pr合并怕是有点麻烦,我给[email protected]发了邮件,目前还是没回复 。第一次感觉给项目做个提交这么麻烦,所以合并到上游这件事可能要搁置了。

另外分享点小技巧:

原本的home-row-expreg-expand-with-letters命令执行选中后无法退回到原光标位置,所以可以尝试先把光标位置写入寄存器。之后可以再通过寄存器来跳回原位置。

(defun my/home-row-expreg-expand-with-prompt ()
  "扩展前存 point,扩展后提示跳回方法。"
  (interactive)
  (point-to-register ?z)
  (call-interactively #'home-row-expreg-expand-with-letters)
  (message "扩展前光标已保存,(jump-to-register) z 可以跳回原点"))