import type { CompleteEvent } from "../../plug-api/types.ts"; import { events, language } from "@silverbulletmd/silverbullet/syscalls"; import type { AttributeCompleteEvent, AttributeCompletion, } from "../index/attributes.ts"; export async function queryComplete(completeEvent: CompleteEvent) { let querySourceMatch: RegExpExecArray | null = null; // Let's check if this is a query block let fencedParent = completeEvent.parentNodes.find((node) => node.startsWith("FencedCode:query") ); if (fencedParent) { // Yep, let's see if we can do source completion querySourceMatch = /^\s*()([\w\-_]*)$/.exec( completeEvent.linePrefix, ); } else { // Not a query, perhaps a template then? fencedParent = completeEvent.parentNodes.find((node) => node.startsWith("FencedCode:template") ); if (fencedParent) { // Match "{{{source" or "{source" (without a { before it, because that would be a variable) querySourceMatch = /([^{]|\{\{)\{(\s*[\w\-_]+)$/.exec( completeEvent.linePrefix, ); } else { // No? Then we're out, sorry. return null; } } if (querySourceMatch) { const allEvents = await events.listEvents(); const completionOptions = allEvents .filter((eventName) => eventName.startsWith("query:") && !eventName.includes("*") ) .map((source) => ({ label: source.substring("query:".length), })); const allObjectTypes: string[] = (await events.dispatchEvent("query_", {})) .flat(); for (const type of allObjectTypes) { completionOptions.push({ label: type, }); } return { from: completeEvent.pos - querySourceMatch[2].length, options: completionOptions, }; } return null; } export async function queryAttributeComplete(completeEvent: CompleteEvent) { const fencedParent = completeEvent.parentNodes.find((node) => node.startsWith("FencedCode:query") || node.startsWith("FencedCode:template") ); if (!fencedParent) { return null; } // For this we do need to find the query source, though, so let's look for it let querySourceMatch: RegExpExecArray | null = null; if (fencedParent.startsWith("FencedCode:query")) { querySourceMatch = /^[\n\r\s]*([\w\-_]+)/.exec( fencedParent.slice("FencedCode:query".length), ); } else { // We're in a template, so let's just consider the current line and see if we can find the source querySourceMatch = /\{(\s*[\w\-_]+)\s+/.exec( completeEvent.linePrefix, ); } const whereMatch = /(where|order\s+by|and|or|select(\s+[\w\s,]+)?)\s+([\w\-_]*)$/ .exec( completeEvent.linePrefix, ); if (querySourceMatch && whereMatch) { const type = querySourceMatch[1]; const attributePrefix = whereMatch[3]; const completions = (await events.dispatchEvent( `attribute:complete:${type}`, { source: type, prefix: attributePrefix, } as AttributeCompleteEvent, )).flat() as AttributeCompletion[]; return { from: completeEvent.pos - attributePrefix.length, options: attributeCompletionsToCMCompletion(completions), }; } } function attributeCompletionsToCMCompletion( completions: AttributeCompletion[], ) { return completions.map( (completion) => ({ label: completion.name, detail: `${completion.attributeType} (${completion.source})`, type: "attribute", }), ); } export async function languageComplete(completeEvent: CompleteEvent) { const languagePrefix = /^(?:```+|~~~+)(\w*)$/.exec( completeEvent.linePrefix, ); if (!languagePrefix) { return null; } const allLanguages = await language.listLanguages(); return { from: completeEvent.pos - languagePrefix[1].length, options: allLanguages.map( (lang) => ({ label: lang, type: "language", }), ), }; }