fish-lsp
Version:
LSP implementation for fish/fish-shell
365 lines (364 loc) • 13.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompletionSymbol = exports.CompleteOptions = void 0;
exports.isCompletionCommandDefinition = isCompletionCommandDefinition;
exports.isMatchingCompletionFlagNodeWithFishSymbol = isMatchingCompletionFlagNodeWithFishSymbol;
exports.isCompletionDefinitionWithName = isCompletionDefinitionWithName;
exports.isCompletionSymbolShort = isCompletionSymbolShort;
exports.isCompletionSymbolLong = isCompletionSymbolLong;
exports.isCompletionSymbolOld = isCompletionSymbolOld;
exports.isCompletionSymbol = isCompletionSymbol;
exports.isCompletionSymbolVerbose = isCompletionSymbolVerbose;
exports.getCompletionSymbol = getCompletionSymbol;
exports.groupCompletionSymbolsTogether = groupCompletionSymbolsTogether;
exports.getGroupedCompletionSymbolsAsArgparse = getGroupedCompletionSymbolsAsArgparse;
exports.processCompletion = processCompletion;
const node_types_1 = require("../utils/node-types");
const options_1 = require("./options");
const tree_sitter_1 = require("../utils/tree-sitter");
const vscode_languageserver_1 = require("vscode-languageserver");
const logger_1 = require("../logger");
const nested_strings_1 = require("./nested-strings");
exports.CompleteOptions = [
options_1.Option.create('-c', '--command').withValue(),
options_1.Option.create('-p', '--path'),
options_1.Option.create('-e', '--erase'),
options_1.Option.create('-s', '--short-option').withValue(),
options_1.Option.create('-l', '--long-option').withValue(),
options_1.Option.create('-o', '--old-option').withValue(),
options_1.Option.create('-a', '--arguments').withValue(),
options_1.Option.create('-k', '--keep-order'),
options_1.Option.create('-f', '--no-files'),
options_1.Option.create('-F', '--force-files'),
options_1.Option.create('-r', '--require-parameter'),
options_1.Option.create('-x', '--exclusive'),
options_1.Option.create('-d', '--description').withValue(),
options_1.Option.create('-w', '--wraps').withValue(),
options_1.Option.create('-n', '--condition').withValue(),
options_1.Option.create('-C', '--do-complete').withValue(),
options_1.Option.long('--escape').withValue(),
options_1.Option.create('-h', '--help'),
];
function isCompletionCommandDefinition(node) {
return (0, node_types_1.isCommandWithName)(node, 'complete');
}
function isMatchingCompletionFlagNodeWithFishSymbol(symbol, node) {
if (!node?.parent || (0, node_types_1.isCommand)(node) || (0, node_types_1.isOption)(node))
return false;
const prevNode = node.previousNamedSibling;
if (!prevNode)
return false;
if (symbol.isFunction()) {
if ((0, options_1.isMatchingOption)(prevNode, options_1.Option.create('-c', '--command'), options_1.Option.create('-w', '--wraps'))) {
return symbol.name === node.text && !symbol.equalsNode(node);
}
if ((0, options_1.isMatchingOption)(prevNode, options_1.Option.create('-n', '--condition'), options_1.Option.create('-a', '--arguments'))) {
return (0, node_types_1.isString)(node)
? (0, nested_strings_1.extractCommands)(node).some(cmd => cmd === symbol.name)
: node.text === symbol.name;
}
}
if (symbol.isArgparse()) {
if (isCompletionSymbol(node)) {
const completionSymbol = getCompletionSymbol(node);
return completionSymbol.equalsArgparse(symbol);
}
}
if (symbol.isVariable()) {
return node.text === symbol.name;
}
return false;
}
function isCompletionDefinitionWithName(node, name, doc) {
if (node.parent && isCompletionCommandDefinition(node.parent)) {
const symbol = getCompletionSymbol(node.parent, doc);
return symbol?.commandName === name && isCompletionSymbol(node);
}
return false;
}
function isCompletionSymbolShort(node) {
if (node.parent && isCompletionCommandDefinition(node.parent)) {
return node.previousSibling && (0, options_1.isMatchingOption)(node.previousSibling, options_1.Option.create('-s', '--short-option'));
}
return false;
}
function isCompletionSymbolLong(node) {
if (node.parent && isCompletionCommandDefinition(node.parent)) {
return node.previousSibling && (0, options_1.isMatchingOption)(node.previousSibling, options_1.Option.create('-l', '--long-option'));
}
return false;
}
function isCompletionSymbolOld(node) {
if (node.parent && isCompletionCommandDefinition(node.parent)) {
return node.previousSibling && (0, options_1.isMatchingOption)(node.previousSibling, options_1.Option.create('-o', '--old-option'));
}
return false;
}
function isCompletionSymbol(node) {
return isCompletionSymbolShort(node)
|| isCompletionSymbolLong(node)
|| isCompletionSymbolOld(node);
}
class CompletionSymbol {
optionType;
commandName;
node;
description;
condition;
requireParameter;
argumentNames;
exclusive;
document;
constructor(optionType = '', commandName = '', node = null, description = '', condition = '', requireParameter = false, argumentNames = '', exclusive = false, document) {
this.optionType = optionType;
this.commandName = commandName;
this.node = node;
this.description = description;
this.condition = condition;
this.requireParameter = requireParameter;
this.argumentNames = argumentNames;
this.exclusive = exclusive;
this.document = document;
}
static createEmpty() {
return new CompletionSymbol();
}
static create({ optionType = '', commandName = '', node = null, description = '', condition = '', requireParameter = false, argumentNames = '', exclusive = false, }) {
return new this(optionType, commandName, node, description, condition, requireParameter, argumentNames, exclusive);
}
isEmpty() {
return this.node === null;
}
isNonEmpty() {
return this.node !== null && this.parent !== null;
}
get parent() {
if (this.node) {
return this.node.parent;
}
return null;
}
get text() {
if (this.isNonEmpty()) {
return this.node.text;
}
return '';
}
isShort() {
return this.optionType === 'short';
}
isLong() {
return this.optionType === 'long';
}
isOld() {
return this.optionType === 'old';
}
isCorrespondingOption(other) {
if (!this.isNonEmpty() || !other.isNonEmpty()) {
return false;
}
return this.parent.equals(other.parent)
&& this.commandName === other.commandName
&& this.optionType !== other.optionType;
}
toFlag() {
if (!this.isNonEmpty())
return '';
switch (this.optionType) {
case 'short':
case 'old':
return `-${this.node.text}`;
case 'long':
return `--${this.node.text}`;
default:
return '';
}
}
toUsage() {
if (!this.isNonEmpty()) {
return '';
}
return `${this.commandName} ${this.toFlag()}`;
}
toUsageVerbose() {
if (!this.isNonEmpty()) {
return '';
}
return `${this.commandName} ${this.toFlag()} # ${this.description}`;
}
equalsArgparse(symbol) {
if (symbol.fishKind !== 'ARGPARSE' || !symbol.parent) {
return false;
}
const commandName = symbol.parent.name;
const symbolName = symbol.argparseFlagName;
return this.commandName === commandName
&& this.node?.text === symbolName;
}
equalsCommand(symbol) {
if (!symbol.isFunction()) {
return false;
}
const commandName = symbol.name;
return this.hasCommandName(commandName);
}
equalsNode(n) {
return this.node?.equals(n);
}
hasCommandName(name) {
return this.commandName === name;
}
isMatchingRawOption(...opts) {
const flag = this.toFlag();
for (const opt of opts) {
if (flag === opt) {
return true;
}
}
return false;
}
getRange() {
if (this.isNonEmpty()) {
return (0, tree_sitter_1.getRange)(this.node);
}
return null;
}
toLocation() {
return vscode_languageserver_1.Location.create(this.document?.uri || '', this.getRange());
}
toPosition() {
if (this.isNonEmpty()) {
return (0, tree_sitter_1.pointToPosition)(this.node.startPosition);
}
return null;
}
toArgparseOpt() {
if (!this.isNonEmpty()) {
return '';
}
return this.text;
}
toArgparseVariableName() {
const prefix = '_flag_';
const fixString = (str) => str.replace(/-/g, '_');
if (!this.isNonEmpty()) {
return '';
}
return prefix + fixString(this.text);
}
static is(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
return obj instanceof CompletionSymbol
&& typeof obj.optionType === 'string'
&& typeof obj.commandName === 'string'
&& typeof obj.description === 'string'
&& typeof obj.condition === 'string'
&& typeof obj.requireParameter === 'boolean'
&& typeof obj.argumentNames === 'string';
}
}
exports.CompletionSymbol = CompletionSymbol;
function isCompletionSymbolVerbose(node, doc) {
if (isCompletionSymbol(node) || !node.parent) {
return true;
}
if (node.parent && isCompletionCommandDefinition(node.parent)) {
const symbol = getCompletionSymbol(node, doc);
return symbol?.isNonEmpty() || false;
}
return false;
}
function getCompletionSymbol(node, doc) {
const result = CompletionSymbol.createEmpty();
if (!isCompletionSymbol(node) || !node.parent) {
return result;
}
switch (true) {
case isCompletionSymbolShort(node):
result.optionType = 'short';
break;
case isCompletionSymbolLong(node):
result.optionType = 'long';
break;
case isCompletionSymbolOld(node):
result.optionType = 'old';
break;
default:
break;
}
result.node = node;
const parent = node.parent;
const children = parent.childrenForFieldName('argument');
result.document = doc;
children.forEach((child, idx) => {
if (idx === 0)
return;
if ((0, options_1.isMatchingOption)(child, options_1.Option.create('-r', '--require-parameter'))) {
result.requireParameter = true;
}
if ((0, options_1.isMatchingOption)(child, options_1.Option.create('-x', '--exclusive'))) {
result.exclusive = true;
}
const prev = child.previousSibling;
if (!prev)
return;
if ((0, options_1.isMatchingOption)(prev, options_1.Option.create('-c', '--command'))) {
result.commandName = child.text;
}
if ((0, options_1.isMatchingOption)(prev, options_1.Option.create('-d', '--description'))) {
result.description = (0, node_types_1.isString)(child) ? child.text.slice(1, -1) : child.text;
}
if ((0, options_1.isMatchingOption)(prev, options_1.Option.create('-n', '--condition'))) {
result.condition = child.text;
}
if ((0, options_1.isMatchingOption)(prev, options_1.Option.create('-a', '--arguments'))) {
result.argumentNames = child.text;
}
});
return result;
}
function groupCompletionSymbolsTogether(...symbols) {
const storedSymbols = new Set();
const groupedSymbols = [];
symbols.forEach((symbol) => {
if (storedSymbols.has(symbol.text)) {
return;
}
const newGroup = [symbol];
const matches = symbols.filter((s) => s.isCorrespondingOption(symbol));
matches.forEach((s) => {
storedSymbols.add(s.text);
newGroup.push(s);
});
groupedSymbols.push(newGroup);
});
return groupedSymbols;
}
function getGroupedCompletionSymbolsAsArgparse(groupedCompletionSymbols, argparseSymbols) {
const missingArgparseValues = [];
for (const symbolGroup of groupedCompletionSymbols) {
if (argparseSymbols.some(argparseSymbol => symbolGroup.find(s => s.equalsArgparse(argparseSymbol)))) {
logger_1.logger.info({
message: 'Skipping symbol group that already has an argparse value',
symbolGroup: symbolGroup.map(s => s.toFlag()),
focusedSymbols: argparseSymbols.find(fs => symbolGroup.find(s => s.equalsArgparse(fs)))?.name,
});
continue;
}
missingArgparseValues.push(symbolGroup);
}
return missingArgparseValues;
}
function processCompletion(document, node) {
const result = [];
for (const child of (0, tree_sitter_1.getChildNodes)(node)) {
if (isCompletionCommandDefinition(node)) {
const newSymbol = getCompletionSymbol(child, document);
if (newSymbol)
result.push(newSymbol);
}
}
return result;
}