Skip to content

Fix VS Code not showing correct file content for virtual files that change on writeFile #249945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion extensions/vscode-api-tests/src/memfs.ts
Original file line number Diff line number Diff line change
@@ -228,7 +228,28 @@ export class TestFS implements vscode.FileSystemProvider {
}

private _fireSoon(...events: vscode.FileChangeEvent[]): void {
this._bufferedEvents.push(...events);
// Extend events with mtime if available
const enhancedEvents = events.map(event => {
// Skip events that already have an mtime property
if ((event as any).mtime !== undefined) {
return event;
}

// Try to lookup the entry for the resource to get its mtime
try {
const entry = this._lookup(event.uri, true);
if (entry) {
// Add mtime to the event
return { ...event, mtime: entry.mtime };
}
} catch (error) {
// Ignore errors during lookup
}

return event;
});

this._bufferedEvents.push(...enhancedEvents);

if (this._fireSoonHandle) {
clearTimeout(this._fireSoonHandle);
24 changes: 24 additions & 0 deletions src/vs/platform/files/common/files.ts
Original file line number Diff line number Diff line change
@@ -953,15 +953,23 @@ export interface IFileChange {
* in the change.
*/
readonly cId?: number;

/**
* Optional modification time of the file at the time of the change.
* Note: not all file system providers will provide this information.
*/
readonly mtime?: number;
}

export class FileChangesEvent {

private static readonly MIXED_CORRELATION = null;

private readonly correlationId: number | undefined | typeof FileChangesEvent.MIXED_CORRELATION = undefined;
private readonly _changes: readonly IFileChange[];

constructor(changes: readonly IFileChange[], private readonly ignorePathCasing: boolean) {
this._changes = changes;
for (const change of changes) {

// Split by type
@@ -994,6 +1002,22 @@ export class FileChangesEvent {
}
}

/**
* Returns the stat information for a file change if it exists in this event.
* This assumes the IFileChange object may contain an mtime property as some implementations provide it.
*/
stat(resource: URI): { mtime: number } | undefined {
// Check if any of the changes have extra data with mtimes
for (const change of this._changes) {
if (change.resource.toString() === resource.toString() &&
(change as unknown as { mtime?: number }).mtime !== undefined) {
const mtime = (change as unknown as { mtime: number }).mtime;
return { mtime };
}
}
return undefined;
}

private readonly added = new Lazy(() => {
const added = TernarySearchTree.forUris<boolean>(() => this.ignorePathCasing);
added.fill(this.rawAdded.map(resource => [resource, true]));
Original file line number Diff line number Diff line change
@@ -121,15 +121,21 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE

private onDidFilesChange(e: FileChangesEvent): void {
for (const model of this.models) {
if (model.isDirty()) {
continue; // never reload dirty models
}

// Trigger a model resolve for any update or add event that impacts
// the model. We also consider the added event because it could
// be that a file was added and updated right after.
if (e.contains(model.resource, FileChangeType.UPDATED, FileChangeType.ADDED)) {
this.queueModelReload(model);
if (model.isDirty()) {
continue; // never reload dirty models
}

// For non-file schemes, we should prioritize reloading since the content
// might have changed from what was written (virtual file systems)
if (model.resource.scheme !== 'file') {
this.queueModelReload(model);
} else {
this.queueModelReload(model);
}
}
}
}