238 lines
7.8 KiB
JavaScript
238 lines
7.8 KiB
JavaScript
'use strict';
|
|
|
|
var go$1 = require('@lezer/go');
|
|
var language = require('@codemirror/language');
|
|
var autocomplete = require('@codemirror/autocomplete');
|
|
var common = require('@lezer/common');
|
|
|
|
/**
|
|
A collection of Go-related [snippets](https://codemirror.net/6/docs/ref/#autocomplete.snippet).
|
|
*/
|
|
const snippets = [
|
|
autocomplete.snippetCompletion("func ${name}(${params}) ${type} {\n\t${}\n}", {
|
|
label: "func",
|
|
detail: "declaration",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("func (${receiver}) ${name}(${params}) ${type} {\n\t${}\n}", {
|
|
label: "func",
|
|
detail: "method declaration",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("var ${name} = ${value}", {
|
|
label: "var",
|
|
detail: "declaration",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("type ${name} ${type}", {
|
|
label: "type",
|
|
detail: "declaration",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("const ${name} = ${value}", {
|
|
label: "const",
|
|
detail: "declaration",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("type ${name} = ${type}", {
|
|
label: "type",
|
|
detail: "alias declaration",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("for ${init}; ${test}; ${update} {\n\t${}\n}", {
|
|
label: "for",
|
|
detail: "loop",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("for ${i} := range ${value} {\n\t${}\n}", {
|
|
label: "for",
|
|
detail: "range",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("select {\n\t${}\n}", {
|
|
label: "select",
|
|
detail: "statement",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("case ${}:\n${}", {
|
|
label: "case",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("switch ${} {\n\t${}\n}", {
|
|
label: "switch",
|
|
detail: "statement",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("switch ${}.(${type}) {\n\t${}\n}", {
|
|
label: "switch",
|
|
detail: "type statement",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("if ${} {\n\t${}\n}", {
|
|
label: "if",
|
|
detail: "block",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("if ${} {\n\t${}\n} else {\n\t${}\n}", {
|
|
label: "if",
|
|
detail: "/ else block",
|
|
type: "keyword"
|
|
}),
|
|
autocomplete.snippetCompletion("import ${name} \"${module}\"\n${}", {
|
|
label: "import",
|
|
detail: "declaration",
|
|
type: "keyword"
|
|
}),
|
|
];
|
|
|
|
const cache = new common.NodeWeakMap();
|
|
const ScopeNodes = new Set([
|
|
"SourceFile", "Block",
|
|
"FunctionDecl", "MethodDecl", "FunctionLiteral",
|
|
"ForStatement", "SwitchStatement", "TypeSwitchStatement", "IfStatement",
|
|
]);
|
|
function defIDs(type, spec) {
|
|
return (node, def) => {
|
|
outer: for (let cur = node.node.firstChild, depth = 0, parent = null;;) {
|
|
while (!cur) {
|
|
if (!depth)
|
|
break outer;
|
|
depth--;
|
|
cur = parent.nextSibling;
|
|
parent = parent.parent;
|
|
}
|
|
if (spec && cur.name == spec || cur.name == "SpecList") {
|
|
depth++;
|
|
parent = cur;
|
|
cur = cur.firstChild;
|
|
}
|
|
else {
|
|
if (cur.name == "DefName")
|
|
def(cur, type);
|
|
cur = cur.nextSibling;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
}
|
|
const gatherCompletions = {
|
|
FunctionDecl: defIDs("function"),
|
|
VarDecl: defIDs("var", "VarSpec"),
|
|
ConstDecl: defIDs("constant", "ConstSpec"),
|
|
TypeDecl: defIDs("type", "TypeSpec"),
|
|
ImportDecl: defIDs("constant", "ImportSpec"),
|
|
Parameter: defIDs("var"),
|
|
__proto__: null
|
|
};
|
|
function getScope(doc, node) {
|
|
let cached = cache.get(node);
|
|
if (cached)
|
|
return cached;
|
|
let completions = [], top = true;
|
|
function def(node, type) {
|
|
let name = doc.sliceString(node.from, node.to);
|
|
completions.push({ label: name, type });
|
|
}
|
|
node.cursor(common.IterMode.IncludeAnonymous).iterate(node => {
|
|
if (top) {
|
|
top = false;
|
|
}
|
|
else if (node.name) {
|
|
let gather = gatherCompletions[node.name];
|
|
if (gather && gather(node, def) || ScopeNodes.has(node.name))
|
|
return false;
|
|
}
|
|
else if (node.to - node.from > 8192) {
|
|
// Allow caching for bigger internal nodes
|
|
for (let c of getScope(doc, node.node))
|
|
completions.push(c);
|
|
return false;
|
|
}
|
|
});
|
|
cache.set(node, completions);
|
|
return completions;
|
|
}
|
|
const Identifier = /^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/;
|
|
const dontComplete = [
|
|
"String", "LineComment", "BlockComment",
|
|
"DefName", "LabelName", "FieldName",
|
|
".", "?."
|
|
];
|
|
/**
|
|
Completion source that looks up locally defined names in Go code.
|
|
*/
|
|
const localCompletionSource = context => {
|
|
let inner = language.syntaxTree(context.state).resolveInner(context.pos, -1);
|
|
if (dontComplete.indexOf(inner.name) > -1)
|
|
return null;
|
|
let isWord = inner.name == "VariableName" ||
|
|
inner.to - inner.from < 20 && Identifier.test(context.state.sliceDoc(inner.from, inner.to));
|
|
if (!isWord && !context.explicit)
|
|
return null;
|
|
let options = [];
|
|
for (let pos = inner; pos; pos = pos.parent) {
|
|
if (ScopeNodes.has(pos.name))
|
|
options = options.concat(getScope(context.state.doc, pos));
|
|
}
|
|
return {
|
|
options,
|
|
from: isWord ? inner.from : context.pos,
|
|
validFor: Identifier
|
|
};
|
|
};
|
|
|
|
/**
|
|
A language provider based on the [Lezer Go
|
|
parser](https://github.com/lezer-parser/go), extended with
|
|
folding and indentation information.
|
|
*/
|
|
const goLanguage = language.LRLanguage.define({
|
|
name: "go",
|
|
parser: go$1.parser.configure({
|
|
props: [
|
|
language.indentNodeProp.add({
|
|
IfStatement: language.continuedIndent({ except: /^\s*({|else\b)/ }),
|
|
LabeledStatement: language.flatIndent,
|
|
"SwitchBlock SelectBlock": context => {
|
|
let after = context.textAfter, closed = /^\s*\}/.test(after), isCase = /^\s*(case|default)\b/.test(after);
|
|
return context.baseIndent + (closed || isCase ? 0 : context.unit);
|
|
},
|
|
Block: language.delimitedIndent({ closing: "}" }),
|
|
BlockComment: () => null,
|
|
Statement: language.continuedIndent({ except: /^{/ }),
|
|
}),
|
|
language.foldNodeProp.add({
|
|
"Block SwitchBlock SelectBlock LiteralValue InterfaceType StructType SpecList": language.foldInside,
|
|
BlockComment(tree) { return { from: tree.from + 2, to: tree.to - 2 }; }
|
|
})
|
|
]
|
|
}),
|
|
languageData: {
|
|
closeBrackets: { brackets: ["(", "[", "{", "'", '"', "`"] },
|
|
commentTokens: { line: "//", block: { open: "/*", close: "*/" } },
|
|
indentOnInput: /^\s*(?:case\b|default\b|\})$/
|
|
}
|
|
});
|
|
let kwCompletion = (name) => ({ label: name, type: "keyword" });
|
|
const keywords = "interface struct chan map package go return break continue goto fallthrough else defer range true false nil".split(" ").map(kwCompletion);
|
|
/**
|
|
Go support. Includes [snippet](https://codemirror.net/6/docs/ref/#lang-go.snippets) and local
|
|
variable completion.
|
|
*/
|
|
function go() {
|
|
let completions = snippets.concat(keywords);
|
|
return new language.LanguageSupport(goLanguage, [
|
|
goLanguage.data.of({
|
|
autocomplete: autocomplete.ifNotIn(dontComplete, autocomplete.completeFromList(completions))
|
|
}),
|
|
goLanguage.data.of({
|
|
autocomplete: localCompletionSource
|
|
})
|
|
]);
|
|
}
|
|
|
|
exports.go = go;
|
|
exports.goLanguage = goLanguage;
|
|
exports.localCompletionSource = localCompletionSource;
|
|
exports.snippets = snippets;
|