Skip to content

An Emmet-powered minor mode for Emacs, offering the full suite of Emmet capabilities and an extensive range of additional features.

Notifications You must be signed in to change notification settings

P233/emmet2-mode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

93 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

emmet2-mode

Emmet2-mode is an Emmet-enhanced minor mode for Emacs. It is built on top of Deno, deno-bridge and the Emmet NPM package, 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 for creating 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). 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 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 section.

Installation

Emmet2-mode is built on top of the deno-bridge; therefore, you will need to install Deno and deno-bridge as dependencies.

To install Deno, follow the instructions provided in the official documentation.

To install and configure deno-bridge and emmet2-mode, refer to the following example using straight.el and use-package:

(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:

((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 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

About

An Emmet-powered minor mode for Emacs, offering the full suite of Emmet capabilities and an extensive range of additional features.

Topics

Resources

Stars

Watchers

Forks