UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

322 lines (321 loc) 10.3 kB
"use strict"; 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; }