UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

151 lines (133 loc) 4.75 kB
import { FishCompletionItem, FishCompletionItemKind } from './types'; import { execCmd } from '../exec'; import { StaticItems } from './static-items'; import { SetupItemsFromCommandConfig } from './startup-config'; import { md } from '../markdown-builder'; export type ItemMapRecord = Record<FishCompletionItemKind, FishCompletionItem[]>; export class CompletionItemMap { constructor(private _items: ItemMapRecord = {} as ItemMapRecord) { } static async initialize(): Promise<CompletionItemMap> { const result: ItemMapRecord = {} as ItemMapRecord; const cmdOutputs: Map<FishCompletionItemKind, string[]> = new Map(); const topLevelLabels: Set<string> = new Set(); await Promise.all(SetupItemsFromCommandConfig.map(async (item) => { const stdout = await execCmd(item.command); cmdOutputs.set(item.fishKind, stdout); })); SetupItemsFromCommandConfig.forEach((item) => { const items: FishCompletionItem[] = []; const stdout = cmdOutputs.get(item.fishKind)!; stdout.forEach((line) => { if (line.trim().length === 0) { return; } const { label, value } = splitLine(line); if (item.topLevel) { if (topLevelLabels.has(label)) { return; } topLevelLabels.add(label); } const detail = getCommandsDetail(value || item.detail); items.push(FishCompletionItem.create(label, item.fishKind, detail, line)); }); result[item.fishKind] = items; }); Object.entries(StaticItems).forEach(([key, value]) => { const kind = key as FishCompletionItemKind; if (!result[kind]) { result[kind] = value.map((item) => FishCompletionItem.create( item.label, kind, item.detail, item.documentation.toString(), item.examples, )); } if (kind === FishCompletionItemKind.FUNCTION || kind === FishCompletionItemKind.VARIABLE) { const toAdd = value .filter((item) => !result[kind].find((i) => i.label === item.label)) .map((item) => FishCompletionItem.create( item.label, kind, item.detail, [ `(${md.italic(kind)}) ${md.bold(item.label)}`, md.separator(), item.documentation.toString(), ].join('\n'), item.examples, ).setUseDocAsDetail()); result[kind].push(...toAdd); } }); return new CompletionItemMap(result); } get(kind: FishCompletionItemKind): FishCompletionItem[] { return this._items[kind] || []; } get allKinds(): FishCompletionItemKind[] { return Object.keys(this._items) as FishCompletionItemKind[]; } allOfKinds(...kinds: FishCompletionItemKind[]): FishCompletionItem[] { return kinds.reduce((acc, kind) => acc.concat(this.get(kind)), [] as FishCompletionItem[]); } entries(): [FishCompletionItemKind, FishCompletionItem[]][] { return Object.entries(this._items) as [FishCompletionItemKind, FishCompletionItem[]][]; } forEach(callbackfn: (key: FishCompletionItemKind, value: FishCompletionItem[]) => void) { this.entries().forEach(([key, value]) => callbackfn(key, value)); } allCompletionsWithoutCommand() { return this.allOfKinds( FishCompletionItemKind.ABBR, FishCompletionItemKind.ALIAS, FishCompletionItemKind.BUILTIN, FishCompletionItemKind.FUNCTION, FishCompletionItemKind.COMMAND, // FishCompletionItemKind.VARIABLE, ); } findLabel(label: string, ...searchKinds: FishCompletionItemKind[]): FishCompletionItem | undefined { const kinds: FishCompletionItemKind[] = searchKinds?.length > 0 ? searchKinds : this.allKinds; for (const kind of kinds) { const item = this.get(kind).find((item) => item.label === label); if (item) { return item; } } return undefined; } get blockedCommands() { return [ 'end', 'else', 'continue', 'break', ]; } } export function splitLine(line: string): { label: string; value?: string; } { const index = line.search(/\s/); // This looks for the first whitespace character if (index === -1) { return { label: line }; } const label = line.slice(0, index); const value = line.slice(index).trimStart(); // No need to add 1 since you want to retain the whitespace in value. return { label, value }; } function getCommandsDetail(value: string) { if (value.trim().length === 0) { return 'command'; } if (value.startsWith('alias')) { return 'alias'; } if (value === 'command link') { return 'command'; } if (value === 'command') { return 'command'; } return value; }