codemirror-languageservice
Version:
Integrate a Language Server Protocol compatible language service into CodeMirror
137 lines • 4.57 kB
JavaScript
import { insertCompletionText, snippet } from '@codemirror/autocomplete';
import { fromMarkupContent } from './markup-content.js';
import { getTextDocument } from './text-document.js';
let alphabet = 'abcdefghijklmnopqrstuvwxyz';
alphabet += alphabet.toUpperCase();
const defaultFromCompletionItemKind = (kind) => {
switch (kind) {
case 1:
case 15:
return 'text';
case 2:
return 'method';
case 3:
return 'function';
case 4:
case 7:
return 'class';
case 5:
case 10:
return 'property';
case 6:
case 12:
case 18:
case 23:
return 'variable';
case 8:
case 22:
case 25:
return 'type';
case 9:
return 'namespace';
case 13:
return 'enum';
case 11:
case 14:
case 24:
return 'keyword';
case 16:
case 21:
return 'constant';
case 20:
return 'enum';
default:
}
};
/**
* Create an LSP based completion source.
*
* @param options
* Options to configure the completion.
* @returns
* A CodeMirror completion source that uses LSP based completions.
*/
export function createCompletionSource(options) {
const fromCompletionItemKind = options.fromCompletionItemKind ?? defaultFromCompletionItemKind;
let triggerCharacters = alphabet;
if (options.triggerCharacters) {
triggerCharacters += options.triggerCharacters;
}
return async (context) => {
const textDocument = getTextDocument(context.state);
let completionContext;
if (context.explicit) {
completionContext = {
triggerKind: 1
};
}
else {
const triggerCharacter = context.state.sliceDoc(context.pos - 1, context.pos);
if (!triggerCharacters.includes(triggerCharacter)) {
return null;
}
completionContext = {
triggerCharacter,
triggerKind: 2
};
}
const completions = await options.doComplete(textDocument, textDocument.positionAt(context.pos), completionContext);
if (!completions) {
return null;
}
if (textDocument.version !== getTextDocument(context.view?.state ?? context.state).version) {
return null;
}
let items;
let itemDefaults;
if (Symbol.iterator in completions) {
items = completions;
}
else {
items = completions.items;
itemDefaults = completions.itemDefaults;
}
const completionOptions = [];
let minFrom = context.pos;
let maxTo = context.pos;
for (const item of items) {
const { commitCharacters, detail, documentation, kind, label, textEdit, textEditText } = item;
const completion = {
commitCharacters,
detail,
info: documentation &&
(() => fromMarkupContent(documentation, document.createDocumentFragment(), options)),
label,
section: options.section,
type: fromCompletionItemKind(kind)
};
if (textEdit) {
const range = 'range' in textEdit ? textEdit.range : textEdit.replace;
const from = textDocument.offsetAt(range.start);
const to = textDocument.offsetAt(range.end);
if (from < minFrom) {
minFrom = from;
}
if (to > maxTo) {
maxTo = to;
}
const insert = textEdit.newText;
const insertTextFormat = item.insertTextFormat ?? itemDefaults?.insertTextFormat;
completion.apply = (view) => insertTextFormat === 2
? snippet(insert.replaceAll(/\$(\d+)/g, '$${$1}'))(view, completion, from, to)
: view.dispatch(insertCompletionText(view.state, insert, from, to));
}
else if (textEditText) {
completion.apply = textEditText;
}
completionOptions.push(completion);
}
return {
from: minFrom,
to: maxTo,
commitCharacters: itemDefaults?.commitCharacters,
options: completionOptions
};
};
}
//# sourceMappingURL=completion.js.map