Skip to content

Commit 3a418c1

Browse files
committed
Fix file change watcher
1 parent bc2de06 commit 3a418c1

1 file changed

Lines changed: 44 additions & 8 deletions

File tree

src/notebooks/deepnote/deepnoteFileChangeWatcher.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export class DeepnoteFileChangeWatcher implements IExtensionSyncActivationServic
145145
* Consumes a self-write marker. Returns true if the fs event was self-triggered.
146146
*/
147147
private consumeSelfWrite(uri: Uri): boolean {
148-
const key = uri.toString();
148+
const key = this.normalizeFileUri(uri);
149149

150150
// Check snapshot self-writes first
151151
if (this.snapshotSelfWriteUris.has(key)) {
@@ -187,6 +187,7 @@ export class DeepnoteFileChangeWatcher implements IExtensionSyncActivationServic
187187
if (liveCells.length !== newCells.length) {
188188
return true;
189189
}
190+
190191
return liveCells.some(
191192
(live, i) =>
192193
live.kind !== newCells[i].kind ||
@@ -332,6 +333,7 @@ export class DeepnoteFileChangeWatcher implements IExtensionSyncActivationServic
332333
}
333334
}
334335

336+
// Apply the edit to update in-memory cells immediately (responsive UX).
335337
const wsEdit = new WorkspaceEdit();
336338
wsEdit.set(notebook.uri, edits);
337339
const applied = await workspace.applyEdit(wsEdit);
@@ -341,13 +343,38 @@ export class DeepnoteFileChangeWatcher implements IExtensionSyncActivationServic
341343
return;
342344
}
343345

344-
// Save to sync mtime — mark as self-write first
345-
this.markSelfWrite(notebook.uri);
346+
// Serialize the notebook and write canonical bytes to disk. This ensures
347+
// the file on disk matches what VS Code's serializer would produce.
348+
// Then save via workspace.save() to clear dirty state and update VS Code's
349+
// internal mtime tracker. Since WE just wrote the file, its mtime is from
350+
// our write (not the external change), avoiding the "content is newer" conflict.
351+
const serializeTokenSource = new CancellationTokenSource();
346352
try {
347-
await workspace.save(notebook.uri);
348-
} catch (error) {
349-
this.consumeSelfWrite(notebook.uri);
350-
throw error;
353+
const serializedBytes = await this.serializer.serializeNotebook(newData, serializeTokenSource.token);
354+
355+
// Write to disk first — this updates the file mtime to "now"
356+
this.markSelfWrite(fileUri);
357+
try {
358+
await workspace.fs.writeFile(fileUri, serializedBytes);
359+
} catch (writeError) {
360+
this.consumeSelfWrite(fileUri);
361+
logger.warn(`[FileChangeWatcher] Failed to write synced file: ${fileUri.path}`, writeError);
362+
}
363+
364+
// Now save — VS Code serializes (same bytes), sees the mtime is from our
365+
// recent write (which its internal watcher has picked up), and writes
366+
// successfully without a "content is newer" conflict.
367+
this.markSelfWrite(fileUri);
368+
try {
369+
await workspace.save(notebook.uri);
370+
} catch (saveError) {
371+
this.consumeSelfWrite(fileUri);
372+
logger.warn(`[FileChangeWatcher] Save after sync write failed: ${notebook.uri.path}`, saveError);
373+
}
374+
} catch (serializeError) {
375+
logger.warn(`[FileChangeWatcher] Failed to serialize for sync write: ${fileUri.path}`, serializeError);
376+
} finally {
377+
serializeTokenSource.dispose();
351378
}
352379

353380
logger.info(`[FileChangeWatcher] Reloaded notebook from external change: ${notebook.uri.path}`);
@@ -523,6 +550,15 @@ export class DeepnoteFileChangeWatcher implements IExtensionSyncActivationServic
523550
return (metadata?.__deepnoteBlockId ?? metadata?.id) as string | undefined;
524551
}
525552

553+
/**
554+
* Normalizes a URI to the underlying file path by stripping query and fragment.
555+
* Notebook URIs include query params (e.g., ?notebook=id) but the filesystem
556+
* watcher fires with the raw file URI — keys must match for self-write detection.
557+
*/
558+
private normalizeFileUri(uri: Uri): string {
559+
return uri.with({ query: '', fragment: '' }).toString();
560+
}
561+
526562
private handleFileChange(uri: Uri): void {
527563
// Deterministic self-write check — no timers involved
528564
if (this.consumeSelfWrite(uri)) {
@@ -581,7 +617,7 @@ export class DeepnoteFileChangeWatcher implements IExtensionSyncActivationServic
581617
* Call before workspace.save() to prevent the resulting fs event from triggering a reload.
582618
*/
583619
private markSelfWrite(uri: Uri): void {
584-
const key = uri.toString();
620+
const key = this.normalizeFileUri(uri);
585621
const count = this.selfWriteCounts.get(key) ?? 0;
586622
this.selfWriteCounts.set(key, count + 1);
587623

0 commit comments

Comments
 (0)