fish-lsp
Version:
LSP implementation for fish/fish-shell
150 lines (149 loc) • 6.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.findFlagsToComplete = findFlagsToComplete;
exports.buildCompleteString = buildCompleteString;
exports.createArgparseCompletionsCodeAction = createArgparseCompletionsCodeAction;
const node_types_1 = require("../utils/node-types");
const tree_sitter_1 = require("../utils/tree-sitter");
const vscode_languageserver_1 = require("vscode-languageserver");
const refactors_1 = require("./refactors");
const translation_1 = require("../utils/translation");
const logger_1 = require("../logger");
const options_1 = require("../parsing/options");
function parseArgparseFlag(text) {
const beforeEquals = text.split('=')[0];
if (beforeEquals.includes('/')) {
const [short, long] = beforeEquals.split('/');
return {
shortOption: short,
longOption: long === '' ? '' : long,
};
}
return {
longOption: beforeEquals,
};
}
function isSkipablePreviousOption(node) {
if (node.text.includes('='))
return false;
return (0, node_types_1.isMatchingOption)(node, options_1.Option.create('-N', '--min-args')) ||
(0, node_types_1.isMatchingOption)(node, options_1.Option.create('-n', '--name')) ||
(0, node_types_1.isMatchingOption)(node, options_1.Option.create('-x', '--exclusive')) ||
(0, node_types_1.isMatchingOption)(node, options_1.Option.create('-X', '--max-args'));
}
function findFlagsToComplete(node) {
if (!(0, node_types_1.isCommandWithName)(node, 'argparse'))
return [];
const flags = [];
for (const child of (0, tree_sitter_1.getChildNodes)(node)) {
if ((0, node_types_1.isEndStdinCharacter)(child))
break;
if ((0, node_types_1.isCommandName)(child))
continue;
if ((0, node_types_1.isOption)(child))
continue;
const prev = child.previousSibling;
if (prev && (0, node_types_1.isOption)(prev) && isSkipablePreviousOption(prev))
continue;
if ((0, node_types_1.isString)(child)) {
const text = child.text.slice(1, -1);
flags.push(parseArgparseFlag(text));
continue;
}
if (child.type === 'word' && !child.text.startsWith('-')) {
flags.push(parseArgparseFlag(child.text));
}
}
return flags;
}
function buildCompleteString(commandName, flags) {
return flags.map(flag => {
let text = `complete -c ${commandName}`;
if (flag.shortOption) {
text += ` -s ${flag.shortOption}`;
}
if (flag.longOption) {
text += ` -l ${flag.longOption}`;
}
return text;
}).join('\n');
}
function buildConfdCompletions(argparseNode, functionNode, functionNameNode, doc) {
logger_1.logger.log(buildConfdCompletions.name, 'params', {
argparseNode: argparseNode.text,
functionNode: functionNode.text,
functionNameNode: functionNameNode.text,
doc: doc.uri,
});
const completionPath = doc.getRelativeFilenameToWorkspace();
const flags = findFlagsToComplete(argparseNode);
if (!(0, node_types_1.isFunctionDefinition)(functionNode)) {
return undefined;
}
const functionName = functionNode.firstNamedChild.text;
const completionText = buildCompleteString(functionName, flags);
const selectedText = `\n# auto generated by fish-lsp\n${completionText}\n`;
const shortPath = (0, translation_1.uriToReadablePath)(completionPath);
const changeAnnotation = {
label: `Create completions for '${functionName}' in file: ${shortPath}`,
description: `Create completions for '${functionName}' to file: ${shortPath}`,
};
const workspaceEdit = {
documentChanges: [
vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(doc.uri, 0), [vscode_languageserver_1.TextEdit.insert((0, tree_sitter_1.getRange)(functionNode).end, selectedText)]),
],
changeAnnotations: { [changeAnnotation.label]: changeAnnotation },
};
logger_1.logger.log(buildConfdCompletions.name, 'return', {
textEdits: workspaceEdit.documentChanges,
});
return {
title: `Create completions for: ${functionName}`,
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
edit: workspaceEdit,
};
}
function getNodesForArgparse(selectedNode) {
const node = selectedNode;
if ((0, node_types_1.isCommandWithName)(node, 'argparse')) {
const functionNode = (0, node_types_1.findParentFunction)(node);
return {
argparseNode: node,
functionNode: functionNode,
functionNameNode: functionNode?.firstNamedChild,
};
}
if (node.type === 'word' && node.parent && (0, node_types_1.isCommandWithName)(node.parent, 'argparse')) {
const functionNode = (0, node_types_1.findParentFunction)(node.parent);
return {
argparseNode: node.parent,
functionNode: functionNode,
functionNameNode: functionNode?.firstNamedChild,
};
}
if (node.type === 'function_definition') {
return {
argparseNode: (0, tree_sitter_1.getChildNodes)(node).find(n => (0, node_types_1.isCommandWithName)(n, 'argparse')),
functionNode: node,
functionNameNode: node.firstNamedChild,
};
}
return {
argparseNode: undefined,
functionNode: undefined,
functionNameNode: undefined,
};
}
function createArgparseCompletionsCodeAction(node, doc) {
const autoloadType = doc.getAutoloadType();
const { argparseNode, functionNode, functionNameNode } = getNodesForArgparse(node);
if (!argparseNode || !functionNode || !functionNameNode)
return undefined;
if (autoloadType === 'functions') {
return (0, refactors_1.extractFunctionWithArgparseToCompletionsFile)(doc, (0, tree_sitter_1.getRange)(functionNode), functionNode);
}
if (autoloadType === 'conf.d') {
return buildConfdCompletions(node, functionNode, functionNameNode, doc);
}
return undefined;
}