fish-lsp
Version:
LSP implementation for fish/fish-shell
337 lines (336 loc) • 13.8 kB
JavaScript
;
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;
}