Indentation and folding without a language

Hi Marijn,

I’d like to enable folding and auto indentation (on enter, based on previous line) for plain text. I’ve tried, and it seems /next’s /fold plugin (and /input/indent) don’t support this?

Is creating a custom language to enable these features the best/only way, or is there something else/better/simpler I could do?

Thank you in advance for your time, and everything you’re doing.

You can wire in whatever logic you want through the indentService and foldService facets, which aren’t dependent on syntax trees.

In case others need this: here is an example of a plain text extension to indent the same amount as the previous line:

import {indentService} from "@codemirror/language"

...

const indentPlainTextExtension = indentService.of((context, pos) => {
  const previousLine = context.lineAt(pos, -1)
  return previousLine.text.match(/^(\s)*/)[0].length
})

This extension would then be added to the list of extensions in your EditorState.create(...) call.

4 Likes

This isn’t working for me. Here is what I have:

import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { indentService } from "@codemirror/language";

function main(): void {
  let startState = EditorState.create({
    doc: '',
    extensions: [
      indentService.of((context, pos) => {
        const previousLine = context.lineAt(pos, -1);
        return previousLine.text.match(/^\s*/)![0].length;
      }),
    ],
  });
  
  let view = new EditorView({
    state: startState,
    parent: document.body,
  });
}

window.addEventListener('DOMContentLoaded', main, {once:true});

I tried adding a console.log to the indent service callback, and it doesn’t seem to be being called when I press enter after typing a line. Any idea where I might be going wrong?

These are the submodule versions I have:

@codemirror/view: 6.1.2
@codemirror/state: 6.1.0
@codemirror/language: 6.2.1

That’s not the previous line, that’s the current line (pos is the start of the line being indented).

Doesn’t the -1 second parameter mean that if a linebreak is touching the given position, it should take the line before the break?

In any case, I think I’ve found the problem I was having (no indentation happening at all): I hadn’t explicitly added a keymap that wires up the Enter key to the insertNewlineAndIndent command.

No, that only affects simulated line breaks which are not part of the document yet. Real line breaks mean the positions before and after them can be assigned to the proper line without looking at the bias at all.

1 Like

Interesting! Thank you.

Would you mind showing your solution in-code? It’s not clear to me how one would wire up the Enter key like that.

EDIT:
I tried this, but got an error saying that InsertNewlineAndIndent wasn’t defined:

{ key: 'Enter', run: insertNewlineAndIndent }

I figured it out. I just needed to import it:

import { insertNewlineAndIndent } from '@codemirror/commands'

Here is a modification to support editors that are setup with tab character indentation (i.e. editors that include the extension: indentUnit.of('\t')).

const indentPlainTextExtension = indentService.of((context, pos) => {
  const previousLine = context.lineAt(pos, -1)
  const previousLineText = previousLine.text.replaceAll('\t', ' '.repeat(context.state.tabSize))
  return previousLineText.match(/^(\s)*/)[0].length
})