workingCopyService.ts ×18

Frontier kind: Code frontier

unlabeled · c_3de32e28d703

3 tests · 23766 LOC · 123 files · introduces 0 tests · 295 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
20 ranges295 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2748 ranges23766 lines · 123 files · Browse complete extent
All tests (intent)
3 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 295 introduced LOC across 20 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/services/workingCopy/common/workingCopyService.ts 288 introduced LOC · 18 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- workingCopyService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
7 > import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
8 > import { Event, Emitter } from '../../../../base/common/event.js';
9 > import { URI } from '../../../../base/common/uri.js';
10 > import { Disposable, IDisposable, toDisposable, DisposableStore, DisposableMap } from '../../../../base/common/lifecycle.js';
11 > import { ResourceMap } from '../../../../base/common/map.js';
12 > import { IWorkingCopy, IWorkingCopyIdentifier, IWorkingCopySaveEvent as IBaseWorkingCopySaveEvent } from './workingCopy.js';
13 > import { onUnexpectedError } from '../../../../base/common/errors.js';
14 >
15 > export const IWorkingCopyService = createDecorator<IWorkingCopyService>('workingCopyService');
16 >
17 > export interface IWorkingCopySaveEvent extends IBaseWorkingCopySaveEvent {
18 >
19 > /**
20 > * The working copy that was saved.
21 > */
22 > readonly workingCopy: IWorkingCopy;
23 > }
24 >
25 > export interface IWorkingCopyService {
26 >
27 > readonly _serviceBrand: undefined;
28 >
29 >
30 > //#region Events
31 >
32 > /**
33 > * An event for when a working copy was registered.
34 > */
35 > readonly onDidRegister: Event<IWorkingCopy>;
36 >
37 > /**
38 > * An event for when a working copy was unregistered.
39 > */
40 > readonly onDidUnregister: Event<IWorkingCopy>;
41 >
42 > /**
43 > * An event for when a working copy dirty state changed.
44 > */
45 > readonly onDidChangeDirty: Event<IWorkingCopy>;
46 >
47 > /**
48 > * An event for when a working copy's content changed.
49 > */
50 > readonly onDidChangeContent: Event<IWorkingCopy>;
51 >
52 > /**
53 > * An event for when a working copy was saved.
54 > */
55 > readonly onDidSave: Event<IWorkingCopySaveEvent>;
56 >
57 > //#endregion
58 >
59 >
60 > //#region Dirty Tracking
61 >
62 > /**
63 > * The number of dirty working copies that are registered.
64 > */
65 > readonly dirtyCount: number;
66 >
67 > /**
68 > * All dirty working copies that are registered.
69 > */
70 > readonly dirtyWorkingCopies: readonly IWorkingCopy[];
71 >
72 > /**
73 > * The number of modified working copies that are registered,
74 > * including scratchpads, which are never dirty.
75 > */
76 > readonly modifiedCount: number;
77 >
78 > /**
79 > * All working copies with unsaved changes,
80 > * including scratchpads, which are never dirty.
81 > */
82 > readonly modifiedWorkingCopies: readonly IWorkingCopy[];
83 >
84 > /**
85 > * Whether there is any registered working copy that is dirty.
86 > */
87 > readonly hasDirty: boolean;
88 >
89 > /**
90 > * Figure out if working copies with the given
91 > * resource are dirty or not.
92 > *
93 > * @param resource the URI of the working copy
94 > * @param typeId optional type identifier to only
95 > * consider working copies of that type.
96 > */
97 > isDirty(resource: URI, typeId?: string): boolean;
98 >
99 > //#endregion
100 >
101 >
102 > //#region Registry
103 >
104 > /**
105 > * All working copies that are registered.
106 > */
107 > readonly workingCopies: readonly IWorkingCopy[];
108 >
109 > /**
110 > * Register a new working copy with the service. This method will
111 > * throw if you try to register a working copy on a resource that
112 > * has already been registered.
113 > *
114 > * Overall there can only ever be 1 working copy with the same
115 > * resource.
116 > */
117 > registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable;
118 >
119 > /**
120 > * Whether a working copy with the given resource or identifier
121 > * exists.
122 > */
123 > has(identifier: IWorkingCopyIdentifier): boolean;
124 > has(resource: URI): boolean;
125 >
126 > /**
127 > * Returns a working copy with the given identifier or `undefined`
128 > * if no such working copy exists.
129 > */
130 > get(identifier: IWorkingCopyIdentifier): IWorkingCopy | undefined;
131 >
132 > /**
133 > * Returns all working copies with the given resource or `undefined`
134 > * if no such working copy exists.
135 > */
136 > getAll(resource: URI): readonly IWorkingCopy[] | undefined;
137 >
138 > //#endregion
139 > }
140 >
141 > class WorkingCopyLeakError extends Error {
142 >
143 > constructor(message: string, stack: string) {
144 super(message);
145
147 this.stack = stack;
148 }
150 >
151 > export class WorkingCopyService extends Disposable implements IWorkingCopyService {
152 >
153 > declare readonly _serviceBrand: undefined;
154 >
155 > //#region Events
156 >
157 > private readonly _onDidRegister = this._register(new Emitter<IWorkingCopy>());
158 > readonly onDidRegister = this._onDidRegister.event;
159 >
160 > private readonly _onDidUnregister = this._register(new Emitter<IWorkingCopy>());
161 > readonly onDidUnregister = this._onDidUnregister.event;
162 >
163 > private readonly _onDidChangeDirty = this._register(new Emitter<IWorkingCopy>());
164 > readonly onDidChangeDirty = this._onDidChangeDirty.event;
165 >
166 > private readonly _onDidChangeContent = this._register(new Emitter<IWorkingCopy>());
167 > readonly onDidChangeContent = this._onDidChangeContent.event;
168 >
169 > private readonly _onDidSave = this._register(new Emitter<IWorkingCopySaveEvent>());
170 > readonly onDidSave = this._onDidSave.event;
171 >
172 > //#endregion
173 >
174 >
175 > //#region Registry
176 >
177 > get workingCopies(): IWorkingCopy[] { return Array.from(this._workingCopies.values()); }
178 > private _workingCopies = new Set<IWorkingCopy>();
179 >
180 > private readonly mapResourceToWorkingCopies = new ResourceMap<Map<string, IWorkingCopy>>();
181 > private readonly mapWorkingCopyToListeners = this._register(new DisposableMap<IWorkingCopy>());
182 >
183 > registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable {
184 > let workingCopiesForResource = this.mapResourceToWorkingCopies.get(workingCopy.resource);
185 > if (workingCopiesForResource?.has(workingCopy.typeId)) {
186 throw new Error(`Cannot register more than one working copy with the same resource ${workingCopy.resource.toString()} and type ${workingCopy.typeId}.`);
187 }
189 > // Registry (all)
190 > this._workingCopies.add(workingCopy);
191 >
192 > // Registry (type based)
193 > if (!workingCopiesForResource) {
194 > workingCopiesForResource = new Map();
195 > this.mapResourceToWorkingCopies.set(workingCopy.resource, workingCopiesForResource);
196 > }
197 > workingCopiesForResource.set(workingCopy.typeId, workingCopy);
198 >
199 > // Wire in Events
200 > const disposables = new DisposableStore();
201 > disposables.add(workingCopy.onDidChangeContent(() => this._onDidChangeContent.fire(workingCopy)));
202 > disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy)));
203 > disposables.add(workingCopy.onDidSave(e => this._onDidSave.fire({ workingCopy, ...e })));
204 > this.mapWorkingCopyToListeners.set(workingCopy, disposables);
205 >
206 > // Send some initial events
207 > this._onDidRegister.fire(workingCopy);
208 > if (workingCopy.isDirty()) {
209 this._onDidChangeDirty.fire(workingCopy);
210 }
212 > // Track Leaks
213 > const leakId = this.trackLeaks(workingCopy);
214 >
215 > return toDisposable(() => {
216 >
217 > // Untrack Leaks
218 > if (leakId) {
219 this.untrackLeaks(leakId);
220 }
222 > // Unregister working copy
223 > this.unregisterWorkingCopy(workingCopy);
224 >
225 > // Signal as event
226 > this._onDidUnregister.fire(workingCopy);
227 > });
228 > }
229 >
230 > protected unregisterWorkingCopy(workingCopy: IWorkingCopy): void {
231 >
232 > // Registry (all)
233 > this._workingCopies.delete(workingCopy);
234 >
235 > // Registry (type based)
236 > const workingCopiesForResource = this.mapResourceToWorkingCopies.get(workingCopy.resource);
237 > if (workingCopiesForResource?.delete(workingCopy.typeId) && workingCopiesForResource.size === 0) {
238 > this.mapResourceToWorkingCopies.delete(workingCopy.resource);
239 > }
240 >
241 > // If copy is dirty, ensure to fire an event to signal the dirty change
242 > // (a disposed working copy cannot account for being dirty in our model)
243 > if (workingCopy.isDirty()) {
244 this._onDidChangeDirty.fire(workingCopy);
245 }
247 > // Remove all listeners associated to working copy
248 > this.mapWorkingCopyToListeners.deleteAndDispose(workingCopy);
249 > }
250 >
251 > has(identifier: IWorkingCopyIdentifier): boolean;
252 > has(resource: URI): boolean;
253 > has(resourceOrIdentifier: URI | IWorkingCopyIdentifier): boolean {
254 if (URI.isUri(resourceOrIdentifier)) {
255 return this.mapResourceToWorkingCopies.has(resourceOrIdentifier);
258 return this.mapResourceToWorkingCopies.get(resourceOrIdentifier.resource)?.has(resourceOrIdentifier.typeId) ?? false;
259 }
261 > get(identifier: IWorkingCopyIdentifier): IWorkingCopy | undefined {
262 return this.mapResourceToWorkingCopies.get(identifier.resource)?.get(identifier.typeId);
263 }
265 > getAll(resource: URI): readonly IWorkingCopy[] | undefined {
266 const workingCopies = this.mapResourceToWorkingCopies.get(resource);
267 if (!workingCopies) {
271 return Array.from(workingCopies.values());
272 }
274 > //#endregion
275 >
276 > //#region Leak Monitoring
277 >
278 > private static readonly LEAK_TRACKING_THRESHOLD = 256;
279 > private static readonly LEAK_REPORTING_THRESHOLD = 2 * WorkingCopyService.LEAK_TRACKING_THRESHOLD;
280 > private static LEAK_REPORTED = false;
281 >
282 > private readonly mapLeakToCounter = new Map<string, number>();
283 >
284 > private trackLeaks(workingCopy: IWorkingCopy): string | undefined {
285 > if (WorkingCopyService.LEAK_REPORTED || this._workingCopies.size < WorkingCopyService.LEAK_TRACKING_THRESHOLD) {
286 > return undefined;
287 > }
288
289 > const leakId = `${workingCopy.resource.scheme}#${workingCopy.typeId || '<no typeId>'}\n${new Error().stack?.split('\n').slice(2).join('\n') ?? ''}`; workingCopyService.ts
290 > const leakCounter = (this.mapLeakToCounter.get(leakId) ?? 0) + 1;
291 > this.mapLeakToCounter.set(leakId, leakCounter);
292 >
293 > if (this._workingCopies.size > WorkingCopyService.LEAK_REPORTING_THRESHOLD) {
294 WorkingCopyService.LEAK_REPORTED = true;
295
303
304 return leakId;
306 >
307 > private untrackLeaks(leakId: string): void {
308 const stackCounter = (this.mapLeakToCounter.get(leakId) ?? 1) - 1;
309 this.mapLeakToCounter.set(leakId, stackCounter);
313 }
314 }
316 > //#endregion
317 >
318 > //#region Dirty Tracking
319 >
320 > get hasDirty(): boolean {
321 for (const workingCopy of this._workingCopies) {
322 if (workingCopy.isDirty()) {
327 return false;
328 }
330 > get dirtyCount(): number {
331 let totalDirtyCount = 0;
332
339 return totalDirtyCount;
340 }
342 > get dirtyWorkingCopies(): IWorkingCopy[] {
343 return this.workingCopies.filter(workingCopy => workingCopy.isDirty());
344 }
346 > get modifiedCount(): number {
347 let totalModifiedCount = 0;
348
355 return totalModifiedCount;
356 }
358 > get modifiedWorkingCopies(): IWorkingCopy[] {
359 return this.workingCopies.filter(workingCopy => workingCopy.isModified());
360 }
362 > isDirty(resource: URI, typeId?: string): boolean {
363 const workingCopies = this.mapResourceToWorkingCopies.get(resource);
364 if (workingCopies) {
381 return false;
382 }
384 > //#endregion
385 > }
386 >
387 > registerSingleton(IWorkingCopyService, WorkingCopyService, InstantiationType.Delayed);
src/vs/workbench/test/common/workbenchTestServices.ts 7 introduced LOC · 2 ranges

Open complete file

211
212 constructor(readonly resource: URI, isDirty = false, readonly typeId = 'testWorkingCopyType') {
213 > super(); workbenchTestServices.ts
214 >
215 > this.name = basename(this.resource);
216 > this.dirty = isDirty;
217 > }
218
219 setDirty(dirty: boolean): void {
229
230 isDirty(): boolean {
231 > return this.dirty; workbenchTestServices.ts
232 > }
233
234 isModified(): boolean {