UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

254 lines (253 loc) 9.54 kB
"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 }; }