Page 1 of 5

Pascal Emmet

Posted: 30 May 2019 13:59
by Rickard Johansson
In RJ TextEd I wrote my own version of Emmet. It is not based on the original Emmet code, but written from scratch in Delphi (object pascal).

You are perfectly free to use it in your own code or applications. But if you're using it in a commercial product I would appreciate a donation.

The Emmet code only expand abbreviation or wrap text with abbreviation. All other editor stuff like handle tab points or multi cursors you will have to handle yourself in your own code. That's what I do in RJ TextEd.

Cheat sheets
There are a few minor differences between my version of Emmet and standard Emmet (e.g. the lorem generator).

Emmet-Pascal cheat sheet:
Standard Emmet cheat sheet:

If you improve the code you may post it in this forum.

Version 1.18 (2020-06-30) or


Version 1.18
* Single quotes in custom attributes should work now. E.g. td[title='Hello world!' colspan=3]

Version 1.17
* Fixed a Lazarus (Free Pascal) string issue in ResolveTabStopsIndex()

Version 1.16 (2020-05-19)
* Added Delphi and Lazarus demos created by Alexey. Made some minor changes to the projects to build properly in the latest versions of Delphi 10.x and Lazarus. Otherwise no changes to the Emmet.pas code.

* If the result string contain both cursor positions and tab stops - cursor positions are replaced by tab stops.

Version 1.15
* Added option to replace cursor positions | with indexed tab positions ${x}

Version 1.14
* Fixed a typo in the snippets file and updated the zip file.

Version 1.14
* Fixed ${1:tabstop} index issue
* Fixed a class issue with span tag

Version 1.13
* Added support for markdown. Supported abbreviations are:

Code: Select all

      a           = link
      b           = bold
      bq          = blockquote
      code        = inline code snippet
      h1..h6      = heading
      hr          = horizontal rule
      i           = italic
      img         = image
      ol          = ordered list
      pre         = code block with language based highlighting
      strike      = strike through
      table       = table
      ul          = unordered list
      @l or @l80  = create lorem generated text
* Added a property to TEmmet to set a maximum multiplication limit. The default value is cMultiplicationMax = 1000.

Version 1.12
* Fixed a word wrap issue in Lorem generated text.

Version 1.11
* The option "CommentTags" in TExpandOptions didn't work. The |c filter did however.
* Added a number check in ProcessTagMultiplication() to prevent an infinite loop. (cMultiplicationMax = 1000)

Version 1.10
* Removed "public" keyword from TExpandOptions.
* Fixed an issue in ExtractFilters().

Version 1.09
* TEmmet can now handle some filters. A filter is added at the end of the abbreviation using a pipe |.

Code: Select all

      E.g. ul>li*|t

      c - Comment important tags (containing class or id attributes).
      e - Escape XML-unsafe characters: <, > and &. E.g. <p>|e => &lt;p&gt;&lt;/p&gt;
      s - Single line. Expand everything to a single line of code.
      t - Trim line markers from wrapped lines e.g. "* ", "- " or "1."
      w - Wordwrap selected or lorem generated text. Default width is 80.
      w<x> - Wordwrap at column x. E.g. |w120 will wrap lines at column 120.

        sAbbrev = ul>li*|t

        sSelText =
          * Line 1
          * Line 2

        Result =
            <li>Line 1</li>
            <li>Line 2</li>
* Added several options in a record "TExpandOptions" that can be passed to the expand function.
Options are:

Code: Select all

  AddSlashToEmptyTags: Boolean;  // Add a slash to empty tags e.g. <img src="" />
  AlwaysAddNewLine: Boolean;     // Always add linefeed after each tag (usually used in XML)
  CommentTags: Boolean;          // Comment important tags (containing class or id attributes).
  IndentChilds: Boolean;         // Indent child tags. If you set this to false - no indention will be used.
  SingleLine: Boolean            // Expand everything to a single line of code.
  TabSize: Integer;              // Tab size in characters. This is only used with wordwrap.
  TrimLineMarkers: Boolean;      // Trim line markers from wrapped lines e.g. "* ", "- " or "1."
  Wordwrap: Boolean;             // Word wrap selected or lorem generated text.
  WordwrapAt: Integer;           // Wrap at given column. The nearest space or symbol will be used as wrap position

* Added overload function to "ExpandAbbreviation()". The first one uses default options and the other one
enables you to set expand options.

Version 1.08
* Direction issue with multiply.
* Changes to the constructior.

Version 1.07
* Added support for placeholders $# used in "Wrap with abbreviation". The placeholder is replaced with one line of selected text.

Code: Select all

        sAbbrev = "ul>li[title=$#]*>{$#}+img[alt=$#]"

        sSelText =

        Result =
              <li title="About">About<img src="" alt="About" /></li>
              <li title="New">New<img src="" alt="New" /></li>
              <li title="Products">Products<img src="" alt="Products" /></li>
              <li title="Contacts">Contacts<img src="" alt="Contacts" /></li>
* Added new parameters to the constructor.

Version 1.06
* Space should be treated as stop character.
* Implicit tag issue.
* User attribute space issue.

Version 1.05
* Fixed a child indent issue.

Version 1.04
* Added standard vendor prefix "-" to CSS. E.g. -bdrs (which works the same as -v-bdrs).
* Space issue with siblings.
* A trim issue that may result in wrong indention.
* Id and class attribute issue.

Version 1.03
* Fixed several issues and updated the snippets.ini file.

Version 1.02
* Addressed some warnings in Lazarus

Version 1.01
* Fixed a multiply issue in ProcessTagMultiplication(...)

Re: Pascal Emmet

Posted: 30 May 2019 21:57
by Alextp
Thank you very much! It is easier to get patches on Github, so I ve put repo here:

Made fix to support FreePascal. (added var DirectorySeparator for Delphi, will test it ASAP.)

Re: Pascal Emmet

Posted: 30 May 2019 22:09
by Alextp
In Lazarus it works, good. But the first abbrev from
don't fully work.
#page>div.logo+ul#navigation>li*5>a{Item $}

Re: Pascal Emmet

Posted: 31 May 2019 00:33
by Alextp
Added EmmetHelper unit (the same repo on github). It can find Emmet abbrev inside any line. It truncates line from <tag> and </tag> but skips Emmet ">" chars.

Re: Pascal Emmet

Posted: 31 May 2019 09:52
by Rickard Johansson
Fixed the multiply issue in ProcessTagMultiplication(...) and added the new version in my first post.


Re: Pascal Emmet

Posted: 31 May 2019 10:33
by Alextp
Thanks, that was fixed. I will test more cases, maybe all examples of Emmet, and will let you know. Will help as a tester.

Re: Pascal Emmet

Posted: 31 May 2019 10:44
by Alextp
we need class (TEmmet) property: IndentString, which can be set to #9 or to StringOfChar(' ', N). You know it is needed for editors.

Re: Pascal Emmet

Posted: 31 May 2019 10:50
by Alextp
And good to have property TabStopChar, with default value '|'.

Re: Pascal Emmet

Posted: 31 May 2019 11:40
by Rickard Johansson
I do all the indent processing in the editor component. Using #9 as indent marker makes processing each line very simple and it's easy to replace with spaces. It's also easier to position the whole block where it's supposed to be.

Instead of adding that to the Emmet.pas - maybe you could write a helper function to process tabs. Maybe have an initial indent position "InitialIndentString" and "IndentString", or something similar...

| - tab stop char
The tab stop char is added to snippets and abbreviations in the file snippets.ini...

Re: Pascal Emmet

Posted: 31 May 2019 11:52
by Rickard Johansson
Actually... It may be helpful to have an editor class (TEditor) that creates the TEmmet object and contain some helpful methods that makes it easier to use.

Re: Pascal Emmet

Posted: 31 May 2019 12:00
by Alextp
Agree - IndentString is not needed, as well as prop for |.
I have founds bugs.

Bugs are

Re: Pascal Emmet

Posted: 31 May 2019 12:23
by Rickard Johansson
They all work at my end :?

Are you using Lazarus? Could there be something in the code that works differently in Lazarus? Linefeeds maybe?

I use #13#10 as linefeed which works perfectly in Delphi and makes the result easy to process.

Re: Pascal Emmet

Posted: 31 May 2019 12:30
by Rickard Johansson
Actually the abbreviation "c" isn't implemented (yet).

Re: Pascal Emmet

Posted: 31 May 2019 12:57
by Alextp
Lazarus, yes. It gives warnings.
IMO these are the reason.

- main is "function ClimpUpOneLevel" result is not set. What result to set?
- func AddExpanded result is not inited. (you have Result:= Result+....)
- func GetAbbreviationNames: param AList must not be "out" - with "out" FreePascal uses write-only parameter
- func GetSnippetNames: AList must not be "out"

Re: Pascal Emmet

Posted: 31 May 2019 13:10
by Rickard Johansson
Fixed (see first post). Hope that helps... :?