UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

444 lines (443 loc) 19.7 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.environmentVariablesUsed = exports.config = exports.Config = exports.FormatOptions = exports.toNumber = exports.toBoolean = exports.ConfigSchema = exports.validHandlers = exports.configHandlers = exports.ConfigHandlerSchema = void 0; exports.updateHandlers = updateHandlers; exports.getConfigFromEnvironmentVariables = getConfigFromEnvironmentVariables; exports.getDefaultConfiguration = getDefaultConfiguration; exports.updateBasedOnSymbols = updateBasedOnSymbols; exports.updateConfigValues = updateConfigValues; exports.handleEnvOutput = handleEnvOutput; const zod_1 = require("zod"); const logger_1 = require("./logger"); const snippets_1 = require("./utils/snippets"); const vscode_languageserver_1 = require("vscode-languageserver"); const action_kinds_1 = require("./code-actions/action-kinds"); const command_1 = require("./command"); const commander_cli_subcommands_1 = require("./utils/commander-cli-subcommands"); 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), definition: zod_1.z.boolean().default(true), implementation: 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), formatRange: zod_1.z.boolean().default(true), typeFormatting: 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), }); exports.configHandlers = exports.ConfigHandlerSchema.parse({}); exports.validHandlers = [ 'complete', 'hover', 'rename', 'definition', 'implementation', 'reference', 'formatting', 'formatRange', 'typeFormatting', '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; } }); Config.fixEnabledDisabledHandlers(); } exports.ConfigSchema = zod_1.z.object({ fish_lsp_enabled_handlers: zod_1.z.array(zod_1.z.string()).default([]), fish_lsp_disabled_handlers: zod_1.z.array(zod_1.z.string()).default([]), fish_lsp_commit_characters: zod_1.z.array(zod_1.z.string()).default(['\t', ';', ' ']), fish_lsp_log_file: zod_1.z.string().default(''), fish_lsp_log_level: zod_1.z.string().default(''), fish_lsp_all_indexed_paths: zod_1.z.array(zod_1.z.string()).default(['$__fish_config_dir', '$__fish_data_dir']), fish_lsp_modifiable_paths: zod_1.z.array(zod_1.z.string()).default(['$__fish_config_dir']), fish_lsp_diagnostic_disable_error_codes: zod_1.z.array(zod_1.z.number()).default([]), fish_lsp_enable_experimental_diagnostics: zod_1.z.boolean().default(false), fish_lsp_max_background_files: zod_1.z.number().default(10000), fish_lsp_show_client_popups: zod_1.z.boolean().default(false), fish_lsp_single_workspace_support: zod_1.z.boolean().default(false), }); 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_log_file: process.env.fish_lsp_log_file || process.env.fish_lsp_logfile, fish_lsp_log_level: process.env.fish_lsp_log_level, 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(exports.toNumber), fish_lsp_enable_experimental_diagnostics: (0, exports.toBoolean)(process.env.fish_lsp_enable_experimental_diagnostics) || false, fish_lsp_max_background_files: (0, exports.toNumber)(process.env.fish_lsp_max_background_files), fish_lsp_show_client_popups: (0, exports.toBoolean)(process.env.fish_lsp_show_client_popups) || false, fish_lsp_single_workspace_support: (0, exports.toBoolean)(process.env.fish_lsp_single_workspace_support) || false, }; 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({}); } function updateBasedOnSymbols(symbols) { const fishLspSymbols = symbols.filter(s => s.kind === vscode_languageserver_1.SymbolKind.Variable && s.name.startsWith('fish_lsp_')); const newConfig = {}; const configCopy = Object.assign({}, exports.config); for (const s of fishLspSymbols) { const configKey = Config.getEnvVariableKey(s.name); if (!configKey) { continue; } if (s.isConfigDefinitionWithErase()) { const schemaType = exports.ConfigSchema.shape[configKey]; exports.config[configKey] = schemaType.parse(schemaType._def.defaultValue()); continue; } const shellValues = s.valuesAsShellValues(); if (shellValues.length > 0) { if (shellValues.length === 1) { const value = shellValues[0]; if ((0, exports.toBoolean)(value)) { newConfig[configKey] = (0, exports.toBoolean)(value); continue; } if ((0, exports.toNumber)(value)) { newConfig[configKey] = (0, exports.toNumber)(value); continue; } newConfig[configKey] = value; continue; } else { if (shellValues.every(v => !!(0, exports.toNumber)(v))) { newConfig[configKey] = shellValues.map(v => (0, exports.toNumber)(v)); } else if (shellValues.every(v => (0, exports.toBoolean)(v))) { newConfig[configKey] = shellValues.map(v => (0, exports.toBoolean)(v)); } else { newConfig[configKey] = shellValues; } } } } Object.assign(exports.config, updateConfigValues(configCopy, newConfig)); } function updateConfigValues(config, newValues) { const updates = {}; Object.keys(newValues).forEach(key => { if (key in config) { const configKey = key; const schemaType = exports.ConfigSchema.shape[configKey]; if (schemaType) { try { const parsedValue = schemaType.safeParse(newValues[key]); if (parsedValue.success) { updates[configKey] = parsedValue.data; } else { updates[configKey] = schemaType._def.defaultValue(); } } catch (error) { logger_1.logger.error(`Failed to parse value for ${key}: ${error instanceof Error ? error.message : String(error)}`); } } } }); return { ...config, ...updates }; } const toBoolean = (s) => typeof s !== 'undefined' ? s === 'true' || s === '1' : undefined; exports.toBoolean = toBoolean; const toNumber = (s) => typeof s !== 'undefined' ? parseInt(s, 10) : undefined; exports.toNumber = toNumber; function buildOutput(confd, result) { return confd ? [ '# built by `fish-lsp env --confd`', 'type -aq fish-lsp || exit', 'if status is-interactive', result.map(line => line.split('\n').map(innerLine => ' ' + innerLine).join('\n').trimEnd()).join('\n\n').trimEnd(), 'end', ].join('\n') : result.join('\n').trimEnd(); } function handleEnvOutput(outputType, callbackfn = (str) => logger_1.logger.logToStdout(str), opts = commander_cli_subcommands_1.SubcommandEnv.defaultHandlerOptions) { const command = getEnvVariableCommand(opts.global, opts.local, opts.export); const result = []; const variables = snippets_1.PrebuiltDocumentationMap .getByType('variable', 'fishlsp') .filter((v) => snippets_1.EnvVariableJson.is(v)) .filter((v) => !v.isDeprecated); const getEnvVariableJsonObject = (keyName) => variables.find(entry => entry.name === keyName); const convertValueToShellOutput = (value) => { if (!Array.isArray(value)) return escapeValue(value) + '\n'; if (value.length === 0) return "''\n"; return value.map(v => escapeValue(v)).join(' ') + '\n'; }; const getDefaultValueAsShellOutput = (key) => { const value = Config.getDefaultValue(key); return convertValueToShellOutput(value); }; const buildBasicLine = (entry, command, key) => { if (!opts.comments) return `${command} ${key} `; return [ snippets_1.EnvVariableJson.toCliOutput(entry), `${command} ${key} `, ].join('\n'); }; const buildOutputSection = (entry, command, key, value) => { let line = buildBasicLine(entry, command, key); switch (outputType) { case 'show': line += convertValueToShellOutput(value); break; case 'showDefault': line += getDefaultValueAsShellOutput(key); break; case 'create': default: line += '\n'; break; } return line; }; for (const item of Object.entries(exports.config)) { const [key, value] = item; if (opts.only && !opts.only.includes(key)) continue; const configKey = key; const entry = getEnvVariableJsonObject(key); const line = buildOutputSection(entry, command, configKey, value); result.push(line); } const output = buildOutput(opts.confd, result); callbackfn(output); return output; } function escapeValue(value) { if (typeof value === 'string') { if (value.startsWith('$__fish')) return `"${value}"`; return `'${value.replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/'/g, "\\'")}'`; } else { return value.toString(); } } 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; } exports.FormatOptions = { insertSpaces: true, tabSize: 4, }; var Config; (function (Config) { function fixPopups(enabled, disabled) { 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; } if (exports.configHandlers.popups === false) { exports.config.fish_lsp_show_client_popups = false; return; } const envValue = (0, exports.toBoolean)(process.env.fish_lsp_show_client_popups); 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; } } if (typeof envValue === 'undefined') { if (exports.config.fish_lsp_enabled_handlers.includes('popups')) { exports.config.fish_lsp_show_client_popups = true; return; } if (exports.config.fish_lsp_disabled_handlers.includes('popups')) { exports.config.fish_lsp_show_client_popups = false; return; } } return; } Config.fixPopups = fixPopups; function getDefaultValue(key) { const defaults = exports.ConfigSchema.parse({}); return defaults[key]; } Config.getDefaultValue = getDefaultValue; function getDocsForKey(key) { const entry = snippets_1.PrebuiltDocumentationMap.getByType('variable', 'fishlsp').find(e => e.name === key); if (entry) { return entry.description; } return ''; } Config.getDocsForKey = getDocsForKey; const getDocsObj = () => { const docsObj = {}; const entries = snippets_1.PrebuiltDocumentationMap.getByType('variable', 'fishlsp'); entries.forEach(entry => { if (snippets_1.EnvVariableJson.is(entry)) { if (entry?.isDeprecated) return; docsObj[entry.name] = entry.shortDescription; } }); return docsObj; }; Config.envDocs = getDocsObj(); Config.deprecatedKeys = { ['fish_lsp_logfile']: 'fish_lsp_log_file', }; const keys = (schema) => { return Object.keys(schema.shape); }; Config.allKeys = keys(exports.ConfigSchema); function getEnvVariableKey(key) { if (key in exports.config) { return key; } if (Object.keys(Config.deprecatedKeys).includes(key)) { return Config.deprecatedKeys[key]; } return undefined; } Config.getEnvVariableKey = getEnvVariableKey; function updateFromInitializationOptions(initializationOptions) { if (!initializationOptions) return; exports.ConfigSchema.parse(initializationOptions); Object.keys(initializationOptions).forEach((key) => { const configKey = getEnvVariableKey(key); if (!configKey) return; exports.config[configKey] = initializationOptions[configKey]; }); if (initializationOptions.fish_lsp_enabled_handlers) { updateHandlers(initializationOptions.fish_lsp_enabled_handlers, true); } if (initializationOptions.fish_lsp_disabled_handlers) { updateHandlers(initializationOptions.fish_lsp_disabled_handlers, false); } } Config.updateFromInitializationOptions = updateFromInitializationOptions; function fixEnabledDisabledHandlers() { exports.config.fish_lsp_enabled_handlers = []; exports.config.fish_lsp_disabled_handlers = []; Object.keys(exports.configHandlers).forEach((key) => { const value = exports.configHandlers[key]; if (!value) { exports.config.fish_lsp_disabled_handlers.push(key); } else { exports.config.fish_lsp_enabled_handlers.push(key); } }); } Config.fixEnabledDisabledHandlers = fixEnabledDisabledHandlers; function getResultCapabilities() { return { capabilities: { textDocumentSync: { openClose: true, change: vscode_languageserver_1.TextDocumentSyncKind.Incremental, save: { includeText: true }, }, completionProvider: exports.configHandlers.complete ? { resolveProvider: true, allCommitCharacters: exports.config.fish_lsp_commit_characters, workDoneProgress: false, } : undefined, hoverProvider: exports.configHandlers.hover, definitionProvider: exports.configHandlers.definition, implementationProvider: exports.configHandlers.implementation, referencesProvider: exports.configHandlers.reference, renameProvider: exports.configHandlers.rename, documentFormattingProvider: exports.configHandlers.formatting, documentRangeFormattingProvider: exports.configHandlers.formatRange, foldingRangeProvider: exports.configHandlers.folding, codeActionProvider: exports.configHandlers.codeAction ? { codeActionKinds: [...action_kinds_1.AllSupportedActions], workDoneProgress: true, resolveProvider: true, } : undefined, executeCommandProvider: exports.configHandlers.executeCommand ? { commands: [...action_kinds_1.AllSupportedActions, ...command_1.LspCommands], workDoneProgress: true, } : undefined, documentSymbolProvider: { label: 'fish-lsp', }, workspaceSymbolProvider: { resolveProvider: true, }, documentHighlightProvider: exports.configHandlers.highlight, inlayHintProvider: exports.configHandlers.inlayHint, signatureHelpProvider: exports.configHandlers.signature ? { workDoneProgress: false, triggerCharacters: ['.'] } : undefined, documentOnTypeFormattingProvider: exports.configHandlers.typeFormatting ? { firstTriggerCharacter: '.', moreTriggerCharacter: [';', '}', ']', ')'], } : undefined, workspace: { workspaceFolders: { supported: true, changeNotifications: true, }, }, }, serverInfo: { name: 'fish-lsp', version: commander_cli_subcommands_1.PackageVersion, }, }; } Config.getResultCapabilities = getResultCapabilities; Config.FileListenerFilter = { filters: [ { pattern: { glob: '**/*.fish', matches: 'file', options: { ignoreCase: true, }, }, }, ], }; function initialize(params, connection) { updateFromInitializationOptions(params.initializationOptions); (0, logger_1.createServerLogger)(exports.config.fish_lsp_log_file, connection.console); const result = getResultCapabilities(); logger_1.logger.log({ onInitializedResult: result }); return result; } Config.initialize = initialize; })(Config || (exports.Config = Config = {})); _a = getConfigFromEnvironmentVariables(), exports.config = _a.config, exports.environmentVariablesUsed = _a.environmentVariablesUsed;