Example: Autocompletion

The @codemirror/autocomplete package provides functionality for displaying input suggestions in the editor. This example shows how to enable it and how to write your own completion sources.

Setting Up

Autocompletion is enabled by including the autocompletion extension (which is included in the basic setup) in your configuration. Some language packages come with support for proper autocompletion built in, such as the HTML package.

By default, the plugin will look for completions whenever the user types something, but you can configure it to only run when activated explicitly via a command.

The default completion keymap binds Ctrl-Space to start completion, arrows to select a completion, Enter to pick it, and Escape to close the tooltip. It is activated by default when you add the extension, but you can disable that if you want to provide your own bindings.

The default bindings do not bind Tab to acceptCompletion, for reasons outlined in the Tab-handling example.

Providing Completions

The completions that the extension shows come from one or more completion sources, which are functions that take a completion context—an object with information about the completion being requested—and return an object that describes the range that's being completed and the options to show. Sources may run asynchronously by returning a promise.

The easiest way to connect a completion source to an editor is to use the override option.

This editor uses the following completion function:

import {CompletionContext} from "@codemirror/autocomplete"

function myCompletions(context: CompletionContext) {
  let word = context.matchBefore(/\w*/)
  if (word.from == word.to && !context.explicit)
    return null
  return {
    from: word.from,
    options: [
      {label: "match", type: "keyword"},
      {label: "hello", type: "variable", info: "(World)"},
      {label: "magic", type: "text", apply: "⠁⭒*.✩.*⭒⠁", detail: "macro"}
    ]
  }
}

This is a very crude way to provide completions, without really looking at the editing context at all. But it demonstrates the basic things a completion function must do.

Completions themselves are objects with a label property, which provides both the text to show in the options list and the text to insert when the completion is picked.

By default, the completion list shows only the label. You'll usually also want to provide a type property, which determines the icon shown next to the completion. detail can be given to show a short string after the label, and info can be used for longer text, shown in a window to the side of the list when the completion is selected.

To override what happens when a completion is picked, you can use the apply property, which can be either a string to replace the completion range with, or a function that will be called to apply an arbitrary action.

When you are providing your completion source as a generic extension, or working with mixed-language documents, setting a global source is not practical. When no override is given, the plugin uses EditorState.languageDataAt with the name "autocomplete" to look up language-appropriate completion functions. Registering those is done with a language object's data facet. For example by including something like this in your state configuration:

myLanguage.data.of({
  autocomplete: myCompletions
})

You can also directly put an array of completion objects in this property, which will cause the library to simply use those (wrapped by completeFromList) as a source.

Sorting and Filtering

The trivial completion source used above didn't have to filter completions against the input—the plugin will take care of that. It uses a form of fuzzy matching to filter and rank completions against the currently typed text, and will highlight the letters in each completion that match.

To influence the ranking of completions, you can give completion objects a boost property, which adds to or subtracts from their match score.

If you really do want to filter and order completions yourself, you can include a filter: false property in your result object to disable the built-in filtering.

Completion Result Validity

Some sources need to recompute their results on every keypress, but for many of them, this is unnecessary and inefficient. They return a full list of completions for a given construct, and as long as the user is typing (or backspacing) inside that construct, that same list can be used (filtered for the currently typed input) to populate the completion list.

This is why it is very much recommended to provide a validFor property on your completion result. It should contain a function or regular expression that tells the extension that, as long as the updated input (the range between the result's from property and the completion point) matches that value, it can continue to use the list of completions.

In the myCompletions function above, since all its completions are simple words, a value like validFor: /^\w*$/ would be appropriate.

Completing from Syntax

To make a completion source a bit more intelligent, it is often useful to inspect the syntax tree around the completion point, and use that to get a better picture of what kind of construct is being completed.

As an example, this completion source for JavaScript will complete (some) JSDoc tags in block comments.

import {syntaxTree} from "@codemirror/language"

const tagOptions = [
  "constructor", "deprecated", "link", "param", "returns", "type"
].map(tag => ({label: "@" + tag, type: "keyword"}))

function completeJSDoc(context: CompletionContext) {
  let nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1)
  if (nodeBefore.name != "BlockComment" ||
      context.state.sliceDoc(nodeBefore.from, nodeBefore.from + 3) != "/**")
    return null
  let textBefore = context.state.sliceDoc(nodeBefore.from, context.pos)
  let tagBefore = /@\w*$/.exec(textBefore)
  if (!tagBefore && !context.explicit) return null
  return {
    from: tagBefore ? nodeBefore.from + tagBefore.index : context.pos,
    options: tagOptions,
    validFor: /^(@\w*)?$/
  }
}

The function starts by finding the syntax node directly in front of the completion position. If that is not a block comment, or it is a block comment without a /** start marker, it returns null to indicate it has no completions.

If the completion does happen in a block comment, we check whether there is an existing tag in front of it. If there is, that is included in the completion (see the from property in the returned object). If there isn't, we only complete if the completion was explicitly started.

You can now use an extension like this to enable this completion source for JavaScript content.

import {javascriptLanguage} from "@codemirror/lang-javascript"

const jsDocCompletions = javascriptLanguage.data.of({
  autocomplete: completeJSDoc
})

Try it out: