UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

349 lines (348 loc) 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HoverFromCompletion = void 0; exports.enrichToMarkdown = enrichToMarkdown; exports.enrichToCodeBlockMarkdown = enrichToCodeBlockMarkdown; exports.enrichWildcard = enrichWildcard; exports.enrichCommandArg = enrichCommandArg; exports.enrichCommandWithFlags = enrichCommandWithFlags; exports.enrichToPlainText = enrichToPlainText; exports.documentationHoverProvider = documentationHoverProvider; exports.documentationHoverProviderForBuiltIns = documentationHoverProviderForBuiltIns; exports.documentationHoverCommandArg = documentationHoverCommandArg; exports.forwardSubCommandCollect = forwardSubCommandCollect; exports.forwardArgCommandCollect = forwardArgCommandCollect; const node_1 = require("vscode-languageserver-protocol/node"); // import { hasPossibleSubCommand } from './utils/builtins'; const exec_1 = require("./utils/exec"); const tree_sitter_1 = require("./utils/tree-sitter"); function enrichToMarkdown(doc) { return { kind: node_1.MarkupKind.Markdown, value: [ doc, ].join(), }; } function enrichToCodeBlockMarkdown(doc, filetype = 'fish') { return { kind: node_1.MarkupKind.Markdown, value: [ '```' + filetype, doc.trim(), '```', ].join('\n'), }; } function enrichWildcard(label, documentation, examples) { const exampleStr = ['---']; for (const [cmd, desc] of examples) { exampleStr.push(`__${cmd}__ - ${desc}`); } return { kind: node_1.MarkupKind.Markdown, value: [ `_${label}_ ${documentation}`, '---', exampleStr.join('\n'), ].join('\n'), }; } function enrichCommandArg(doc) { const [_first, ...after] = doc.split('\t'); const first = _first?.trim() || ''; const second = after?.join('\t').trim() || ''; const arg = '__' + first + '__'; const desc = '_' + second + '_'; const enrichedDoc = [ arg, desc, ].join(' '); return enrichToMarkdown(enrichedDoc); } function enrichCommandWithFlags(command, flags) { const retString = [ `___${command}___`, '___', flags.map(line => line.split('\t')) .map(line => `__${line[0]}__ _${line.slice(1).join(' ')}_`) .join('\n'), ].join('\n'); return enrichToMarkdown(retString); } function enrichToPlainText(doc) { return { kind: node_1.MarkupKind.PlainText, value: doc.trim(), }; } async function documentationHoverProvider(cmd) { const cmdDocs = await (0, exec_1.execCommandDocs)(cmd); const cmdType = await (0, exec_1.execCommandType)(cmd); if (!cmdDocs) { return null; } else { return { contents: cmdType === 'command' ? enrichToCodeBlockMarkdown(cmdDocs, 'man') : enrichToCodeBlockMarkdown(cmdDocs, 'fish'), }; } } async function documentationHoverProviderForBuiltIns(cmd) { const cmdDocs = await (0, exec_1.execCommandDocs)(cmd); if (!cmdDocs) { return null; } const splitDocs = cmdDocs.split('\n'); const startIndex = splitDocs.findIndex((line) => line.trim() === 'NAME'); return { contents: { kind: node_1.MarkupKind.Markdown, value: [ `__${cmd.toUpperCase()}__ - _https://fishshell.com/docs/current/cmds/${cmd.trim()}.html_`, '___', '```man', splitDocs.slice(startIndex).join('\n'), '```', ].join('\n'), }, }; } function commandStringHelper(cmd) { const cmdArray = cmd.split(' ', 1); return cmdArray.length > 1 ? '___' + cmdArray[0] + '___' + ' ' + cmdArray[1] : '___' + cmdArray[0] + '___'; } function documentationHoverCommandArg(root, cmp) { let text = ''; const argsArray = [...cmp.args.keys()]; for (const node of (0, tree_sitter_1.getChildNodes)(root)) { const nodeText = (0, tree_sitter_1.getNodeText)(node); if (nodeText.startsWith('-') && argsArray.includes(nodeText)) { text += '\n' + '_' + nodeText + '_ ' + cmp.args.get(nodeText); } } const cmd = commandStringHelper(cmp.command.trim()); return { contents: enrichToMarkdown([ cmd, '---', text.trim(), ].join('\n')), }; } function forwardSubCommandCollect(rootNode) { const stringToComplete = []; for (const curr of rootNode.children) { if (curr.text.startsWith('-') && curr.text.startsWith('$')) { break; } else { stringToComplete.push(curr.text); } } return stringToComplete; } function forwardArgCommandCollect(rootNode) { const stringToComplete = []; const _currentNode = rootNode.children; for (const curr of rootNode.children) { if (curr.text.startsWith('-') && curr.text.startsWith('$')) { stringToComplete.push(curr.text); } else { continue; } } return stringToComplete; } // export function collectCompletionOptions(rootNode: SyntaxNode) { // let cmdText = [rootNode.children[0]!.text]; // if (hasPossibleSubCommand(cmdText[0]!)) { // cmdText = forwardSubCommandCollect(rootNode); // } // // DIFF FLAG FORMATS // // consider the difference between, find -name .git // // and ls --long -l // // // do complete and check for each flagsToFind // // // //exec // // const flagsToFind = forwardArgCommandCollect(rootNode); // } /*export async function hoverForCommandArgument(node: SyntaxNode): Promise<Hover | null> {*/ /*const text = getNodeText(node) */ /*if (text.startsWith('-')) {*/ /*const parent = findParentCommand(node);*/ /*const hoverCompletion = new HoverFromCompletion(parent)*/ /*return await hoverCompletion.generate()*/ /*}*/ /*return null*/ /*}*/ function getFlagString(arr) { return '__' + arr[0] + '__' + ' ' + arr[1] + '\n'; } class HoverFromCompletion { currentNode; commandNode; commandString = ''; entireCommandString = ''; completions = []; oldOptions = false; flagsGiven = []; constructor(commandNode, currentNode) { this.currentNode = currentNode; this.commandNode = commandNode; this.commandString = commandNode.child(0)?.text || ''; this.entireCommandString = commandNode.text || ''; this.flagsGiven = this.entireCommandString .split(' ').slice(1) .filter(flag => flag.startsWith('-')) .map(flag => flag.split('=')[0]) || []; } /** * set this.commandString for possible subcommands * handles a command such as: * $ string match -ra '.*' -- "hello all people" */ async checkForSubCommands() { const spaceCmps = await (0, exec_1.execCompleteSpace)(this.commandString); if (spaceCmps.length === 0) { return this.commandString; } const cmdArr = this.commandNode.text.split(' ').slice(1); let i = 0; while (i < cmdArr.length) { const argStr = cmdArr[i].trim(); if (!argStr.startsWith('-') && spaceCmps.includes(argStr)) { this.commandString += ' ' + argStr.toString(); } else if (argStr.includes('-')) { break; } i++; } return this.commandString; } isSubCommand() { const currentNodeText = this.currentNode.text; if (currentNodeText.startsWith('-') || currentNodeText.startsWith("'") || currentNodeText.startsWith('"')) { return false; } const cmdArr = this.commandString.split(' '); if (cmdArr.length > 1) { return cmdArr.includes(currentNodeText); } return false; } /** * @see man complete: styles --> long options * enables the ability to differentiate between * short flags chained together, or a command * that * a command option like: * '-Wall' or --> returns true * find -name '.git' --> returns true * * ls -la --> returns false * @param {string[]} cmpFlags - [TODO:description] * @returns {boolean} true if old styles are valid * false if short flags can be chained */ hasOldStyleFlags() { for (const cmpArr of this.completions) { if (cmpArr[0]?.startsWith('--')) { continue; } else if (cmpArr[0]?.startsWith('-') && cmpArr[0]?.length > 2) { return true; } } return false; } /** * handles splitting short options if the command has no * old style flags. * @see this.hasOldStyleFlags() */ reparseFlags() { const shortFlagsHandled = []; for (const flag of this.flagsGiven) { if (flag.startsWith('--')) { shortFlagsHandled.push(flag); } else if (flag.startsWith('-') && flag.length > 2) { const splitShortFlags = flag.split('').slice(1).map(str => '-' + str); shortFlagsHandled.push(...splitShortFlags); } } return shortFlagsHandled; } async buildCompletions() { this.commandString = await this.checkForSubCommands(); const preBuiltCompletions = await (0, exec_1.execCompleteCmdArgs)(this.commandString); for (const cmp of preBuiltCompletions) { this.completions.push(cmp.split('\t')); } return this.completions; } findCompletion(flag) { for (const flagArr of this.completions) { if (flagArr[0] === flag) { return flagArr; } } return null; } async checkForHoverDoc() { const cmd = await (0, exec_1.documentCommandDescription)(this.commandString); const cmdArr = cmd.trim().split(' '); const cmdStrLen = this.commandString.split(' ').length; const boldText = '__' + cmdArr.slice(0, cmdStrLen).join(' ') + '__'; const otherText = ' ' + cmdArr.slice(cmdStrLen).join(' '); return boldText + otherText; } async generateForFlags() { let text = ''; this.completions = await this.buildCompletions(); this.oldOptions = this.hasOldStyleFlags(); const cmd = await this.checkForHoverDoc(); if (!this.oldOptions) { this.flagsGiven = this.reparseFlags(); } for (const flag of this.flagsGiven) { const found = this.findCompletion(flag); if (found) { text += getFlagString(found); } } return { contents: enrichToMarkdown([ cmd, '---', text.trim(), ].join('\n')), }; } async generateForSubcommand() { return await documentationHoverProvider(this.commandString); } async generate() { this.commandString = await this.checkForSubCommands(); if (this.isSubCommand()) { const output = await documentationHoverProvider(this.commandString); //console.log(output) if (output) { return output; } } else { return await this.generateForFlags(); } return; } } exports.HoverFromCompletion = HoverFromCompletion;