UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

221 lines (220 loc) 9.81 kB
"use strict"; 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(); }