UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

296 lines (295 loc) 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LspCommands = exports.CommandNames = void 0; exports.createExecuteCommandHandler = createExecuteCommandHandler; const vscode_languageserver_1 = require("vscode-languageserver"); const code_action_handler_1 = require("./code-actions/code-action-handler"); const quick_fixes_1 = require("./code-actions/quick-fixes"); const config_1 = require("./config"); const validate_1 = require("./diagnostics/validate"); const execute_handler_1 = require("./execute-handler"); const logger_1 = require("./logger"); const env_manager_1 = require("./utils/env-manager"); const exec_1 = require("./utils/exec"); const snippets_1 = require("./utils/snippets"); const translation_1 = require("./utils/translation"); const tree_sitter_1 = require("./utils/tree-sitter"); const workspace_manager_1 = require("./utils/workspace-manager"); exports.CommandNames = { EXECUTE_RANGE: 'fish-lsp.executeRange', EXECUTE_LINE: 'fish-lsp.executeLine', EXECUTE: 'fish-lsp.execute', EXECUTE_BUFFER: 'fish-lsp.executeBuffer', CREATE_THEME: 'fish-lsp.createTheme', SHOW_STATUS_DOCS: 'fish-lsp.showStatusDocs', SHOW_WORKSPACE_MESSAGE: 'fish-lsp.showWorkspaceMessage', UPDATE_WORKSPACE: 'fish-lsp.updateWorkspace', FIX_ALL: 'fish-lsp.fixAll', TOGGLE_SINGLE_WORKSPACE_SUPPORT: 'fish-lsp.toggleSingleWorkspaceSupport', GENERATE_ENV_VARIABLES: 'fish-lsp.generateEnvVariables', CHECK_HEALTH: 'fish-lsp.checkHealth', SHOW_REFERENCES: 'fish-lsp.showReferences', }; exports.LspCommands = [...Array.from(Object.values(exports.CommandNames))]; function createExecuteCommandHandler(connection, docs, analyzer) { async function executeRange(path, startLine, endLine) { const cached = analyzer.analyzePath(path); if (!cached) return; const { document } = cached; const current = document; if (!current) return; const start = current.getLineStart(startLine - 1); const end = current.getLineEnd(endLine - 1); const range = vscode_languageserver_1.Range.create(start.line, start.character, end.line, end.character); logger_1.logger.log('executeRange', current.uri, range); const text = current.getText(range); const output = (await (0, exec_1.execAsync)(text)).stdout || ''; logger_1.logger.log('onExecuteCommand', text); logger_1.logger.log('onExecuteCommand', output); const response = (0, execute_handler_1.buildExecuteNotificationResponse)(text.split('\n').map(s => s.replace(/;\s?$/, '')).join('; '), { stdout: '\n' + output, stderr: '' }); (0, execute_handler_1.useMessageKind)(connection, response); } async function executeLine(path, line) { const cached = analyzer.analyzePath(path); if (!cached) return; const { document } = cached; logger_1.logger.log('executeLine', document.uri, line); if (!document) return; const numberLine = Number.parseInt(line.toString()) - 1; const text = document.getLine(numberLine); const cmdOutput = await (0, exec_1.execAsyncF)(`${text}; echo "\\$status: $status"`); logger_1.logger.log('executeLine.cmdOutput', cmdOutput); const output = (0, execute_handler_1.buildExecuteNotificationResponse)(text, { stdout: cmdOutput, stderr: '' }); logger_1.logger.log('onExecuteCommand', text); logger_1.logger.log('onExecuteCommand', output); (0, execute_handler_1.useMessageKind)(connection, output); } async function createTheme(path, asVariables = true) { const cached = analyzer.analyzePath(path); if (!cached) return; const { document } = cached; const output = (await (0, exec_1.execAsyncFish)('fish_config theme dump; or true')).stdout.split('\n'); if (!document) { logger_1.logger.error('createTheme', 'Document not found'); connection.sendNotification('window/showMessage', { message: ` Document not found: ${(0, translation_1.uriToReadablePath)((0, translation_1.pathToUri)(path))} `, type: vscode_languageserver_1.MessageType.Error, }); return; } const outputArr = []; if (asVariables) { outputArr.push('\n\n# created by fish-lsp'); } for (const line of output) { if (asVariables) { outputArr.push(`set -gx ${line}`); } else { outputArr.push(`${line}`); } } const outputStr = outputArr.join('\n'); const docsEnd = document.positionAt(document.getLines()); const workspaceEdit = { changes: { [document.uri]: [ vscode_languageserver_1.TextEdit.insert(docsEnd, outputStr), ], }, }; await connection.workspace.applyEdit(workspaceEdit); await connection.sendRequest('window/showDocument', { uri: document.uri, takeFocus: true, }); (0, execute_handler_1.useMessageKind)(connection, { message: `${execute_handler_1.fishLspPromptIcon} appended theme variables to end of file`, kind: 'info', }); } async function executeBuffer(path) { const output = await (0, execute_handler_1.execEntireBuffer)(path); (0, execute_handler_1.useMessageKind)(connection, output); } function handleShowStatusDocs(statusCode) { const statusInfo = snippets_1.PrebuiltDocumentationMap.getByType('status') .find(item => item.name === statusCode); if (statusInfo) { connection.window.showInformationMessage(statusInfo.description); } } function showWorkspaceMessage() { const message = `${execute_handler_1.fishLspPromptIcon} Workspace: ${workspace_manager_1.workspaceManager.current?.name}\n\n Total files analyzed: ${workspace_manager_1.workspaceManager.current?.uris.indexedCount}`; logger_1.logger.log('showWorkspaceMessage', config_1.config); connection.sendNotification('window/showMessage', { message: message, type: vscode_languageserver_1.MessageType.Info, }); return undefined; } async function _updateWorkspace(path, ...args) { const silence = args.includes('--quiet') || args.includes('-q'); const uri = (0, translation_1.pathToUri)(path); workspace_manager_1.workspaceManager.handleUpdateDocument(uri); const message = `${execute_handler_1.fishLspPromptIcon} Workspace: ${workspace_manager_1.workspaceManager.current?.path}`; connection.sendNotification('workspace/didChangeWorkspaceFolders', { event: { added: [path], removed: [], }, }); if (silence) return undefined; connection.sendNotification('window/showMessage', { message: message, type: vscode_languageserver_1.MessageType.Info, }); return undefined; } async function updateConfig(path) { const cached = analyzer.analyzePath(path); if (!cached) return; const { document } = cached; if (!document) return; analyzer.updateConfigInWorkspace(document.uri); connection.sendNotification('window/showMessage', { message: config_1.config, type: vscode_languageserver_1.MessageType.Info, }); return undefined; } async function fixAllDiagnostics(path) { const uri = (0, translation_1.pathToUri)(path); logger_1.logger.log('fixAllDiagnostics', uri); const cached = analyzer.analyzePath(path); if (!cached) return; const { document } = cached; const root = analyzer.getRootNode(uri); if (!document || !root) return; const diagnostics = root ? (0, validate_1.getDiagnostics)(root, document) : []; logger_1.logger.warning('fixAllDiagnostics', diagnostics.length, 'diagnostics found'); if (diagnostics.length === 0) { logger_1.logger.log('No diagnostics found'); return; } const { onCodeAction } = (0, code_action_handler_1.codeActionHandlers)(docs, analyzer); const actions = await onCodeAction({ textDocument: document.asTextDocumentIdentifier(), range: (0, tree_sitter_1.getRange)(root), context: { diagnostics: diagnostics, }, }); logger_1.logger.log('fixAllDiagnostics', actions); const fixAllAction = (0, quick_fixes_1.createFixAllAction)(document, actions); if (!fixAllAction) { logger_1.logger.log('fixAllDiagnostics did not find any fixAll actions'); return; } const fixCount = fixAllAction?.data.totalEdits || 0; if (fixCount > 0) { logger_1.logger.log('fixAllDiagnostics', `Can apply ${fixCount} fixes`); const result = await connection.window.showInformationMessage(`Fix all ${fixAllAction.data.totalEdits} diagnostics on ${(0, translation_1.uriToReadablePath)(uri)}`, { title: 'Yes' }, { title: 'Cancel' }); const { title } = result?.title ? result : { title: 'Cancel' }; if (title === 'Cancel') { connection.sendNotification('window/showMessage', { type: vscode_languageserver_1.MessageType.Info, message: ' No changes were made to the file. ', }); return; } const workspaceEdit = fixAllAction.edit; if (!workspaceEdit) return; await connection.workspace.applyEdit(workspaceEdit); connection.sendNotification('window/showMessage', { type: vscode_languageserver_1.MessageType.Info, message: ` Applied ${fixCount} quick fixes `, }); } } function toggleSingleWorkspaceSupport() { const currentConfig = config_1.config.fish_lsp_single_workspace_support; config_1.config.fish_lsp_single_workspace_support = !currentConfig; connection.sendNotification('window/showMessage', { type: vscode_languageserver_1.MessageType.Info, message: ` Single workspace support: ${config_1.config.fish_lsp_single_workspace_support ? 'ENABLED' : 'DISABLED'} `, }); } function outputFishLspEnv(path) { const cached = analyzer.analyzePath(path); if (!cached) return; const { document } = cached; if (!document) return; const output = ['\n']; const outputCallback = (s) => { output.push(s); }; (0, config_1.handleEnvOutput)('show', outputCallback, { confd: false, comments: true, global: true, local: false, export: true, only: undefined, }); connection.sendNotification('window/showMessage', { type: vscode_languageserver_1.MessageType.Info, message: ` Fish LSP Environment Variables: \n ${env_manager_1.env.getAutoloadedKeys().join('\n')} `, }); const docsEnd = document.positionAt(document.getLines()); const workspaceEdit = { changes: { [document.uri]: [ vscode_languageserver_1.TextEdit.insert(docsEnd, output.join('\n')), ], }, }; connection.workspace.applyEdit(workspaceEdit); } async function showReferences(path, position, references) { const uri = (0, translation_1.pathToUri)(path); logger_1.logger.log('handleShowReferences', { path, uri, position, references }); connection.sendNotification('window/showMessage', { type: vscode_languageserver_1.MessageType.Info, message: ` Fish LSP found ${references.length} references to this symbol `, }); return references; } const commandHandlers = { 'fish-lsp.executeRange': executeRange, 'fish-lsp.executeLine': executeLine, 'fish-lsp.executeBuffer': executeBuffer, 'fish-lsp.execute': executeBuffer, 'fish-lsp.createTheme': createTheme, 'fish-lsp.showStatusDocs': handleShowStatusDocs, 'fish-lsp.showWorkspaceMessage': showWorkspaceMessage, 'fish-lsp.updateWorkspace': _updateWorkspace, 'fish-lsp.updateConfig': updateConfig, 'fish-lsp.fixAll': fixAllDiagnostics, 'fish-lsp.toggleSingleWorkspaceSupport': toggleSingleWorkspaceSupport, 'fish-lsp.generateEnvVariables': outputFishLspEnv, 'fish-lsp.showReferences': showReferences, }; return async function onExecuteCommand(params) { logger_1.logger.log('onExecuteCommand', params); const handler = commandHandlers[params.command]; if (!handler) { logger_1.logger.log(`Unknown command: ${params.command}`); return; } await handler(...params.arguments || []); }; }