fish-lsp
Version:
LSP implementation for fish/fish-shell
372 lines (371 loc) • 12.7 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");
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'));
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));
}
this._items.addItems(this.itemsMap.allOfKinds('function'));
this._items.addItems(this.itemsMap.allOfKinds('comment'));
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')) {
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({
word: word,
command: command,
index: index,
});
this._items.reset();
const data = types_1.FishCompletionItem.createData(setupData.uri, line || '', word || '', setupData.position, setupData.context);
const { variables, functions } = sortSymbols(symbols);
if (!word && !command) {
return this.completeEmpty(symbols);
}
this.logger.log('Pager.complete.data =', { command, word });
const stdout = [];
if (!this.itemsMap.blockedCommands.includes(command || '')) {
const toAdd = await this.getSubshellStdoutCompletions(line);
stdout.push(...toAdd);
}
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(' '))));
}
const isOption = this.inlineParser.lastItemIsOption(line);
for (const [name, description] of stdout) {
//if (this.itemsMap.skippableItem(name, description)) continue;
if (isOption || name.startsWith('-') || command) {
this._items.addItem(types_1.FishCompletionItem.create(name, 'argument', description, [line, name, description].join(' ').trim()));
continue;
}
const item = this.itemsMap.findLabel(name);
if (!item) {
continue;
}
this._items.addItem(item);
}
if (command) {
this._items.addSymbols(variables);
if (index === 1) {
this._items.addItems(addFirstIndexedItems(command, this.itemsMap));
}
else {
this._items.addItems(addSpecialItems(command, line, this.itemsMap));
}
}
else if (word && !command) {
this._items.addSymbols(functions);
}
switch (wordsFirstChar(word)) {
case '$':
this._items.addItems(this.itemsMap.allOfKinds('variable'));
this._items.addSymbols(variables);
break;
case '/':
this._items.addItems(this.itemsMap.allOfKinds('wildcard'));
//let addedStdout = await this.getSubshellStdoutCompletions(word!)
//stdout = stdout.concat(addedStdout)
break;
default:
break;
}
const result = this._items.addData(data).build();
this._items.log();
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 "end":
// return items.allOfKinds("pipe");
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 };
}
/////////////////////////////////////////////////////////////////////////////////////////
// Trying functional approach
/////////////////////////////////////////////////////////////////////////////////////////
function _addItemsForWord(word) {
const firstChar = wordsFirstChar(word);
switch (firstChar) {
case "'":
return ['esc_chars'];
case '"':
return ['esc_chars', 'variable'];
case '$':
return ['variable'];
case '/':
return ['path'];
case '%':
return ['status'];
case '\\':
return ['esc_chars'];
case ')':
return ['combiner', 'pipe'];
case ':':
case '-':
default:
return [];
}
}
var CommandHas;
(function (CommandHas) {
function string(command, word) {
if (!command) {
return false;
}
return word.startsWith('"') || word.startsWith("'");
}
CommandHas.string = string;
function path(command, word) {
if (!command) {
return false;
}
return word.includes('/') || word.startsWith('~');
}
CommandHas.path = path;
})(CommandHas || (CommandHas = {}));
function _addItemsForWordAndCommand(command, word) {
switch (true) {
case CommandHas.string(command, word):
return ['esc_chars'];
//case isCommandWithRegex(command, word):
// return ['regex'];
//case CommandHas.
case CommandHas.path(command, word):
return ['path', 'wildcard', 'variable'];
default:
return [];
}
}
function _addItemsJustByCommand(command) {
switch (command) {
case 'set':
return ['variable'];
case 'function':
return ['function'];
case 'printf':
return ['format_str', 'esc_chars'];
case 'string':
return ['esc_chars', 'regex'];
case 'end':
return ['pipe'];
case 'return':
return ['status', 'variable'];
default:
return [];
}
}
function _addItemsForCommandOnly(command) {
switch (command) {
case 'set':
return ['variable'];
case 'function':
return ['function'];
case 'printf':
return ['format_str', 'esc_chars'];
case 'string':
return ['esc_chars', 'regex'];
case 'end':
return ['pipe'];
case 'return':
return ['status', 'variable'];
default:
return [];
}
}
function _addItemsForCommand(command) {
switch (command) {
case 'set':
return ['variable'];
case 'function':
return ['function'];
case 'printf':
return ['format_str', 'esc_chars'];
case 'string':
return ['esc_chars', 'regex'];
case 'end':
return ['pipe'];
case 'return':
return ['status', 'variable'];
default:
return [];
}
}
function _addItemTypes(line, parser) {
const { word, command: _command } = parser.getNodeContext(line);
const wordFirstChar = wordsFirstChar(word);
switch (wordFirstChar) {
case '$': return ['variable'];
case '\\':
case '/':
case '%':
// goes together
case '-':
case ':':
break;
default:
break;
}
return [];
}