LaTeX3-sensitive editors

Update (2017-02-14)

Thanks to the time spent on the answer below, and to some new features in WinEdt 10.2, now WinEdt 10.2 is really "LaTeX3-sensitive".

WinEdt 10.2 incorporates the highlighting scheme in this answer (with many improvements) and features many other functionalities to support LaTeX3 (e.g. command completion for commands and environments defined with \NewDocumentCommand, \NewDocumentEnvironment and alike).


Complete Highlighting Scheme for WinEdt

This is a highlighting scheme for expl3 in WinEdt 8.

New: It is now available as an add-on: LaTeX3


Highlighting LaTeX3 guards in .dtx files

Add the following lines in Switches.ini just before the switch SWITCH="DTX Single Guard"

SWITCH="DTX LaTeX3 Guard"
  ENABLED=1
  MODE_FILTER="DTX"
  START="DTX %"
  STOP=">"
  HIGHLIGHT_START=1
  STEP_OVER_STOP=1
  HIGHLIGHT_STOP=1
  SCOPE=1
  DOMINANT_PRIORITY=0
  STRICT_PRIORITY=1
  PRIORITY=9
  INDENTED=0
  BOLN_ONLY=1
  CASE_SENSITIVE=1
  START1_TRIGGER=""
  START2_TRIGGER="<@@="
  STOP1_TRIGGER=""
  STOP2_TRIGGER=""
    TEXT_COLOR=5
    BACKGROUND_RGB="$F8F8F8"
    TRANSPARENT=144
    DEFAULT_FONT=1

Result:

enter image description here

Collecting LaTeX3 guards in the "Tree"

Add the following lines in Tree.ini inside the branch BRANCH="Guards-DTX", just between the lines END="</?>" and ITEM="<?>"

  ITEM="<@@=?>"
    MODE_FILTER="DTX"
    CASE_SENSITIVE=1
    LINE_START="%"
    BEGINNING_OF_LINE_ONLY=2
    CURRENT_DOCUMENT_ONLY=1
    ALL_OPENED_DOCUMENTS=0
    COMPLETE_PROJECT_TREE=1
    ICON="ArrowPurple"
    LEVEL=0
    CAPTION="%?"
    MAX_LINE_SPAN=1
    ON_CTRL_CLICK_MACRO="GlobalMark;TreeTrack(2);"
    ON_CLICK_MACRO="TreeTrack(2,1);"
    ON_DBL_CLICK_MACRO="TreeTrack(2,2);"
    ACTION="Find"
      IMAGE="Find"
      MACRO="TreeTrack(2);"
  END="<@@=?>"

Result:

enter image description here

Highlighting LaTeX3 commands

Add the following lines in FilterSets.ini just before the set FILTER_SET="~AlphaNumeric"

FILTER_SET="~Alpha@_:"
  ENABLED=0
  MODE_FILTER=""
  SET=:~(Alpha+["@_:"])
  BEFORE=""
  AFTER=""
  BOLN_NOT_OK=0
  EOLN_NOT_OK=0
  STRICT_PRIORITY=0
  PRIORITY=0
    DEFAULT_FONT=1

Then, in Switches.ini, in the switch SWITCH="\", replace the line

STOP="~Alpha@"

with

STOP="~Alpha@_:"

Finally, in Keywords.ini, replace all occurrences of

AFTER="~Alpha@"

with

AFTER="~Alpha@_:"

Result:

enter image description here

Highlighting LaTeX3 Kernel keywords

Add the following lines in Switches.ini just before the switch SWITCH="\"

SWITCH="LaTeX3 Kernel Engine Prefixes"
  ENABLED=1
  MODE_FILTER="TeX"
  START="\"
  STOP="~Alpha@_:"
  HIGHLIGHT_START=1
  STEP_OVER_STOP=0
  HIGHLIGHT_STOP=0
  SCOPE=0
  DOMINANT_PRIORITY=0
  STRICT_PRIORITY=0
  PRIORITY=5
  INDENTED=0
  BOLN_ONLY=0
  CASE_SENSITIVE=1
  START1_TRIGGER=""
  START2_TRIGGER="luatex_if_engine"
  START2_TRIGGER="pdftex_if_engine"
  START2_TRIGGER="xetex_if_engine"
  STOP1_TRIGGER=""
  STOP2_TRIGGER=""
    TEXT_COLOR=5
    DEFAULT_FONT=1
    BOLD=1

SWITCH="LaTeX3 Kernel Prefixes"
  ENABLED=1
  MODE_FILTER="TeX"
  START="\"
  STOP="~Alpha@_:"
  HIGHLIGHT_START=1
  STEP_OVER_STOP=0
  HIGHLIGHT_STOP=0
  SCOPE=0
  DOMINANT_PRIORITY=0
  STRICT_PRIORITY=0
  PRIORITY=5
  INDENTED=0
  BOLN_ONLY=0
  CASE_SENSITIVE=1
  START1_TRIGGER=""
  START2_TRIGGER="alloc_"
  START2_TRIGGER="bool_"
  START2_TRIGGER="box_"
  START2_TRIGGER="cctab_"
  START2_TRIGGER="char_"
  START2_TRIGGER="chk_"
  START2_TRIGGER="clist_"
  START2_TRIGGER="codedoc_"
  START2_TRIGGER="coffin_"
  START2_TRIGGER="color_"
  START2_TRIGGER="cs_"
  START2_TRIGGER="dim_"
  START2_TRIGGER="driver_"
  START2_TRIGGER="else_"
  START2_TRIGGER="etex_"
  START2_TRIGGER="exp_"
  START2_TRIGGER="expl_"
  START2_TRIGGER="fi_"
  START2_TRIGGER="fp_"
  START2_TRIGGER="group_"
  START2_TRIGGER="hbox_"
  START2_TRIGGER="hcoffin_"
  START2_TRIGGER="if_"
  START2_TRIGGER="insert_"
  START2_TRIGGER="int_"
  START2_TRIGGER="kernel_"
  START2_TRIGGER="keys_"
  START2_TRIGGER="keyval_"
  START2_TRIGGER="lua_"
  START2_TRIGGER="luatex_"
  START2_TRIGGER="mode_"
  START2_TRIGGER="muskip_"
  START2_TRIGGER="or_"
  START2_TRIGGER="pdftex_"
  START2_TRIGGER="peek_"
  START2_TRIGGER="prop_"
  START2_TRIGGER="quark_"
  START2_TRIGGER="reverse_"
  START2_TRIGGER="seq_"
  START2_TRIGGER="skip_"
  START2_TRIGGER="str_"
  START2_TRIGGER="tex_"
  START2_TRIGGER="tl_"
  START2_TRIGGER="token_"
  START2_TRIGGER="use_"
  START2_TRIGGER="vbox_"
  START2_TRIGGER="vcoffin_"
  START2_TRIGGER="xetex_"
  STOP1_TRIGGER=""
  STOP2_TRIGGER=""
    TEXT_COLOR=4
    DEFAULT_FONT=1
    BOLD=1

SWITCH="LaTeX3 File Handling"
  ENABLED=1
  MODE_FILTER="TeX"
  START="\"
  STOP="~Alpha@_:"
  HIGHLIGHT_START=1
  STEP_OVER_STOP=0
  HIGHLIGHT_STOP=0
  SCOPE=0
  DOMINANT_PRIORITY=0
  STRICT_PRIORITY=0
  PRIORITY=5
  INDENTED=0
  BOLN_ONLY=0
  CASE_SENSITIVE=1
  START1_TRIGGER=""
  START2_TRIGGER="file_"
  START2_TRIGGER="g_file_"
  START2_TRIGGER="ior_"
  START2_TRIGGER="iow_"
  START2_TRIGGER="l_ior_"
  START2_TRIGGER="l_iow_"
  STOP1_TRIGGER=""
  STOP2_TRIGGER=""
   TEXT_COLOR=2
   DEFAULT_FONT=1
   BOLD=1

SWITCH="LaTeX3 Package Messages"
 ENABLED=1
 MODE_FILTER="TeX"
 START="\"
 STOP="~Alpha@_:"
 HIGHLIGHT_START=1
 STEP_OVER_STOP=0
 HIGHLIGHT_STOP=0
 SCOPE=0
 DOMINANT_PRIORITY=0
 STRICT_PRIORITY=0
 PRIORITY=5
 INDENTED=0
 BOLN_ONLY=0
 CASE_SENSITIVE=1
 START1_TRIGGER=""
 START2_TRIGGER="msg_"
 STOP1_TRIGGER=""
 STOP2_TRIGGER=""
  TEXT_COLOR=9
  DEFAULT_FONT=1
  ITALIC=1

SWITCH="LaTeX3 NoOp Functions"
 ENABLED=1
 MODE_FILTER="TeX"
 START="\"
 STOP="~Alpha@_:"
 HIGHLIGHT_START=1
 STEP_OVER_STOP=0
 HIGHLIGHT_STOP=0
 SCOPE=0
 DOMINANT_PRIORITY=0
 STRICT_PRIORITY=0
 PRIORITY=5
 INDENTED=0
 BOLN_ONLY=0
 CASE_SENSITIVE=1
 START1_TRIGGER=""
 START2_TRIGGER="prg_"
 START2_TRIGGER="scan_"
 STOP1_TRIGGER=""
 STOP2_TRIGGER=""
  TEXT_COLOR=3
  DEFAULT_FONT=1
  BOLD=1

Then, add the following lines in Keywords.ini just before the group KEYWORD_GROUP="TeX Units"

KEYWORD_GROUP="LaTeX3  Keywords"
  ENABLED=1
  MODE_FILTER="TeX"
  BEFORE="\ (single)"
  AFTER="~Alpha@_:"
  BOLN_NOT_OK=1
  EOLN_NOT_OK=0
  STRICT_PRIORITY=0
  PRIORITY=6
  CASE_SENSITIVE=1
    TEXT_COLOR=0
    DEFAULT_FONT=1
    BOLD=1
    UNDERLINE=1
LIST="END_LIST"
ExplSyntaxOff
ExplSyntaxOn
END_LIST

KEYWORD_GROUP="LaTeX3  Keywords..."
  ENABLED=1
  MODE_FILTER="TeX"
  BEFORE="\ (single)"
  AFTER="~Alpha@_:"
  BOLN_NOT_OK=1
  EOLN_NOT_OK=0
  STRICT_PRIORITY=0
  PRIORITY=6
  CASE_SENSITIVE=1
    TEXT_COLOR=4
    DEFAULT_FONT=1
    BOLD=1
LIST="END_LIST"
DeclareDocumentCommand
DeclareDocumentEnvironment
NewDocumentCommand
NewDocumentEnvironment
ProvideDocumentCommand
ProvideDocumentEnvironment
RenewDocumentCommand
RenewDocumentEnvironment
END_LIST

Result:

enter image description here

Highlight LaTeX3 properties & constants

This is the best that can be done for this one.

Add the following lines in FilterSets.ini just before the set FILTER_SET="="

FILTER_SET="."
  ENABLED=0
  MODE_FILTER=""
  SET=:["."]
  BEFORE=""
  AFTER=""
  BOLN_NOT_OK=0
  EOLN_NOT_OK=0
  STRICT_PRIORITY=0
  PRIORITY=0
    DEFAULT_FONT=1

Then, add the following lines in Switches.ini just before the switch SWITCH="\" (after the newly defined switch SWITCH="LaTeX3 Kernel Prefixes")

SWITCH="LaTeX3 Properties"
  ENABLED=1
  MODE_FILTER="TeX"
  START="."
  STOP=":"
  HIGHLIGHT_START=1
  STEP_OVER_STOP=1
  HIGHLIGHT_STOP=1
  SCOPE=0
  DOMINANT_PRIORITY=0
  STRICT_PRIORITY=0
  PRIORITY=5
  INDENTED=0
  BOLN_ONLY=0
  CASE_SENSITIVE=1
  START1_TRIGGER=""
  START2_TRIGGER="code"
  START2_TRIGGER="choice"
  START2_TRIGGER="choice_code"
  START2_TRIGGER="generate_choices"
  START2_TRIGGER="initial"
  START2_TRIGGER="default"
  START2_TRIGGER="meta"
  START2_TRIGGER="alloc_"
  START2_TRIGGER="bool_"
  START2_TRIGGER="box_"
  START2_TRIGGER="cctab_"
  START2_TRIGGER="char_"
  START2_TRIGGER="chk_"
  START2_TRIGGER="clist_"
  START2_TRIGGER="codedoc_"
  START2_TRIGGER="coffin_"
  START2_TRIGGER="color_"
  START2_TRIGGER="cs_"
  START2_TRIGGER="dim_"
  START2_TRIGGER="driver_"
  START2_TRIGGER="else_"
  START2_TRIGGER="etex_"
  START2_TRIGGER="exp_"
  START2_TRIGGER="expl_"
  START2_TRIGGER="fi_"
  START2_TRIGGER="file_"
  START2_TRIGGER="fp_"
  START2_TRIGGER="group_"
  START2_TRIGGER="hbox_"
  START2_TRIGGER="hcoffin_"
  START2_TRIGGER="if_"
  START2_TRIGGER="insert_"
  START2_TRIGGER="int_"
  START2_TRIGGER="ior_"
  START2_TRIGGER="iow_"
  START2_TRIGGER="kernel_"
  START2_TRIGGER="keys_"
  START2_TRIGGER="keyval_"
  START2_TRIGGER="lua_"
  START2_TRIGGER="luatex_"
  START2_TRIGGER="mode_"
  START2_TRIGGER="msg_"
  START2_TRIGGER="muskip_"
  START2_TRIGGER="or_"
  START2_TRIGGER="pdftex_"
  START2_TRIGGER="peek_"
  START2_TRIGGER="prg_"
  START2_TRIGGER="prop_"
  START2_TRIGGER="quark_"
  START2_TRIGGER="reverse_"
  START2_TRIGGER="scan_"
  START2_TRIGGER="seq_"
  START2_TRIGGER="skip_"
  START2_TRIGGER="str_"
  START2_TRIGGER="tex_"
  START2_TRIGGER="tl_"
  START2_TRIGGER="token_"
  START2_TRIGGER="use_"
  START2_TRIGGER="vbox_"
  START2_TRIGGER="vcoffin_"
  START2_TRIGGER="xetex_"
  STOP1_TRIGGER=""
  STOP2_TRIGGER="n"
  STOP2_TRIGGER="N"
  STOP2_TRIGGER="x"
    TEXT_COLOR=1
    DEFAULT_FONT=1
    ITALIC=1

SWITCH="LaTeX3 Kernel Constants"
  ENABLED=1
  MODE_FILTER="TeX"
  START="\"
  STOP="~Alpha@_:"
  HIGHLIGHT_START=1
  STEP_OVER_STOP=0
  HIGHLIGHT_STOP=0
  SCOPE=0
  DOMINANT_PRIORITY=0
  STRICT_PRIORITY=0
  PRIORITY=5
  INDENTED=0
  BOLN_ONLY=0
  CASE_SENSITIVE=1
  START1_TRIGGER=""
  START2_TRIGGER="c_"
  STOP1_TRIGGER=""
  STOP2_TRIGGER=""
    TEXT_COLOR=6
    DEFAULT_FONT=1
    ITALIC=1

Result:

enter image description here


For the record, here's my font-lock settings for Emacs with LaTeX3. To make use of these, I define a latex3-mode which is derived from the inbuild latex-mode. (Note: I don't use AucTeX, I use the simple TeX modes.) The idea of the font-locking is similar to Joseph's in that it adds more matches for highlighting. I've added a specials for core functions (probably not included them all as yet). There's also a distinction between macros with a _ and ones without (such as ordinary LaTeX2e macros/functions and the xparse ones). I could probably do with defining a few more faces to further distinguish, but this is currently working well enough for me.

(defconst latex3-font-lock-keywords
   (eval-when-compile
     (let* (;; Commands relevant to data structures
            (specials "\\(bool\\|char\\|clist\\|cs\\|exp\\|file\\|group\\|keys\\|prop\\|q\\|seq\\|tl\\)_[a-zA-Z_:]+")
            (general "\\([a-zA-Z_:]+\\)")
            (camel "\\([a-zA-Z@]+\\)")
            (nocamel "\\(?:[^a-zA-Z@_:]\\)")
            (slash "\\\\")
            )
       (list
        (list (concat "\\(" slash specials "\\)")
              1 'font-lock-function-name-face)
        (list (concat "\\(" slash camel "\\)" nocamel) 1 'font-lock-function-name-face)
        (list (concat "\\(" slash general "\\)") 1 'font-lock-variable-name-face)
        )
       )
     )
  "Extra commands to highlight in LaTeX3 modes."
  )

(define-derived-mode latex3-mode latex-mode "LaTeX3"
  "Major mode to edit LaTeX3 files."
  (set (make-local-variable 'font-lock-defaults)
       '((tex-font-lock-keywords latex3-font-lock-keywords) 
         nil nil ((?$ . "\"")) nil
         ;; Who ever uses that anyway ???
         (font-lock-mark-block-function . mark-paragraph)
         (font-lock-syntactic-face-function
          . tex-font-lock-syntactic-face-function)
         (font-lock-unfontify-region-function
          . tex-font-lock-unfontify-region)
         (font-lock-syntactic-keywords
          . tex-font-lock-syntactic-keywords)
         (parse-sexp-lookup-properties . t)))
 )

(add-hook 'latex3-mode-hook (function (lambda () (setq ispell-parser 'tex))))
(add-hook 'latex3-mode-hook '(lambda () (flyspell-mode 0)))

Here's a screenshot (where you can see that I should add box and vbox to my list of special functions):

Emacs with L3 font-locking


TeXworks uses a simple regex-based approach to syntax highlighting, with the information stored in the file syntax-patterns.txt inside the folder TeXworks/configuration, which lives in a system-dependent location. I have a set of patterns for working with .dtx (LaTeX documented source) files, which include highlighting for expl3 code:

[LaTeX DTX]

# comments
red        Y    \^\^A.*

# Guards
orange         N    %<(?:[A-Za-z0-9!\|]+|.)>
limegreen      N    %<\*(?:[A-Za-z0-9!\|]+|.)>
crimson        N    %</(?:[A-Za-z0-9!\|]+|.)>
darkviolet     N    %<@@=(?:[A-Za-z]+|.)>

# special characters
darkred        N    \^\^\^\^\^[0-9a-z]{5}
darkred        N    \^\^\^\^[0-9a-z]{4}
darkred        N    \^\^\^[0-9a-z]{3}
darkred        N    \^\^[0-9a-z]{2}
darkred        N    [$#^_{}&]
gray        N    ^%%.*
gray        N    ^%

# Macrocode
green        N    \\(?:begin|end)\{macrocode\}

# LaTeX environments
darkgreen    N    \\(?:begin|end)\s*\{[^}]*\}

# control sequences
blue        N    \\(?:[A-Za-z@:_]+|.)

(The colour scheme is based on that used by WinEdt for .dtx editing, as I used to use WinEdt.)

The key line is the last one, as regex \\(?:[A-Za-z@:_]+|.) matches both expl3 and 'standard' LaTeX2e macro names. If you wanted to only highlight expl3 code-level macros in one colour, you could go with something like

# functions
blue        N    \\(?:[A-Za-z_]+):(?:[A-Za-z_:]+|.)
# Variables
blue    N    \\(?:[A-Za-z_]+)_(?:[A-Za-z_]+|.)

which then won't highlight document/design level (just letter in the name) or LaTeX2e internal (@ in the name) control sequences. You could of course get more sophisticated and give them all different highlighting! You could also extend the regexes in the way Andrew Stacey does in his answer to differentiate expl3 core and 'additional' names, using a more sophisticated set of regexes.

Note that I've also extended the .dtx guards recognised to cover the %<@@=...> one that l3doscrip adds to the 'standard' set, for marking internal names. It's covered by the line

darkviolet     N    %<@@=(?:[A-Za-z]+|.)>

where I had to find a new colour to fit in with those that were already in use!