fish-lsp
Version:
LSP implementation for fish/fish-shell
444 lines (443 loc) • 19.7 kB
JavaScript
"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;