UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

832 lines (831 loc) 34 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Analyzer = exports.analyzer = exports.AnalyzedDocument = void 0; const LSP = __importStar(require("vscode-languageserver")); const vscode_languageserver_1 = require("vscode-languageserver"); const config_1 = require("./config"); const document_1 = require("./document"); const logger_1 = require("./logger"); const argparse_1 = require("./parsing/argparse"); const complete_1 = require("./parsing/complete"); const source_1 = require("./parsing/source"); const symbol_1 = require("./parsing/symbol"); const references_1 = require("./references"); const exec_1 = require("./utils/exec"); const file_operations_1 = require("./utils/file-operations"); const flatten_1 = require("./utils/flatten"); const node_types_1 = require("./utils/node-types"); const translation_1 = require("./utils/translation"); const tree_sitter_1 = require("./utils/tree-sitter"); const workspace_manager_1 = require("./utils/workspace-manager"); const validate_1 = require("./diagnostics/validate"); const parser_1 = require("./parser"); const startup_1 = require("./utils/startup"); var AnalyzedDocument; (function (AnalyzedDocument) { function create(document, documentSymbols, tree, commandNodes = [], sourceNodes = []) { return { document, documentSymbols, tree, root: tree.rootNode, commandNodes, sourceNodes, }; } AnalyzedDocument.create = create; function createFull(document, documentSymbols, tree) { const commandNodes = []; const sourceNodes = []; tree.rootNode.descendantsOfType('command').forEach(node => { if ((0, source_1.isSourceCommandWithArgument)(node)) sourceNodes.push(node.child(1)); commandNodes.push(node); }); return { document, documentSymbols, tree, root: tree.rootNode, commandNodes, sourceNodes, }; } AnalyzedDocument.createFull = createFull; function createPartial(document, tree) { const commandNodes = []; const sourceNodes = []; tree.rootNode.descendantsOfType('command').forEach(node => { if ((0, source_1.isSourceCommandWithArgument)(node)) sourceNodes.push(node.child(1)); commandNodes.push(node); }); return { document, documentSymbols: [], tree, root: tree.rootNode, commandNodes, sourceNodes, }; } AnalyzedDocument.createPartial = createPartial; function isPartial(analyzedDocument) { return analyzedDocument.documentSymbols.length === 0; } AnalyzedDocument.isPartial = isPartial; function isFull(analyzedDocument) { return analyzedDocument.documentSymbols.length > 0; } AnalyzedDocument.isFull = isFull; })(AnalyzedDocument || (exports.AnalyzedDocument = AnalyzedDocument = {})); class Analyzer { parser; cache = new AnalyzedDocumentCache(); globalSymbols = new GlobalDefinitionCache(); started = false; constructor(parser) { this.parser = parser; } static async initialize() { const parser = await (0, parser_1.initializeParser)(); exports.analyzer = new Analyzer(parser); exports.analyzer.started = true; return exports.analyzer; } analyze(document) { const analyzedDocument = this.getAnalyzedDocument(document); this.cache.setDocument(document.uri, analyzedDocument); for (const symbol of (0, flatten_1.iterateNested)(...analyzedDocument.documentSymbols)) { if (symbol.isGlobal()) this.globalSymbols.add(symbol); } return analyzedDocument; } analyzePath(rawFilePath) { const path = (0, translation_1.uriToPath)(rawFilePath); const document = file_operations_1.SyncFileHelper.loadDocumentSync(path); if (!document) { logger_1.logger.warning(`analyzer.analyzePath: ${path} not found`); return undefined; } return this.analyze(document); } analyzePartial(document) { const tree = this.parser.parse(document.getText()); const analyzedDocument = AnalyzedDocument.createPartial(document, tree); this.cache.setDocument(document.uri, analyzedDocument); return analyzedDocument; } getAnalyzedDocument(document) { const tree = this.parser.parse(document.getText()); const documentSymbols = (0, symbol_1.processNestedTree)(document, tree.rootNode); return AnalyzedDocument.createFull(document, documentSymbols, tree); } async analyzeWorkspace(workspace, progress = undefined, callbackfn = (text) => logger_1.logger.log(`analyzer.analyzerWorkspace(${workspace.name})`, text)) { const startTime = performance.now(); if (workspace.isAnalyzed()) { callbackfn(`[fish-lsp] workspace ${workspace.name} already analyzed`); progress?.done(); return { count: 0, workspace, duration: '0.00' }; } const docs = workspace.pendingDocuments(); const maxSize = Math.min(docs.length, config_1.config.fish_lsp_max_background_files); const currentDocuments = workspace.pendingDocuments().slice(0, maxSize); const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const BATCH_SIZE = Math.max(1, Math.floor(currentDocuments.length / 20)); const UPDATE_DELAY = currentDocuments.length > 100 ? 10 : 25; let lastUpdateTime = 0; const MIN_UPDATE_INTERVAL = 15; currentDocuments.forEach(async (doc, idx) => { try { if (doc.getAutoloadType() === 'completions') { this.analyzePartial(doc); } else { this.analyze(doc); } workspace.uris.markIndexed(doc.uri); const reportPercent = Math.ceil(idx / maxSize * 100); progress?.report(reportPercent, `Analyzing ${idx}/${docs.length} files`); } catch (err) { logger_1.logger.log(`[fish-lsp] ERROR analyzing workspace '${workspace.name}' (${err?.toString() || ''})`); } const currentTime = performance.now(); const isLastItem = idx === currentDocuments.length - 1; const isBatchEnd = idx % BATCH_SIZE === BATCH_SIZE - 1; const timeToUpdate = currentTime - lastUpdateTime > MIN_UPDATE_INTERVAL; if (isLastItem || isBatchEnd && timeToUpdate) { const percentage = Math.ceil((idx + 1) / maxSize * 100); progress?.report(`${percentage}% Analyzing ${idx + 1}/${maxSize} ${maxSize > 1 ? 'documents' : 'document'}`); lastUpdateTime = currentTime; await delay(UPDATE_DELAY); } }); progress?.done(); const endTime = performance.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); const count = currentDocuments.length; const message = `Analyzed ${count} document${count > 1 ? 's' : ''} in ${duration}s`; callbackfn(message); return { count: currentDocuments.length, workspace: workspace, duration, }; } updateConfigInWorkspace(documentUri) { const workspace = workspace_manager_1.workspaceManager.current; let symbols = this.getFlatDocumentSymbols(documentUri).filter(symbol => symbol.kind === vscode_languageserver_1.SymbolKind.Variable && Object.keys(config_1.config).includes(symbol.name)); if (!workspace || !config_1.config.fish_lsp_single_workspace_support) { if (symbols.length === 0) { const prev = config_1.config.fish_lsp_single_workspace_support; Object.assign(config_1.config, (0, config_1.getDefaultConfiguration)()); config_1.config.fish_lsp_single_workspace_support = prev; return; } (0, config_1.updateBasedOnSymbols)(symbols); return; } symbols = this.findSymbols((sym, doc) => { if (doc && workspace.contains(doc?.uri)) return false; if (sym.kind === vscode_languageserver_1.SymbolKind.Variable && Object.keys(config_1.config).includes(sym.name)) { return true; } return false; }); if (symbols.length > 0) { (0, config_1.updateBasedOnSymbols)(symbols); } } getSymbolAtLocation(location) { const symbols = this.cache.getFlatDocumentSymbols(location.uri); return symbols.find((symbol) => symbol.equalsLocation(location)); } findDocumentSymbol(document, position) { const symbols = (0, flatten_1.flattenNested)(...this.cache.getDocumentSymbols(document.uri)); return symbols.find((symbol) => { return (0, tree_sitter_1.isPositionWithinRange)(position, symbol.selectionRange); }); } findDocumentSymbols(document, position) { const symbols = (0, flatten_1.flattenNested)(...this.cache.getDocumentSymbols(document.uri)); return symbols.filter((symbol) => { return (0, tree_sitter_1.isPositionWithinRange)(position, symbol.selectionRange); }); } findSymbol(callbackfn) { const currentWs = workspace_manager_1.workspaceManager.current; const uris = this.cache.uris().filter(uri => currentWs ? currentWs?.contains(uri) : true); for (const uri of uris) { const symbols = this.cache.getFlatDocumentSymbols(uri); const document = this.cache.getDocument(uri)?.document; const symbol = symbols.find(s => callbackfn(s, document)); if (symbol) { return symbol; } } return undefined; } findSymbols(callbackfn) { const currentWs = workspace_manager_1.workspaceManager.current; const uris = this.cache.uris().filter(uri => currentWs ? currentWs?.contains(uri) : true); const symbols = []; for (const uri of uris) { const document = this.cache.getDocument(uri)?.document; const symbols = this.getFlatDocumentSymbols(document.uri); const newSymbols = symbols.filter(s => callbackfn(s, document)); if (newSymbols) { symbols.push(...newSymbols); } } return symbols; } findNode(callbackfn) { const uris = this.cache.uris(); for (const uri of uris) { const root = this.cache.getRootNode(uri); const document = this.cache.getDocument(uri).document; if (!root || !document) continue; const node = (0, tree_sitter_1.getChildNodes)(root).find((n) => callbackfn(n, document)); if (node) { return node; } } return undefined; } findNodes(callbackfn) { const currentWs = workspace_manager_1.workspaceManager.current; const uris = this.cache.uris().filter(uri => currentWs ? currentWs?.contains(uri) : uri); const result = []; for (const uri of uris) { const root = this.cache.getRootNode(uri); const document = this.cache.getDocument(uri).document; if (!root || !document) continue; const nodes = (0, tree_sitter_1.getChildNodes)(root).filter((node) => callbackfn(node, document)); if (nodes.length > 0) { result.push({ uri: document.uri, nodes }); } } return result; } *findDocumentsGen() { const currentWs = workspace_manager_1.workspaceManager.current; const uris = this.cache.uris().filter(uri => currentWs ? currentWs?.contains(uri) : true); for (const uri of uris) { const document = this.cache.getDocument(uri)?.document; if (document) { yield document; } } } *findSymbolsGen() { const currentWs = workspace_manager_1.workspaceManager.current; const uris = this.cache.uris().filter(uri => currentWs ? currentWs?.contains(uri) : true); for (const uri of uris) { const symbols = this.cache.getFlatDocumentSymbols(uri); const document = this.cache.getDocument(uri)?.document; if (!document || !symbols) continue; yield { document, symbols }; } } *findNodesGen() { const currentWs = workspace_manager_1.workspaceManager.current; const uris = this.cache.uris().filter(uri => currentWs ? currentWs?.contains(uri) : true); for (const uri of uris) { const root = this.cache.getRootNode(uri); const document = this.cache.getDocument(uri)?.document; if (!root || !document) continue; yield { document, nodes: this.nodesGen(document.uri).nodes }; } } allSymbolsAccessibleAtPosition(document, position) { const symbolNames = new Set(); const symbols = (0, flatten_1.flattenNested)(...this.cache.getDocumentSymbols(document.uri)) .filter((symbol) => symbol.scope.containsPosition(position)); symbols.forEach((symbol) => symbolNames.add(symbol.name)); const sourcedUris = this.collectReachableSources(document.uri, position); for (const sourcedUri of Array.from(sourcedUris)) { const sourcedSymbols = this.cache.getFlatDocumentSymbols(sourcedUri) .filter(s => !symbolNames.has(s.name) && (0, node_types_1.isTopLevelDefinition)(s.focusedNode) && s.uri !== document.uri); symbols.push(...sourcedSymbols); sourcedSymbols.forEach((symbol) => symbolNames.add(symbol.name)); } for (const globalSymbol of this.globalSymbols.allSymbols) { if (symbolNames.has(globalSymbol.name)) continue; if (globalSymbol.uri !== document.uri) { symbols.push(globalSymbol); symbolNames.add(globalSymbol.name); } else if (globalSymbol.uri === document.uri) { symbols.push(globalSymbol); symbolNames.add(globalSymbol.name); } } return symbols; } getWorkspaceSymbols(query = '') { const workspace = workspace_manager_1.workspaceManager.current; logger_1.logger.log({ searching: workspace?.path, query }); return this.globalSymbols.allSymbols .filter(symbol => workspace?.contains(symbol.uri) || symbol.uri === workspace?.uri) .map((s) => s.toWorkspaceSymbol()) .filter((symbol) => { return symbol.name.startsWith(query); }); } getDefinitionHelper(document, position) { const symbols = []; const localSymbols = this.getFlatDocumentSymbols(document.uri); const word = this.wordAtPoint(document.uri, position.line, position.character); const node = this.nodeAtPoint(document.uri, position.line, position.character); if (!word || !node) return []; const localSymbol = localSymbols.find((s) => { return s.name === word && (0, tree_sitter_1.containsRange)(s.selectionRange, (0, tree_sitter_1.getRange)(node)); }); if (localSymbol) { symbols.push(localSymbol); } else { const toAdd = localSymbols.filter((s) => { const variableBefore = s.kind === vscode_languageserver_1.SymbolKind.Variable ? (0, tree_sitter_1.precedesRange)(s.selectionRange, (0, tree_sitter_1.getRange)(node)) : true; return (s.name === word && (0, tree_sitter_1.containsRange)((0, tree_sitter_1.getRange)(s.scope.scopeNode), (0, tree_sitter_1.getRange)(node)) && variableBefore); }); symbols.push(...toAdd); } if (!symbols.length) { symbols.push(...this.globalSymbols.find(word)); } return symbols; } getDefinition(document, position) { const symbols = this.getDefinitionHelper(document, position); const word = this.wordAtPoint(document.uri, position.line, position.character); const node = this.nodeAtPoint(document.uri, position.line, position.character); if (node && (0, node_types_1.isExportVariableDefinitionName)(node)) { return symbols.find(s => s.name === word) || symbols.pop(); } if (node && (0, node_types_1.isAliasDefinitionName)(node)) { return symbols.find(s => s.name === word) || symbols.pop(); } if (node && (0, argparse_1.isArgparseVariableDefinitionName)(node)) { const atPos = this.getFlatDocumentSymbols(document.uri).findLast(s => s.containsPosition(position) && s.fishKind === 'ARGPARSE') || symbols.pop(); return atPos; } if (node && (0, complete_1.isCompletionSymbol)(node)) { const completionSymbols = this.getFlatCompletionSymbols(document.uri); const completionSymbol = completionSymbols.find(s => s.equalsNode(node)); if (!completionSymbol) { return null; } const symbol = this.findSymbol((s) => completionSymbol.equalsArgparse(s)); if (symbol) return symbol; } if (node && (0, node_types_1.isOption)(node)) { const symbol = this.findSymbol((s) => { if (s.parent && s.fishKind === 'ARGPARSE') { return node.parent?.firstNamedChild?.text === s.parent?.name && s.parent.isGlobal() && node.text.startsWith(s.argparseFlag); } return false; }); if (symbol) return symbol; } return symbols.pop() || null; } getDefinitionLocation(document, position) { const node = this.nodeAtPoint(document.uri, position.line, position.character); if (node && (0, source_1.isSourceCommandArgumentName)(node)) { return this.getSourceDefinitionLocation(node); } if (node && node.parent && (0, source_1.isSourceCommandArgumentName)(node.parent)) { return this.getSourceDefinitionLocation(node.parent); } const symbol = this.getDefinition(document, position); if (symbol) { if (symbol.isEvent()) return [symbol.toLocation()]; const newSymbol = (0, symbol_1.filterFirstPerScopeSymbol)(document.uri) .find((s) => s.equalDefinition(symbol)); if (newSymbol) return [newSymbol.toLocation()]; } if (symbol) return [symbol.toLocation()]; if (!config_1.config.fish_lsp_single_workspace_support && workspace_manager_1.workspaceManager.current) { const node = this.nodeAtPoint(document.uri, position.line, position.character); if (node && (0, node_types_1.isCommandName)(node)) { const text = node.text.toString(); const locations = (0, exec_1.execCommandLocations)(text); for (const { uri, path } of locations) { const content = file_operations_1.SyncFileHelper.read(path, 'utf8'); const doc = document_1.LspDocument.createTextDocumentItem(uri, content); workspace_manager_1.workspaceManager.handleOpenDocument(doc); startup_1.connection.sendNotification('workspace/didChangeWorkspaceFolders', { event: { added: [path], removed: [], }, }); workspace_manager_1.workspaceManager.analyzePendingDocuments(); } return locations.map(({ uri }) => vscode_languageserver_1.Location.create(uri, { start: { line: 0, character: 0 }, end: { line: 0, character: 0 }, })); } } return []; } getImplementation(document, position) { const definition = this.getDefinition(document, position); if (!definition) return []; const locations = (0, references_1.getImplementation)(document, position); return locations; } getSourceDefinitionLocation(node) { if (node && (0, source_1.isSourceCommandArgumentName)(node)) { const expanded = (0, source_1.getExpandedSourcedFilenameNode)(node); let sourceDoc = this.getDocumentFromPath(expanded); if (!sourceDoc) { this.analyzePath(expanded); sourceDoc = this.getDocumentFromPath(expanded); } if (sourceDoc) { return [ vscode_languageserver_1.Location.create(sourceDoc.uri, LSP.Range.create(0, 0, 0, 0)), ]; } } return []; } getHover(document, position) { const tree = this.getTree(document.uri); const node = this.nodeAtPoint(document.uri, position.line, position.character); if (!tree || !node) return null; const symbol = this.getDefinition(document, position) || this.globalSymbols.findFirst(node.text); if (!symbol) return null; logger_1.logger.log(`analyzer.getHover: ${symbol.name}`, { name: symbol.name, uri: symbol.uri, detail: symbol.detail, text: symbol.node.text, kind: (0, translation_1.symbolKindToString)(symbol.kind), }); return symbol.toHover(); } getTree(documentUri) { if (this.cache.hasUri(documentUri)) { return this.cache.getDocument(documentUri)?.tree; } return this.analyzePath((0, translation_1.uriToPath)(documentUri))?.tree; } getRootNode(documentUri) { return this.cache.getParsedTree(documentUri)?.rootNode; } getDocument(documentUri) { return this.cache.getDocument(documentUri)?.document; } getDocumentFromPath(path) { const uri = (0, translation_1.pathToUri)(path); return this.getDocument(uri); } getDocumentSymbols(documentUri) { return this.cache.getDocumentSymbols(documentUri); } getFlatDocumentSymbols(documentUri) { return this.cache.getFlatDocumentSymbols(documentUri); } getFlatCompletionSymbols(documentUri) { const doc = this.cache.getDocument(documentUri); if (!doc) return []; const { document, commandNodes } = doc; const childrenSymbols = commandNodes.filter(n => (0, complete_1.isCompletionCommandDefinition)(n)); const result = []; for (const child of childrenSymbols) { result.push(...(0, complete_1.processCompletion)(document, child)); } return result; } nodesGen(documentUri) { const document = this.cache.getDocument(documentUri)?.document; if (!document) { return undefined; } const root = this.getRootNode(documentUri); if (!root) { return undefined; } return { nodes: (0, tree_sitter_1.nodesGen)(root), namedNodes: (0, tree_sitter_1.namedNodesGen)(root), }; } getNodes(documentUri) { const document = this.cache.getDocument(documentUri)?.document; if (!document) { return []; } return (0, tree_sitter_1.getChildNodes)(this.parser.parse(document.getText()).rootNode); } getNamedNodes(documentUri) { const document = this.cache.getDocument(documentUri)?.document; if (!document) { return []; } return (0, tree_sitter_1.getNamedChildNodes)(this.parser.parse(document.getText()).rootNode); } getDiagnostics(documentUri) { const doc = this.getDocument(documentUri); const root = this.getRootNode(documentUri); if (!doc || !root) { return []; } return (0, validate_1.getDiagnostics)(root, doc); } collectSources(documentUri, sources = this.cache.getSources(documentUri)) { const visited = new Set(); const collectionStack = Array.from(sources); while (collectionStack.length > 0) { const source = collectionStack.pop(); if (visited.has(source)) continue; visited.add(source); if (file_operations_1.SyncFileHelper.isDirectory((0, translation_1.uriToPath)(source))) continue; if (!file_operations_1.SyncFileHelper.isFile((0, translation_1.uriToPath)(source))) continue; const cahedSourceDoc = this.cache.hasUri(source) ? this.cache.getDocument(source) : this.analyzePath((0, translation_1.uriToPath)(source)); if (!cahedSourceDoc) continue; const sourced = this.cache.getSources(cahedSourceDoc.document.uri); collectionStack.push(...Array.from(sourced)); } return visited; } collectReachableSources(documentUri, position) { const currentNode = this.nodeAtPoint(documentUri, position.line, position.character); let currentParent; if (currentNode) currentParent = (0, node_types_1.findParentFunction)(currentNode); const sourceNodes = this.cache.getSourceNodes(documentUri) .filter(node => { if ((0, node_types_1.isTopLevelDefinition)(node) && (0, tree_sitter_1.isPositionAfter)((0, tree_sitter_1.getRange)(node).start, position)) { return true; } const parentFunction = (0, node_types_1.findParentFunction)(node); if (currentParent && parentFunction?.equals(currentParent) && (0, tree_sitter_1.isPositionAfter)((0, tree_sitter_1.getRange)(node).start, position)) { return true; } return false; }); const sources = new Set(); for (const node of sourceNodes) { const sourced = (0, source_1.getExpandedSourcedFilenameNode)(node); if (sourced) { sources.add((0, translation_1.pathToUri)(sourced)); } } return this.collectSources(documentUri, sources); } collectAllSources(documentUri) { const allSources = this.collectSources(documentUri); for (const source of Array.from(allSources)) { const sourceDoc = this.cache.getDocument(source); if (!sourceDoc) { this.analyzePath(source); } } return allSources; } parseCurrentLine(document, position) { const line = document .getLineBeforeCursor(position) .replace(/^(.*)\n$/, '$1') || ''; const word = this.wordAtPoint(document.uri, position.line, Math.max(position.character - 1, 0)) || ''; const lineRootNode = this.parser.parse(line).rootNode; const lineLastNode = lineRootNode.descendantForPosition({ row: 0, column: line.length - 1, }); return { line, word, lineRootNode, lineLastNode }; } wordAtPoint(uri, line, column) { const node = this.nodeAtPoint(uri, line, column); if (!node || node.childCount > 0 || node.text.trim() === '') { return null; } if ((0, node_types_1.isAliasDefinitionName)(node) || (0, node_types_1.isExportVariableDefinitionName)(node)) return node.text.split('=')[0].trim(); return node.text.trim(); } nodeAtPoint(uri, line, column) { const tree = this.cache.getParsedTree(uri); if (!tree?.rootNode) { return null; } return tree.rootNode.descendantForPosition({ row: line, column }); } commandNameAtPoint(uri, line, column) { let node = this.nodeAtPoint(uri, line, column); while (node && !(0, node_types_1.isCommand)(node)) { node = node.parent; } if (!node) return null; const firstChild = node.firstNamedChild; if (!firstChild || !(0, node_types_1.isCommandName)(firstChild)) return null; return firstChild.text.trim(); } commandAtPoint(uri, line, column) { const node = this.nodeAtPoint(uri, line, column) ?? undefined; if (node && (0, node_types_1.isCommand)(node)) return node; const parentCommand = (0, node_types_1.findParentCommand)(node); return parentCommand; } getTextAtLocation(location) { const document = this.cache.getDocument(location.uri); if (!document) { return ''; } const text = document.document.getText(location.range); return text; } ensureCachedDocument(doc) { if (this.cache.hasUri(doc.uri)) { return this.cache.getDocument(doc.uri); } return this.analyze(doc); } } exports.Analyzer = Analyzer; class GlobalDefinitionCache { _definitions; constructor(_definitions = new Map()) { this._definitions = _definitions; } add(symbol) { const current = this._definitions.get(symbol.name) || []; if (!current.some(s => s.equals(symbol))) { current.push(symbol); } this._definitions.set(symbol.name, current); } find(name) { return this._definitions.get(name) || []; } findFirst(name) { const symbols = this.find(name); if (symbols.length === 0) { return undefined; } return symbols[0]; } has(name) { return this._definitions.has(name); } uniqueSymbols() { const unique = []; this.allNames.forEach(name => { const u = this.findFirst(name); if (u) { unique.push(u); } }); return unique; } get allSymbols() { const all = []; for (const [_, symbols] of this._definitions.entries()) { all.push(...symbols); } return all; } get allNames() { return [...this._definitions.keys()]; } get map() { return this._definitions; } } class AnalyzedDocumentCache { _documents; constructor(_documents = new Map()) { this._documents = _documents; } uris() { return [...this._documents.keys()]; } setDocument(uri, analyzedDocument) { this._documents.set(uri, analyzedDocument); } getDocument(uri) { if (!this._documents.has(uri)) { return undefined; } return this._documents.get(uri); } hasUri(uri) { return this._documents.has(uri); } updateUri(oldUri, newUri) { const oldValue = this.getDocument(oldUri); if (oldValue) { this._documents.delete(oldUri); this._documents.set(newUri, oldValue); } } getDocumentSymbols(uri) { return this._documents.get(uri)?.documentSymbols || []; } getFlatDocumentSymbols(uri) { return (0, flatten_1.flattenNested)(...this.getDocumentSymbols(uri)); } getCommands(uri) { return this._documents.get(uri)?.commandNodes || []; } getRootNode(uri) { return this.getParsedTree(uri)?.rootNode; } getParsedTree(uri) { return this._documents.get(uri)?.tree; } getSymbolTree(uri) { const document = this.getDocument(uri); if (!document) { return []; } return document.documentSymbols; } getSources(uri) { const document = this.getDocument(uri); if (!document) { return new Set(); } const result = new Set(); const sourceNodes = document.sourceNodes.map(node => (0, source_1.getExpandedSourcedFilenameNode)(node)).filter(s => !!s); for (const source of sourceNodes) { const sourceUri = (0, translation_1.pathToUri)(source); result.add(sourceUri); } return result; } getSourceNodes(uri) { const document = this.getDocument(uri); if (!document) { return []; } return document.sourceNodes; } clear(uri) { this._documents.delete(uri); } }