F10: interactive track-changes review in the markdown preview (#29) #30
@@ -18,6 +18,7 @@ import { addProposal, removeProposal } from "./proposalModel";
|
|||||||
import type { AttributionController } from "./attributionController";
|
import type { AttributionController } from "./attributionController";
|
||||||
import type { VersionGuard } from "./versionGuard";
|
import type { VersionGuard } from "./versionGuard";
|
||||||
import { isAuthorable } from "./workspacePath";
|
import { isAuthorable } from "./workspacePath";
|
||||||
|
import type { ProposalView } from "./trackChangesModel";
|
||||||
|
|
||||||
/** Test-facing snapshot of what is currently rendered for a document. */
|
/** Test-facing snapshot of what is currently rendered for a document. */
|
||||||
export interface RenderedProposal {
|
export interface RenderedProposal {
|
||||||
@@ -44,6 +45,9 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
private readonly disposables: vscode.Disposable[] = [];
|
private readonly disposables: vscode.Disposable[] = [];
|
||||||
private readonly docs = new Map<string, DocState>(); // keyed by docPath
|
private readonly docs = new Map<string, DocState>(); // keyed by docPath
|
||||||
private readonly statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 89);
|
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(
|
constructor(
|
||||||
private readonly store: SidecarRouter,
|
private readonly store: SidecarRouter,
|
||||||
@@ -52,11 +56,16 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
private readonly guard: VersionGuard,
|
private readonly guard: VersionGuard,
|
||||||
) {
|
) {
|
||||||
this.disposables.push(this.statusItem);
|
this.disposables.push(this.statusItem);
|
||||||
|
this.disposables.push(this.onDidChangeProposalsEmitter);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
vscode.workspace.onDidChangeTextDocument((e) => this.onDidChange(e)),
|
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 {
|
private isTracked(document: vscode.TextDocument): boolean {
|
||||||
return isAuthorable(document.uri.scheme);
|
return isAuthorable(document.uri.scheme);
|
||||||
}
|
}
|
||||||
@@ -64,6 +73,27 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
private keyOf(document: vscode.TextDocument): string {
|
private keyOf(document: vscode.TextDocument): string {
|
||||||
return this.store.keyOf(docIdentity(document));
|
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 {
|
private ensureState(document: vscode.TextDocument): DocState {
|
||||||
const docPath = this.keyOf(document);
|
const docPath = this.keyOf(document);
|
||||||
let state = this.docs.get(docPath);
|
let state = this.docs.get(docPath);
|
||||||
@@ -181,6 +211,7 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.renderStatus(state);
|
this.renderStatus(state);
|
||||||
|
this.fireChanged(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shared-watcher entry point (extension.ts): a sidecar changed externally. */
|
/** Shared-watcher entry point (extension.ts): a sidecar changed externally. */
|
||||||
|
|||||||
Reference in New Issue
Block a user