fish-lsp
Version:
LSP implementation for fish/fish-shell
349 lines (348 loc) • 11.7 kB
JavaScript
"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;