分享更细粒度的按单词跳转/删除函数

@manateelazycatdelete-block 启发,编写了下面的函数:

(defun toki/backward-fragment-pos ()
  "Return the position one fragment before point."
  (cond
   ((bobp) (point))
   ((bolp) (1- (point)))
   (t
    (let* ((syntax-move-pos (save-excursion
                              (forward-same-syntax -1)
                              (point)))
           (subword-move-pos (save-excursion
                               (subword-backward)
                               (point))))
      (max syntax-move-pos subword-move-pos (line-beginning-position))))))

(defun toki/forward-fragment-pos ()
  "Return the position one fragment after point."
  (cond
   ((eobp) (point))
   ((eolp) (1+ (point)))
   (t
    (let* ((syntax-move-pos (save-excursion
                              (forward-same-syntax)
                              (point)))
           (subword-move-pos (save-excursion
                               (subword-forward)
                               (point))))
      (min syntax-move-pos subword-move-pos (line-end-position))))))

(defun toki-backward-word ()
  "A finer version of `backward-word'.

If there's *only one* space, '-' or '_' between point and previous word, move
before it, then jump back by a fragment.  A fragment is a continuous region with
the same syntax, like a word, a bunch of whitespaces/punctuations, etc.

This doesn't fly over most punctuations, while `backward-word' does. So I find
this command easier to use."
  (interactive)
  (ignore-errors
      (when (and (member (char-before) '(?\s ?\t ?- ?_))
             (eq (char-syntax (char-before (1- (point)))) ?w))
      (backward-char)))
  (goto-char (toki/backward-fragment-pos)))

(defun toki-forward-word ()
  "A finer version of `forward-word'.

If there's *only one* space, '-' or '_' between point and next word, move after
it, then jump forward by a fragment.  A fragment is a continuous region with the
same syntax, like a word, a bunch of whitespaces/punctuations, etc.

This doesn't fly over most punctuations, while `backward-word' does. So I find
this command easier to use."
  (interactive)
  (ignore-errors
    (when (and (member (char-after) '(?\s ?\t ?- ?_))
               (eq (char-syntax (char-after (1+ (point)))) ?w))
      (forward-char)))
  (goto-char (toki/forward-fragment-pos)))

(defun toki-backward-kill-word ()
  "A finer version of `backward-kill-word'.

Kills the region between current point and the point after calling
`toki-backward-word'. See its documentation for details."
  (interactive)
  (let ((prev-point (point)))
    (toki-backward-word)
    (kill-region (point) prev-point)))

(defun toki-forward-kill-word ()
  "A finer version of `forward-kill-word'.

Kills the region between current point and the point after calling
`toki-forward-word'. See its documentation for details."
  (interactive)
  (let ((prev-point (point)))
    (toki-forward-word)
    (kill-region (point) prev-point)))

依赖 subword,我直接启用了 global-subword-mode

(use-package subword
  :config
  (global-subword-mode))

动机:默认的函数在大多数时候都挺好用,但偶尔目标单词和光标之间有连续空格,标点,\\(倾斜牙签)时,默认的命令会把它们都带走,这点比较烦。所以写了这几个函数,规定只有目标单词和光标之间仅有一个连字符或空格时才能带走。详细请看函数文档。

Update: 增强了行首/行尾的处理

3 个赞

才知道subword-mode,加了两个evil text object,把camelCase改成dinosaurCase这种需求的时候方便多了,真香。(楼主的改进暂时不用,我一般在一个symbol内部用)

;; 这两个命令没自带autoload
(use-package subword
  :ensure nil
  :init
  (general-define-key
   :states '(visual operator)
   "x" 'subword-forward
   "z" 'subword-backward)
  :commands (subword-forward subword-backward))