如何复刻web-mode在标签中回车多插入一行的行为?

在web-mode中,<div>¦</div>经过newline-and-indent会变成

<div>
  ¦
</div>

而不是

<div>
¦</div>

我现在用rjsx-mode编辑react的jsx,希望能复刻这个行为。

不明白web-mode是怎么做的,同样是newline-and-indent,别的mode不会这样,我看这个函数有个ad-Advice-newline-and-indent的advice(spacemacs加的?),似乎跟我的问题无关。

一个简单的实现:

(defun web/newline-and-indent (&optional arg interactive)
  (interactive "*P\np")
  (when (and
         (looking-back ">[\s\t]*" (point-at-bol))
         (not (looking-back "/[^>]*>" (point-at-bol)))
         (looking-at "[\s\t]*</"))
    (newline arg interactive)
    (forward-line -1)
    (end-of-line))
  (newline arg interactive))

光标在 tag 之间:

<!-- before -->

<tag>|</tag>

<!-- after -->

<tag>
  |
</tag>

光标在 tag 之外:

<!-- before -->

<tag></tag>|<tag></tag>

<!-- after -->

<tag></tag>
|<tag></tag>
1 个赞

但是web-mode是用了什么黑魔法只用newline-and-indent就做出了这个效果呢,好奇 :joy:

也许就是那个 advice,你看一下。

或者绑定的不是 newline-and-indent,这是 simple.el 的一个函数:

(defun newline-and-indent ()
  (interactive "*")
  (delete-horizontal-space t)
  (newline nil t)
  (indent-according-to-mode))

只插入了一个换行,除非 web-mode 对 newline / indent-according-to-mode 进行了扩展。

可以单步调试看看到底最后去了哪里,也可以添加一个 after advice,然后 (error “trap”) 把调用栈打出来。

还可能和smartparens有关。如果开了smartparens-mode不妨disable一下再试试

(defvar sp--html-modes '(
                         sgml-mode
                         html-mode
                         rhtml-mode
                         nxhtml-mode
                         nxml-mode
                         web-mode
                         jinja2-mode
                         html-erb-mode
                         js2-jsx-mode
                         rjsx-mode
                         )
  "List of HTML modes.")

喔不过web-mode和rjsx-mode都在支持列表里 如果是sp干的应该都有影响才对

但是sp的:post-handler是可以实现这个效果的

没开smartparens:

我用的doom-emacs 开了sp和yasnippet 没遇到你说的问题(spacemacs issue和sp被禁用) image image

doom里和sp有关的hack应该就这些

23 candidates:
./core/core-editor.el:180:  ;; disable smartparens in evil-mode's replace state (they conflict)
./core/core-editor.el:181:  (add-hook 'evil-replace-state-entry-hook #'turn-off-smartparens-mode)
./core/core-editor.el:182:  (add-hook 'evil-replace-state-exit-hook  #'turn-on-smartparens-mode)
./modules/lang/python/config.el:70:  (define-key python-mode-map (kbd "DEL") nil) ; interferes with smartparens
./modules/lang/web/+html.el:15:  (add-hook 'web-mode-hook #'turn-off-smartparens-mode)
./modules/feature/snippets/config.el:40:  ;; fix an error caused by smartparens interfering with yasnippet bindings
./modules/lang/cc/config.el:102:  ;; Completely disable electric keys because it interferes with smartparens and
./modules/lang/cc/config.el:108:  ;; Smartparens and cc-mode both try to autoclose angle-brackets intelligently.
./modules/lang/cc/config.el:113:  ;; ...and leave it to smartparens

和yasnippt相关的应该只有

  ;; fix an error caused by smartparens interfering with yasnippet bindings
  (advice-add #'yas-expand :before #'sp-remove-active-pair-overlay)

但并不像你说的问题

这个效果跟 newline-and-indent 这个名字正好相符「换行并且缩进」。

C-h f newline-and-indent:

Insert a newline, then indent according to major mode.

Indentation is done using the value of indent-line-function

或许是这个,每个 Buffer 都能自定义独立的 indent-line-function

设置了 post-command-hook 和开关 web-mode-enable-auto-opening

参见函数 web-mode-on-post-command

2 个赞

抄过来了:

但是不能用
;; like web-mode-on-post-command
(defun my/on-post-newline ()
  "Insert an extra line if inside a tag."
  (let (n)
    (when (and (member this-command '(newline electric-newline-and-maybe-indent newline-and-indent))
               (and (not (eobp))
                    (eq (char-after) ?\<)
                    (eq (get-text-property (point) 'tag-type) 'end)
                    (looking-back ">\n[ \t]*" (point-min))
                    (setq n (length (match-string-no-properties 0)))
                    (eq (get-text-property (- (point) n) 'tag-type) 'start)
                    (string= (get-text-property (- (point) n) 'tag-name)
                             (get-text-property (point) 'tag-name))
                    ))
      (newline-and-indent)
      (forward-line -1)
      (indent-according-to-mode)
      )
    ))

不对只有web-mode才有tag-type tag-name这些text-property :sweat:

结合 @twlz0ne 的答案和web-mode里的写法:

;; like web-mode-on-post-command
(defun jjpandari/on-post-newline ()
  "Insert an extra line if inside a tag."
  (let (n)
    (when (and (member this-command '(newline electric-newline-and-maybe-indent newline-and-indent))
               (and (looking-back ">\n[\s\t]*" (point-min))
                    (not (looking-back "/[^>]*>\n[\s\t]*" (point-min)))
                    (looking-at "[\s\t]*</")
                    ))
      (newline-and-indent)
      (forward-line -1)
      (indent-according-to-mode)
      )
    ))

所以还是用我前面那个实现吧,刚刚更新了:

             before                              after
|----------------------------------|---------------------------------
| <tag> | </tag>                   | <tag>
|                                  |   |
|                                  | </tag>
|----------------------------------|---------------------------------
| <tag>|<tag></tag></tag>          | <tag>
|                                  |   |<tag></tag></tag>
|----------------------------------|---------------------------------
| <tag><tag></tag>|</tag>          | <tag><tag></tag>
|                                  | |</tag>
|----------------------------------|---------------------------------
| <tag>                            | <tag>
|   <tag>|</tag></tag>             |   <tag>
|                                  |     |
|                                  |   </tag></tag>
|----------------------------------|---------------------------------
| <tag                             | <tag
|   class="foo"><tag>|</tag></tag> |   class="foo"><tag>
|                                  |   |
|                                  |   </tag></tag>
|----------------------------------|---------------------------------

有点不一样,用这个post-command-hook的话是在

<tag>
|</tag>

时执行的。

是把when的条件部分抄过来就行了是吧(要不你把这个做法也更新上去)?我上面的更新了

另外(add-hook 'post-command-hook 'my/func nil t)加在use-package:config里面对么?(我刚想重启试试,启动报错了)不行,要把它放在rjsx-mode-hook

可以考虑用emmet,然后应该有个hook

之前也在找相应的实现,后来发现emacs自带的 electric-pair-mode 就是最好实现方式。
打开这个mode,需要的功能基本就有了。
但是 electric-pair-mode 进行了一些按键绑定,用得不合适就自己修改下。
我就是吧 electric-pair-mode 绑定的 DEL 键删除了。