fish-lsp
Version:
LSP implementation for fish/fish-shell
832 lines (831 loc) • 34 kB
JavaScript
"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);
}
}