F10: interactive track-changes review in the markdown preview (#29) #30

Merged
benstull merged 17 commits from f10-interactive-review into main 2026-06-12 07:39:51 +00:00
Showing only changes of commit fe45569218 - Show all commits
+31
View File
@@ -18,6 +18,7 @@ import { addProposal, removeProposal } from "./proposalModel";
import type { AttributionController } from "./attributionController";
import type { VersionGuard } from "./versionGuard";
import { isAuthorable } from "./workspacePath";
import type { ProposalView } from "./trackChangesModel";
/** Test-facing snapshot of what is currently rendered for a document. */
export interface RenderedProposal {
@@ -44,6 +45,9 @@ export class ProposalController implements vscode.Disposable {
private readonly disposables: vscode.Disposable[] = [];
private readonly docs = new Map<string, DocState>(); // keyed by docPath
private readonly statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 89);
private readonly onDidChangeProposalsEmitter = new vscode.EventEmitter<{ uri: string }>();
/** Fires on propose / accept / reject / external sidecar change (F10). */
readonly onDidChangeProposals = this.onDidChangeProposalsEmitter.event;
constructor(
private readonly store: SidecarRouter,
@@ -52,11 +56,16 @@ export class ProposalController implements vscode.Disposable {
private readonly guard: VersionGuard,
) {
this.disposables.push(this.statusItem);
this.disposables.push(this.onDidChangeProposalsEmitter);
this.disposables.push(
vscode.workspace.onDidChangeTextDocument((e) => this.onDidChange(e)),
);
}
private fireChanged(document: vscode.TextDocument): void {
this.onDidChangeProposalsEmitter.fire({ uri: document.uri.toString() });
}
private isTracked(document: vscode.TextDocument): boolean {
return isAuthorable(document.uri.scheme);
}
@@ -64,6 +73,27 @@ export class ProposalController implements vscode.Disposable {
private keyOf(document: vscode.TextDocument): string {
return this.store.keyOf(docIdentity(document));
}
/** The doc key F4 uses (F8 routing) — exposed for F10's preview. */
keyFor(document: vscode.TextDocument): string {
return this.keyOf(document);
}
/** Resolved proposal views for the F10 preview (anchorStart=null when unresolved). */
listProposals(document: vscode.TextDocument): ProposalView[] {
const docPath = this.keyOf(document);
const artifact = this.store.load(docPath) ?? emptyArtifact(docPath);
const text = document.getText();
return artifact.proposals.map((p) => {
const fp = artifact.anchors[p.anchorId]?.fingerprint;
const resolved = fp ? resolve(text, fp) : "orphaned";
return {
id: p.id,
anchorStart: resolved === "orphaned" ? null : resolved.start,
anchorEnd: resolved === "orphaned" ? null : resolved.end,
replaced: fp?.text ?? "",
replacement: p.replacement,
};
});
}
private ensureState(document: vscode.TextDocument): DocState {
const docPath = this.keyOf(document);
let state = this.docs.get(docPath);
@@ -181,6 +211,7 @@ export class ProposalController implements vscode.Disposable {
}
}
this.renderStatus(state);
this.fireChanged(document);
}
/** Shared-watcher entry point (extension.ts): a sidecar changed externally. */