自带 HTML+ 模式和 electric-pair-mode 在 JS 区域的一个问题

自带的 HTML+ 模式很酷,不知道用了什么 hack 支持 CSS、JS,不只是语法高亮,keymap 、甚至代码补全也支持。目前我遇到了一个问题,我开启了 electric-pair-mode 这个全局模式来输入括号,在 HTML+ 模式下输入 <,会得到 <>,这很对,因为这是 HTML 标签,但是光标移动到 JS 中还是这样就不对了,我已经有 N 次因为它把 for 循环写错,后面多出来的 > 就是 electric-pair-mode 插入的:

for (let i = 0; i < 10; i++>)

不知道如何避免。

awesome-pair会自动识别vue文件的html,css,js不同部分,智能决定什么时候补全括号。

1 个赞

之前不知道在哪看到的避免方式:

  ;; disable <> auto pairing in electric-pair-mode for org-mode
  (add-hook 'org-mode-hook
            '(lambda ()
               (setq-local electric-pair-inhibit-predicate
                           `(lambda (c)
                              (if (char-equal c ?<) t
                                (,electric-pair-inhibit-predicate c))))))

我没找到 HTML+,它在哪个文件?


找到了,正式名称是 mhtml-mode

用了一下发现,它有子模式的概念:

(defconst mhtml--js-submode
  (mhtml--construct-submode 'js-mode
                            :name "JS"
                            :end-tag "</script>"
                            :syntax-table js-mode-syntax-table
                            :propertize #'js-syntax-propertize
                            :keymap js-mode-map))

进入 JS 区域自动切换成 js-mode (可运行 M-: major-mode 观察)。这就好办了,把 < 加入 electric-pair 禁止列表即可:

(add-to-list 'electric-pair-inhibit-predicate-mode-chars-alist
             '(js-mode . (?<)))

有这个变量吗?我这没有,搜索了下 Emacs 源代码也没发现。是不是你自己定制了 electric-pair-inhibit-predicate

哎呀,不好意思。是我自己定义的,很久没改这部分配置,都忘了,完整代码:

;;; pair.el --- Electric Pair -*- lexical-binding: t; -*-

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

(setq electric-pair-inhibit-predicate-mode-chars-alist
      '((example-mode . (?<))
        (t . nil)))

t means for all modes.")

(defun electric-pair-inhibit-predicate-function (c)
  (let ((chars
         (-concat
          (assoc-default major-mode electric-pair-inhibit-predicate-mode-chars-alist)
          (assoc-default t          electric-pair-inhibit-predicate-mode-chars-alist))))
    (or (member c chars)
        (electric-pair-default-inhibit c))))

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

;;; pair.el ends here
1 个赞

我也试着改了下 electric-pair-inhibit-predicate,只修复 MHTML 下 JS 的问题,简单试了下符合预期

(defun chunyang-mhtml-mode-electric-pair-inhibit-predicate (char)
  (or (and (eq major-mode 'js-mode)
           (= char ?<))
      (electric-pair-default-inhibit char)))

(defun chunyang-mhtml-mode-setup ()
  (setq-local electric-pair-inhibit-predicate
              #'chunyang-mhtml-mode-electric-pair-inhibit-predicate))

(add-hook 'mhtml-mode-hook #'chunyang-mhtml-mode-setup)

electric-pair会不会自动补全是syntax table决定的吧,应该改syntax table。另外输入html一定要用emmet-mode的emmet-expand-line/emmet-expand-yas

2 个赞

不懂 mhtml 的 submode 的实现,在单独的 js-mode 下是正常的,< 不会匹配。

被主模式“传染”的吧。

在 mhtml 模式下,文件任何地方执行 (electric-pair-syntax-info ?<) 都会得到:

(40 62 nil nil)

这条 syntax 在 sgml 模式中有定义(mhtml << html << sgml):

(defun sgml-make-syntax-table (specials)
  (let ((table (make-syntax-table text-mode-syntax-table)))
    (modify-syntax-entry ?< "(>" table)
    ;;                       ^^
    ...
    ))

在单独的 js 模式下 (electric-pair-syntax-info ?<) 返回 nil。

1 个赞

升级了一下,支持 3 种规则定义:

  1. 字符
    ?\{                 ;; 禁止输入 { 时候补全
  1. 字符-字符串
    (?\{ . ":{")        ;; 禁止输入 :{ 时候补全,但不禁止其他情况下的 { 补全
  1. 字符-函数
    (?\{ . (lambda (_c) ;; 函数可以做更多的事,以下实现的是 2. 的效果
             (eq ?: (char-before (1- (point))))))

完整代码:

;;; pair.el --- Electric Pair -*- lexical-binding: t; -*-

(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))

;;; pair.el ends here
1 个赞

推荐试一下web-mode

@twlz0ne 我用你的方法实现了 typescript-ts-mode 中的 <> 配对,多谢多谢。

写了一个简单的 wrap,自定义 rules 来同时实现 某个major-mode 的 pair 和 inhibit。

(require 'elec-pair-extra)
(add-hook 'after-init-hook #'elec-pair-extra-setup)

;; set rules for major-mode
(setq elec-pair-extra-rules
      '(;; enable <> auto pair for generics usage in typescript, disable pair
        ;; when ?< following a space
        (typescript-ts-mode :pair ((?\< . ?\>)) :inhibit ((?\< . " <")))
        ;; disable pair <> in HTML+JS submode in mhtml-mode
        (mhtml-mode :pair nil :inhibit ((?\< . (lambda (c)
                                                 (and (eq major-mode 'js-mode)
                                                      (= c ?<))))))))
1 个赞