UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

365 lines (364 loc) 13.5 kB
"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; }