UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

353 lines (352 loc) 16.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.environmentVariablesUsed = exports.config = exports.Config = exports.ConfigSchema = exports.validHandlers = exports.configHandlers = exports.ConfigHandlerSchema = void 0; exports.updateHandlers = updateHandlers; exports.getConfigFromEnvironmentVariables = getConfigFromEnvironmentVariables; exports.getDefaultConfiguration = getDefaultConfiguration; exports.generateJsonSchemaShellScript = generateJsonSchemaShellScript; exports.showJsonSchemaShellScript = showJsonSchemaShellScript; exports.adjustInitializeResultCapabilitiesFromConfig = adjustInitializeResultCapabilitiesFromConfig; const os_1 = __importDefault(require("os")); const zod_1 = require("zod"); const logger_1 = require("./logger"); const fishlspEnvVariables_json_1 = __importDefault(require("./snippets/fishlspEnvVariables.json")); const vscode_languageserver_1 = require("vscode-languageserver"); const action_kinds_1 = require("./code-actions/action-kinds"); const command_1 = require("./command"); /******************************************** ********** Handlers/Providers *********** *******************************************/ exports.ConfigHandlerSchema = zod_1.z.object({ complete: zod_1.z.boolean().default(true), hover: zod_1.z.boolean().default(true), rename: zod_1.z.boolean().default(true), reference: zod_1.z.boolean().default(true), logger: zod_1.z.boolean().default(true), formatting: zod_1.z.boolean().default(true), codeAction: zod_1.z.boolean().default(true), codeLens: zod_1.z.boolean().default(true), folding: zod_1.z.boolean().default(true), signature: zod_1.z.boolean().default(true), executeCommand: zod_1.z.boolean().default(true), inlayHint: zod_1.z.boolean().default(true), highlight: zod_1.z.boolean().default(true), diagnostic: zod_1.z.boolean().default(true), popups: zod_1.z.boolean().default(true), }); /** * The configHandlers object stores the enabled/disabled state of the cli flags * for the language server handlers. * * USAGE: * 1.) This object first uses the parsed shell env values found in the variables: * - `fish_lsp_enabled_handlers` * - `fish_lsp_disabled_handlers` * * 2.) Next, it uses the cli flags parsed from the `--enable` and `--disable` flags: * - keys are from the validHandlers array. * * 3.) Finally, its values can be used to determine if a handler is enabled or disabled. */ exports.configHandlers = exports.ConfigHandlerSchema.parse({}); exports.validHandlers = [ 'complete', 'hover', 'rename', 'reference', 'formatting', 'codeAction', 'codeLens', 'folding', 'signature', 'executeCommand', 'inlayHint', 'highlight', 'diagnostic', 'popups', ]; function updateHandlers(keys, value) { keys.forEach(key => { if (exports.validHandlers.includes(key)) { exports.configHandlers[key] = value; } }); } /******************************************** ********** User Env *********** *******************************************/ exports.ConfigSchema = zod_1.z.object({ /** Handlers that are enabled in the language server */ fish_lsp_enabled_handlers: zod_1.z.array(zod_1.z.string()).default([]), /** Handlers that are disabled in the language server */ fish_lsp_disabled_handlers: zod_1.z.array(zod_1.z.string()).default([]), /** Characters that completion items will be accepted on */ fish_lsp_commit_characters: zod_1.z.array(zod_1.z.string()).default(['\t', ';', ' ']), /** Path to the log files */ fish_lsp_logfile: zod_1.z.string().default(''), /** All workspaces/paths for the language-server to index */ fish_lsp_all_indexed_paths: zod_1.z.array(zod_1.z.string()).default(['/usr/share/fish', `${os_1.default.homedir()}/.config/fish`]), /** All workspace/paths that the language-server should be able to rename inside*/ fish_lsp_modifiable_paths: zod_1.z.array(zod_1.z.string()).default([`${os_1.default.homedir()}/.config/fish`]), /** error code numbers to disable */ fish_lsp_diagnostic_disable_error_codes: zod_1.z.array(zod_1.z.number()).default([]), /** max background files */ fish_lsp_max_background_files: zod_1.z.number().default(1000), /** show startup analysis notification */ fish_lsp_show_client_popups: zod_1.z.boolean().default(true), }); function getConfigFromEnvironmentVariables() { const rawConfig = { fish_lsp_enabled_handlers: process.env.fish_lsp_enabled_handlers?.split(' '), fish_lsp_disabled_handlers: process.env.fish_lsp_disabled_handlers?.split(' '), fish_lsp_commit_characters: process.env.fish_lsp_commit_characters?.split(' '), fish_lsp_logfile: process.env.fish_lsp_logfile, fish_lsp_all_indexed_paths: process.env.fish_lsp_all_indexed_paths?.split(' '), fish_lsp_modifiable_paths: process.env.fish_lsp_modifiable_paths?.split(' '), fish_lsp_diagnostic_disable_error_codes: process.env.fish_lsp_diagnostic_disable_error_codes?.split(' ').map(toNumber), fish_lsp_max_background_files: toNumber(process.env.fish_lsp_max_background_files), fish_lsp_show_client_popups: toBoolean(process.env.fish_lsp_show_client_popups), }; const environmentVariablesUsed = Object.entries(rawConfig) .map(([key, value]) => typeof value !== 'undefined' ? key : null) .filter((key) => key !== null); const config = exports.ConfigSchema.parse(rawConfig); return { config, environmentVariablesUsed }; } function getDefaultConfiguration() { return exports.ConfigSchema.parse({}); } /** * convert boolean & number shell strings to their correct type */ const toBoolean = (s) => typeof s !== 'undefined' ? s === 'true' || s === '1' : undefined; const toNumber = (s) => typeof s !== 'undefined' ? parseInt(s, 10) : undefined; /** * generateJsonSchemaShellScript - just prints the starter template for the schema * in fish-shell */ function generateJsonSchemaShellScript(showComments, useGlobal, useLocal, useExport) { const result = []; const command = getEnvVariableCommand(useGlobal, useLocal, useExport); Object.values(fishlspEnvVariables_json_1.default).forEach(entry => { const { name, description, valueType } = entry; const line = !showComments ? `${command} ${name}\n` : [ `# ${name} <${valueType.toUpperCase()}>`, formatDescription(description, 80), `${command} ${name}`, '', ].join('\n'); result.push(line); }); const output = result.join('\n').trimEnd(); (0, logger_1.logToStdout)(output); } /** * showJsonSchemaShellScript - prints the current environment schema * in fish */ function showJsonSchemaShellScript(showComments, useGlobal, useLocal, useExport) { const { config } = getConfigFromEnvironmentVariables(); const command = getEnvVariableCommand(useGlobal, useLocal, useExport); const findValue = (keyName) => { return Object.values(fishlspEnvVariables_json_1.default).find(entry => { const { name } = entry; return name === keyName; }); }; const result = []; for (const item of Object.entries(config)) { const [key, value] = item; const entry = findValue(key); let line = !showComments ? `${command} ${key} ` : [ `# ${entry.name} <${entry.valueType.toUpperCase()}>`, formatDescription(entry.description, 80), `${command} ${key} `, ].join('\n'); if (Array.isArray(value)) { if (value.length === 0) { line += "''\n"; // Print two single quotes for empty arrays } else { // Map each value to ensure any special characters are escaped const escapedValues = value.map(v => escapeValue(v)); line += escapedValues.join(' ') + '\n'; // Join array values with a space } } else { // Use a helper function to handle string escaping line += escapeValue(value) + '\n'; } result.push(line); } const output = result.join('\n').trimEnd(); (0, logger_1.logToStdout)(output); } /************************************* ******* formatting helpers ******** ************************************/ // Function to format descriptions into multi-line comments function formatDescription(description, maxLineLength = 80) { const words = description.split(' '); let currentLine = '#'; let formattedDescription = ''; for (const word of words) { // Check if adding the next word would exceed the line length if (currentLine.length + word.length + 1 > maxLineLength) { formattedDescription += currentLine + '\n'; currentLine = '# ' + word; // Start a new line with the word } else { // Append word to the current line currentLine += (currentLine.length > 1 ? ' ' : ' ') + word; } } // Append any remaining text in the current line if (currentLine.length > 1) { formattedDescription += currentLine; } return formattedDescription; } function escapeValue(value) { if (typeof value === 'string') { // Replace special characters with their escaped equivalents return `'${value.replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/'/g, "\\'")}'`; } else { // Return non-string types as they are return value.toString(); } } /** * getEnvVariableCommand - returns the correct command for setting environment variables * in fish-shell. Used for generating `fish-lsp env` output. Result string will be * either `set -g`, `set -l`, `set -gx`, or `set -lx`, depending on the flags passed. * ___ * ```fish * >_ fish-lsp env --no-global --no-export --no-comments | head -n 1 * set -l fish_lsp_enabled_handlers * ``` * ___ * @param {boolean} useGlobal - whether to use the global flag * @param {boolean} useLocal - allows for skipping the local flag * @param {boolean} useExport - whether to use the export flag * @returns {string} - the correct command for setting environment variables */ function getEnvVariableCommand(useGlobal, useLocal, useExport) { let command = 'set'; command = useGlobal ? `${command} -g` : useLocal ? `${command} -l` : command; command = useExport ? command.endsWith('-g') || command.endsWith('-l') ? `${command}x` : `${command} -x` : command; return command; } /******************************************** *** initializeResult *** *******************************************/ /* in server onInitialize() */ function adjustInitializeResultCapabilitiesFromConfig(configHandlers, userConfig) { return { capabilities: { textDocumentSync: vscode_languageserver_1.TextDocumentSyncKind.Incremental, completionProvider: configHandlers.complete ? { resolveProvider: true, allCommitCharacters: userConfig.fish_lsp_commit_characters, workDoneProgress: true, triggerCharacters: ['$'], } : undefined, hoverProvider: configHandlers.hover, definitionProvider: configHandlers.reference, referencesProvider: configHandlers.reference, renameProvider: configHandlers.rename, documentFormattingProvider: configHandlers.formatting, documentRangeFormattingProvider: configHandlers.formatting, foldingRangeProvider: configHandlers.folding, codeActionProvider: configHandlers.codeAction ? { codeActionKinds: [...action_kinds_1.AllSupportedActions], workDoneProgress: true, resolveProvider: true, } : undefined, executeCommandProvider: configHandlers.executeCommand ? { commands: [...action_kinds_1.AllSupportedActions, ...command_1.LspCommands], workDoneProgress: true, } : undefined, documentSymbolProvider: { label: 'Fish-LSP', }, workspaceSymbolProvider: { resolveProvider: true, }, documentHighlightProvider: configHandlers.highlight, inlayHintProvider: configHandlers.inlayHint, signatureHelpProvider: configHandlers.signature ? { workDoneProgress: false, triggerCharacters: ['.'] } : undefined, }, }; } /******************************************** *** Config *** *******************************************/ var Config; (function (Config) { /** * fixPopups - updates the `config.fish_lsp_show_client_popups` value based on the 3 cases: * - cli flags include 'popups' -> directly sets `fish_lsp_show_client_popups` * - `config.fish_lsp_enabled_handlers`/`config.fish_lsp_disabled_handlers` includes 'popups' * - if both set && env doesn't set popups -> disable popups * - if enabled && env doesn't set popups-> enable popups * - if disabled && env doesn't set popups -> disable popups * - if env sets popups -> use env for popups && don't override with handler * - `config.fish_lsp_show_client_popups` is set in the environment variables * @param {string[]} enabled - the cli flags that are enabled * @param {string[]} disabled - the cli flags that are disabled * @returns {void} */ function fixPopups(enabled, disabled) { /* * `enabled/disabled` cli flag arrays are used instead of `configHandlers` * because `configHandlers` always sets `popups` to true */ if (enabled.includes('popups') || disabled.includes('popups')) { if (enabled.includes('popups')) exports.config.fish_lsp_show_client_popups = true; if (disabled.includes('popups')) exports.config.fish_lsp_show_client_popups = false; return; } /** * `configHandlers.popups` is set to false, so popups are disabled */ if (exports.configHandlers.popups === false) { exports.config.fish_lsp_show_client_popups = false; return; } // envValue is the value of `process.env.fish_lsp_show_client_popups` const envValue = toBoolean(process.env.fish_lsp_show_client_popups); // check error case where both are set if (exports.config.fish_lsp_enabled_handlers.includes('popups') && exports.config.fish_lsp_disabled_handlers.includes('popups')) { if (envValue) { exports.config.fish_lsp_show_client_popups = envValue; return; } else { exports.config.fish_lsp_show_client_popups = false; return; } } /** * `process.env.fish_lsp_show_client_popups` is not set, and * `fish_lsp_enabled_handlers/fish_lsp_disabled_handlers` includes 'popups' */ if (typeof envValue === 'undefined') { if (exports.config.fish_lsp_enabled_handlers.includes('popups')) { exports.config.fish_lsp_show_client_popups = true; return; } /** config.fish_lsp_disabled_handlers is from the fish env */ if (exports.config.fish_lsp_disabled_handlers.includes('popups')) { exports.config.fish_lsp_show_client_popups = false; return; } } // `process.env.fish_lsp_show_client_popups` is set and 'popups' is enabled/disabled in the handlers return; } Config.fixPopups = fixPopups; })(Config || (exports.Config = Config = {})); // create config to be used globally _a = getConfigFromEnvironmentVariables(), exports.config = _a.config, exports.environmentVariablesUsed = _a.environmentVariablesUsed;