fish-lsp
Version:
LSP implementation for fish/fish-shell
254 lines (253 loc) • 9.54 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompletionPager = void 0;
exports.initializeCompletionPager = initializeCompletionPager;
const types_1 = require("./types");
const exec_1 = require("../exec");
const logger_1 = require("../../logger");
const inline_parser_1 = require("./inline-parser");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const list_1 = require("./list");
const shell_1 = require("./shell");
class CompletionPager {
inlineParser;
itemsMap;
logger;
_items;
constructor(inlineParser, itemsMap, logger) {
this.inlineParser = inlineParser;
this.itemsMap = itemsMap;
this.logger = logger;
this._items = new list_1.FishCompletionListBuilder(this.logger);
}
empty() {
return {
items: [],
isIncomplete: false,
};
}
create(isIncomplete, items = []) {
return {
isIncomplete,
items,
};
}
async completeEmpty(symbols) {
this._items.reset();
this._items.addSymbols(symbols, true);
this._items.addItems(this.itemsMap.allOfKinds('builtin').map(item => item.setPriority(10)));
try {
const stdout = [];
const toAdd = await this.getSubshellStdoutCompletions(' ');
stdout.push(...toAdd);
for (const [name, description] of stdout) {
this._items.addItem(types_1.FishCompletionItem.create(name, 'command', description, name).setPriority(1));
}
}
catch (e) {
logger_1.logger.info('Error getting subshell stdout completions', e);
}
this._items.addItems(this.itemsMap.allOfKinds('comment').map(item => item.setPriority(95)));
this._items.addItems(this.itemsMap.allOfKinds('function').map(item => item.setPriority(30)));
return this._items.build(false);
}
async completeVariables(line, word, setupData, symbols) {
this._items.reset();
const data = types_1.FishCompletionItem.createData(setupData.uri, line, word || '', setupData.position);
const { variables } = sortSymbols(symbols);
for (const variable of variables) {
const variableItem = types_1.FishCompletionItem.fromSymbol(variable);
variableItem.insertText = '$' + variable.name;
this._items.addItem(variableItem);
}
for (const item of this.itemsMap.allOfKinds('variable')) {
if (item.label) {
continue;
}
item.insertText = '$' + item.label;
this._items.addItem(item);
}
const result = this._items.addData(data).build();
result.isIncomplete = false;
return result;
}
async complete(line, setupData, symbols) {
const { word, command, commandNode: _commandNode, index } = this.inlineParser.getNodeContext(line || '');
logger_1.logger.log({
line,
word: word,
command: command,
index: index,
});
this._items.reset();
const data = types_1.FishCompletionItem.createData(setupData.uri, line || '', word || '', setupData.position, command || '', setupData.context);
const { variables, functions } = sortSymbols(symbols);
if (!word && !command) {
return this.completeEmpty(symbols);
}
const stdout = [];
if (command && this.itemsMap.blockedCommands.includes(command)) {
this._items.addItems(this.itemsMap.allOfKinds('pipe'), 85);
return this._items.build(false);
}
const toAdd = await (0, shell_1.shellComplete)(line);
stdout.push(...toAdd);
logger_1.logger.log('toAdd =', toAdd.slice(0, 5));
if (word && word.includes('/')) {
this.logger.log('word includes /', word);
const toAdd = await this.getSubshellStdoutCompletions(`__fish_complete_path ${word}`);
this._items.addItems(toAdd.map((item) => types_1.FishCompletionItem.create(item[0], 'path', item[1], item.join(' '))), 1);
}
const isOption = this.inlineParser.lastItemIsOption(line);
for (const [name, description] of stdout) {
if (isOption || name.startsWith('-') || command) {
this._items.addItem(types_1.FishCompletionItem.create(name, 'argument', description, [
line.slice(0, line.lastIndexOf(' ')),
name,
].join(' ').trim()).setPriority(1));
continue;
}
const item = this.itemsMap.findLabel(name);
if (!item) {
continue;
}
this._items.addItem(item.setPriority(1));
}
if (command && line.includes(' ')) {
this._items.addSymbols(variables);
if (index === 1) {
this._items.addItems(addFirstIndexedItems(command, this.itemsMap), 25);
}
else {
this._items.addItems(addSpecialItems(command, line, this.itemsMap), 24);
}
}
else if (word && !command) {
this._items.addSymbols(functions);
}
switch (wordsFirstChar(word)) {
case '$':
this._items.addItems(this.itemsMap.allOfKinds('variable'), 55);
this._items.addSymbols(variables);
break;
case '/':
this._items.addItems(this.itemsMap.allOfKinds('wildcard'));
break;
default:
break;
}
const result = this._items.addData(data).build();
return result;
}
getData(uri, position, line, word) {
return {
uri,
position,
line,
word,
};
}
async getSubshellStdoutCompletions(line) {
const resultItem = (splitLine) => {
const name = splitLine[0] || '';
const description = splitLine.length > 1 ? splitLine.slice(1).join(' ') : '';
return [name, description];
};
const outputLines = await (0, exec_1.execCompleteLine)(line);
return outputLines
.filter((line) => line.trim().length !== 0)
.map((line) => line.split('\t'))
.map((splitLine) => resultItem(splitLine));
}
}
exports.CompletionPager = CompletionPager;
async function initializeCompletionPager(logger, items) {
const inline = await inline_parser_1.InlineParser.create();
return new CompletionPager(inline, items, logger);
}
function addFirstIndexedItems(command, items) {
switch (command) {
case 'functions':
case 'function':
return items.allOfKinds('event', 'variable');
case 'end':
return items.allOfKinds('pipe');
case 'printf':
return items.allOfKinds('format_str', 'esc_chars');
case 'set':
return items.allOfKinds('variable');
case 'return':
return items.allOfKinds('status', 'variable');
default:
return [];
}
}
function addSpecialItems(command, line, items) {
const lastIndex = line.lastIndexOf(command) + 1;
const afterItems = line.slice(lastIndex).trim().split(' ');
const lastItem = afterItems.at(-1);
switch (command) {
case 'return':
return items.allOfKinds('status', 'variable');
case 'printf':
case 'set':
return items.allOfKinds('variable');
case 'function':
switch (lastItem) {
case '-e':
case '--on-event':
return items.allOfKinds('event');
case '-v':
case '--on-variable':
case '-V':
case '--inherit-variable':
return items.allOfKinds('variable');
default:
return [];
}
case 'string':
if (includesFlag('-r', '--regex', ...afterItems)) {
return items.allOfKinds('regex', 'esc_chars');
}
else {
return items.allOfKinds('esc_chars');
}
default:
return items.allOfKinds('combiner', 'pipe');
}
}
function wordsFirstChar(word) {
return word?.charAt(0) || ' ';
}
function includesFlag(shortFlag, longFlag, ...toSearch) {
const short = shortFlag.startsWith('-') ? shortFlag.slice(1) : shortFlag;
const long = longFlag.startsWith('--') ? longFlag.slice(2) : longFlag;
for (const item of toSearch) {
if (item.startsWith('-') && !item.startsWith('--')) {
const opts = item.slice(1).split('');
if (opts.some((opt) => opt === short)) {
return true;
}
}
if (item.startsWith('--')) {
const opts = item.slice(2).split('');
if (opts.some((opt) => opt === long)) {
return true;
}
}
}
return false;
}
function sortSymbols(symbols) {
const variables = [];
const functions = [];
symbols.forEach((symbol) => {
if (symbol.kind === vscode_languageserver_protocol_1.SymbolKind.Variable) {
variables.push(symbol);
}
if (symbol.kind === vscode_languageserver_protocol_1.SymbolKind.Function) {
functions.push(symbol);
}
});
return { variables, functions };
}