fish-lsp
Version:
LSP implementation for fish/fish-shell
322 lines (321 loc) • 10.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentationCache = exports.extraBuiltins = void 0;
exports.createCachedItem = createCachedItem;
exports.resolveItem = resolveItem;
exports.getFunctionDocString = getFunctionDocString;
exports.getStaticDocString = getStaticDocString;
exports.getAbbrDocString = getAbbrDocString;
exports.getBuiltinDocString = getBuiltinDocString;
exports.getAliasDocString = getAliasDocString;
exports.getEventHandlerDocString = getEventHandlerDocString;
exports.getVariableDocString = getVariableDocString;
exports.getCommandDocString = getCommandDocString;
exports.initializeMap = initializeMap;
exports.initializeDocumentationCache = initializeDocumentationCache;
const vscode_languageserver_1 = require("vscode-languageserver");
const exec_1 = require("./exec");
const builtins_1 = require("./builtins");
function createCachedItem(type, uri) {
return {
type: type,
resolved: false,
uri: uri,
referenceUris: uri ? new Set([...uri]) : new Set(),
};
}
async function getNewDocString(name, item) {
switch (item.type) {
case vscode_languageserver_1.SymbolKind.Variable:
return await getVariableDocString(name);
case vscode_languageserver_1.SymbolKind.Function:
return await getFunctionDocString(name);
case vscode_languageserver_1.SymbolKind.Class:
return await getBuiltinDocString(name);
default:
return undefined;
}
}
async function resolveItem(name, item, uri) {
if (uri !== undefined) {
item.referenceUris.add(uri);
}
if (item.resolved) {
return item;
}
if (item.type === vscode_languageserver_1.SymbolKind.Function) {
item.uri = await getFunctionUri(name);
}
const newDocStr = await getNewDocString(name, item);
item.resolved = true;
if (!newDocStr) {
return item;
}
item.docs = newDocStr;
return item;
}
async function getFunctionUri(name) {
const uriString = await (0, exec_1.execEscapedCommand)(`type -ap ${name}`);
const uri = uriString.join('\n').trim();
if (!uri) {
return undefined;
}
return uri;
}
function _escapePathStr(functionTitleLine) {
const afterComment = functionTitleLine.split(' ').slice(1);
const pathIndex = afterComment.findIndex((str) => str.includes('/'));
const path = afterComment[pathIndex]?.toString() || '';
return [
'**' + afterComment.slice(0, pathIndex).join(' ').trim() + '**',
`*\`${path}\`*`,
'**' + afterComment.slice(pathIndex + 1).join(' ').trim() + '**',
].join(' ');
}
function _ensureMinLength(arr, minLength, fillValue) {
while (arr.length < minLength) {
arr.push(fillValue);
}
return arr;
}
async function getFunctionDocString(name) {
const functionDoc = await (0, exec_1.execCmd)(`functions ${name}`);
const title = `___(function)___ - _${name}_`;
if (!functionDoc)
return;
return [
title,
'___',
'```fish',
functionDoc.join('\n'),
'```',
].join('\n');
}
async function getStaticDocString(item) {
let result = [
'```text',
`${item.label} - ${item.documentation}`,
'```',
].join('\n');
item.examples?.forEach((example) => {
result += [
'___',
'```fish',
`# ${example.title}`,
example.shellText,
'```',
].join('\n');
});
return result;
}
async function getAbbrDocString(name) {
const items = await (0, exec_1.execCmd)('abbr --show | string split \' -- \' -m1 -f2');
function getAbbr(items) {
const start = `${name} `;
for (const item of items) {
if (item.startsWith(start)) {
return [start.trimEnd(), item.slice(start.length)];
}
}
return ['', ''];
}
const [title, body] = getAbbr(items);
return [
`Abbreviation: \`${title}\``,
'___',
'```fish',
body.trimEnd(),
'```',
].join('\n') || '';
}
async function getBuiltinDocString(name) {
if (!(0, builtins_1.isBuiltin)(name))
return undefined;
const cmdDocs = await (0, exec_1.execCommandDocs)(name);
if (!cmdDocs) {
return undefined;
}
const splitDocs = cmdDocs.split('\n');
const startIndex = splitDocs.findIndex((line) => line.trim() === 'NAME');
return [
`__${name.toUpperCase()}__ - _https://fishshell.com/docs/current/cmds/${name.trim()}.html_`,
'___',
'```man',
splitDocs.slice(startIndex).join('\n'),
'```',
].join('\n');
}
async function getAliasDocString(label, line) {
return [
`Alias: _${label}_`,
'___',
'```fish',
line.split('\t')[1],
'```',
].join('\n');
}
async function getEventHandlerDocString(documentation) {
const [label, ...commandArr] = documentation.split(/\s/, 2);
const command = commandArr.join(' ');
const doc = await getFunctionDocString(command);
if (!doc) {
return [
`Event: \`${label}\``,
'___',
`Event handler for \`${command}\``,
].join('\n');
}
return [
`Event: \`${label}\``,
'___',
doc,
].join('\n');
}
async function getVariableDocString(name) {
const vName = name.startsWith('$') ? name.slice(name.lastIndexOf('$')) : name;
const out = await (0, exec_1.execCmd)(`set --show --long ${vName}`);
const { first, middle, last } = out.reduce((acc, curr, idx, arr) => {
if (idx === 0) {
acc.first = curr;
}
else if (idx === arr.length - 1) {
acc.last = curr;
}
else {
acc.middle.push(curr);
}
return acc;
}, { first: '', middle: [], last: '' });
return first ? [
first,
'___',
middle.join('\n'),
'___',
last,
].join('\n') : undefined;
}
async function getCommandDocString(name) {
const cmdDocs = await (0, exec_1.execCommandDocs)(name);
if (!cmdDocs) {
return undefined;
}
const splitDocs = cmdDocs.split('\n');
const startIndex = splitDocs.findIndex((line) => line.trim() === 'NAME');
return [
'```man',
splitDocs.slice(startIndex).join('\n'),
'```',
].join('\n');
}
function initializeMap(collection, type, _uri) {
const items = new Map();
collection.forEach((item) => {
items.set(item, createCachedItem(type));
});
return items;
}
exports.extraBuiltins = [
'export',
];
class DocumentationCache {
_variables = new Map();
_functions = new Map();
_builtins = new Map();
_unknowns = new Map();
get items() {
return [
...this._variables.keys(),
...this._functions.keys(),
...this._builtins.keys(),
...this._unknowns.keys(),
];
}
async parse(uri) {
this._unknowns = initializeMap([], vscode_languageserver_1.SymbolKind.Null, uri);
await Promise.all([
(0, exec_1.execEscapedCommand)('set -n'),
(0, exec_1.execEscapedCommand)('functions -an | string collect'),
(0, exec_1.execEscapedCommand)('builtin -n'),
]).then(([vars, funcs, builtins]) => {
this._variables = initializeMap(vars, vscode_languageserver_1.SymbolKind.Variable, uri);
this._functions = initializeMap(funcs, vscode_languageserver_1.SymbolKind.Function, uri);
this._builtins = initializeMap(builtins, vscode_languageserver_1.SymbolKind.Class, uri);
});
exports.extraBuiltins.forEach((builtin) => {
this._builtins.set(builtin, createCachedItem(vscode_languageserver_1.SymbolKind.Class));
});
return this;
}
find(name, type) {
if (type === vscode_languageserver_1.SymbolKind.Variable) {
return this._variables.get(name);
}
if (type === vscode_languageserver_1.SymbolKind.Function) {
return this._functions.get(name);
}
if (type === vscode_languageserver_1.SymbolKind.Class) {
return this._builtins.get(name);
}
return this._unknowns.get(name);
}
findType(name) {
if (this._variables.has(name)) {
return vscode_languageserver_1.SymbolKind.Variable;
}
if (this._functions.has(name)) {
return vscode_languageserver_1.SymbolKind.Function;
}
if (this._builtins.has(name)) {
return vscode_languageserver_1.SymbolKind.Class;
}
return vscode_languageserver_1.SymbolKind.Null;
}
async resolve(name, uri, type) {
const itemType = type || this.findType(name);
let item = this.find(name, itemType);
if (!item) {
item = createCachedItem(itemType, uri);
this._unknowns.set(name, item);
}
if (item.resolved && item.docs) {
return item;
}
if (!item.resolved) {
item = await resolveItem(name, item);
}
if (!item.docs) {
this._unknowns.set(name, item);
}
this.setItem(name, item);
return item;
}
setItem(name, item) {
switch (item.type) {
case vscode_languageserver_1.SymbolKind.Variable:
this._variables.set(name, item);
break;
case vscode_languageserver_1.SymbolKind.Function:
this._functions.set(name, item);
break;
case vscode_languageserver_1.SymbolKind.Class:
this._builtins.set(name, item);
break;
default:
this._unknowns.set(name, item);
break;
}
}
getItem(name) {
const item = this.find(name);
if (!item || item.type === vscode_languageserver_1.SymbolKind.Null) {
return undefined;
}
return item;
}
}
exports.DocumentationCache = DocumentationCache;
async function initializeDocumentationCache() {
const cache = new DocumentationCache();
await cache.parse();
return cache;
}