/** * Command completions — build the dropdown items for journal input * autocomplete based on the current raw text and completions data. * * Pure function with no component dependencies. */ import type { CompletionItem } from "./command-parser"; import type { JournalCompletions } from "./completions"; export interface CompletionsContext { data: JournalCompletions; isGm: boolean; } export function buildCompletions( raw: string, ctx: CompletionsContext, ): CompletionItem[] { const { data, isGm } = ctx; const commands = [ { label: "/roll", kind: "command" as const, insertText: "/roll " }, { label: "/spark", kind: "command" as const, insertText: "/spark " }, { label: "/link", kind: "command" as const, insertText: "/link " }, { label: "/stat", kind: "command" as const, insertText: "/stat " }, ]; // Show commands when user types / or starts typing a command name if (raw.startsWith("/") && !raw.includes(" ")) { const prefix = raw.toLowerCase(); let matches = commands.filter((c) => c.label.toLowerCase().startsWith(prefix), ); if (!isGm) { matches = matches.filter((c) => c.label === "/stat"); } return matches.length > 0 ? matches : [{ label: "未知命令", kind: "no-results", insertText: "" }]; } // After /roll — show dice suggestions if (raw.startsWith("/roll ")) { const prefix = raw.slice("/roll ".length).toLowerCase(); const matches = data.dice .filter( (d) => d.notation.toLowerCase().includes(prefix) || d.label.toLowerCase().includes(prefix), ) .slice(0, 8); if (matches.length === 0) { return [{ label: "未找到骰子", kind: "no-results", insertText: "" }]; } return matches.map((d) => ({ label: d.notation, kind: "value" as const, insertText: "/roll " + d.notation, })); } // After /spark — show spark table suggestions if (raw.startsWith("/spark ")) { const prefix = raw.slice("/spark ".length).toLowerCase(); const matches = data.sparkTables .filter( (s) => s.slug.toLowerCase().includes(prefix) || s.label.toLowerCase().includes(prefix), ) .slice(0, 8); if (matches.length === 0) { return [{ label: "未找到种子表", kind: "no-results", insertText: "" }]; } return matches.map((s) => ({ label: `${s.slug} (${s.notation})`, kind: "value" as const, insertText: `/spark ${s.slug}`, })); } // After /link — show article and heading suggestions if (raw.startsWith("/link ")) { const prefix = raw.slice("/link ".length).toLowerCase(); const matches = data.links .filter( (l) => l.path.toLowerCase().includes(prefix) || l.label.toLowerCase().includes(prefix), ) .slice(0, 8); if (matches.length === 0) { return [{ label: "未找到链接", kind: "no-results", insertText: "" }]; } return matches.map((l) => { const insert = l.section ? `/link ${l.path}#${l.section}` : `/link ${l.path}`; return { label: l.label, kind: "value" as const, insertText: insert }; }); } // After /stat — show subcommands or stat keys if (raw.startsWith("/stat ")) { const rest = raw.slice("/stat ".length).trim(); if (!rest || rest.length < 3) { const subs = ["set", "del", "roll"]; const matches = subs.filter((s) => s.startsWith(rest.toLowerCase())); if (matches.length === 0) return [{ label: "set/del/roll", kind: "no-results", insertText: "" }]; return matches.map((s) => ({ label: `/stat ${s}`, kind: "value" as const, insertText: `/stat ${s} `, })); } if (rest.startsWith("set ")) { const prefix = rest.slice("set ".length).toLowerCase(); const matches = data.stats .filter((s) => s.key.toLowerCase().includes(prefix)) .slice(0, 8); if (matches.length === 0) return [{ label: "未找到属性", kind: "no-results", insertText: "" }]; return matches.map((s) => ({ label: `${s.key} (${s.label})`, kind: "value" as const, insertText: `/stat set ${s.key}=`, })); } if (rest.startsWith("del ") || rest.startsWith("roll ")) { const [cmd, ...restParts] = rest.split(" "); const prefix = restParts.join(" ").toLowerCase(); const matches = data.stats .filter((s) => s.key.toLowerCase().startsWith(prefix)) .slice(0, 8); if (matches.length === 0) return [{ label: "未找到属性", kind: "no-results", insertText: "" }]; return matches.map((s) => ({ label: `${s.key} (${s.label})`, kind: "value" as const, insertText: `/stat ${cmd} ${s.key}`, })); } } return []; }