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