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