Change:
I wonder why all of line 14 becomes gray (i.e. unused) after I remove line 13. I guess this is because the decorations of line 13 get applied to line 14 which becomes line 13. I think we can improve this by listening to text edits and updating the cache accordingly - when a line is deleted, its decorations should be deleted too and not just applied to whatever lines becomes the deleted line. ! Another example: !
This is for TypeScript: ! And this for JSON: ! This indicates that this is indeed a problem of TypeScript, and not diagnostics in general (
how about a small TS repro sample?
!
Looks pretty normal here. Also just checked with that TypeScript uses plain diagnostics. Can be because of another setting/feature that interfers here? https://user-
Interesting. It also happens when I reset all my settings, but it only happens in the VS Code repository (but there every single time)...
I can't reproduce any problems when there is a single diagnostic present. Having a single diagnostic updates great: Using the following sample in an untitled file in the repo: ts /
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See in the project root for license information. *
-------------/ const x = 3; ('' x + '' / 3); https://user- However, when removing the trailing such that also creates a diagnostic, I can see that the squiggle gets out of sync. I suspect that is constantly updating its diagnostic, while is not. Most likely, the fact that updates its diagnostic results in firing and the re-reading the markers from it and re-applying them. The marker goes to the right spot since it was newly refreshed by , but the marker goes to its original spot since didn't update it: https://user- To tackle this and only update the decorations for a certain diagnostics provider, the events would need to be more fine-grained and then could selectively update decorations by owner.
That's a nice find!
A cheap solution here would be for markers to record a model version id at the time they are set in the marker service. The could then read the markers and recreate squiggles only for the markers which are "fresh".
Yeah, that will work but there is no guarantee for this - since markers can be pushed for any resource. Tho, in this case it will help
adds (which can be undefined tho)
Added the version to markers, un-assigning myself to leave the remaining work to

Discussion:
return this.map.has(value); } } <ins> /** * A map that allows access both by keys and values. * **NOTE**: values need to be unique. */ export class BidirectionalMap<K, V> { private readonly _m1 = new Map<K, V>(); private readonly _m2 = new Map<V, K>(); constructor(entries?: readonly (readonly [K, V])[]) { if (entries) { for (const [key, value] of entries) { this.set(key, value); } } } clear(): void { this._m1.clear(); this._m2.clear(); } set(key: K, value: V): void { this._m1.set(key, value); this._m2.set(value, key); } get(key: K): V | undefined { return this._m1.get(key); } getKey(value: V): K | undefined { return this._m2.get(value); } delete(key: K): boolean { const value = this._m1.get(key); if (value === undefined) { return false; } this._m1.delete(key); this._m2.delete(value); return true; } forEach(callbackfn: (value: V, key: K, map: BidirectionalMap<K, V>) => void, thisArg?: any): void { this._m1.forEach((value, key) => { callbackfn.call(thisArg, value, key, this); }); } keys(): IterableIterator<K> { return this._m1.keys(); } values(): IterableIterator<V> { return this._m1.values(); } } </ins>

*--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; <del> import { LinkedMap, LRUCache, ResourceMap, Touch } from 'vs/base/common/map'; </del> <ins> import { BidirectionalMap, LinkedMap, LRUCache, ResourceMap, Touch } from 'vs/base/common/map'; </ins> import { extUriIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri';

}); }); <ins> suite('BidirectionalMap', () => { test('should set and get values correctly', () => { const map = new BidirectionalMap<string, number>(); map.set('one', 1); map.set('two', 2); map.set('three', 3); assert.strictEqual(map.get('one'), 1); assert.strictEqual(map.get('two'), 2); assert.strictEqual(map.get('three'), 3); }); test('should get keys by value correctly', () => { const map = new BidirectionalMap<string, number>(); map.set('one', 1); map.set('two', 2); map.set('three', 3); assert.strictEqual(map.getKey(1), 'one'); assert.strictEqual(map.getKey(2), 'two'); assert.strictEqual(map.getKey(3), 'three'); }); test('should delete values correctly', () => { const map = new BidirectionalMap<string, number>(); map.set('one', 1); map.set('two', 2); map.set('three', 3); assert.strictEqual(map.delete('one'), true); assert.strictEqual(map.get('one'), undefined); assert.strictEqual(map.getKey(1), undefined); assert.strictEqual(map.delete('two'), true); assert.strictEqual(map.get('two'), undefined); assert.strictEqual(map.getKey(2), undefined); assert.strictEqual(map.delete('three'), true); assert.strictEqual(map.get('three'), undefined); assert.strictEqual(map.getKey(3), undefined); }); test('should handle non-existent keys correctly', () => { const map = new BidirectionalMap<string, number>(); map.set('one', 1); map.set('two', 2); map.set('three', 3); assert.strictEqual(map.get('four'), undefined); assert.strictEqual(map.getKey(4), undefined); assert.strictEqual(map.delete('four'), false); }); test('should handle forEach correctly', () => { const map = new BidirectionalMap<string, number>(); map.set('one', 1); map.set('two', 2); map.set('three', 3); const keys: string[] = []; const values: number[] = []; map.forEach((value, key) => { keys.push(key); values.push(value); }); assert.deepStrictEqual(keys, ['one', 'two', 'three']); assert.deepStrictEqual(values, [1, 2, 3]); }); test('should handle clear correctly', () => { const map = new BidirectionalMap<string, number>(); map.set('one', 1); map.set('two', 2); map.set('three', 3); map.clear(); assert.strictEqual(map.get('one'), undefined); assert.strictEqual(map.get('two'), undefined); assert.strictEqual(map.get('three'), undefined); assert.strictEqual(map.getKey(1), undefined); assert.strictEqual(map.getKey(2), undefined); assert.strictEqual(map.getKey(3), undefined); }); }); </ins>

import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; <del> import { ResourceMap } from 'vs/base/common/map'; class MarkerDecorations extends Disposable { private readonly _markersData: Map<string, IMarker> = new Map<string, IMarker>(); constructor( readonly model: ITextModel ) { super(); this._register(toDisposable(() => { this.model.deltaDecorations([...this._markersData.keys()], []); this._markersData.clear(); })); } public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): boolean { const oldIds = [...this._markersData.keys()]; this._markersData.clear(); const ids = this.model.deltaDecorations(oldIds, newDecorations); for (let index = 0; index < ids.length; index++) { this._markersData.set(ids[index], markers[index]); } return oldIds.length !== 0 || ids.length !== 0; } getMarker(decoration: IModelDecoration): IMarker | undefined { return this._markersData.get(decoration.id); } getMarkers(): [Range, IMarker][] { const res: [Range, IMarker][] = []; this._markersData.forEach((marker, id) => { const range = this.model.getDecorationRange(id); if (range) { res.push([range, marker]); } }); return res; } } </del> <ins> import { BidirectionalMap, ResourceMap } from 'vs/base/common/map'; import { diffSets } from 'vs/base/common/collections'; </ins> export class MarkerDecorationsService extends Disposable implements IMarkerDecorationsService {

private _updateDecorations(markerDecorations: MarkerDecorations): void { // Limit to the first 500 errors/warnings const markers = this._markerService.read({ resource: markerDecorations.model.uri, take: 500 }); <del> const newModelDecorations: IModelDeltaDecoration[] = markers.map((marker) => { </del> <ins> if (markerDecorations.update(markers)) { this._onDidChangeMarker.fire(markerDecorations.model); } } } class MarkerDecorations extends Disposable { private readonly _map = new BidirectionalMap<IMarker, /*decoration id*/string>(); constructor( readonly model: ITextModel ) { super(); this._register(toDisposable(() => { this.model.deltaDecorations([...this._map.values()], []); this._map.clear(); })); } public update(markers: IMarker[]): boolean { // We use the fact that marker instances are not recreated when different owners // update. So we can compare references to find out what changed since the last update. const { added, removed } = diffSets(new Set(this._map.keys()), new Set(markers)); if (added.length === 0 && removed.length === 0) { return false; } const oldIds: string[] = removed.map(marker => this._map.get(marker)!); const newDecorations: IModelDeltaDecoration[] = added.map(marker => { </ins> return { <del> range: this._createDecorationRange(markerDecorations.model, marker), </del> <ins> range: this._createDecorationRange(this.model, marker), </ins> options: this._createDecorationOption(marker) }; }); <del> if (markerDecorations.update(markers, newModelDecorations)) { this._onDidChangeMarker.fire(markerDecorations.model); </del> <ins> const ids = this.model.deltaDecorations(oldIds, newDecorations); for (const removedMarker of removed) { this._map.delete(removedMarker); </ins> } <ins> for (let index = 0; index < ids.length; index++) { this._map.set(added[index], ids[index]); } return true; } getMarker(decoration: IModelDecoration): IMarker | undefined { return this._map.getKey(decoration.id); } getMarkers(): [Range, IMarker][] { const res: [Range, IMarker][] = []; this._map.forEach((id, marker) => { const range = this.model.getDecorationRange(id); if (range) { res.push([range, marker]); } }); return res; </ins> } private _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range {