cm-tarnation
Version:
An alternative parser for CodeMirror 6
108 lines • 4.33 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { ensureSyntaxTree } from "@codemirror/language";
import { NodeTypeProp } from "../grammar/node";
import { TarnationCompletionContext } from "./context";
/** Handles autocompletion for a {@link TarnationLanguage}. */
export class Autocompleter {
language;
/** A map of node names to completion handlers. */
handlers = new Map();
/** @param language - The {@link TarnationLanguage} to provide completions for. */
constructor(language) {
this.language = language;
if (this.language.configure.autocomplete) {
for (const key in this.config) {
const handler = this.config[key];
if (key === "*" || typeof handler !== "function")
continue;
for (const name of key.trim().split(/\s+/))
this.handlers.set(name, handler);
}
for (const node of this.language.grammar.repository.nodes()) {
if (node.autocomplete && !this.handlers.has(node.autocomplete)) {
console.warn(`No autocomplete handler for ${node.autocomplete}`);
}
}
}
}
/** Configuration for the autocomplete. */
get config() {
return this.language.configure.autocomplete;
}
/**
* Gets a handler via a name,
*
* @param handler - The name of the handler.
*/
get(handler) {
if (!handler)
return null;
if (!this.handlers.has(handler))
return null;
return this.handlers.get(handler);
}
/**
* Gets an autocomplete handler for a {@link Node}.
*
* @param type - The {@link Node} to get a handler for.
*/
getHandlerFor(type) {
if (!type)
return null;
let handler = null;
if (type.autocomplete)
handler = this.get(type.autocomplete);
if (!handler && this.config._alsoTypeNames)
handler = this.get(type.name);
if (!handler && this.config._alsoEmitNames)
handler = this.get(type.type.name);
return handler;
}
/**
* Tries to get an autocomplete handler for a `SyntaxNode`. Will traverse
* up the node's parents if allowed to do so in the configuration.
*
* @param node - The `SyntaxNode` to get a handler for.
* @param root - Internal parameter. Defaults to true.
*/
traverse(node, root = true) {
if (!node)
return { node: null, type: null, handler: null, traversed: false };
const type = node?.type.prop(NodeTypeProp) ?? null;
const handler = this.getHandlerFor(type);
if (!type || !handler) {
if (this.config._traverseUpwards && node.parent) {
const { node: child, type, handler } = this.traverse(node.parent, false);
if (child && type && handler) {
return { node: child, type, handler, traversed: true };
}
}
if (root && this.config["*"]) {
return { node, type, handler: this.config["*"], traversed: false };
}
}
return { node, type, handler, traversed: false };
}
/**
* CodeMirror autocompletion handler function. Delegates to autocomplete
* handlers defined in the language's configuartion.
*
* @param context - The `CompletionContext` provided by CodeMirror.
*/
handle(context) {
if (!this.handlers.size)
return null;
const tree = ensureSyntaxTree(context.state, context.pos);
if (!tree)
return null;
const current = tree.resolve(context.pos, -1);
const { node, type, handler, traversed } = this.traverse(current);
if (!node || !type || !handler)
return null;
const mutated = TarnationCompletionContext.mutate(context, type, tree, node, traversed);
return handler.call(this.language, mutated);
}
}
//# sourceMappingURL=autcomplete.js.map