fish-lsp
Version:
LSP implementation for fish/fish-shell
179 lines (178 loc) • 10.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRefactorAction = createRefactorAction;
exports.extractFunctionWithArgparseToCompletionsFile = extractFunctionWithArgparseToCompletionsFile;
exports.extractFunctionToFile = extractFunctionToFile;
exports.extractToFunction = extractToFunction;
exports.extractCommandToFunction = extractCommandToFunction;
exports.extractToVariable = extractToVariable;
exports.convertIfToCombiners = convertIfToCombiners;
const os_1 = __importDefault(require("os"));
const vscode_languageserver_1 = require("vscode-languageserver");
const tree_sitter_1 = require("../utils/tree-sitter");
const node_types_1 = require("../utils/node-types");
const action_kinds_1 = require("./action-kinds");
const combiner_1 = require("./combiner");
const path_1 = __importDefault(require("path"));
const translation_1 = require("../utils/translation");
const logger_1 = require("../logger");
const argparse_completions_1 = require("./argparse-completions");
function createRefactorAction(title, kind, edits, preferredAction = false) {
return {
title,
kind,
edit: { changes: edits },
isPreferred: preferredAction,
};
}
function extractFunctionWithArgparseToCompletionsFile(document, range, node) {
logger_1.logger.log('extractFunctionWithArgparseToCompletionsFile', document, range, { node: { text: node.text, type: node.type } });
let selectedNode = node;
if ((0, node_types_1.isFunctionDefinitionName)(node)) {
selectedNode = node.parent;
}
if ((0, node_types_1.isCommandWithName)(selectedNode, 'argparse') || selectedNode.text.startsWith('argparse')) {
selectedNode = (0, tree_sitter_1.findEnclosingScope)(selectedNode);
}
if (selectedNode.type !== 'function_definition')
return;
const argparseNode = (0, tree_sitter_1.getChildNodes)(selectedNode).find(n => (0, node_types_1.isCommandWithName)(n, 'argparse'));
const hasArgparse = !!argparseNode;
if (!hasArgparse)
return;
const functionName = (0, tree_sitter_1.getChildNodes)(selectedNode).find(n => (0, node_types_1.isFunctionDefinitionName)(n)).text;
const autoloadType = document.getAutoloadType();
if (functionName !== document.getAutoLoadName() || !['functions', 'config.fish'].includes(autoloadType))
return;
const completionPath = path_1.default.join(os_1.default.homedir(), '.config', 'fish', 'completions', `${functionName}.fish`);
const completionUri = (0, translation_1.pathToUri)(completionPath);
const completionFlags = (0, argparse_completions_1.findFlagsToComplete)(argparseNode);
const completionText = (0, argparse_completions_1.buildCompleteString)(functionName, completionFlags);
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 createFileAction = vscode_languageserver_1.CreateFile.create(completionUri, { ignoreIfExists: true, overwrite: false });
const selectedText = `\n# auto generated by fish-lsp\n${completionText}\n`;
const createFileEdit = vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(completionUri, 0), [vscode_languageserver_1.TextEdit.insert({ line: 0, character: 0 }, selectedText)]);
const workspaceEdit = {
documentChanges: [
createFileAction,
createFileEdit,
],
changeAnnotations: { [changeAnnotation.label]: changeAnnotation },
};
return {
title: `Create completions for '${functionName}' in file: ${shortPath}`,
kind: action_kinds_1.SupportedCodeActionKinds.RefactorExtract,
edit: workspaceEdit,
};
}
function extractFunctionToFile(document, range, node) {
logger_1.logger.log('extractFunctionToFile', document, range, { node: { text: node.text, type: node.type } });
let selectedNode = node;
if ((0, node_types_1.isFunctionDefinitionName)(node)) {
selectedNode = node.parent;
}
if (selectedNode.type !== 'function_definition')
return;
const functionName = (0, tree_sitter_1.getChildNodes)(selectedNode).find(n => (0, node_types_1.isFunctionDefinitionName)(n)).text;
if (functionName === document.getAutoLoadName())
return;
const functionPath = path_1.default.join(os_1.default.homedir(), '.config', 'fish', 'functions', `${functionName}.fish`);
const functionUri = (0, translation_1.pathToUri)(functionPath);
const shortPath = (0, translation_1.uriToReadablePath)(functionPath);
const changeAnnotation = {
label: `Extract function '${functionName}' to file: ${shortPath}`,
description: `Extract function '${functionName}' to file: ${shortPath}`,
};
const createFileAction = vscode_languageserver_1.CreateFile.create(functionUri, { ignoreIfExists: false, overwrite: true });
const selectedText = document.getText((0, tree_sitter_1.getRange)(selectedNode));
const createFileEdit = vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(functionUri, 0), [vscode_languageserver_1.TextEdit.insert({ line: 0, character: 0 }, selectedText)]);
const removeOldFunction = vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(document.uri, document.version), [vscode_languageserver_1.TextEdit.del((0, tree_sitter_1.getRange)(selectedNode))]);
const workspaceEdit = {
documentChanges: [
createFileAction,
createFileEdit,
removeOldFunction,
],
changeAnnotations: { [changeAnnotation.label]: changeAnnotation },
};
return {
title: `Extract function '${functionName}' to file: ${shortPath}`,
kind: action_kinds_1.SupportedCodeActionKinds.RefactorExtract,
edit: workspaceEdit,
};
}
function extractToFunction(document, range) {
logger_1.logger.log('extractToFunction', document, range);
const functionName = `extracted_function_${Math.floor(Math.random() * 1000)}`;
const selectedText = document.getText(range);
if (selectedText.trim() === '' && document.getLine(range.start.line).trim() !== '')
return;
const indent = document.getIndentAtLine(range.start.line);
const functionText = [
`\n${indent}function ${functionName}`,
...selectedText.split('\n').map(line => `${indent} ${line}`),
`${indent}end\n`,
].join('\n');
const insertEdit = vscode_languageserver_1.TextEdit.insert({ line: range.start.line, character: 0 }, `\n${functionText}\n`);
const replaceEdit = vscode_languageserver_1.TextEdit.replace(range, `${functionName}`);
return createRefactorAction(`Extract to local function '${functionName}'`, action_kinds_1.SupportedCodeActionKinds.RefactorExtract, {
[document.uri]: [replaceEdit, insertEdit],
});
}
function extractCommandToFunction(document, selectedNode) {
logger_1.logger.log('extractCommandToFunction', document, { selectedNode: { text: selectedNode.text, type: selectedNode.type } });
const functionName = `extracted_function_${Math.floor(Math.random() * 1000)}`;
let cmd = selectedNode;
if (selectedNode.type !== 'command') {
cmd = (0, node_types_1.findParentCommand)(selectedNode) || selectedNode;
}
if (!cmd || !(0, node_types_1.isCommand)(cmd))
return;
const selectedText = document.getText((0, tree_sitter_1.getRange)(cmd));
const functionText = [
`\nfunction ${functionName}`,
...selectedText.split('\n').map(line => ` ${line}`),
'end\n',
].join('\n');
const replaceEdit = vscode_languageserver_1.TextEdit.replace((0, tree_sitter_1.getRange)(cmd), `${functionName}`);
const insertEdit = vscode_languageserver_1.TextEdit.insert({ line: document.getLines(), character: 0 }, `\n${functionText}\n`);
return createRefactorAction(`Extract command to local function '${functionName}'`, action_kinds_1.SupportedCodeActionKinds.RefactorExtract, {
[document.uri]: [replaceEdit, insertEdit],
});
}
function extractToVariable(document, range, selectedNode) {
logger_1.logger.log('extractToVariable', document, { selectedNode: { text: selectedNode.text, type: selectedNode.type } });
if (!(0, node_types_1.isCommand)(selectedNode))
return undefined;
const selectedText = document.getText(range);
const varName = `extracted_var_${Math.floor(Math.random() * 1000)}`;
const declaration = `set -l ${varName} (${selectedText})\n`;
const replaceEdit = vscode_languageserver_1.TextEdit.replace(range, declaration);
return createRefactorAction(`Extract selected '${selectedNode.firstNamedChild.text}' command to local variable '${varName}'`, action_kinds_1.SupportedCodeActionKinds.RefactorExtract, {
[document.uri]: [replaceEdit],
});
}
function convertIfToCombiners(document, selectedNode, isSelected = true) {
logger_1.logger.log('convertIfToCombiners', document, { selectedNode: { text: selectedNode.text, type: selectedNode.type } });
let node = selectedNode;
if (node.type === 'if' && !(0, node_types_1.isIfStatement)(node)) {
node = node.parent;
}
if (!(0, node_types_1.isIfStatement)(node))
return undefined;
const combinerString = (0, combiner_1.convertIfToCombinersString)(node);
const formattedString = (0, translation_1.formatTextWithIndents)(document, selectedNode.startPosition.row, combinerString).trimStart();
const message = isSelected ?
`Convert selected if statement to conditionally executed statement (line: ${node.startPosition.row + 1})` :
`Convert if statement to conditionally executed statement (line: ${node.startPosition.row + 1})`;
return createRefactorAction(message, action_kinds_1.SupportedCodeActionKinds.RefactorRewrite, {
[document.uri]: [vscode_languageserver_1.TextEdit.replace((0, tree_sitter_1.getRange)(node), formattedString)],
}, true);
}