felipeochoa / rjsx-mode

A JSX major mode for Emacs
https://github.com/felipeochoa/rjsx-mode
MIT License
641 stars 32 forks source link

[Not a bug] How could I get rjsx-mode to newline and indent when I'm in between tags? #108

Open mch1992 opened 5 years ago

mch1992 commented 5 years ago

When you press RET between {} it does the following (where | is the cursor):

{|}
{
   [indent-level] |
}

How can I get rjsx-mode to do the same with html/react component tags

<component>|</component>

RET

<component>
   [indent-level] |
</component>
sooqua commented 5 years ago

Seems like I managed to bring this functionality from web-mode in a very hackish way. And I'm using Doom Emacs, but this shouldn't be too hard to modify for vanilla emacs:

(when (featurep! :lang javascript)
  (setq-default js-indent-level 2)
  (when (featurep! :lang web)
    ;; Web mode tag expansion in rjsx
    (after! rjsx-mode
      (defun ~+javascript-rjsx-mode-h ()
        (require 'web-mode)
        (setq web-mode-markup-indent-offset 2)
        (add-hook 'after-change-functions 'web-mode-on-after-change nil t)
        (add-hook 'post-command-hook      'web-mode-on-post-command nil t))
      (setq ~+javascript-rjsx-do-web-mode-indent nil)
      (defun ~+javascript-rjsx-newline-and-indent-a ()
        (when (derived-mode-p 'rjsx-mode)
          (setq ~+javascript-rjsx-do-web-mode-indent t)))
      (defun ~+javascript-rjsx-indent-line-a ()
        (when (and (derived-mode-p 'rjsx-mode)
                   ~+javascript-rjsx-do-web-mode-indent)
          (web-mode-indent-line)
          (setq ~+javascript-rjsx-do-web-mode-indent nil)))
      (advice-add 'newline-and-indent :before #'~+javascript-rjsx-newline-and-indent-a)
      (advice-add 'rjsx-indent-line :before #'~+javascript-rjsx-indent-line-a)
      (add-hook 'rjsx-mode-hook #'~+javascript-rjsx-mode-h))))
felipeochoa commented 5 years ago

Seems like a very heavyweight solution. Here's what electric-pair does in case you want to adapt. (Check out electric-pair-post-self-insert-function)

  1. Use post-self-insert-hook and check for newline (eq last-command-event ?\n)
  2. Check if we are in a region of whitespace between two matchings "pairs". This would be the rjsx-specific bit. You could do a quick regex/syntax check for > preceding and </ following followed by confirmation via the AST
  3. (save-excursion (newline 1 t))
(when (and (if (functionp electric-pair-open-newline-between-pairs)
                      (funcall electric-pair-open-newline-between-pairs)
                    electric-pair-open-newline-between-pairs)
                  (eq last-command-event ?\n)
                  (< (1+ (point-min)) (point) (point-max))
                  (eq (save-excursion
                        (skip-chars-backward "\t\s")
                        (char-before (1- (point))))
                      (matching-paren (char-after))))
         (save-excursion (newline 1 t)))
sooqua commented 5 years ago

For some reason this doesn't work at all. Even (matching-paren (char-after)) is failing right before the <. It only works for (){}. And I've now mapped a lot of refactoring functions from web-mode so I think I'll stick to it for now. Thanks for the idea though.

Maxxen commented 4 years ago

For those that want a quick fix that just works similarly to web-mode

(defun tag-expand ()
  (interactive)
  (if (and
       (looking-at "[ \t]*<")
       (looking-back ">[ \t]*"))
      (progn (newline-and-indent)
             (save-excursion (newline-and-indent))
             (indent-according-to-mode))
    (newline-and-indent)))

(add-hook 'rjsx-mode-hook (lambda () (local-set-key (kbd "RET") 'tag-expand)))