adding indentation

If wanted to indent a specific keyword after a user types it what would be the best strategy.

The keywords are tagged correctly as nodes with a lezer grammar.

I’m currently using an update listener with a regex to check each line and add spaces but the results are flaky. Is an indentation more than just some spaces? I wanted to use the regex to be certain the word only appears at the start of a line.


If your language’s auto-indent provides the desired indentation for such lines, indentOnInput, with appropriate language data adding the proper regexp, might do what you want here.

Looking at the lang-lezer grammar package:

CM indents the next line after a “{” and knows to unindent on “}”. It’s not obvious to me how it’s doing it.

Where, between the lezer grammar and the parser does the editor know to indent after “{” and unindent after “}”?

Another angle I’m looking at comes from the “Writing a Language Package” example. It seems like indentNodeProp might be what I’m looking for?

With a line in my language like

color: blue

I would like it to indent whenever color: is typed

I’m not sure how to apply that to the parser using this grammar with the target nodes being of the type Modifier.

@top Stack { expression }

@skip { space  }

expression {
  Modifier | StackModifier | Keyword | NotText

@tokens {
  Keyword {
  | "**:"
  | "#:"
  | "##:"
  | "box:"
  | "form:"
  | "line:"
  | "link:"
  | "read:"
  | "title:"
  | "subtitle:" 
  | "supertitle:" 

 StackModifier {

  ( "filter:"
  | "stackName" )  ![\n]* } 


  NotText {  
  (  "backdrop:"  
  | "blank:" 
  | "image:" 
  | "tap:"
  | "video:")  ![\n]* } 

  Modifier {
  ( "~:"
  | "background:"
  | "blur:"
  | "box-color:"
  | "box-corner:"
  | "border-color:"
  | "border-thickness:"
  | "color:"
  | "columns:"
  | "corner-radius:"
  | "decoration:"
  | "delay:"
  | "font:"
  | "height:"
  | "line-height:"
  | "muted:"
  | "opacity:"
  | "padding:"
  | "padding-bottom:"
  | "padding-left:"
  | "padding-right:"
  | "padding-top:"
  | "position:"
  | "repeat:"
  | "size:"
  | "style:"
  | "url:"
  | "weight:"
  | "width:" ) ![\n]* } 

  space { $[ \t\n\r]+ }



If I add

            Stack: context => {
                return context.column(context.node.from) + context.unit;

The nodeType stack has the correct function as a prop and every line indents.

However when I add the following, changing the selector to Modifier the nodeType Modifier has the correct function as a prop but no indentation takes place. How do I tell code mirror to indent lines with Modifiers?

            Modifier: context => {
                return context.column(context.node.from) + context.unit;

Another approach which is working for me, as I also want it to be an auto-correcting linter is to use an updateListener and iterate thru the syntax tree and check the Modifier nodes for the correct indentation.

const simpleLinter = EditorView.updateListener.of((update) => {
  if (!update.docChanged) return;
  // don't take action on deletes
  for (const tr of update.transactions) {
    if (tr.isUserEvent("delete.backward")) return;
  syntaxTree(update.state).cursor().iterate(node => {
    const pos = node.from;
    const line = update.state.doc.lineAt(pos);
    let text = line.text;
    let indent = false;

    if ( === "Modifier") {
     // lint the line and check it's indentation
      [text, indent] = checkModifierIndent(text);
    if (indent) {
      let indentation = indentString(update.state, 2);
      changes.push({ from: line.from, to: line.from, insert: indentation });
  if (changes.length > 0) {
    update.view.dispatch({ changes });

See this blog post for a general overview of how syntax-tree indentation works. Sometimes you need to write a bit more complicated indentation functions that figure out where in the node you are (i.e. before an else in a conditional) to adjust indentation.

1 Like