Elisp 有注释的情况下,怎么选中括号?

比如,下面的代码:

;; (defun test (x)
;;   (let ((var1 exp1)
;;         (var2 exp2)
;;         (var3 exp2))
;;     body))

鼠标双击 let 左边的那个括号,或者使用 expand-region 好像都不能选中整个 let 表达式?

有什么好办法吗? :thinking:

没用过 expand-region ,就鼠标使用说下吧。

;;(defun test (x)
;;(let ((var1 exp1)
      (var2 exp2)
(var3 exp2))
;;    body))
;;

把上面的代码改成这个样子,然后双击 let 里面的第一个 ( ,是可以选中 let 表达式的变量部分的,就像这样 3 但是在全部注释的情况下,点击这个 ( 会得到 forward-sexp: Scan error: "Unbalanced parentheses" 的报错,这说明它和 forward-sexp 有关系。forward-sexp 应该是处理了注释相关的问题。

使用 C-h k 再原地双击两下可以找到调用的函数:

  <down-mouse-1> at that spot runs the command mouse-drag-region
  <mouse-1> at that spot runs the command mouse-set-point
  <down-mouse-1> (translated from <double-down-mouse-1>) at that spot runs the command mouse-drag-region
  <mouse-1> (translated from <double-mouse-1>) at that spot runs the command mouse-set-point

mouse-drag-region 里面可以找到它调用了哪些函数,主要关注的还是 mouse-drag-track ,它内部使用了 mouse-begin-end 来确定选中区域。这个函数我就不细说了,在单击次数为 2 时,如果鼠标在 () 上,它会使用 forward-sexpbackward-sexp 来找到对应的结束位置。

backward-sexp 直接调用 forward-sexp 。而 forward-sexp 默认使用 scan-sexps 函数来找到匹配括号。它在找不到时(也就对应上面有注释的时候)就会报错。不过我们可以通过 forward-sexp-function 来修改这个函数的行为。但还是算了,直接修改的话影响可能很大。

我想到的方法是给 mouse-begin-end 上个 around advice,当点击 () 时特殊处理一下,其他的交给原函数处理。其实也就是重写 forward-sexp

(defun my-forward-sexp (n)
  ;;n must be 1
  (let ((curr-point (point))
	    (cnt 1))
    (do ((i 1 (+ i 1)))
	    ((or (= (+ curr-point i) (point-max))
	         (= cnt 0))
	     (goto-char (+ curr-point i)))
      (cond
       ((= (char-syntax (char-after (+ curr-point i))) ?\( )
	    (cl-incf cnt))
       ((= (char-syntax (char-after (+ curr-point i))) ?\) )
	    (cl-decf cnt))))))

(defun my-backward-sexp (n)
  ;;n must be 1
  (goto-char (1- (point)))
  (let ((curr-point (point))
	    (cnt 1))
    (do ((i 1 (+ i 1)))
	    ((or (= (- curr-point i) (point-min))
	         (= cnt 0))
	     (goto-char (- curr-point i -1)))
      (cond
       ((= (char-syntax (char-after (- curr-point i))) ?\) )
	     (cl-incf cnt))
       ((= (char-syntax (char-after (- curr-point i))) ?\( )
	     (cl-decf cnt))))))

(defun my-mouse-start-end (fn start end mode)
  (if (and (= mode 1)
	   (= start end)
	   (char-after start)
	   (or (= (char-syntax (char-after start)) ?\( )
	       (= (char-syntax (char-after start)) ?\) )))
           (letf (((symbol-function 'forward-sexp) 'my-forward-sexp)
	              ((symbol-function 'backward-sexp) 'my-backward-sexp))
	          (funcall fn start end mode))
    (funcall fn start end mode)))

(advice-add 'mouse-start-end :around 'my-mouse-start-end)
(advice-remove 'mouse-start-end 'my-mouse-start-end)

随手糊了个,可能准确性不是很好,只考虑了对 mouse-start-end 的配合。加张 gif 说明一下 4

多谢,可惜不会忽略中间的 ;;

原本考虑用块注释,但 Elisp 似乎不支持?

/*
Block
comment
*/

中间的 ;; 我也不知道咋弄,region 操作我不是很熟 :rofl:

Elisp 应该只有 ; 注释,就我所知的话, Scheme 里面有 #| |# 注释,其他就不清楚了。

其实我是想问,有没有插件已经实现了这类功能(能选择注释里面的括号),感觉挺常用的。

没想到你直接写了一个版本。。。

练练手吧,插件不是很清楚

那我们等等有没有现成的解决方案吧,我这个匹配的很粗糙,没有考虑字符串内括号,和 ?\( ?\) 之类的问题

考虑一下大佬的separedit

2 个赞

对,separedit 或者 poporg之类的是正解

1 个赞

定义一个啥也不干的 comment 宏,把代码直接包裹在里面,无需注释符。

1 个赞