Emmet React Mode

My first attempt at an Emacs minor mode. Used to generate JSX/Svelte style components.

Emmet React Mode

Published: Sun, 04 Feb 2024 14:03:54

emmet-react-mode is my first attempt at creating a minor mode in Emacs. It is designed as an extension for emmet-mode though it doesn't rely on it. I find myself frequently writing out components for Svelte and React that have several props and I wanted a convenient way to quickly write them in Emacs.

Expand Component

The minor mode is centered around three interactive functions. The first, emmet-react-expand-component expands a shorthand version of a of a component with props that are separated by periods. If you write the shorthand starting with the component name and then separate the props with periods in the follow form Foo.bar.baz, it will expand to <Foo bar='' baz='' /> and move the cursor between the first single quotes. If you want to use {} or "", you can set emmet-react-prop-pair to any pair of characters you want to wrap with.

  (defun emmet-react-expand-component ()
    (interactive)
    (message (string (region-end)))
    (let ((component)
          (propList))
      (setq propList (emmet-react-get-component))
      (setq component (pop propList))
      (emmet-react-insert-component propList component)))

Wrap Component

emmet-react-wrap-component is useful for situations where you want to wrap Svelte or JSX in a component. You will be prompted to enter the component in the echo area. If you want to add props to the component, you can separate them with periods and they will be automatically separated. For example Foo.bar.baz will be expanded to <Foo bar='' baz''></Foo> and the cursor will be moved between the quotes after baz.

(defun emmet-react-wrap-component ()
  (interactive)
  (let ((str (read-string "Enter component: "))
        (propList)
        (body (buffer-substring (region-end) (region-beginning)))
        (component)
        (propPos))
    (setq propList (split-string str "\\."))
    (setq component (pop propList))
    (delete-region (region-end) (region-beginning))
    (insert (format "<%s" component))
    (js-indent-line)
    (while propList
      (insert (format " %s=%s"
                      (pop propList)
                      emmet-react-prop-pair))
      (if (not propPos)
            (setq propPos (- (point) 1))))
    (insert ">\n")
    (insert body)
    (insert (format "</%s>\n" component))
    (if emmet-react-auto-indent
        (web-mode-buffer-indent))
    (cond ((eq evil-state 'visual) (evil-insert-state)))
    (if propPos
        (goto-char propPos))))

Expand Wrap Component

If you want to generate a wrapping style JSX component with props you can use emmet-react-expand-wrap-component. This function is not used to wrap highlighted text, it simply generates the component but with an opening and closing tag. For example Foo.bar.baz will expand to <Foo bar='' baz=''></Foo>.

  (defun emmet-react-expand-wrap-component ()
    (interactive)
    (let ((component)
          (propList))
      (setq propList (emmet-react-get-component))
      (setq component (pop propList))
      (emmet-react-insert-wrap-component propList component)))

Future plans

There are a few things I would like to add to this project over time. I would like the functions to be smart enough to recognize when the user has set the prop to a specific value. I think the easiest way to do this will be to continue to split the strings on the periods, but to later also split them by = and wrap what comes after the equals between the selected characters. I think it would also be nice to have a variable to determine where the cursor should be moved after expanding the component.

System Crafters Web Ring

Messing around with computers and coding since I was 8. Now getting paid to do what I love.