Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better JSX support #117

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,28 @@ Or if you don't want to move cursor after expanding:

(setq emmet-move-cursor-after-expanding nil) ;; default t

If you want to use emmet with react-js's JSX, you probably want emmet to expand 'className="..."' instead of 'class="..."':

(setq emmet-expand-jsx-className? t) ;; default nil

If you want to customize Self-closing tags style:

(setq emmet-self-closing-tag-style " /") ;; default "/"

;; only " /", "/" and "" are valid.
;; eg. <meta />, <meta/>, <meta>

### 4. JSX Support
If current major-mode is in `emmet-jsx-major-modes`, then JSX features will be supported:

- Expand `.class` to `className="..."` instead of `class="..."`
- Expand value of properties as variables: `div[value={v}]` -> `<div value={v}></div>`
- Expand variables inside text: `div{{v}}` -> `<div>{v}</div>`
**Note**: '}' can be escaped using backslash, i.e. `div{{v\}}}` -> `<div>{v}}</div>`.
Please make sure your curly braces(not counting escaped ones) are always balanced.

To enable the JSX support, add your major-mode to `emmet-jsx-major-modes`:

```lisp
(add-to-list 'emmet-jsx-major-modes 'your-jsx-major-mode)
```

## Usage

Place point in a emmet snippet and press C-j to expand it (or alternatively,
Expand Down
2 changes: 1 addition & 1 deletion conf/preferences.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"figure": {"block": true, "selfClosing": false},
"font": {"block": false, "selfClosing": false},
"footer": {"block": true, "selfClosing": false},
"form": {"block": true, "selfClosing": false, "defaultAttr": {"action": ""}},
"form": {"block": true, "selfClosing": false},
"frame": {"block": false, "selfClosing": true},
"frameset": {"block": true, "selfClosing": false},
"head": {"block": true, "selfClosing": false},
Expand Down
18 changes: 9 additions & 9 deletions conf/snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
"ols:r": "outline-style:ridge;",
"ols:i": "outline-style:inset;",
"ols:o": "outline-style:outset;",
"olc": "outline-color:#${1:000};",
"olc": "outline-color:${1:#000};",
"olc:i": "outline-color:invert;",
"bfv": "backface-visibility:|;",
"bfv:h": "backface-visibility:hidden;",
Expand All @@ -194,7 +194,7 @@
"bdcl": "border-collapse:|;",
"bdcl:c": "border-collapse:collapse;",
"bdcl:s": "border-collapse:separate;",
"bdc": "border-color:#${1:000};",
"bdc": "border-color:${1:#000};",
"bdc:t": "border-color:transparent;",
"bdi": "border-image:url(|);",
"bdi:n": "border-image:none;",
Expand Down Expand Up @@ -257,31 +257,31 @@
"bdt:n": "border-top:none;",
"bdts": "border-top-style:|;",
"bdts:n": "border-top-style:none;",
"bdtc": "border-top-color:#${1:000};",
"bdtc": "border-top-color:${1:#000};",
"bdtc:t": "border-top-color:transparent;",
"bdr": "border-right:|;",
"br": "border-right:|;",
"bdr+": "border-right:${1:1px} ${2:solid} ${3:#000};",
"bdr:n": "border-right:none;",
"bdrst": "border-right-style:|;",
"bdrst:n": "border-right-style:none;",
"bdrc": "border-right-color:#${1:000};",
"bdrc": "border-right-color:${1:#000};",
"bdrc:t": "border-right-color:transparent;",
"bdb": "border-bottom:|;",
"bb": "border-bottom:|;",
"bdb+": "border-bottom:${1:1px} ${2:solid} ${3:#000};",
"bdb:n": "border-bottom:none;",
"bdbs": "border-bottom-style:|;",
"bdbs:n": "border-bottom-style:none;",
"bdbc": "border-bottom-color:#${1:000};",
"bdbc": "border-bottom-color:${1:#000};",
"bdbc:t": "border-bottom-color:transparent;",
"bdl": "border-left:|;",
"bl": "border-left:|;",
"bdl+": "border-left:${1:1px} ${2:solid} ${3:#000};",
"bdl:n": "border-left:none;",
"bdls": "border-left-style:|;",
"bdls:n": "border-left-style:none;",
"bdlc": "border-left-color:#${1:000};",
"bdlc": "border-left-color:${1:#000};",
"bdlc:t": "border-left-color:transparent;",
"bdrs": "border-radius:|;",
"bdtrrs": "border-top-right-radius:|;",
Expand All @@ -292,7 +292,7 @@
"bg+": "background:${1:#fff} url(${2}) ${3:0} ${4:0} ${5:no-repeat};",
"bg:n": "background:none;",
"bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');",
"bgc": "background-color:#${1:fff};",
"bgc": "background-color:${1:fff};",
"bgc:t": "background-color:transparent;",
"bgi": "background-image:url(|);",
"bgi:n": "background-image:none;",
Expand Down Expand Up @@ -325,7 +325,7 @@
"bgsz:a": "background-size:auto;",
"bgsz:ct": "background-size:contain;",
"bgsz:cv": "background-size:cover;",
"c": "color:#${1:000};",
"c": "color:${1:#000};",
"c:r": "color:rgb(${1:0}, ${2:0}, ${3:0});",
"c:ra": "color:rgba(${1:0}, ${2:0}, ${3:0}, .${4:5});",
"cm": "/* |${child} */",
Expand Down Expand Up @@ -514,7 +514,7 @@
"fw:b": "font-weight:bold;",
"fw:br": "font-weight:bolder;",
"fw:lr": "font-weight:lighter;",
"fs": "font-style:${italic};",
"fs": "font-style:italic;",
"fs:n": "font-style:normal;",
"fs:i": "font-style:italic;",
"fs:o": "font-style:oblique;",
Expand Down
130 changes: 98 additions & 32 deletions emmet-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
(maphash #'(lambda (k v) (setq vs (cons v vs))) hash)
vs))

(defun emmet-jsx-prop-value-var? (prop-value)
(string-match "{.+}" prop-value))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Generic parsing macros and utilities

Expand Down Expand Up @@ -251,6 +254,10 @@ e. g. without semicolons")
"File local filter used by `emmet-default-filter'.")
(make-variable-buffer-local 'emmet-file-filter)

(defvar emmet-jsx-major-modes
'(rjsx-mode
typescript-tsx-mode))

(defun emmet-transform (input)
(if (or (emmet-detect-style-tag-and-attr) emmet-use-css-transform)
(emmet-css-transform input)
Expand Down Expand Up @@ -796,7 +803,7 @@ See `emmet-preview-online'."
(puthash "bdb" "border-bottom:|;" tbl)
(puthash "bdb+" "border-bottom:${1:1px} ${2:solid} ${3:#000};" tbl)
(puthash "bdb:n" "border-bottom:none;" tbl)
(puthash "bdbc" "border-bottom-color:#${1:000};" tbl)
(puthash "bdbc" "border-bottom-color:${1:#000};" tbl)
(puthash "bdbc:t" "border-bottom-color:transparent;" tbl)
(puthash "bdbi" "border-bottom-image:url(|);" tbl)
(puthash "bdbi:n" "border-bottom-image:none;" tbl)
Expand All @@ -813,7 +820,7 @@ See `emmet-preview-online'."
(puthash "bdbs" "border-bottom-style:|;" tbl)
(puthash "bdbs:n" "border-bottom-style:none;" tbl)
(puthash "bdbw" "border-bottom-width:|;" tbl)
(puthash "bdc" "border-color:#${1:000};" tbl)
(puthash "bdc" "border-color:${1:#000};" tbl)
(puthash "bdc:t" "border-color:transparent;" tbl)
(puthash "bdci" "border-corner-image:url(|);" tbl)
(puthash "bdci:c" "border-corner-image:continue;" tbl)
Expand All @@ -834,7 +841,7 @@ See `emmet-preview-online'."
(puthash "bdl" "border-left:|;" tbl)
(puthash "bdl+" "border-left:${1:1px} ${2:solid} ${3:#000};" tbl)
(puthash "bdl:n" "border-left:none;" tbl)
(puthash "bdlc" "border-left-color:#${1:000};" tbl)
(puthash "bdlc" "border-left-color:${1:#000};" tbl)
(puthash "bdlc:t" "border-left-color:transparent;" tbl)
(puthash "bdlen" "border-length:|;" tbl)
(puthash "bdlen:a" "border-length:auto;" tbl)
Expand All @@ -846,7 +853,7 @@ See `emmet-preview-online'."
(puthash "bdr" "border-right:|;" tbl)
(puthash "bdr+" "border-right:${1:1px} ${2:solid} ${3:#000};" tbl)
(puthash "bdr:n" "border-right:none;" tbl)
(puthash "bdrc" "border-right-color:#${1:000};" tbl)
(puthash "bdrc" "border-right-color:${1:#000};" tbl)
(puthash "bdrc:t" "border-right-color:transparent;" tbl)
(puthash "bdri" "border-right-image:url(|);" tbl)
(puthash "bdri:n" "border-right-image:none;" tbl)
Expand All @@ -872,7 +879,7 @@ See `emmet-preview-online'."
(puthash "bdt" "border-top:|;" tbl)
(puthash "bdt+" "border-top:${1:1px} ${2:solid} ${3:#000};" tbl)
(puthash "bdt:n" "border-top:none;" tbl)
(puthash "bdtc" "border-top-color:#${1:000};" tbl)
(puthash "bdtc" "border-top-color:${1:#000};" tbl)
(puthash "bdtc:t" "border-top-color:transparent;" tbl)
(puthash "bdti" "border-top-image:url(|);" tbl)
(puthash "bdti:n" "border-top-image:none;" tbl)
Expand Down Expand Up @@ -902,7 +909,7 @@ See `emmet-preview-online'."
(puthash "bgbk:bb" "background-break:bounding-box;" tbl)
(puthash "bgbk:c" "background-break:continuous;" tbl)
(puthash "bgbk:eb" "background-break:each-box;" tbl)
(puthash "bgc" "background-color:#${1:fff};" tbl)
(puthash "bgc" "background-color:${1:fff};" tbl)
(puthash "bgc:t" "background-color:transparent;" tbl)
(puthash "bgcp" "background-clip:${1:padding-box};" tbl)
(puthash "bgcp:bb" "background-clip:border-box;" tbl)
Expand Down Expand Up @@ -938,7 +945,7 @@ See `emmet-preview-online'."
(puthash "bxz" "box-sizing:${1:border-box};" tbl)
(puthash "bxz:bb" "box-sizing:border-box;" tbl)
(puthash "bxz:cb" "box-sizing:content-box;" tbl)
(puthash "c" "color:#${1:000};" tbl)
(puthash "c" "color:${1:#000};" tbl)
(puthash "c:r" "color:rgb(${1:0}, ${2:0}, ${3:0});" tbl)
(puthash "c:ra" "color:rgba(${1:0}, ${2:0}, ${3:0}, .${4:5});" tbl)
(puthash "cl" "clear:${1:both};" tbl)
Expand Down Expand Up @@ -1051,7 +1058,7 @@ See `emmet-preview-online'."
(puthash "fl:l" "float:left;" tbl)
(puthash "fl:n" "float:none;" tbl)
(puthash "fl:r" "float:right;" tbl)
(puthash "fs" "font-style:${italic};" tbl)
(puthash "fs" "font-style:italic;" tbl)
(puthash "fs:i" "font-style:italic;" tbl)
(puthash "fs:n" "font-style:normal;" tbl)
(puthash "fs:o" "font-style:oblique;" tbl)
Expand Down Expand Up @@ -1143,7 +1150,7 @@ See `emmet-preview-online'."
(puthash "mt:a" "margin-top:auto;" tbl)
(puthash "ol" "outline:|;" tbl)
(puthash "ol:n" "outline:none;" tbl)
(puthash "olc" "outline-color:#${1:000};" tbl)
(puthash "olc" "outline-color:${1:#000};" tbl)
(puthash "olc:i" "outline-color:invert;" tbl)
(puthash "olo" "outline-offset:|;" tbl)
(puthash "ols" "outline-style:|;" tbl)
Expand Down Expand Up @@ -2699,9 +2706,6 @@ tbl) tbl)
tbl) tbl)
(puthash "form" (let ((tbl (make-hash-table :test 'equal)))
(puthash "block" t tbl)
(puthash "defaultAttr" (let ((tbl (make-hash-table :test 'equal)))
(puthash "action" "" tbl)
tbl) tbl)
(puthash "selfClosing" nil tbl)
tbl) tbl)
(puthash "frame" (let ((tbl (make-hash-table :test 'equal)))
Expand Down Expand Up @@ -3067,7 +3071,7 @@ tbl))

(defun emmet-expr (input)
"Parse a zen coding expression with optional filters."
(emmet-pif (emmet-parse "\\(.*?\\)|" 2 "expr|filter" it)
(emmet-pif (emmet-extract-expr-filters input)
(let ((input (elt it 1))
(filters (elt it 2)))
(emmet-pif (emmet-extract-filters filters)
Expand All @@ -3091,6 +3095,18 @@ tbl))
it
'(error "no match, expecting ( or a-zA-Z0-9"))))))))

(defun emmet-extract-expr-filters (input)
(cl-flet ((string-match-reverse (str regexp)
(emmet-aif (string-match regexp (reverse str)) (- (length str) 1 it))))
(let ((err '(error "expected expr|filter")))
(emmet-aif (string-match-reverse input "|")
(if (string-match "[\"}]" (substring input (+ it 1)))
err
`(,input
,(substring input 0 it)
,(substring input (+ it 1))))
err))))

(defun emmet-extract-filters (input)
"Extract filters from expression."
(emmet-pif (emmet-parse "\\([^\\|]+?\\)|" 2 "" it)
Expand All @@ -3101,6 +3117,32 @@ tbl))
it))
(emmet-parse "\\([^\\|]+\\)" 1 "filter name" `(,(elt it 1)))))

(defun emmet-extract-inner-text (input)
"Extract inner-text in the form of {inner_text}...
Right curly braces can be escaped by backslash, i.e. '\\}'
Return `(,inner-text ,input-without-inner-text) if succeeds, otherwise return
`(error ,error-message)"
(cl-labels (
(string-find-paired-right-curly-brace
(str)
(cl-labels ((char-at-pos (str pos) (string-to-char (substring str pos)))
(helper (str pos cnt)
(let ((len (length str)))
(if (>= pos len) nil
(let ((c (char-at-pos str pos)))
(cond ((char-equal c ?{) (setq cnt (+ cnt 1)))
((and (char-equal c ?}) (not (char-equal (char-at-pos str (- pos 1)) ?\\))) (setq cnt (- cnt 1))))
(if (= cnt 0) pos (helper str (+ pos 1) cnt)))))
))
(helper str 0 0))))
(let ((err '(error "expected inner text")))
(if (or (< (length input) 2) (not (char-equal (string-to-char input) ?{))) err
(emmet-aif (string-find-paired-right-curly-brace input)
`(,input
,(substring input 1 it)
,(substring input (+ it 1)))
err)))))

(defun emmet-filter (input filters)
"Construct AST with specified filters."
(emmet-pif (emmet-subexpr input)
Expand Down Expand Up @@ -3324,11 +3366,23 @@ tbl))
(input (elt it 2)))
`((,(read name) ,(emmet-split-numbering-expressions value)) . ,input)))
it
(emmet-parse "=\\([^\\,\\+\\>\\{\\}\\ )]*\\)" 2
"=property value"
(let ((value (elt it 1))
(input (elt it 2)))
`((,(read name) ,(emmet-split-numbering-expressions value)) . ,input)))))
(let ((emmet--prop-value-parse-any (lambda () (emmet-parse "=\\([^\\,\\+\\>\\{\\}\\ )]*\\)" 2
"=property value"
(let ((value (elt it 1))
(input (elt it 2)))
`((,(read name) ,(emmet-split-numbering-expressions value)) . ,input))))))
(if (memq major-mode emmet-jsx-major-modes)
(emmet-pif
(emmet-parse "=\\({.*?}\\)" 2
"=\"property value\""
(let ((value (elt it 1))
(input (elt it 2)))
`((,(read name) ,(emmet-split-numbering-expressions value)) . ,input)))
it
(funcall emmet--prop-value-parse-any))
(funcall emmet--prop-value-parse-any))
)
))

(defun emmet-tag-classes (tag input)
(let ((tag-data (cadr tag)))
Expand All @@ -3349,12 +3403,23 @@ tbl))

(defun emmet-text (input)
"A zen coding expression innertext."
(emmet-parse "{\\(\\(?:\\\\.\\|[^\\\\}]\\)*?\\)}" 2 "inner text"
(let ((txt (emmet-split-numbering-expressions (elt it 1))))
(if (listp txt)
(setq txt (cons (car txt) (cons (replace-regexp-in-string "\\\\\\(.\\)" "\\1" (cadr txt)) (cddr txt))))
(setq txt (replace-regexp-in-string "\\\\\\(.\\)" "\\1" txt)))
`((text ,txt) . ,input))))
(emmet-pif (emmet-extract-inner-text input)
(let ((txt (emmet-split-numbering-expressions (elt it 1))))
(if (listp txt)
(setq txt (cons (car txt) (cons (replace-regexp-in-string "\\\\\\(.\\)" "\\1" (cadr txt)) (cddr txt))))
(setq txt (replace-regexp-in-string "\\\\\\(.\\)" "\\1" txt)))
`((text ,txt) . ,(elt it 2)))
'(error "expected inner text")))

;; (defun emmet-text (input)
;; "A zen coding expression innertext."
;; (emmet-parse "{\\(\\(?:\\\\.\\|[^\\\\}]\\|\\\\}\\)*?\\)}" 2 "inner text"
;; (let ((txt (emmet-split-numbering-expressions (elt it 1))))
;; (if (listp txt)
;; (setq txt (cons (car txt) (cons (replace-regexp-in-string "\\\\\\(.\\)" "\\1" (cadr txt)) (cddr txt))))
;; (setq txt (replace-regexp-in-string "\\\\\\(.\\)" "\\1" txt)))
;; `((text ,txt) . ,input))
;; '(error "expected inner text")))

(defun emmet-properties (input)
"A bracketed emmet property expression."
Expand Down Expand Up @@ -3486,9 +3551,6 @@ tbl))
"Function to execute when expanding a leaf node in the
Emmet AST.")

(defvar emmet-expand-jsx-className? nil
"Wether to use `className' when expanding `.classes'")

(emmet-defparameter
emmet-tag-settings-table
(gethash "tags" (gethash "html" emmet-preferences)))
Expand Down Expand Up @@ -3612,7 +3674,7 @@ tbl))
(puthash tag-name fn emmet-tag-snippets-table)))

(let* ((id (emmet-concat-or-empty " id=\"" tag-id "\""))
(class-attr (if emmet-expand-jsx-className? " className=\"" " class=\""))
(class-attr (if (memq major-mode emmet-jsx-major-modes) " className=\"" " class=\""))
(classes (emmet-mapconcat-or-empty class-attr tag-classes " " "\""))
(props (let* ((tag-props-default
(and settings (gethash "defaultAttr" settings)))
Expand All @@ -3623,9 +3685,13 @@ tbl))
(emmet-mapconcat-or-empty
" " merged-tag-props " " nil
(lambda (prop)
(let ((key (car prop)))
(concat (if (symbolp key) (symbol-name key) key)
"=\"" (cadr prop) "\""))))))
(let* ((key (car prop))
(val (cadr prop))
(format-string (if
(and (memq major-mode emmet-jsx-major-modes) (emmet-jsx-prop-value-var? val)) "%s=%s"
"%s=\"%s\"")))
(format format-string (if (symbolp key) (symbol-name key) key) val)
)))))
(content-multiline? (and content (string-match "\n" content)))
(block-tag? (and settings (gethash "block" settings)))
(self-closing? (and (not (or tag-txt content))
Expand Down Expand Up @@ -4116,7 +4182,7 @@ tbl))
(emmet-join-string
(mapcar
#'(lambda (expr)
(let*
(let*
((hash-map (if emmet-use-sass-syntax emmet-sass-snippets emmet-css-snippets))
(basement
(emmet-aif
Expand Down
Loading