UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

179 lines (178 loc) 10.3 kB
"use strict"; 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); }