# emmet2-mode Emmet2-mode is an [Emmet](https://emmet.io/)-enhanced minor mode for Emacs. It is built on top of [Deno](https://deno.com/runtime), [deno-bridge](https://github.com/manateelazycat/deno-bridge) and the [Emmet NPM package](https://www.npmjs.com/package/emmet), delivering the complete set of Emmet functionalities while also integrating a wide array of extra features, such as: - Expand abbreviation from any character - Expand JSX class attribute with CSS modules object and class names constructor - Automatically detect `style={{}}` attribute and then expand CSS in JS - Automatically detect `<style></style>` tag and `style=""` attribute, then expand CSS - Expand CSS and SCSS at-rules - Expand CSS pseudo-selectors - Numerous enhancements for Emmet CSS abbreviations The credit goes to [@manateelazycat](https://github.com/manateelazycat) for creating [deno-bridge](https://github.com/manateelazycat/deno-bridge). ## How it works? This minor mode has two parts: the elisp front-end and the deno back-end. The front-end detects abbreviations and syntax, sends data to the deno back-end, injects snippets into the buffer, reformats code, and repositions the cursor. The back-end expands abbreviations to snippets using the Emmet NPM package and sends them back to the front-end. It preprocesses abbreviations before involving Emmet and modifies the output snippets. Emmet2-mode detects the abbreviation under the cursor and expands it when you press `C-j` (which is the default expansion key, the same as [emmet-mode](https://github.com/smihica/emmet-mode)). It uses file extensions to determine syntax as follows: 1. `.tsx` and `.jsx` files are considered to have `JSX` syntax. 2. Both `.scss` and `.css` files are considered to have `SCSS` syntax (CSS is treated as `SCSS` syntax). 3. All other markup files are considered to have `HTML` syntax. When editing markup files, emmet2-mode detects if the cursor is in between a `<style>|</style>` tag or in a `style="|"` attribute, and expands CSS within. It also detects if the cursor is inside a `style={{|}}` JSX attribute and expands CSS in JS. Additionally, [Solid.js](https://www.solidjs.com/) is supported, which is very similar to React.js. However, Solid.js uses `class=` instead of `className=`. To work with Solid.js, you'll need to set the `emmet2-markup-variant` to `solid`. For more information, refer to the [Custom Options](#custom-options) section. ## Installation Emmet2-mode is built on top of the [deno-bridge](https://github.com/manateelazycat/deno-bridge); therefore, you will need to install [Deno](https://deno.land/) and deno-bridge as dependencies. As of now, Deno 2.0 or higher is required. To install Deno, follow the instructions provided in the [official documentation](https://deno.land/manual/getting_started/installation). To install and configure deno-bridge and emmet2-mode, refer to the following example using [straight.el](https://github.com/radian-software/straight.el) and [use-package](https://github.com/jwiegley/use-package): ```elisp (use-package deno-bridge :straight (:type git :host github :repo "manateelazycat/deno-bridge") :init (use-package websocket)) (use-package emmet2-mode :straight (:type git :host github :repo "p233/emmet2-mode" :files (:defaults "*.ts" "src" "data")) :after deno-bridge :hook ((web-mode css-mode) . emmet2-mode) ;; Enable emmet2-mode for web-mode and css-mode and other major modes based on them, such as the build-in scss-mode :config ;; OPTIONAL (unbind-key "C-j" emmet2-mode-map) ;; Unbind the default expand key (define-key emmet2-mode-map (kbd "C-c C-.") 'emmet2-expand)) ;; Bind custom expand key ``` ## Usage If you're not familiar with Emmet, a great place to start is by exploring the cheat sheet found at https://docs.emmet.io/cheat-sheet/. New added features are listed below, and see the `test/` folder for more usage examples. ### Custom Options Emmet2-mode has three custom options: 1. `emmet2-markup-variant`: This option has a single value, `"solid"`. When set, emmet2-mode outputs `class=` instead of `className=`. 2. `emmet2-css-modules-object`: This option allows you to set the CSS Modules object for your project. 3. `emmet2-class-names-constructor`: This option allows you to set the JSX class names constructor for your project. All these options work for expanding markups only, and they are project-based. If you need to customize any of them, create a `.dir-locals.el` file at the root of your project and add the following code: ```elisp ((web-mode . ((emmet2-markup-variant . "solid") (emmet2-css-modules-object . "style") ;; Default value is "css" (emmet2-class-names-constructor . "classnames")))) ;; Default value is "clsx" ``` After configuring the custom options, the abbreviation `a.link.active` will be expanded to `<a href="" class={classnames(style.link, style.active)}>|</a>` in this project, where the pipe symbol `|` represents the cursor position after expansion. ### Expand Markups #### HTML ``` . -> <div class="">|</div> .class -> <div class="class">|</div> ``` #### React JSX ``` Component -> <Component>|</Component> Component/ -> <Component /> Component./ -> <Component className={|} /> Component.class -> <Component className={css.class}>|</Component> Component.Subcomponent -> <Component.Subcomponent>|</Component.Subcomponent> Component.Subcomponent.class -> <Component.Subcomponent className={css.class}>|</Component.Subcomponent> Component.Subcomponent.a.b.c -> <Component.Subcomponent className={clsx(css.a, css.b, css.c)}>|</Component.Subcomponent> Component.Subcomponent.a.b.c/ -> <Component.Subcomponent className={clsx(css.a, css.b, css.c)} /> Component{{props.value}} -> <Component>{props.value}</Component> ``` #### Solid JSX ``` Component.class -> <Component class={css.class}>|</Component> ``` #### Automatically detect markup abbreviations Emmet2-mode allows you to expand abbreviations at any character within them, which can be helpful if you're working on a complex abbreviation and want to make tweaks. However, detecting the correct abbreviation under the cursor can be a bit tricky. If you encounter any issues related to this, please create an issue. ### Expand CSS #### Remove default color ``` c -> color: |; // instead of color: #000; bg -> background: |; // instead of background: #000; ``` #### [Modular Scale](https://github.com/modularscale/modularscale-sass) and vertical rhythm functions Only `fw` triggers the `ms()` function while the other properties will expand with the `rhythm()` function. ``` fz(1) -> font-size: ms(1); ``` ``` t(2) -> top: rhythm(2); p(1)(2)(3) -> padding: rhythm(1) rhythm(2) rhythm(3); ``` #### Custom properties ``` m--gutter -> margin: var(--gutter); p--a--b--c -> padding: var(--a) var(--b) var(--c); ``` #### Raw property value brackets ``` p[1px 2px 3px] -> padding: 1px 2px 3px; ``` #### Opinionated alias ``` posa -> position: absolute; z-index: |; posa1000 -> position: absolute; z-index: 1000; all -> top: |; right: ; bottom: ; left: ; all8 -> top: 8px; right: 8px; bottom: 8px; left: 8px; fw2 -> font-weight: 200; fw7 -> font-weight: 700; wf -> width: 100%; hf -> height: 100%; ``` #### camelCase alias In Emmet, `:` and `-` are used to separate properties and values. For example, `m:a` expands to `margin: auto;`. However, in emmet2-mode, `:` is designed to expand pseudo-selectors. To avoid this conflict, consider using camelCase instead. For instance, `mA` is equivalent to both `m:a` and `m-a`. ``` mA -> margin: auto; allA -> top: auto; right: auto; bottom: auto; left: auto; ``` #### Comma as abbreviations spliter As a user of the Dvorak keyboard layout, I find it much easier to press the `,` key than the `+` key. ``` t0,r0,b0,l0 == t0+r0+b0+l0 ``` #### At rules To expand CSS at-rules, start with the `@` symbol, followed by two or three distinct letters. SCSS at-rules are also supported. ``` @cs -> @charset @kf -> @keyframes @md -> @media @us -> @use "|"; @in -> @if not | {} ``` #### Pseudo-class and pseudo-element To expand CSS pseudo-classes or pseudo-elements, start with the `:` symbol, followed by two or three distinct letters. When dealing with pseudo-elements, use `:` instead of `::`. There is a shorthand for pseudo-functions like `:n(:fc)`, which expands to `&:not(:first-child) {|}`, and `:n(:fc,:lc)` expands to `&:not(:first-child):not(:last-child) {|}`. It's important to note that spaces are not allowed within the `()` parentheses. ``` :fu -> &:focus { | } _:fu -> :focus { | } :hv:af -> &:hover::after { | } :n(:fc) -> &:not(:first-child) { | } :n(:fc,:lc):be -> &:not(:first-child):not(:last-child)::before { | } :nc(2n-1) -> &:nth-child(2n-1) { | } ``` ## Credits - [deno-bridge](https://github.com/manateelazycat/deno-bridge) - [Emmet](https://emmet.io/) - [emmet-mode](https://github.com/smihica/emmet-mode). - [VS Code Custom Data](https://github.com/microsoft/vscode-custom-data)