UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

372 lines (371 loc) 12.7 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"); 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 []; }