// The commit box's logic (spec §5.1), kept free of the vscode module so it // can be tested: signing state from `oxy identity show`, the placeholder // that shows it, message history, and conflict marker detection. export interface SigningInfo { /** `Name `, or empty without an identity. */ who: string; /** Whether the secret key is in the key directory. */ secret: boolean; /** `change`: every commit is signed; `seal`: changes are sealed at push. */ mode: "change" | "seal"; } /** Parse `oxy --plain identity show`; `null` when no identity is configured. */ export function parseIdentityShow(text: string): SigningInfo | null { const head = text.match(/^#\s*identity\s+(.+)$/m); if (!head) { return null; } const secretLine = text.match(/^\s*secret\s+(.+)$/m)?.[1] ?? ""; const signingLine = text.match(/^\s*signing\s+(.+)$/m)?.[1] ?? ""; return { who: head[1].trim(), secret: /available/.test(secretLine), mode: /core\.sign\s*=\s*change/.test(signingLine) ? "change" : "seal", }; } /** A short name for the placeholder: the name without the address. */ function shortName(who: string): string { return who.replace(/\s*<[^>]*>\s*$/, "").trim() || who; } /** How the next commit will be authenticated, for the commit box. */ export function signingLabel(info: SigningInfo | null | undefined): string { if (info === undefined) { return ""; } if (info === null) { return "⚠ unsigned: no identity (oxy identity create)"; } if (!info.secret) { return `⚠ unsigned: the key for ${shortName(info.who)} is missing`; } return info.mode === "change" ? `✔ signed as ${shortName(info.who)}` : `◈ sealed at push as ${shortName(info.who)}`; } /** The commit box placeholder for the current mode. */ export function placeholder(opts: { amend: boolean; resolution: boolean; signing: string }): string { const what = opts.amend ? "Amend the last change: new message, or empty to keep it" : opts.resolution ? "Describe the resolution" : "Message"; const key = opts.amend ? "Ctrl+Enter to amend" : "Ctrl+Enter to commit"; return `${what} (${key})${opts.signing ? ` · ${opts.signing}` : ""}`; } /** Recent commit messages, newest first, without duplicates. */ export class MessageHistory { static readonly LIMIT = 30; private items: string[]; constructor(items: readonly string[] = []) { this.items = items.filter((m) => m.trim()).slice(0, MessageHistory.LIMIT); } add(message: string): void { const m = message.trim(); if (!m) { return; } this.items = [m, ...this.items.filter((x) => x !== m)].slice(0, MessageHistory.LIMIT); } get entries(): readonly string[] { return this.items; } } /** * Line numbers (1-based) that open or close a conflict: Oxygen's markers * (`<<<<<<< oxy conflict …`) and Git's alike, since both begin with seven * angle brackets. */ export function conflictMarkerLines(text: string): number[] { const out: number[] = []; text.split("\n").forEach((l, i) => { if (/^(<{7}|>{7})( |\r?$)/.test(l)) { out.push(i + 1); } }); return out; }