UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

337 lines (336 loc) 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FishDocumentSymbol = void 0; exports.symbolIsImmutable = symbolIsImmutable; exports.isGlobalSymbol = isGlobalSymbol; exports.isUniversalSymbol = isUniversalSymbol; exports.filterGlobalSymbols = filterGlobalSymbols; exports.filterLocalSymbols = filterLocalSymbols; exports.filterLastPerScopeSymbol = filterLastPerScopeSymbol; exports.findSymbolsForCompletion = findSymbolsForCompletion; exports.findSymbolReferences = findSymbolReferences; exports.findLastDefinition = findLastDefinition; exports.definitionSymbolHandler = definitionSymbolHandler; exports.getFishDocumentSymbols = getFishDocumentSymbols; const vscode_languageserver_1 = require("vscode-languageserver"); const node_types_1 = require("./utils/node-types"); //import { findVariableDefinitionOptions } from './utils/options'; const symbol_documentation_builder_1 = require("./utils/symbol-documentation-builder"); const tree_sitter_1 = require("./utils/tree-sitter"); const definition_scope_1 = require("./utils/definition-scope"); const generic_tree_1 = require("./utils/generic-tree"); var FishDocumentSymbol; (function (FishDocumentSymbol) { /** * Creates a new symbol information literal. * * @param name The name of the symbol. * @param uri The documentUri of the symbol. * @param text The text in the symbol scope. * @param detail The detail of the symbol. (Markdown included inside 'range') * @param kind The kind of the symbol. * @param range The enclosing range of the symbol. * @param selectionRange The selectionRange of the symbol. * @param children Children of the symbol. */ function create(name, uri, text, detail, kind, range, selectionRange, scope, children) { return { name, uri, text, detail, kind, range, selectionRange, scope, children, }; } FishDocumentSymbol.create = create; function copy(symbol, newChildren = []) { return create(symbol.name, symbol.uri, symbol.text, symbol.detail, symbol.kind, symbol.range, symbol.selectionRange, symbol.scope, newChildren); } FishDocumentSymbol.copy = copy; function equal(a, b) { return (a.name === b.name && a.uri === b.uri && a.range.start.character === b.range.start.character && a.range.start.line === b.range.start.line && a.range.end.character === b.range.end.character && a.range.end.line === b.range.end.line && a.selectionRange.start.character === b.selectionRange.start.character && a.selectionRange.start.line === b.selectionRange.start.line && a.selectionRange.end.line === b.selectionRange.end.line && a.selectionRange.end.character === b.selectionRange.end.character); } FishDocumentSymbol.equal = equal; function toWorkspaceSymbol(symbol) { return vscode_languageserver_1.WorkspaceSymbol.create(symbol.name, symbol.kind, symbol.uri, symbol.range); } FishDocumentSymbol.toWorkspaceSymbol = toWorkspaceSymbol; function toLocation(symbol) { return vscode_languageserver_1.Location.create(symbol.uri, symbol.selectionRange); } FishDocumentSymbol.toLocation = toLocation; function logString(symbol) { const symbolIcon = symbol.kind === vscode_languageserver_1.SymbolKind.Function ? '  ' : '  '; return `${symbolIcon}${symbol.name} :::: ${symbol.scope.scopeTag}`; } FishDocumentSymbol.logString = logString; function flattenArray(symbols) { function* flattenGenerator(symbols) { for (const symbol of symbols) { yield symbol; yield* flattenGenerator(symbol.children); } } return [...flattenGenerator(symbols)]; } FishDocumentSymbol.flattenArray = flattenArray; function equalScopes(a, b) { if (a.scope.scopeNode && b.scope.scopeNode) { if ([a.scope.scopeTag, b.scope.scopeTag].includes('inherit')) { return a.scope.scopeNode.equals(b.scope.scopeNode); } else if (['global', 'universal'].includes(a.scope.scopeTag) && ['global', 'universal'].includes(b.scope.scopeTag)) { return true; } return a.scope.scopeTag === b.scope.scopeTag && a.scope.scopeNode.equals(b.scope.scopeNode); } return false; } FishDocumentSymbol.equalScopes = equalScopes; /* * the first symbol is before the second symbol */ function isBefore(first, second) { return first.range.start.line < second.range.start.line; } FishDocumentSymbol.isBefore = isBefore; /* * the first symbol is after the second symbol */ function isAfter(first, second) { return first.range.start.line > second.range.start.line; } FishDocumentSymbol.isAfter = isAfter; function getSyntaxNode(root, symbol) { return (0, tree_sitter_1.getNodeAtRange)(root, symbol.range); } FishDocumentSymbol.getSyntaxNode = getSyntaxNode; function toTree(symbols) { return new generic_tree_1.GenericTree(symbols); } FishDocumentSymbol.toTree = toTree; function debug(symbol) { const positionString = (pos) => `(line: ${pos.line}, char: ${pos.character})`; const rangeString = (n) => { const range = (0, tree_sitter_1.getRange)(n); return `${positionString(range.start)} --- ${positionString(range.end)}`; }; const scopeNodeLines = symbol.scope.scopeNode.text.split('\n'); return { name: symbol.name, range: positionString(symbol.range.start) + ' --- ' + positionString(symbol.range.end), selectionRange: positionString(symbol.selectionRange.start) + ' --- ' + positionString(symbol.selectionRange.end), text: symbol.text.split('\n').length > 1 ? symbol.text + '...' : symbol.text, scope: { scopeTag: symbol.scope.scopeTag, scopeNode: { text: scopeNodeLines[0] + '...', type: symbol.scope.scopeNode.type, range: rangeString(symbol.scope.scopeNode), }, }, type: symbol.kind === vscode_languageserver_1.SymbolKind.Function ? 'function' : 'variable', uri: symbol.uri, }; } FishDocumentSymbol.debug = debug; function toFoldingRange(symbol) { return { startLine: symbol.range.start.line, endLine: symbol.range.end.line, collapsedText: symbol.name, }; //return FoldingRange.create( // symbol.range.start.line, // symbol.range.end.line, // symbol.range.start.character, // symbol.range.end.character, // FoldingRangeKind.Region, // symbol.name //) } FishDocumentSymbol.toFoldingRange = toFoldingRange; function toMock(symbol) { const { name, scope, range } = symbol; return { name, scope: scope.scopeTag, range, }; } FishDocumentSymbol.toMock = toMock; function createMock(name, scope, range) { return { name, scope, range, }; } FishDocumentSymbol.createMock = createMock; })(FishDocumentSymbol || (exports.FishDocumentSymbol = FishDocumentSymbol = {})); /** * Checks if a FishDocumentSymbol's state, should NOT be changeable. * Renaming a FishDocumentSymbol across the entire workspace, shouldn't * be possible for internal symbols (seen in '/usr/share/fish/**.fish'). */ function symbolIsImmutable(symbol) { const { uri, scope } = symbol; return uri.startsWith('/usr/share/fish/') || scope.scopeTag === 'universal'; } function isGlobalSymbol(symbol) { return symbol.scope.scopeTag === 'global'; } function isUniversalSymbol(symbol) { return symbol.scope.scopeTag === 'universal'; } function filterGlobalSymbols(symbols) { return FishDocumentSymbol .toTree(symbols) .toFlatArray() .filter((symbol) => symbol.scope.scopeTag === 'global'); } function filterLocalSymbols(symbols) { return FishDocumentSymbol .toTree(symbols) .toFlatArray() .filter((symbol) => symbol.scope.scopeTag !== 'global' && symbol.scope.scopeTag !== 'universal'); } function filterLastPerScopeSymbol(symbolArray) { const symbolTree = new generic_tree_1.GenericTree(symbolArray); const flatArray = symbolTree.toFlatArray(); return symbolTree .filterToTree((symbol) => !flatArray.some((s) => { return (s.name === symbol.name && !FishDocumentSymbol.equal(symbol, s) && FishDocumentSymbol.equalScopes(symbol, s) && FishDocumentSymbol.isBefore(symbol, s)); })) .toArray(); } const compareSymbolToPosition = (symbol, position) => { const compareHelper = (symbol, position) => { const { scope } = symbol; if (['global', 'universal'].includes(scope.scopeTag)) { return true; } return scope.containsPosition(position); }; return symbol.kind === vscode_languageserver_1.SymbolKind.Function ? compareHelper(symbol, position) : symbol.scope.containsPosition(position) && (0, tree_sitter_1.isPositionAfter)(symbol.selectionRange.end, position); }; function findSymbolsForCompletion(symbols, position) { const symbolTree = new generic_tree_1.GenericTree(symbols); const possibleDuplicates = symbolTree .filterToTree((symbol) => compareSymbolToPosition(symbol, position)) .toFlatArray() .reverse(); const uniqueSymbolsArray = []; for (const symbol of possibleDuplicates) { if (uniqueSymbolsArray.some((s) => s.name === symbol.name)) { continue; } uniqueSymbolsArray.push(symbol); } return uniqueSymbolsArray; } /** * finds all symbols (variables and function that have been defined) */ function findSymbolReferences(symbols, matchSymbol) { return new generic_tree_1.GenericTree(symbols) .filterToTree((symbol) => { //if (symbol.scope.scopeTag === 'global' ) return true; return matchSymbol.name === symbol.name && FishDocumentSymbol.equalScopes(matchSymbol, symbol); }) .toFlatArray(); } function findLastDefinition(symbols, matchNode) { const symbolTree = new generic_tree_1.GenericTree(symbols); const symbolFunctionCompare = (symbol, matchNode) => { const matchPosition = (0, tree_sitter_1.pointToPosition)(matchNode.startPosition); const { name, kind: _kind, scope: _scope } = symbol; return name === matchNode.text && compareSymbolToPosition(symbol, matchPosition); }; return symbolTree .filterToTree((symbol) => symbolFunctionCompare(symbol, matchNode)) .toFlatArray() .pop(); } /** * TreeSitter definition nodes in fish shell rely on commands, and thus create trees that * need specific traversals per command. Creates a standard object of properties to be * deconstructed into a FishDocumentSymbol. Where parent is the root most node of the * entire command to create a symbol. Child is the identifier of the symbol. * * See fish below: * --------------------------------------------------------------------------------------- * set -gx FOO BAR; # FOO is a variable we globally define and export * --------------------------------------------------------------------------------------- * Child is just the identifier `$FOO` * Parent is the entire string `set -gx FOO BAR;` for the command */ function definitionSymbolHandler(node) { let shouldCreate = false; let [child, parent] = [node, node.parent || node]; let kind = vscode_languageserver_1.SymbolKind.Null; if ((0, node_types_1.isVariableDefinitionName)(node)) { parent = (0, node_types_1.refinedFindParentVariableDefinitionKeyword)(node).parent; kind = vscode_languageserver_1.SymbolKind.Variable; shouldCreate = true; if (node.text.startsWith('$')) { shouldCreate = false; } } else if (node.firstNamedChild && (0, node_types_1.isFunctionDefinitionName)(node.firstNamedChild)) { parent = node; child = node.firstNamedChild; kind = vscode_languageserver_1.SymbolKind.Function; shouldCreate = true; } return { shouldCreate, kind, child, parent, }; } /** * Creates all FishDocumentSymbols in a file * @param {string} uri - path to the file * @param {SyntaxNode[]} currentNodes - root node(s) to traverse for definitions * @returns {FishDocumentSymbol[]} - all defined FishDocumentSymbol's in file */ function getFishDocumentSymbols(document, ...currentNodes) { const symbols = []; for (const node of currentNodes) { const childrenSymbols = getFishDocumentSymbols(document, ...node.children); const { shouldCreate, kind, child, parent } = definitionSymbolHandler(node); if (shouldCreate) { symbols.push(FishDocumentSymbol.create(child.text, document.uri, parent.text, symbol_documentation_builder_1.DocumentSymbolDetail.create(child.text, document.uri, kind, child), kind, (0, tree_sitter_1.getRange)(parent), (0, tree_sitter_1.getRange)(child), (0, definition_scope_1.getScope)(document, child), childrenSymbols)); continue; } symbols.push(...childrenSymbols); } return symbols; }