[已解决] Emacs 27, rust-mode, 如何让单引号自动的 autopair, 但是,单引号前面如果是 & 则例外。

我的意思是可以针对awesome-pair.el进行开发。

electric-pair 提供了修改方法,可自行扩展。

在《 自带 HTML+ 模式和 electric-pair-mode 在 JS 区域的一个问题 - #4,来自 twlz0ne 》里讨论过,用我回帖中的方法,添加一条规则即可:

(add-to-list 'electric-pair-inhibit-predicate-mode-chars-alist
             '(rust-mode . ((?' . "&'"))))
3 个赞

smartparens里已经有了这些配置

(require 'smartparens-rust)

你的这个方案似乎不工作诶。

下面是我的完整代码:

(defvar electric-pair-inhibit-predicate-mode-chars-alist
  '((t . nil))
  "A list of major-mode and inhibit chars.

Each element is in the form of (MODE . (CHAR/CHAR-STRING/CHAR-FUNCTION ...)).

MODE
    A mode, or t for all modes.

CHAR
    A character to match the input. for example:

        ?\{

CHAR-STRING
    A pair of character and string, the character to match the input,
    the string for ‘looking-back’. for example:

        (?\{ . \":{\")

CHAR-FUNCTION
    A pair of character and function, the character to match the input,
    the function accept the input character as parameter. for example:

        (?\{ . (lambda (_c)
                 (eq ?: (char-before (1- (point))))))")

(defun electric-pair-inhibit-predicate-function (c)
  (let ((alist
         (append
          (assoc-default major-mode electric-pair-inhibit-predicate-mode-chars-alist)
          (assoc-default t          electric-pair-inhibit-predicate-mode-chars-alist))))
    (or (cl-member c
                   alist
                   :test
                   (lambda (c it)
                     (cond
                      ((characterp it) (equal c it))
                      ((and (consp it) (equal c (car it)))
                       (cond ((stringp   (cdr it)) (looking-back (cdr it) 1))
                             ((functionp (cdr it)) (funcall (cdr it) c)))))))
        (electric-pair-default-inhibit c))))

(with-eval-after-load 'elec-pair
  (setq electric-pair-inhibit-predicate
        #'electric-pair-inhibit-predicate-function))

(add-hook 'rust-mode-hook
          #'(lambda ()
              (add-to-list (make-local-variable 'electric-pair-pairs) '(?' . ?'))
              (add-to-list 'electric-pair-inhibit-predicate-mode-chars-alist
                           '(rust-mode . ((?' . "&'"))))
              ))

smartparens 这个包我一直没用过,很早的时候尝试过,记得当时因为某个小 bug, 然后放弃了。 如果没有其他解决方案,回头我再试试。

smartparens我觉得现在稳定多了, 以前我都只敢自己fork一份改着用, 现在已经可以直接melpa了.

初步判断存在以下问题:

  1. rust-mode.el 有设置自己的 inhibit 函数,抢走了执行权:
(setq-local electric-pair-inhibit-predicate 'rust-electric-pair-inhibit-predicate-wrap)
  1. 不应该设置 electric-pair-pairs,它的优先级高于 inhibit 函数,等于是强制插入配对字符。应该采用 modify-syntax-entry 来定义成对符号。

html-mode 为例:

(with-temp-buffer
  (html-mode)
  (electric-pair-syntax-info ?<))
;; => (40 62 nil nil)
;;      | |   |
;;      | |   '-- 为 nil 表示将执行 inhibit 函数。
;;      | |       如果用 electric-pair-pairs 设置配对符号,
;;      | |       则必返回 t,表示无需后续判断,强制插入配对符号。
;;      | '------ 配对的右侧符号 ?>。
;;      '-------- 40 说明当前插入的是成对符号的左侧。

试试以下配置:

(defun rust/electric-pair-inhibit-predicate (c)
  (let ((al (assoc major-mode electric-pair-inhibit-predicate-mode-chars-alist)))
    (if (assoc c (cdr al))
        (electric-pair-inhibit-predicate-function c)
      (rust-electric-pair-inhibit-predicate-wrap c))))

(with-eval-after-load 'rust-mode
  (modify-syntax-entry ?' "\"" rust-mode-syntax-table)
  (add-to-list 'electric-pair-inhibit-predicate-mode-chars-alist
               '(rust-mode . ((?' . "&'"))))
  (add-hook 'rust-mode-hook
            (lambda ()
              (setq-local electric-pair-inhibit-predicate
                          #'rust/electric-pair-inhibit-predicate))))
2 个赞

多谢,似乎将 (add-to-list (make-local-variable 'electric-pair-pairs) '(?' . ?')) 替换为 (modify-syntax-entry ?' "\"" rust-mode-syntax-table) 就工作了。

下面是完整的代码:

(defvar electric-pair-inhibit-predicate-mode-chars-alist
  '((t . nil))
  "A list of major-mode and inhibit chars.

Each element is in the form of (MODE . (CHAR/CHAR-STRING/CHAR-FUNCTION ...)).

MODE
    A mode, or t for all modes.

CHAR
    A character to match the input. for example:

        ?\{

CHAR-STRING
    A pair of character and string, the character to match the input,
    the string for ‘looking-back’. for example:

        (?\{ . \":{\")

CHAR-FUNCTION
    A pair of character and function, the character to match the input,
    the function accept the input character as parameter. for example:

        (?\{ . (lambda (_c)
                 (eq ?: (char-before (1- (point))))))")

(defun electric-pair-inhibit-predicate-function (c)
  (let ((alist
         (append
          (assoc-default major-mode electric-pair-inhibit-predicate-mode-chars-alist)
          (assoc-default t          electric-pair-inhibit-predicate-mode-chars-alist))))
    (or (cl-member c
                   alist
                   :test
                   (lambda (c it)
                     (cond
                      ((characterp it) (equal c it))
                      ((and (consp it) (equal c (car it)))
                       (cond ((stringp   (cdr it)) (looking-back (cdr it) 1))
                             ((functionp (cdr it)) (funcall (cdr it) c)))))))
        (electric-pair-default-inhibit c))))

(with-eval-after-load 'elec-pair
  (setq electric-pair-inhibit-predicate
        #'electric-pair-inhibit-predicate-function))
		
(add-to-list 'electric-pair-inhibit-predicate-mode-chars-alist
                           '(rust-mode . ((?' . "&'"))))
						   
(modify-syntax-entry ?' "\"" rust-mode-syntax-table)						   

下面的代码必须用 setq, 用 setq-local 无效。

(setq electric-pair-inhibit-predicate
        #'electric-pair-inhibit-predicate-function)

1

(modify-syntax-entry ?' "\"" rust-mode-syntax-table) 在 rust-mode.el 文件加载的时候执行一次就好,没必要放在 rust-mode-hook 里打开每个文件都执行。

2

electric-pair-inhibit-predicate-function 不够,rust-mode 自己的 inhibit 函数不能丢,否则 < 符号的处理不正常。

3

setq-local 要在 rust-mode-hook 才有效,因为 rust-mode 每次启用都重置了 electric-pair-inhibit-predicate 变量。

#12 楼的配置流程大致上应该没问题,至少我这边验证过了。

不懂这个不够是什么意思,< 处理不正常,有办法重现吗?

可以贴一个完整的方案吗? 如果可以的话,请在 [已解决] Emacs 27, rust-mode, 如何让单引号自动的 autopair, 但是,单引号前面如果是 & 则例外。 - #13,来自 zw963 上面修改。

例如,需要区分 i < j<T>

它大致算一个完整方案了,直接拿去用没问题。判定规则上可能会有所遗漏,如果 electric-pair-inhibit-predicate-mode-chars-alist 有添加全局规则的话,需改为:

(defun rust/electric-pair-inhibit-predicate-fn (c)
- (let ((al (assoc major-mode electric-pair-inhibit-predicate-mode-chars-alist))
+ (let ((al (append
+            (assoc-default major-mode electric-pair-inhibit-predicate-mode-chars-alist)
+            (assoc-default t          electric-pair-inhibit-predicate-mode-chars-alist)))

如果没有全局规则就不用改。

在 rust-mode 下面似乎并没有遇到这个问题。

您错会了我的意思,我指的 完整方案,是希望您把所有需要的代码全部贴出来(不用参考我贴的代码),我好采纳为答案,目前这样,每次回复时,写一部分代码我有点糊涂了。

好吧,我这个需求 smartparens 支持得很好了,不需要做额外配置。

确实,以前用的最老的配置,没加config那个。。。也是总感觉不太舒服

smartparens 解决了自己制造的问题:

1. electric-pair

$ emacsq.sh -P rust-mode -M electric-pair-mode --eval \
            "(progn
              (rust-mode)
              (insert \"let str: &\")
              (execute-kbd-macro (string-to-vector \"'\")) ;; 触发补全
              (message (buffer-string)))" -nw --batch

let str: &'

2. electric-pair & smartparens

$ emacsq.sh -P rust-mode,smartparens -M electric-pair-mode,smartparens-global-strict-mode --eval \
            "(progn
              (rust-mode)
              (insert \"let str: &\")
              (execute-kbd-macro (string-to-vector \"'\")) ;; 触发补全
              (message (buffer-string)))" -nw --batch

let str: &''

3. electric-pair & smartparens [+ rust]

$ emacsq.sh -P rust-mode,smartparens -M electric-pair-mode,smartparens-global-strict-mode --eval \
            "(progn
              (rust-mode)
              (require 'smartparens-rust) ;; ++
              (insert \"let str: &\")
              (execute-kbd-macro (string-to-vector \"'\")) ;; 触发补全
              (message (buffer-string)))" -nw --batch

let str: &'
1 个赞

BTW,在Emacs里写Rust的话最好用rustic而不是官方的rust-mode

我刚开始用的是 rustic, 因为 rustic 和 lsp-rust 是一个维护者,后来 … 遇到一些问题,我提了 issue, 讨论挺久的,一直没解决,就用了 rust-mode 了,其实有 lsp, 我感觉上用那个都一样的,rust-mode 更少 bug.

也许是我用的少,还没发现吧。

现在最新版的 rustic 依赖 rust-mode,以前 rustic 是 fork 的 rust-mode, 现在可以将 rustic 理解为 rust-mode 的扩展,和 lsp 集成更方便一些。我用的 eglot + flycheck

哦,这样还行,可以回头再试试。

但使用 rustic 有个小问题,它必须设置一个LSP 前端,默认是 lsp-mode。我改成了 eglot 以后,每次打开 rust 文件就会启动 eglot。这个没法像 rust-mode 那样自由,前端的启动时机是由前端自己来控制的。

不知道是否有办法设置!