F8: out-of-workspace authoring — hybrid sidecar persistence (#25) #26
@@ -10,14 +10,14 @@
|
|||||||
*/
|
*/
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { CoauthorStore } from "./store";
|
import { SidecarRouter, docIdentity } from "./sidecarRouter";
|
||||||
import { newId, type AttributionRecord, type Provenance } from "./model";
|
import { newId, type AttributionRecord, type Provenance } from "./model";
|
||||||
import { buildFingerprint, resolve, type OffsetRange } from "./anchorer";
|
import { buildFingerprint, resolve, type OffsetRange } from "./anchorer";
|
||||||
import { gitUserEmail } from "./identity";
|
import { gitUserEmail } from "./identity";
|
||||||
import { applyChange, coalesce, type LiveSpan } from "./attributionTracker";
|
import { applyChange, coalesce, type LiveSpan } from "./attributionTracker";
|
||||||
import { minimizeReplace, PendingEditRegistry } from "./pendingEdits";
|
import { minimizeReplace, PendingEditRegistry } from "./pendingEdits";
|
||||||
import type { VersionGuard } from "./versionGuard";
|
import type { VersionGuard } from "./versionGuard";
|
||||||
import { isUnderRoot } from "./workspacePath";
|
import { isAuthorable } from "./workspacePath";
|
||||||
|
|
||||||
/** Test-facing snapshot of live attribution state for a document. */
|
/** Test-facing snapshot of live attribution state for a document. */
|
||||||
export interface RenderedSpan {
|
export interface RenderedSpan {
|
||||||
@@ -74,8 +74,8 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
readonly onDidApplyAgentEdit: vscode.Event<{ document: vscode.TextDocument }> = this.applyEmitter.event;
|
readonly onDidApplyAgentEdit: vscode.Event<{ document: vscode.TextDocument }> = this.applyEmitter.event;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly store: CoauthorStore,
|
private readonly store: SidecarRouter,
|
||||||
private readonly rootDir: string,
|
private readonly rootDir: string | undefined,
|
||||||
private readonly guard: VersionGuard,
|
private readonly guard: VersionGuard,
|
||||||
) {
|
) {
|
||||||
this.disposables.push(this.agentType, this.humanType, this.statusItem, this.output, this.applyEmitter);
|
this.disposables.push(this.agentType, this.humanType, this.statusItem, this.output, this.applyEmitter);
|
||||||
@@ -88,14 +88,15 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isTracked(document: vscode.TextDocument): boolean {
|
private isTracked(document: vscode.TextDocument): boolean {
|
||||||
return document.uri.scheme === "file" && isUnderRoot(document.uri.fsPath, this.rootDir);
|
return isAuthorable(document.uri.scheme);
|
||||||
}
|
}
|
||||||
private docPathOf(uri: vscode.Uri): string {
|
/** The single document key (F8): repo-relative path in-workspace, URI string otherwise. */
|
||||||
return vscode.workspace.asRelativePath(uri, false);
|
private keyOf(document: vscode.TextDocument): string {
|
||||||
|
return this.store.keyOf(docIdentity(document));
|
||||||
}
|
}
|
||||||
private currentAuthor(): Provenance {
|
private currentAuthor(): Provenance {
|
||||||
const id = vscode.workspace.getConfiguration("git").get<string>("user.name") || process.env.USER || "human";
|
const id = vscode.workspace.getConfiguration("git").get<string>("user.name") || process.env.USER || "human";
|
||||||
const email = gitUserEmail(this.rootDir);
|
const email = this.rootDir !== undefined ? gitUserEmail(this.rootDir) : undefined;
|
||||||
return { kind: "human", id, ...(email !== undefined ? { email } : {}) };
|
return { kind: "human", id, ...(email !== undefined ? { email } : {}) };
|
||||||
}
|
}
|
||||||
private state(docPath: string): DocAttribution {
|
private state(docPath: string): DocAttribution {
|
||||||
@@ -112,7 +113,7 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
/** Load the sidecar and re-resolve every attribution (live span | orphan). */
|
/** Load the sidecar and re-resolve every attribution (live span | orphan). */
|
||||||
loadAll(document: vscode.TextDocument): void {
|
loadAll(document: vscode.TextDocument): void {
|
||||||
if (!this.isTracked(document)) return;
|
if (!this.isTracked(document)) return;
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
const s = this.state(docPath);
|
const s = this.state(docPath);
|
||||||
const artifact = this.store.load(docPath);
|
const artifact = this.store.load(docPath);
|
||||||
s.spans = [];
|
s.spans = [];
|
||||||
@@ -143,7 +144,7 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
handleExternalSidecarChange(uri: vscode.Uri): void {
|
handleExternalSidecarChange(uri: vscode.Uri): void {
|
||||||
for (const s of this.docs.values()) {
|
for (const s of this.docs.values()) {
|
||||||
if (this.store.sidecarPath(s.docPath) === uri.fsPath) {
|
if (this.store.sidecarPath(s.docPath) === uri.fsPath) {
|
||||||
const doc = vscode.workspace.textDocuments.find((d) => this.docPathOf(d.uri) === s.docPath);
|
const doc = vscode.workspace.textDocuments.find((d) => this.keyOf(d) === s.docPath);
|
||||||
if (doc) this.loadAll(doc);
|
if (doc) this.loadAll(doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +154,7 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
|
|
||||||
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
||||||
if (!this.isTracked(e.document) || e.contentChanges.length === 0) return;
|
if (!this.isTracked(e.document) || e.contentChanges.length === 0) return;
|
||||||
const docPath = this.docPathOf(e.document.uri);
|
const docPath = this.keyOf(e.document);
|
||||||
if (!e.document.isDirty && this.matchesDisk(e.document)) {
|
if (!e.document.isDirty && this.matchesDisk(e.document)) {
|
||||||
// Disk sync (revert / external reload): buffer now equals the file on
|
// Disk sync (revert / external reload): buffer now equals the file on
|
||||||
// disk — re-resolve, never attribute (PUC-4). A real edit can also
|
// disk — re-resolve, never attribute (PUC-4). A real edit can also
|
||||||
@@ -248,7 +249,7 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (!this.isTracked(document)) return false;
|
if (!this.isTracked(document)) return false;
|
||||||
if (opts?.expectedVersion !== undefined && document.version !== opts.expectedVersion) return false;
|
if (opts?.expectedVersion !== undefined && document.version !== opts.expectedVersion) return false;
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
const startOffset = document.offsetAt(range.start);
|
const startOffset = document.offsetAt(range.start);
|
||||||
const endOffset = document.offsetAt(range.end);
|
const endOffset = document.offsetAt(range.end);
|
||||||
const oldText = document.getText(range);
|
const oldText = document.getText(range);
|
||||||
@@ -298,8 +299,8 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
|
|
||||||
private onDidSave(document: vscode.TextDocument): void {
|
private onDidSave(document: vscode.TextDocument): void {
|
||||||
if (!this.isTracked(document)) return;
|
if (!this.isTracked(document)) return;
|
||||||
if (this.guard.isReadOnly(this.docPathOf(document.uri))) return;
|
if (this.guard.isReadOnly(this.keyOf(document))) return;
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
const s = this.docs.get(docPath);
|
const s = this.docs.get(docPath);
|
||||||
// Allow save when hadAttributions is true even if spans/orphans are now empty:
|
// Allow save when hadAttributions is true even if spans/orphans are now empty:
|
||||||
// that means the user deliberately deleted all attributed text, and we must
|
// that means the user deliberately deleted all attributed text, and we must
|
||||||
@@ -351,7 +352,7 @@ export class AttributionController implements vscode.Disposable {
|
|||||||
|
|
||||||
private render(document: vscode.TextDocument): void {
|
private render(document: vscode.TextDocument): void {
|
||||||
if (!this.isTracked(document)) return;
|
if (!this.isTracked(document)) return;
|
||||||
const s = this.docs.get(this.docPathOf(document.uri));
|
const s = this.docs.get(this.keyOf(document));
|
||||||
const spans = this.visible && s ? s.spans : [];
|
const spans = this.visible && s ? s.spans : [];
|
||||||
const toRange = (sp: LiveSpan) =>
|
const toRange = (sp: LiveSpan) =>
|
||||||
new vscode.Range(document.positionAt(sp.start), document.positionAt(sp.end));
|
new vscode.Range(document.positionAt(sp.start), document.positionAt(sp.end));
|
||||||
|
|||||||
+15
-14
@@ -10,13 +10,13 @@
|
|||||||
* Claude-attributed with zero new attribution code.
|
* Claude-attributed with zero new attribution code.
|
||||||
*/
|
*/
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { CoauthorStore } from "./store";
|
import { SidecarRouter, docIdentity } from "./sidecarRouter";
|
||||||
import { emptyArtifact, type Artifact, type Fingerprint, type Proposal, type Provenance } from "./model";
|
import { emptyArtifact, type Artifact, type Fingerprint, type Proposal, type Provenance } from "./model";
|
||||||
import { resolve, shift, type OffsetRange } from "./anchorer";
|
import { resolve, shift, type OffsetRange } from "./anchorer";
|
||||||
import { addProposal, proposalBody, removeProposal } from "./proposalModel";
|
import { addProposal, proposalBody, removeProposal } from "./proposalModel";
|
||||||
import type { AttributionController } from "./attributionController";
|
import type { AttributionController } from "./attributionController";
|
||||||
import type { VersionGuard } from "./versionGuard";
|
import type { VersionGuard } from "./versionGuard";
|
||||||
import { isUnderRoot } from "./workspacePath";
|
import { isAuthorable } from "./workspacePath";
|
||||||
|
|
||||||
/** 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 {
|
||||||
@@ -54,9 +54,9 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
private readonly statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 89);
|
private readonly statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 89);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly store: CoauthorStore,
|
private readonly store: SidecarRouter,
|
||||||
private readonly attribution: AttributionController,
|
private readonly attribution: AttributionController,
|
||||||
private readonly rootDir: string,
|
private readonly rootDir: string | undefined,
|
||||||
private readonly guard: VersionGuard,
|
private readonly guard: VersionGuard,
|
||||||
) {
|
) {
|
||||||
// No commentingRangeProvider: humans never open proposal threads by hand —
|
// No commentingRangeProvider: humans never open proposal threads by hand —
|
||||||
@@ -71,13 +71,14 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isTracked(document: vscode.TextDocument): boolean {
|
private isTracked(document: vscode.TextDocument): boolean {
|
||||||
return document.uri.scheme === "file" && isUnderRoot(document.uri.fsPath, this.rootDir);
|
return isAuthorable(document.uri.scheme);
|
||||||
}
|
}
|
||||||
private docPathOf(uri: vscode.Uri): string {
|
/** The single document key (F8): repo-relative path in-workspace, URI string otherwise. */
|
||||||
return vscode.workspace.asRelativePath(uri, false);
|
private keyOf(document: vscode.TextDocument): string {
|
||||||
|
return this.store.keyOf(docIdentity(document));
|
||||||
}
|
}
|
||||||
private ensureState(document: vscode.TextDocument): DocState {
|
private ensureState(document: vscode.TextDocument): DocState {
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
let state = this.docs.get(docPath);
|
let state = this.docs.get(docPath);
|
||||||
if (!state) {
|
if (!state) {
|
||||||
state = {
|
state = {
|
||||||
@@ -108,8 +109,8 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
opts?: { turnId?: string; instruction?: string },
|
opts?: { turnId?: string; instruction?: string },
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
if (!this.isTracked(document)) return undefined;
|
if (!this.isTracked(document)) return undefined;
|
||||||
if (this.guard.isReadOnly(this.docPathOf(document.uri))) return undefined;
|
if (this.guard.isReadOnly(this.keyOf(document))) return undefined;
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
let proposalId: string | undefined;
|
let proposalId: string | undefined;
|
||||||
this.store.update(docPath, (a) => {
|
this.store.update(docPath, (a) => {
|
||||||
proposalId = addProposal(a, fp, replacement, author, opts).proposalId;
|
proposalId = addProposal(a, fp, replacement, author, opts).proposalId;
|
||||||
@@ -185,7 +186,7 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
/** Load + (re)render every pending proposal at its resolved anchor (or flagged). */
|
/** Load + (re)render every pending proposal at its resolved anchor (or flagged). */
|
||||||
renderAll(document: vscode.TextDocument): void {
|
renderAll(document: vscode.TextDocument): void {
|
||||||
if (!this.isTracked(document)) return;
|
if (!this.isTracked(document)) return;
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
const state = this.ensureState(document);
|
const state = this.ensureState(document);
|
||||||
state.artifact = this.store.load(docPath) ?? emptyArtifact(docPath);
|
state.artifact = this.store.load(docPath) ?? emptyArtifact(docPath);
|
||||||
for (const vsThread of state.vsThreads.values()) vsThread.dispose();
|
for (const vsThread of state.vsThreads.values()) vsThread.dispose();
|
||||||
@@ -212,14 +213,14 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
handleExternalSidecarChange(uri: vscode.Uri): void {
|
handleExternalSidecarChange(uri: vscode.Uri): void {
|
||||||
for (const state of this.docs.values()) {
|
for (const state of this.docs.values()) {
|
||||||
if (this.store.sidecarPath(state.docPath) === uri.fsPath) {
|
if (this.store.sidecarPath(state.docPath) === uri.fsPath) {
|
||||||
const doc = vscode.workspace.textDocuments.find((d) => this.docPathOf(d.uri) === state.docPath);
|
const doc = vscode.workspace.textDocuments.find((d) => this.keyOf(d) === state.docPath);
|
||||||
if (doc) this.renderAll(doc);
|
if (doc) this.renderAll(doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
||||||
const state = this.docs.get(this.docPathOf(e.document.uri));
|
const state = this.docs.get(this.keyOf(e.document));
|
||||||
if (!state || state.live.size === 0) return;
|
if (!state || state.live.size === 0) return;
|
||||||
for (const change of e.contentChanges) {
|
for (const change of e.contentChanges) {
|
||||||
const edit = { start: change.rangeOffset, end: change.rangeOffset + change.rangeLength, newLength: change.text.length };
|
const edit = { start: change.rangeOffset, end: change.rangeOffset + change.rangeLength, newLength: change.text.length };
|
||||||
@@ -298,7 +299,7 @@ export class ProposalController implements vscode.Disposable {
|
|||||||
// ---- lookups ----------------------------------------------------------------------------
|
// ---- lookups ----------------------------------------------------------------------------
|
||||||
|
|
||||||
private openDoc(state: DocState): vscode.TextDocument | undefined {
|
private openDoc(state: DocState): vscode.TextDocument | undefined {
|
||||||
return vscode.workspace.textDocuments.find((d) => this.docPathOf(d.uri) === state.docPath);
|
return vscode.workspace.textDocuments.find((d) => this.keyOf(d) === state.docPath);
|
||||||
}
|
}
|
||||||
private byThread(vsThread: vscode.CommentThread): { state: DocState; proposal: Proposal } | undefined {
|
private byThread(vsThread: vscode.CommentThread): { state: DocState; proposal: Proposal } | undefined {
|
||||||
for (const state of this.docs.values()) {
|
for (const state of this.docs.values()) {
|
||||||
|
|||||||
+16
-19
@@ -8,13 +8,13 @@
|
|||||||
* orphaned thread (INV-1).
|
* orphaned thread (INV-1).
|
||||||
*/
|
*/
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { CoauthorStore } from "./store";
|
import { SidecarRouter, docIdentity } from "./sidecarRouter";
|
||||||
import { emptyArtifact, type Artifact, type Provenance } from "./model";
|
import { emptyArtifact, type Artifact, type Provenance } from "./model";
|
||||||
import { buildFingerprint, resolve, shift, type OffsetRange } from "./anchorer";
|
import { buildFingerprint, resolve, shift, type OffsetRange } from "./anchorer";
|
||||||
import { gitUserEmail } from "./identity";
|
import { gitUserEmail } from "./identity";
|
||||||
import { addThread, appendMessage, setStatus } from "./threadModel";
|
import { addThread, appendMessage, setStatus } from "./threadModel";
|
||||||
import type { VersionGuard } from "./versionGuard";
|
import type { VersionGuard } from "./versionGuard";
|
||||||
import { isUnderRoot } from "./workspacePath";
|
import { isAuthorable } from "./workspacePath";
|
||||||
|
|
||||||
/** Test-facing snapshot of what is currently rendered for a document. */
|
/** Test-facing snapshot of what is currently rendered for a document. */
|
||||||
export interface RenderedThread {
|
export interface RenderedThread {
|
||||||
@@ -42,14 +42,14 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
private readonly docs = new Map<string, DocState>(); // keyed by docPath
|
private readonly docs = new Map<string, DocState>(); // keyed by docPath
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly store: CoauthorStore,
|
private readonly store: SidecarRouter,
|
||||||
private readonly rootDir: string,
|
private readonly rootDir: string | undefined,
|
||||||
private readonly guard: VersionGuard,
|
private readonly guard: VersionGuard,
|
||||||
) {
|
) {
|
||||||
this.controller = vscode.comments.createCommentController("cowriting.threads", "Coauthoring Threads");
|
this.controller = vscode.comments.createCommentController("cowriting.threads", "Coauthoring Threads");
|
||||||
this.controller.commentingRangeProvider = {
|
this.controller.commentingRangeProvider = {
|
||||||
provideCommentingRanges: (document) => {
|
provideCommentingRanges: (document) => {
|
||||||
if (!this.isInRoot(document.uri)) return [];
|
if (!isAuthorable(document.uri.scheme)) return [];
|
||||||
return [new vscode.Range(0, 0, Math.max(0, document.lineCount - 1), 0)];
|
return [new vscode.Range(0, 0, Math.max(0, document.lineCount - 1), 0)];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -69,17 +69,14 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isInRoot(uri: vscode.Uri): boolean {
|
/** The single document key (F8): repo-relative path in-workspace, URI string otherwise. */
|
||||||
return uri.scheme === "file" && isUnderRoot(uri.fsPath, this.rootDir);
|
private keyOf(document: vscode.TextDocument): string {
|
||||||
}
|
return this.store.keyOf(docIdentity(document));
|
||||||
|
|
||||||
private docPathOf(uri: vscode.Uri): string {
|
|
||||||
return vscode.workspace.asRelativePath(uri, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private currentAuthor(): Provenance {
|
private currentAuthor(): Provenance {
|
||||||
const id = vscode.workspace.getConfiguration("git").get<string>("user.name") || process.env.USER || "human";
|
const id = vscode.workspace.getConfiguration("git").get<string>("user.name") || process.env.USER || "human";
|
||||||
const email = gitUserEmail(this.rootDir);
|
const email = this.rootDir !== undefined ? gitUserEmail(this.rootDir) : undefined;
|
||||||
return { kind: "human", id, ...(email !== undefined ? { email } : {}) };
|
return { kind: "human", id, ...(email !== undefined ? { email } : {}) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +90,7 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ensureState(document: vscode.TextDocument): DocState {
|
private ensureState(document: vscode.TextDocument): DocState {
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
let state = this.docs.get(docPath);
|
let state = this.docs.get(docPath);
|
||||||
if (!state) {
|
if (!state) {
|
||||||
state = {
|
state = {
|
||||||
@@ -113,8 +110,8 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
|
|
||||||
async createThreadOnSelection(firstBody = "New thread"): Promise<string | undefined> {
|
async createThreadOnSelection(firstBody = "New thread"): Promise<string | undefined> {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (!editor || editor.selection.isEmpty || !this.isInRoot(editor.document.uri)) return undefined;
|
if (!editor || editor.selection.isEmpty || !isAuthorable(editor.document.uri.scheme)) return undefined;
|
||||||
if (this.guard.isReadOnly(this.docPathOf(editor.document.uri))) return undefined;
|
if (this.guard.isReadOnly(this.keyOf(editor.document))) return undefined;
|
||||||
const document = editor.document;
|
const document = editor.document;
|
||||||
const state = this.ensureState(document);
|
const state = this.ensureState(document);
|
||||||
const offsets: OffsetRange = {
|
const offsets: OffsetRange = {
|
||||||
@@ -155,7 +152,7 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
|
|
||||||
/** Load + (re)render every thread for a document at its resolved anchor (or orphaned). */
|
/** Load + (re)render every thread for a document at its resolved anchor (or orphaned). */
|
||||||
renderAll(document: vscode.TextDocument): void {
|
renderAll(document: vscode.TextDocument): void {
|
||||||
const docPath = this.docPathOf(document.uri);
|
const docPath = this.keyOf(document);
|
||||||
const state = this.ensureState(document);
|
const state = this.ensureState(document);
|
||||||
// fresh artifact from disk (reload / external change)
|
// fresh artifact from disk (reload / external change)
|
||||||
state.artifact = this.store.load(docPath) ?? emptyArtifact(docPath);
|
state.artifact = this.store.load(docPath) ?? emptyArtifact(docPath);
|
||||||
@@ -182,14 +179,14 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
const p = uri.fsPath;
|
const p = uri.fsPath;
|
||||||
for (const state of this.docs.values()) {
|
for (const state of this.docs.values()) {
|
||||||
if (this.store.sidecarPath(state.docPath) === p) {
|
if (this.store.sidecarPath(state.docPath) === p) {
|
||||||
const doc = vscode.workspace.textDocuments.find((d) => this.docPathOf(d.uri) === state.docPath);
|
const doc = vscode.workspace.textDocuments.find((d) => this.keyOf(d) === state.docPath);
|
||||||
if (doc) this.renderAll(doc);
|
if (doc) this.renderAll(doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
private onDidChange(e: vscode.TextDocumentChangeEvent): void {
|
||||||
const state = this.docs.get(this.docPathOf(e.document.uri));
|
const state = this.docs.get(this.keyOf(e.document));
|
||||||
if (!state || state.live.size === 0) return;
|
if (!state || state.live.size === 0) return;
|
||||||
for (const change of e.contentChanges) {
|
for (const change of e.contentChanges) {
|
||||||
const edit = { start: change.rangeOffset, end: change.rangeOffset + change.rangeLength, newLength: change.text.length };
|
const edit = { start: change.rangeOffset, end: change.rangeOffset + change.rangeLength, newLength: change.text.length };
|
||||||
@@ -205,7 +202,7 @@ export class ThreadController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onDidSave(document: vscode.TextDocument): void {
|
private onDidSave(document: vscode.TextDocument): void {
|
||||||
const state = this.docs.get(this.docPathOf(document.uri));
|
const state = this.docs.get(this.keyOf(document));
|
||||||
if (!state || state.live.size === 0) return;
|
if (!state || state.live.size === 0) return;
|
||||||
if (this.guard.isReadOnly(state.docPath)) return;
|
if (this.guard.isReadOnly(state.docPath)) return;
|
||||||
const text = document.getText();
|
const text = document.getText();
|
||||||
|
|||||||
Reference in New Issue
Block a user