Change:
<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Yes (?) I test it in the docker container <!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --- VS Code Version: OS Version: OSS (?) Steps to Reproduce: a project from a Github repository two files from the file explorer (generating two tabs) a breakpoint at the function resolveFromFile on another tab with the debugger what this function does. The file focused will be reloaded every time. In case it is a large file it won't be due to a final check in the code but this does not seem the right behavior. I think it should not attempt to reload every time and even if it is a large file maybe it should reload if it has been modified (maybe disabling controls and showing a popup while it does) It seems that the problem is in the lastResolvedFileStat: it is reset or not updated at some point. This is where the check fails: File , line 419 EDIT: The problem seems to happen only when using a git repository. Looks like TextFileEditorModelManage.doResolve receives "git:fileuri" while it has only registered "file:fileuri" and therefore every time onEditorsChanged is fired it generates a new instance of TextFileEditorModel which consequently has no lastResolvedFileStat I'd like to try to fix it myself and make a pull request if I am confirmed that this is an issue and you don't have any objection.
I don't know what this function does but if I comment it, there is no longer this issue and there is no apparent side-effect. Line 1481 of I think this architecture design that involves creating new models to replace the old ones every time there is an event seems wrong and prone to cause issues like this one.
We resolve the contents of a file from disk when you click on a tab. We do that in the background without blocking the opening. We do that to ensure the latest contents are loaded from disk. As for the dirty diff decorator, moving to respective owners.
I don't quite understand what the bug is. Is there something not quite working in the product?
Thanks for your comments. I think you really do not resolve the contents from the disk when the file is large... but anyway, I don't think anyone will notice it except if digging into the code. Your solution doesn't seem elegant and seems prone to cause future problems, but I was just looking at the code and found that casually. I am not really interested. I was interested in starting a branch for visualizing very large files in a google maps way (for example for visualizing genomic data) and found that "problem". But I lost interest as the code seems quite hard to work with (too many events for my taste and I didn't find any documentation for the events). I think this issue can be closed if you are not interested. It could be interesting working on the large files navigation though if you can help me find information for not having to debug every event.
Actually my comment in was wrong: we do NOT resolve the contents of the file on disk every time but only when we think it has changed. We compare the of the file with our last state and skip resolving when the file did not advance in . But as you indicated before, this requires the model to stay in memory so that we can compare the previous state to the current one. I think you figured out that we use concept for this. But you are also right that this optimization fails to apply for the dirty decorator. I ran an experiment with clicking between 2 tabs and this is the log output: So there is evidence that the optimization is not working for git dirty diff decorators because the text file model that is used for it is disposed once we compute the next diff. If we wanted to improve this, the models would have to be kept around in memory until the tab is closed maybe.
I am pretty sure that there is also a check somewhere hidden at the end of a chain of events in which the file is not reopened even if the etag check is passed but the file is too large. But I was checking that more than a month ago and now I can't find it.
What's the best way to verify this?
Verified offline with

Discussion:
import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as ext from 'vs/workbench/common/contributions'; <del> import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; </del> import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; <del> import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; </del> import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri';

import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IColorTheme, themeColorFromId, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { editorErrorForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; <del> import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; </del> <ins> import { ICodeEditor, IEditorMouseEvent, isCodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; </ins> import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; import { PeekViewWidget, getOuterEditor, peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView'; import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey';

import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; import { Color } from 'vs/base/common/color'; import { Iterable } from 'vs/base/common/iterator'; <ins> import { ResourceMap } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; </ins> class DiffActionRunner extends ActionRunner {

} export interface IModelRegistry { <del> getModel(editorModel: IEditorModel): DirtyDiffModel | null; </del> <ins> getModel(editorModel: IEditorModel): DirtyDiffModel | undefined; </ins> } export const isDirtyDiffVisible = new RawContextKey<boolean>('dirtyDiffVisible', false);

class DirtyDiffItem { <del> constructor(readonly model: DirtyDiffModel, readonly decorator: DirtyDiffDecorator) { } </del> <ins> constructor( readonly model: DirtyDiffModel, readonly decorator: DirtyDiffDecorator ) { } </ins> dispose(): void { this.decorator.dispose();

private enabled = false; private viewState: IViewState = { width: 3, visibility: 'always' }; <del> private items = new Map<IResolvedTextFileEditorModel, DirtyDiffItem>(); </del> <ins> private items = new ResourceMap<DirtyDiffItem>(); </ins> private readonly transientDisposables = this._register(new DisposableStore()); private stylesheet: HTMLStyleElement;

this.disable(); } <del> this.transientDisposables.add(this.editorService.onDidVisibleEditorsChange(() => this.onEditorsChanged())); </del> <ins> this.transientDisposables.add(Event.any(this.editorService.onDidCloseEditor, this.editorService.onDidVisibleEditorsChange)(() => this.onEditorsChanged())); </ins> this.onEditorsChanged(); this.enabled = true; }

this.enabled = false; } <del> // HACK: This is the best current way of figuring out whether to draw these decorations // or not. Needs context from the editor, to know whether it is a diff editor, in place editor // etc. </del> private onEditorsChanged(): void { <del> const models = this.editorService.visibleTextEditorControls // only interested in code editor widgets .filter(c => c instanceof CodeEditorWidget) </del> <ins> for (const editor of this.editorService.visibleTextEditorControls) { if (isCodeEditor(editor)) { const textModel = editor.getModel(); const controller = DirtyDiffController.get(editor); </ins> <del> // set model registry and map to models .map(editor => { const codeEditor = editor as CodeEditorWidget; const controller = DirtyDiffController.get(codeEditor); </del> if (controller) { controller.modelRegistry = this; } <del> return codeEditor.getModel(); }) // remove nulls and duplicates .filter((m, i, a) => !!m && !!m.uri && a.indexOf(m, i + 1) === -1) // only want resolved text file service models .map(m => this.textFileService.files.get(m!.uri)) .filter(m => m?.isResolved()) as IResolvedTextFileEditorModel[]; const set = new Set(models); const newModels = models.filter(o => !this.items.has(o)); const oldModels = [...this.items.keys()].filter(m => !set.has(m)); oldModels.forEach(m => this.onModelInvisible(m)); newModels.forEach(m => this.onModelVisible(m)); } private onModelVisible(textFileModel: IResolvedTextFileEditorModel): void { const model = this.instantiationService.createInstance(DirtyDiffModel, textFileModel); const decorator = new DirtyDiffDecorator(textFileModel.textEditorModel, model, this.configurationService); this.items.set(textFileModel, new DirtyDiffItem(model, decorator)); } </del> <ins> if (textModel && !this.items.has(textModel.uri)) { const textFileModel = this.textFileService.files.get(textModel.uri); </ins> <del> private onModelInvisible(textFileModel: IResolvedTextFileEditorModel): void { this.items.get(textFileModel)!.dispose(); this.items.delete(textFileModel); } </del> <ins> if (textFileModel?.isResolved()) { const dirtyDiffModel = this.instantiationService.createInstance(DirtyDiffModel, textFileModel); const decorator = new DirtyDiffDecorator(textFileModel.textEditorModel, dirtyDiffModel, this.configurationService); this.items.set(textModel.uri, new DirtyDiffItem(dirtyDiffModel, decorator)); } } } } </ins> <del> getModel(editorModel: ITextModel): DirtyDiffModel | null { for (const [model, diff] of this.items) { if (model.textEditorModel.id === editorModel.id) { return diff.model; </del> <ins> for (const [uri, item] of this.items) { if (!this.editorService.isOpened({ resource: uri, typeId: FILE_EDITOR_INPUT_ID, editorId: DEFAULT_EDITOR_ASSOCIATION.id })) { item.dispose(); this.items.delete(uri); </ins> } } <ins> } </ins> <del> return null; </del> <ins> getModel(editorModel: ITextModel): DirtyDiffModel | undefined { return this.items.get(editorModel.uri)?.model; </ins> } override dispose(): void {