fish-lsp
Version:
LSP implementation for fish/fish-shell
296 lines (295 loc) • 13.3 kB
JavaScript
"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 || []);
};
}