fish-lsp
Version:
LSP implementation for fish/fish-shell
221 lines (220 loc) • 9.81 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.unindentNestedSyntaxNode = unindentNestedSyntaxNode;
exports.isAutoloadedPath = isAutoloadedPath;
exports.getAutoloadType = getAutoloadType;
exports.createDetail = createDetail;
const vscode_languageserver_1 = require("vscode-languageserver");
const symbol_1 = require("./symbol");
const markdown_builder_1 = require("../utils/markdown-builder");
const options_1 = require("./options");
const function_1 = require("./function");
const node_types_1 = require("../utils/node-types");
const translation_1 = require("../utils/translation");
const snippets_1 = require("../utils/snippets");
const set_1 = require("./set");
const alias_1 = require("./alias");
const env_manager_1 = require("../utils/env-manager");
const logger_1 = require("../logger");
function unindentNestedSyntaxNode(node) {
const lines = node.text.split('\n');
if (lines.length > 1) {
const lastLine = node.lastChild?.startPosition.column || 0;
return lines
.map(line => line.replace(' '.repeat(lastLine), ''))
.join('\n')
.trimEnd();
}
return node.text;
}
function getSymbolKind(symbol) {
const kind = symbol_1.fishSymbolKindToSymbolKind[symbol.fishKind];
switch (kind) {
case vscode_languageserver_1.SymbolKind.Variable:
return 'variable';
case vscode_languageserver_1.SymbolKind.Function:
return 'function';
default:
return '';
}
}
function isAutoloadedPath(uriOrPath, type) {
const path = uriOrPath.startsWith('file://') ? (0, translation_1.uriToPath)(uriOrPath) : uriOrPath;
const autoloadedKeys = env_manager_1.env.getAutoloadedKeys();
for (const key of autoloadedKeys) {
if (type && !key.toLowerCase().includes(type.toLowerCase())) {
continue;
}
const values = env_manager_1.env.getAsArray(key);
for (const value of values) {
if (path.startsWith(value)) {
return true;
}
}
}
return false;
}
function getAutoloadType(uriOrPath) {
const path = uriOrPath.startsWith('file://') ? (0, translation_1.uriToPath)(uriOrPath) : uriOrPath;
const autoloadTypes = ['functions', 'completions', 'conf.d', 'config'];
for (const type of autoloadTypes) {
if (path.includes(`/fish/${type}`)) {
return type;
}
if (type === 'config' && path.endsWith('config.fish')) {
return 'config';
}
}
if (isAutoloadedPath(path)) {
return 'autoloaded';
}
return '';
}
function buildFunctionDetail(symbol) {
const { name, node, fishKind } = symbol;
if (fishKind === 'ALIAS') {
return alias_1.FishAlias.buildDetail(node);
}
const options = (0, options_1.findOptions)((0, function_1.findFunctionDefinitionChildren)(node), function_1.FunctionOptions);
const descriptionOption = options.found.find(option => option.option.isOption('-d', '---description'));
const description = [`(${markdown_builder_1.md.bold('function')}) ${markdown_builder_1.md.inlineCode(name)}`];
if (descriptionOption && descriptionOption.value) {
description.push((0, node_types_1.isString)(descriptionOption.value)
? descriptionOption.value.text.slice(1, -1)
: descriptionOption.value.text);
}
description.push(markdown_builder_1.md.separator());
const scope = [];
if (isAutoloadedPath(symbol.uri)) {
scope.push('autoloaded');
}
if (symbol.isGlobal()) {
scope.push('globally scoped');
}
if (scope.length > 0) {
description.push(scope.join(', '));
}
description.push(`located in file: ${markdown_builder_1.md.inlineCode((0, translation_1.uriToReadablePath)(symbol.uri))}`);
description.push(markdown_builder_1.md.separator());
const prebuilt = snippets_1.PrebuiltDocumentationMap.getByType('command').find(c => c.name === name);
if (prebuilt) {
description.push(prebuilt.description);
description.push(markdown_builder_1.md.separator());
}
description.push(markdown_builder_1.md.codeBlock('fish', unindentNestedSyntaxNode(node)));
const argumentNamesOption = options.found.filter(option => option.option.isOption('-a', '--argument-names'));
if (argumentNamesOption && argumentNamesOption.length) {
const functionCall = [name];
for (const arg of argumentNamesOption) {
functionCall.push(arg.value.text);
}
description.push(markdown_builder_1.md.separator());
description.push(markdown_builder_1.md.codeBlock('fish', functionCall.join(' ')));
}
return description.join(markdown_builder_1.md.newline());
}
function isVariableArgumentNamed(node, name) {
if (node.type !== 'function_definition')
return '';
const children = (0, function_1.findFunctionDefinitionChildren)(node);
if ((0, options_1.findOptions)(children, function_1.FunctionOptions).found
.filter(flag => flag.option.isOption('-a', '--argument-names'))
.some(flag => flag.value.text === name)) {
return true;
}
return false;
}
function getArgumentNamesIndexString(node, name) {
if (node?.type && node?.type !== 'function_definition')
return '';
const children = (0, function_1.findFunctionDefinitionChildren)(node);
const index = (0, options_1.findOptions)(children, function_1.FunctionOptions).found
.filter(flag => flag.option.isOption('-a', '--argument-names'))
.findIndex((flag) => flag.value.text === name);
const argvStr = '$argv[' + (index + 1) + ']';
return `${markdown_builder_1.md.italic('named argument')}: ${markdown_builder_1.md.inlineCode(argvStr)}`;
}
function buildVariableDetail(symbol) {
const { name, node, uri, fishKind } = symbol;
if (!node)
return '';
const description = [`(${markdown_builder_1.md.bold('variable')}) ${markdown_builder_1.md.inlineCode(name)}`];
description.push(markdown_builder_1.md.separator());
if (fishKind === 'SET' || fishKind === 'READ') {
const setModifiers = set_1.SetModifiers.filter(option => option.equalsRawLongOption('--universal', '--global', '--function', '--local', '--export', '--unexport'));
const options = (0, options_1.findOptions)(node.childrenForFieldName('argument'), setModifiers);
const modifier = options.found.find(o => o.option.equalsRawOption('-U', '-g', '-f', '-l', '-x', '-u'));
if (modifier) {
description.push((0, set_1.setModifierDetailDescriptor)(node));
}
}
else if (fishKind === 'ARGPARSE') {
description.push('locally scoped');
}
else if (node && isVariableArgumentNamed(node, name)) {
try {
const result = getArgumentNamesIndexString(node, name);
description.push(result);
}
catch (e) {
logger_1.logger.error('ERROR: building variable detail', e);
}
}
description.push(`located in file: ${markdown_builder_1.md.inlineCode((0, translation_1.uriToReadablePath)(uri))}`);
const prebuilt = snippets_1.PrebuiltDocumentationMap.getByType('variable').find(c => c.name === name);
if (prebuilt) {
description.push(markdown_builder_1.md.separator());
description.push(prebuilt.description);
}
description.push(markdown_builder_1.md.separator());
description.push(markdown_builder_1.md.codeBlock('fish', unindentNestedSyntaxNode(node)));
const scopeCommand = symbol.scope.scopeNode?.type === 'program'
? `${(0, translation_1.uriToReadablePath)(uri)}`
: `${symbol.scope.scopeNode?.firstNamedChild?.text}` || `${symbol.node}`;
if (fishKind === 'ARGPARSE') {
const argumentNamesOption = symbol.name.slice('_flag_'.length).replace(/_/g, '-');
if (argumentNamesOption.length > 1) {
description.push(markdown_builder_1.md.separator());
description.push(markdown_builder_1.md.codeBlock('fish', `${scopeCommand} --${argumentNamesOption}`));
}
else if (argumentNamesOption.length === 1) {
description.push(markdown_builder_1.md.separator());
description.push(markdown_builder_1.md.codeBlock('fish', `${scopeCommand} -${argumentNamesOption}`));
}
}
else if (name === 'argv') {
description.push(markdown_builder_1.md.separator());
description.push(markdown_builder_1.md.codeBlock('fish', `${scopeCommand} $argv`));
}
else if (node.type === 'function_definition') {
const children = (0, function_1.findFunctionDefinitionChildren)(node);
const resultFlags = [];
(0, options_1.findOptions)(children, function_1.FunctionOptions).found
.filter(flag => flag.option.isOption('-a', '--argument-names'))
.forEach((flag, idx) => {
if (flag.value.text === name)
resultFlags.push(flag.value.text);
else
resultFlags.push(`\$argv[${idx + 1}]`);
});
if (resultFlags.length) {
description.push(markdown_builder_1.md.separator());
description.push(markdown_builder_1.md.codeBlock('fish', `${scopeCommand} ${resultFlags.join(' ')}`));
}
}
return description.join(markdown_builder_1.md.newline());
}
function createDetail(symbol) {
if (symbol.fishKind === 'EXPORT')
return symbol.detail.toString();
const symbolKind = getSymbolKind(symbol);
if (symbolKind === '')
return symbol.detail;
if (symbolKind === 'function') {
return buildFunctionDetail(symbol);
}
if (symbolKind === 'variable') {
return buildVariableDetail(symbol);
}
return symbol.detail.toString();
}