UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

497 lines (496 loc) 19.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fishSymbolKindToSymbolKind = exports.FishKindGroups = exports.fromFishSymbolKindToSymbolKind = exports.FishSymbolKind = exports.SetModifierToScopeTag = exports.FishSymbol = void 0; exports.filterLastPerScopeSymbol = filterLastPerScopeSymbol; exports.filterFirstPerScopeSymbol = filterFirstPerScopeSymbol; exports.filterFirstUniqueSymbolperScope = filterFirstUniqueSymbolperScope; exports.findLocalLocations = findLocalLocations; exports.formatFishSymbolTree = formatFishSymbolTree; exports.processNestedTree = processNestedTree; const vscode_languageserver_1 = require("vscode-languageserver"); const document_1 = require("../document"); const tree_sitter_1 = require("../utils/tree-sitter"); const set_1 = require("./set"); const read_1 = require("./read"); const function_1 = require("./function"); const for_1 = require("./for"); const argparse_1 = require("./argparse"); const options_1 = require("./options"); const alias_1 = require("./alias"); const symbol_detail_1 = require("./symbol-detail"); const config_1 = require("../config"); const flatten_1 = require("../utils/flatten"); const translation_1 = require("../utils/translation"); const node_types_1 = require("../utils/node-types"); const file_operations_1 = require("../utils/file-operations"); const export_1 = require("./export"); const complete_1 = require("./complete"); const analyze_1 = require("../analyze"); const emit_1 = require("./emit"); const reference_comparator_1 = require("./reference-comparator"); const equality_utils_1 = require("./equality-utils"); const symbol_converters_1 = require("./symbol-converters"); const symbol_kinds_1 = require("./symbol-kinds"); Object.defineProperty(exports, "FishKindGroups", { enumerable: true, get: function () { return symbol_kinds_1.FishKindGroups; } }); Object.defineProperty(exports, "FishSymbolKind", { enumerable: true, get: function () { return symbol_kinds_1.FishSymbolKind; } }); Object.defineProperty(exports, "fishSymbolKindToSymbolKind", { enumerable: true, get: function () { return symbol_kinds_1.fishSymbolKindToSymbolKind; } }); Object.defineProperty(exports, "fromFishSymbolKindToSymbolKind", { enumerable: true, get: function () { return symbol_kinds_1.fromFishSymbolKindToSymbolKind; } }); class FishSymbol { children = []; aliasedNames = []; document; constructor(obj) { this.name = obj.name || obj.focusedNode.text; this.kind = (0, symbol_kinds_1.fromFishSymbolKindToSymbolKind)(obj.fishKind); this.fishKind = obj.fishKind; this.document = obj.document; this.uri = obj.uri || obj.document.uri.toString(); this.range = obj.range || (0, tree_sitter_1.getRange)(obj.node); this.selectionRange = obj.selectionRange || (0, tree_sitter_1.getRange)(obj.focusedNode); this.node = obj.node; this.focusedNode = obj.focusedNode; this.scope = obj.scope; this.children = obj.children; this.children.forEach(child => { child.parent = this; }); this.detail = obj.detail; this.setupDetail(); } setupDetail() { this.detail = (0, symbol_detail_1.createDetail)(this); } static create(name, node, focusedNode, fishKind, document, uri = document.uri.toString(), detail, scope, children = []) { return new this({ name: name || focusedNode.text, fishKind, document, uri, detail, node, focusedNode, scope, children, }); } static fromObject(obj) { return new this(obj); } copy() { return symbol_converters_1.SymbolConverters.copySymbol(this); } static is(obj) { return (0, equality_utils_1.isFishSymbol)(obj); } addChildren(...children) { this.children.push(...children); children.forEach(child => { child.parent = this; }); return this; } addAliasedNames(...names) { this.aliasedNames.push(...names); return this; } nameEqualsNodeText(node) { return (0, equality_utils_1.fishSymbolNameEqualsNodeText)(this, node); } get argparseFlagName() { return FishSymbol.argparseFlagFromName(this.name); } static argparseFlagFromName(name) { return name.replace(/^_flag_/, '').replace(/_/g, '-'); } get argparseFlag() { if (this.fishKind !== 'ARGPARSE') return this.name; const flagName = this.argparseFlagName; if (flagName.length === 1) { return `-${flagName}`; } return `--${flagName}`; } isArgparseCompletionFlag(node) { if (this.fishKind === 'ARGPARSE') return false; if (node.parent && (0, node_types_1.isCommandWithName)(node, 'complete')) { const flagName = this.argparseFlagName; if (node.previousSibling) { return flagName.length === 1 ? options_1.Option.create('-s', '--short').matches(node.previousSibling) : options_1.Option.create('-l', '--long').matches(node.previousSibling); } } return false; } isCommandCompletionFlag(node) { if (this.fishKind === 'COMPLETE') return false; if (node.parent && (0, node_types_1.isCommandWithName)(node.parent, 'complete')) { if (node.previousSibling) { return options_1.Option.create('-c', '--command').matches(node.previousSibling); } } return false; } isEqualLocation(node) { if (!node.isNamed || this.focusedNode.equals(node) || !this.nameEqualsNodeText(node)) { return false; } switch (this.fishKind) { case 'FUNCTION': case 'ALIAS': return node.parent && (0, node_types_1.isCommandWithName)(node.parent, 'complete') ? !(0, node_types_1.isVariableDefinitionName)(node) && !(0, node_types_1.isCommand)(node) && this.isCommandCompletionFlag(node) : !(0, node_types_1.isVariableDefinitionName)(node) && !(0, node_types_1.isCommand)(node); case 'ARGPARSE': return !(0, node_types_1.isFunctionDefinitionName)(node) || this.isArgparseCompletionFlag(node); case 'SET': case 'READ': case 'FOR': case 'VARIABLE': return !(0, node_types_1.isFunctionDefinitionName)(node); case 'EXPORT': return (0, export_1.isExportVariableDefinitionName)(node); case 'FUNCTION_VARIABLE': return (0, function_1.isFunctionVariableDefinitionName)(node); case 'EVENT': return (0, emit_1.isEmittedEventDefinitionName)(node); case 'FUNCTION_EVENT': return (0, emit_1.isGenericFunctionEventHandlerDefinitionName)(node); case 'COMPLETE': return (0, complete_1.isCompletionCommandDefinition)(node) || (0, complete_1.isCompletionSymbol)(node); default: return false; } } get path() { return (0, translation_1.uriToPath)(this.uri); } get workspacePath() { const path = this.path; const pathItems = path.split('/'); let lastItem = pathItems.at(-1); if (lastItem === 'config.fish') { return pathItems.slice(0, -1).join('/'); } lastItem = pathItems.at(-2); if (['functions', 'completions', 'conf.d'].includes(lastItem)) { return pathItems.slice(0, -2).join('/'); } return pathItems.slice(0, -1).join('/'); } get scopeTag() { return this.scope.scopeTag; } get scopeNode() { return this.scope.scopeNode; } toString() { return symbol_converters_1.SymbolConverters.symbolToString(this); } toWorkspaceSymbol() { return symbol_converters_1.SymbolConverters.symbolToWorkspaceSymbol(this); } toDocumentSymbol() { return symbol_converters_1.SymbolConverters.symbolToDocumentSymbol(this); } toLocation() { return symbol_converters_1.SymbolConverters.symbolToLocation(this); } toPosition() { return symbol_converters_1.SymbolConverters.symbolToPosition(this); } toFoldingRange() { return symbol_converters_1.SymbolConverters.symbolToFoldingRange(this); } toMarkupContent() { return symbol_converters_1.SymbolConverters.symbolToMarkupContent(this); } toHover(currentUri = '') { return symbol_converters_1.SymbolConverters.symbolToHover(this, currentUri); } isLocal() { return !this.isGlobal(); } isGlobal() { return this.scope.scopeTag === 'global' || this.scope.scopeTag === 'universal'; } isRootLevel() { return (0, node_types_1.isTopLevelDefinition)(this.node); } isEventHook() { return this.fishKind === 'FUNCTION_EVENT'; } isEmittedEvent() { return this.fishKind === 'EVENT'; } isEvent() { return symbol_kinds_1.FishKindGroups.EVENTS.includes(this.fishKind); } isFunction() { return symbol_kinds_1.FishKindGroups.FUNCTIONS.includes(this.fishKind); } isVariable() { return symbol_kinds_1.FishKindGroups.VARIABLES.includes(this.fishKind); } isArgparse() { return symbol_kinds_1.FishKindGroups.ARGPARSE.includes(this.fishKind); } isSymbolImmutable() { if (!config_1.config.fish_lsp_modifiable_paths.some(path => this.path.startsWith(path))) { return true; } return false; } isConfigDefinition() { if (this.kind !== vscode_languageserver_1.SymbolKind.Variable || this.fishKind !== 'SET') { return false; } return Object.keys(config_1.config).includes(this.name); } isConfigDefinitionWithErase() { if (!this.isConfigDefinition()) return false; const eraseOption = options_1.Option.create('-e', '--erase'); const definitionNode = this.focusedNode; const children = (0, set_1.findSetChildren)(this.node) .filter(s => s.startIndex < definitionNode.startIndex); return children.some(s => (0, options_1.isMatchingOption)(s, eraseOption)); } findValueNodes() { const valueNodes = []; if (!this.isConfigDefinition()) return valueNodes; let node = this.focusedNode.nextNamedSibling; while (node) { if (!(0, node_types_1.isEmptyString)(node)) valueNodes.push(node); node = node.nextNamedSibling; } return valueNodes; } valuesAsShellValues() { return this.findValueNodes().map(node => { let text = node.text; if ((0, node_types_1.isString)(node)) text = text.slice(1, -1); return file_operations_1.SyncFileHelper.expandEnvVars(text); }); } equalArgparse(other) { if (FishSymbol.is(other)) { const equalNames = this.name !== other.name && this.aliasedNames.includes(other.name) && other.aliasedNames.includes(this.name); const equalParents = this.parent && other.parent ? this.parent.equals(other.parent) : !this.parent && !other.parent; return equalNames && this.uri === other.uri && this.fishKind === 'ARGPARSE' && other.fishKind === 'ARGPARSE' && this.focusedNode.equals(other.focusedNode) && this.node.equals(other.node) && equalParents && this.scopeNode.equals(other.scopeNode); } return false; } hasEventHook() { if (!this.isFunction()) return false; for (const child of this.children) { if (child.isEventHook()) { return true; } } return false; } equalsEvent(other) { if (!FishSymbol.is(other)) return false; if (!this.isEvent() || !other.isEvent()) return false; if (this.fishKind === other.fishKind) return false; const parent = this.fishKind === 'FUNCTION_EVENT' ? this.parent : other.parent; const child = this.fishKind === 'EVENT' ? this : other; return !!(parent && child && child.name === parent.name); } isReference(document, node, excludeEqualNode = false) { return (0, reference_comparator_1.isSymbolReference)(this, document, node, excludeEqualNode); } equals(other) { return (0, equality_utils_1.equalSymbols)(this, other); } equalsLocation(location) { return (0, equality_utils_1.symbolEqualsLocation)(this, location); } equalDefinition(other) { return (0, equality_utils_1.equalSymbolDefinitions)(this, other); } equalsNode(node, opts = { strict: false }) { return (0, equality_utils_1.symbolEqualsNode)(this, node, opts.strict); } containsScope(other) { return (0, equality_utils_1.symbolContainsScope)(this, other); } equalScopes(other) { return (0, equality_utils_1.equalSymbolScopes)(this, other); } scopeContainsNode(node) { return (0, equality_utils_1.symbolScopeContainsNode)(this, node); } containsNode(node) { return (0, equality_utils_1.symbolContainsNode)(this, node); } containsPosition(position) { return (0, equality_utils_1.symbolContainsPosition)(this, position); } } exports.FishSymbol = FishSymbol; const SetModifierToScopeTag = (modifier) => { switch (true) { case modifier.isOption('-U', '--universal'): return 'universal'; case modifier.isOption('-g', '--global'): return 'global'; case modifier.isOption('-f', '--function'): return 'function'; case modifier.isOption('-l', '--local'): return 'local'; default: return 'local'; } }; exports.SetModifierToScopeTag = SetModifierToScopeTag; function filterLastPerScopeSymbol(symbols) { const flatArray = (0, flatten_1.flattenNested)(...symbols); const array = []; for (const symbol of symbols) { const lastSymbol = flatArray.findLast((s) => { return s.name === symbol.name && s.kind === symbol.kind && s.uri === symbol.uri && s.equalScopes(symbol); }); if (lastSymbol && lastSymbol.equals(symbol)) { array.push(symbol); } } return array; } function filterFirstPerScopeSymbol(document) { const uri = document_1.LspDocument.is(document) ? document.uri : document; const symbols = analyze_1.analyzer.getFlatDocumentSymbols(uri); const flatArray = Array.from(symbols); const array = []; for (const symbol of symbols) { const firstSymbol = flatArray.find((s) => s.equalDefinition(symbol)); if (firstSymbol && firstSymbol.equals(symbol)) { array.push(symbol); } } return array; } function filterFirstUniqueSymbolperScope(document) { const uri = document_1.LspDocument.is(document) ? document.uri : document; const symbols = analyze_1.analyzer.getFlatDocumentSymbols(uri); const result = []; for (const symbol of symbols) { const alreadyExists = result.some(existing => existing.name === symbol.name && existing.equalDefinition(symbol)); if (!alreadyExists) { result.push(symbol); } } return result; } function findLocalLocations(symbol, allSymbols, includeSelf = true) { const result = []; const matchingNodes = allSymbols.filter(s => s.name === symbol.name && !symbol.equalScopes(s)) .map(s => symbol.fishKind === 'ALIAS' ? s.node : s.scopeNode); for (const node of (0, tree_sitter_1.getChildNodes)(symbol.scopeNode)) { if (matchingNodes.some(n => (0, tree_sitter_1.containsNode)(n, node))) continue; if (symbol.isEqualLocation(node)) result.push(node); } return [ includeSelf && symbol.name !== 'argv' ? symbol.toLocation() : undefined, ...result.map(node => symbol.fishKind === 'ARGPARSE' ? vscode_languageserver_1.Location.create(symbol.uri, (0, argparse_1.convertNodeRangeWithPrecedingFlag)(node)) : vscode_languageserver_1.Location.create(symbol.uri, (0, tree_sitter_1.getRange)(node))), ].filter(Boolean); } function formatFishSymbolTree(symbols, indentLevel = 0) { let result = ''; const indentString = ' '; for (const symbol of symbols) { const indent = indentString.repeat(indentLevel); const scopeTag = symbol.scope?.scopeTag || 'unknown'; result += `${indent}${symbol.name} (${symbol.fishKind}) (${scopeTag})\n`; if (symbol.children && symbol.children.length > 0) { result += formatFishSymbolTree(symbol.children, indentLevel + 1); } } return result; } function buildNested(document, node, ...children) { const firstNamedChild = node.firstNamedChild; const newSymbols = []; switch (node.type) { case 'function_definition': newSymbols.push(...(0, function_1.processFunctionDefinition)(document, node, children)); break; case 'for_statement': newSymbols.push(...(0, for_1.processForDefinition)(document, node, children)); break; case 'command': if (!firstNamedChild?.text) break; switch (firstNamedChild.text) { case 'set': newSymbols.push(...(0, set_1.processSetCommand)(document, node, children)); break; case 'read': newSymbols.push(...(0, read_1.processReadCommand)(document, node, children)); break; case 'argparse': newSymbols.push(...(0, argparse_1.processArgparseCommand)(document, node, children)); break; case 'alias': newSymbols.push(...(0, alias_1.processAliasCommand)(document, node, children)); break; case 'export': newSymbols.push(...(0, export_1.processExportCommand)(document, node, children)); break; case 'emit': newSymbols.push(...(0, emit_1.processEmitEventCommandName)(document, node, children)); break; default: break; } break; } return newSymbols; } function processNestedTree(document, ...nodes) { const symbols = []; if (!document.isAutoloadedUri()) { const programNode = nodes.find(node => node.type === 'program'); if (programNode) symbols.push(...(0, function_1.processArgvDefinition)(document, programNode)); } for (const node of nodes) { const childSymbols = processNestedTree(document, ...node.children); const newSymbols = buildNested(document, node, ...childSymbols); if (newSymbols.length > 0) { symbols.push(...newSymbols); } else if (childSymbols.length > 0) { symbols.push(...childSymbols); } } return symbols; }