src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts

539 LOC · 201 covered · 338 uncovered · 19 ranges · 513 concepts · 1 introducers · 257 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

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 related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- promptsServiceImpl.ts ×65
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 { localize } from '../../../../nls.js';
7 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
8 > import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
9 > import { Event, Emitter } from '../../../../base/common/event.js';
10 > import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
11 > import { RawContextKey, IContextKeyService, IContextKey } from '../../../../platform/contextkey/common/contextkey.js';
12 > import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
13 > import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, IFileStatWithMetadata, IFileService, IBaseFileStat, hasReadonlyCapability, IFilesConfigurationNode } from '../../../../platform/files/common/files.js';
14 > import { equals } from '../../../../base/common/objects.js';
15 > import { URI } from '../../../../base/common/uri.js';
16 > import { isWeb } from '../../../../base/common/platform.js';
17 > import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
18 > import { ResourceGlobMatcher } from '../../../common/resources.js';
19 > import { GlobalIdleValue } from '../../../../base/common/async.js';
20 > import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
21 > import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
22 > import { LRUCache, ResourceMap } from '../../../../base/common/map.js';
23 > import { IMarkdownString } from '../../../../base/common/htmlContent.js';
24 > import { EditorInput } from '../../../common/editor/editorInput.js';
25 > import { EditorResourceAccessor, SaveReason, SideBySideEditor } from '../../../common/editor.js';
26 > import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js';
27 > import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js';
28 > import { IStringDictionary } from '../../../../base/common/collections.js';
29 >
30 > export const AutoSaveAfterShortDelayContext = new RawContextKey<boolean>('autoSaveAfterShortDelayContext', false, true);
31 >
32 > export interface IAutoSaveConfiguration {
33 > autoSave?: 'afterDelay' | 'onFocusChange' | 'onWindowChange';
34 > autoSaveDelay?: number;
35 > autoSaveWorkspaceFilesOnly?: boolean;
36 > autoSaveWhenNoErrors?: boolean;
37 > }
38 >
39 > interface ICachedAutoSaveConfiguration extends IAutoSaveConfiguration {
40 >
41 > // Some extra state that we cache to reduce the amount
42 > // of lookup we have to do since auto save methods
43 > // are being called very often, e.g. when content changes
44 >
45 > isOutOfWorkspace?: boolean;
46 > isShortAutoSaveDelay?: boolean;
47 > }
48 >
49 > export const enum AutoSaveMode {
50 > OFF,
51 > AFTER_SHORT_DELAY,
52 > AFTER_LONG_DELAY,
53 > ON_FOCUS_CHANGE,
54 > ON_WINDOW_CHANGE
55 > }
56 >
57 > export const enum AutoSaveDisabledReason {
58 > SETTINGS = 1,
59 > OUT_OF_WORKSPACE,
60 > ERRORS,
61 > DISABLED
62 > }
63 >
64 > export type IAutoSaveMode = IEnabledAutoSaveMode | IDisabledAutoSaveMode;
65 >
66 > export interface IEnabledAutoSaveMode {
67 > readonly mode: AutoSaveMode.AFTER_SHORT_DELAY | AutoSaveMode.AFTER_LONG_DELAY | AutoSaveMode.ON_FOCUS_CHANGE | AutoSaveMode.ON_WINDOW_CHANGE;
68 > }
69 >
70 > export interface IDisabledAutoSaveMode {
71 > readonly mode: AutoSaveMode.OFF;
72 > readonly reason: AutoSaveDisabledReason;
73 > }
74 >
75 > export const IFilesConfigurationService = createDecorator<IFilesConfigurationService>('filesConfigurationService');
76 >
77 > export interface IFilesConfigurationService {
78 >
79 > readonly _serviceBrand: undefined;
80 >
81 > //#region Auto Save
82 >
83 > readonly onDidChangeAutoSaveConfiguration: Event<void>;
84 >
85 > readonly onDidChangeAutoSaveDisabled: Event<URI>;
86 >
87 > getAutoSaveConfiguration(resourceOrEditor: EditorInput | URI | undefined): IAutoSaveConfiguration;
88 >
89 > hasShortAutoSaveDelay(resourceOrEditor: EditorInput | URI | undefined): boolean;
90 >
91 > getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined, saveReason?: SaveReason): IAutoSaveMode;
92 >
93 > toggleAutoSave(): Promise<void>;
94 >
95 > enableAutoSaveAfterShortDelay(resourceOrEditor: EditorInput | URI): IDisposable;
96 > disableAutoSave(resourceOrEditor: EditorInput | URI): IDisposable;
97 >
98 > //#endregion
99 >
100 > //#region Configured Readonly
101 >
102 > readonly onDidChangeReadonly: Event<void>;
103 >
104 > isReadonly(resource: URI, stat?: IBaseFileStat): boolean | IMarkdownString;
105 >
106 > updateReadonly(resource: URI, readonly: true | false | 'toggle' | 'reset'): Promise<void>;
107 > updateReadonly(resource: URI[], readonly: true | false | 'reset'): Promise<void>;
108 >
109 > //#endregion
110 >
111 > readonly onDidChangeFilesAssociation: Event<void>;
112 >
113 > readonly isHotExitEnabled: boolean;
114 >
115 > readonly hotExitConfiguration: string | undefined;
116 >
117 > preventSaveConflicts(resource: URI, language?: string): boolean;
118 > }
119 >
120 > export class FilesConfigurationService extends Disposable implements IFilesConfigurationService {
121 >
122 > declare readonly _serviceBrand: undefined;
123 >
124 > private static readonly DEFAULT_AUTO_SAVE_MODE = isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF;
125 > private static readonly DEFAULT_AUTO_SAVE_DELAY = 1000;
126 >
127 > private static readonly READONLY_MESSAGES = {
128 > providerReadonly: { value: localize('providerReadonly', "Editor is read-only because the file system of the file is read-only."), isTrusted: true },
129 > sessionReadonly: { value: localize({ key: 'sessionReadonly', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change', '{Locked="](command:{0})"}'] }, "Editor is read-only because the file was set read-only in this session. [Click here](command:{0}) to set writeable.", 'workbench.action.files.setActiveEditorWriteableInSession'), isTrusted: true },
130 > configuredReadonly: { value: localize({ key: 'configuredReadonly', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change', '{Locked="](command:{0})"}'] }, "Editor is read-only because the file was set read-only via settings. [Click here](command:{0}) to configure or [toggle for this session](command:{1}).", `workbench.action.openSettings?${encodeURIComponent('["files.readonly"]')}`, 'workbench.action.files.toggleActiveEditorReadonlyInSession'), isTrusted: true },
131 > fileLocked: { value: localize({ key: 'fileLocked', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change', '{Locked="](command:{0})"}'] }, "Editor is read-only because of file permissions. [Click here](command:{0}) to set writeable anyway.", 'workbench.action.files.setActiveEditorWriteableInSession'), isTrusted: true },
132 > fileReadonly: { value: localize('fileReadonly', "Editor is read-only because the file is read-only."), isTrusted: true }
133 > };
134 >
135 > private readonly _onDidChangeAutoSaveConfiguration = this._register(new Emitter<void>());
136 > readonly onDidChangeAutoSaveConfiguration = this._onDidChangeAutoSaveConfiguration.event;
137 >
138 > private readonly _onDidChangeAutoSaveDisabled = this._register(new Emitter<URI>());
139 > readonly onDidChangeAutoSaveDisabled = this._onDidChangeAutoSaveDisabled.event;
140 >
141 > private readonly _onDidChangeFilesAssociation = this._register(new Emitter<void>());
142 > readonly onDidChangeFilesAssociation = this._onDidChangeFilesAssociation.event;
143 >
144 > private readonly _onDidChangeReadonly = this._register(new Emitter<void>());
145 > readonly onDidChangeReadonly = this._onDidChangeReadonly.event;
146 >
147 > private currentGlobalAutoSaveConfiguration: IAutoSaveConfiguration;
148 > private currentFilesAssociationConfiguration: IStringDictionary<string> | undefined;
149 > private currentHotExitConfiguration: string;
150 >
151 > private readonly autoSaveConfigurationCache = new LRUCache<URI, ICachedAutoSaveConfiguration>(1000);
152 >
153 > private readonly autoSaveAfterShortDelayOverrides = new ResourceMap<number /* counter */>();
154 > private readonly autoSaveDisabledOverrides = new ResourceMap<number /* counter */>();
155 >
156 > private readonly autoSaveAfterShortDelayContext: IContextKey<boolean>;
157 >
158 > private readonly readonlyIncludeMatcher = this._register(new GlobalIdleValue(() => this.createReadonlyMatcher(FILES_READONLY_INCLUDE_CONFIG)));
159 > private readonly readonlyExcludeMatcher = this._register(new GlobalIdleValue(() => this.createReadonlyMatcher(FILES_READONLY_EXCLUDE_CONFIG)));
160 > private configuredReadonlyFromPermissions: boolean | undefined;
161 >
162 > private readonly sessionReadonlyOverrides = new ResourceMap<boolean>(resource => this.uriIdentityService.extUri.getComparisonKey(resource));
163 >
164 > constructor(
165 @IContextKeyService contextKeyService: IContextKeyService,
166 @IConfigurationService private readonly configurationService: IConfigurationService,
167 @IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
168 @IEnvironmentService private readonly environmentService: IEnvironmentService,
169 @IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
170 @IFileService private readonly fileService: IFileService,
171 @IMarkerService private readonly markerService: IMarkerService,
172 @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService
173 ) {
174 super();
175
176 this.autoSaveAfterShortDelayContext = AutoSaveAfterShortDelayContext.bindTo(contextKeyService);
177
178 const configuration = configurationService.getValue<IFilesConfiguration>();
179
180 this.currentGlobalAutoSaveConfiguration = this.computeAutoSaveConfiguration(undefined, configuration.files);
181 this.currentFilesAssociationConfiguration = configuration?.files?.associations;
182 this.currentHotExitConfiguration = configuration?.files?.hotExit || HotExitConfiguration.ON_EXIT;
183
184 this.onFilesConfigurationChange(configuration, false);
185
186 this.registerListeners();
187 }
189 > private createReadonlyMatcher(config: string) {
190 const matcher = this._register(new ResourceGlobMatcher(
191 resource => this.configurationService.getValue(config, { resource }),
192 event => event.affectsConfiguration(config),
193 this.contextService,
194 this.configurationService
195 ));
196
197 this._register(matcher.onExpressionChange(() => this._onDidChangeReadonly.fire()));
198
199 return matcher;
200 }
202 > isReadonly(resource: URI, stat?: IBaseFileStat): boolean | IMarkdownString {
203
204 // if the entire file system provider is readonly, we respect that
205 // and do not allow to change readonly. we take this as a hint that
206 // the provider has no capabilities of writing.
207 const provider = this.fileService.getProvider(resource.scheme);
208 if (provider && hasReadonlyCapability(provider)) {
209 return provider.readOnlyMessage ?? FilesConfigurationService.READONLY_MESSAGES.providerReadonly;
210 }
211
212 // session override always wins over the others
213 const sessionReadonlyOverride = this.sessionReadonlyOverrides.get(resource);
214 if (typeof sessionReadonlyOverride === 'boolean') {
215 return sessionReadonlyOverride === true ? FilesConfigurationService.READONLY_MESSAGES.sessionReadonly : false;
216 }
217
218 if (
219 this.uriIdentityService.extUri.isEqualOrParent(resource, this.environmentService.userRoamingDataHome) ||
220 this.uriIdentityService.extUri.isEqual(resource, this.contextService.getWorkspace().configuration ?? undefined)
221 ) {
222 return false; // explicitly exclude some paths from readonly that we need for configuration
223 }
224
225 // configured glob patterns win over stat information
226 if (this.readonlyIncludeMatcher.value.matches(resource)) {
227 return !this.readonlyExcludeMatcher.value.matches(resource) ? FilesConfigurationService.READONLY_MESSAGES.configuredReadonly : false;
228 }
229
230 // check if file is locked and configured to treat as readonly
231 if (this.configuredReadonlyFromPermissions && stat?.locked) {
232 return FilesConfigurationService.READONLY_MESSAGES.fileLocked;
233 }
234
235 // check if file is marked readonly from the file system provider
236 if (stat?.readonly) {
237 return FilesConfigurationService.READONLY_MESSAGES.fileReadonly;
238 }
239
240 return false;
241 }
243 > async updateReadonly(resource: URI | URI[], readonly: true | false | 'toggle' | 'reset'): Promise<void> {
244 if (Array.isArray(resource)) {
245 for (const r of resource) {
246 this.applyReadonly(r, readonly as true | false | 'reset');
247 }
248 if (resource.length > 0) {
249 this._onDidChangeReadonly.fire();
250 }
251 return;
252 }
253
254 if (readonly === 'toggle') {
255 let stat: IFileStatWithMetadata | undefined = undefined;
256 try {
257 stat = await this.fileService.resolve(resource, { resolveMetadata: true });
258 } catch (error) {
259 // ignore
260 }
261
262 readonly = !this.isReadonly(resource, stat);
263 }
264
265 this.applyReadonly(resource, readonly);
266 this._onDidChangeReadonly.fire();
267 }
269 > private applyReadonly(resource: URI, readonly: true | false | 'reset'): void {
270 if (readonly === 'reset') {
271 this.sessionReadonlyOverrides.delete(resource);
272 } else {
273 this.sessionReadonlyOverrides.set(resource, readonly);
274 }
275 }
277 > private registerListeners(): void {
278
279 // Files configuration changes
280 this._register(this.configurationService.onDidChangeConfiguration(e => {
281 if (e.affectsConfiguration('files')) {
282 this.onFilesConfigurationChange(this.configurationService.getValue<IFilesConfiguration>(), true);
283 }
284 }));
285 }
287 > protected onFilesConfigurationChange(configuration: IFilesConfiguration, fromEvent: boolean): void {
288
289 // Auto Save
290 this.currentGlobalAutoSaveConfiguration = this.computeAutoSaveConfiguration(undefined, configuration.files);
291 this.autoSaveConfigurationCache.clear();
292 this.autoSaveAfterShortDelayContext.set(this.getAutoSaveMode(undefined).mode === AutoSaveMode.AFTER_SHORT_DELAY);
293 if (fromEvent) {
294 this._onDidChangeAutoSaveConfiguration.fire();
295 }
296
297 // Check for change in files associations
298 const filesAssociation = configuration?.files?.associations;
299 if (!equals(this.currentFilesAssociationConfiguration, filesAssociation)) {
300 this.currentFilesAssociationConfiguration = filesAssociation;
301 if (fromEvent) {
302 this._onDidChangeFilesAssociation.fire();
303 }
304 }
305
306 // Hot exit
307 const hotExitMode = configuration?.files?.hotExit;
308 if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
309 this.currentHotExitConfiguration = hotExitMode;
310 } else {
311 this.currentHotExitConfiguration = HotExitConfiguration.ON_EXIT;
312 }
313
314 // Readonly
315 const readonlyFromPermissions = Boolean(configuration?.files?.readonlyFromPermissions);
316 if (readonlyFromPermissions !== Boolean(this.configuredReadonlyFromPermissions)) {
317 this.configuredReadonlyFromPermissions = readonlyFromPermissions;
318 if (fromEvent) {
319 this._onDidChangeReadonly.fire();
320 }
321 }
322 }
324 > getAutoSaveConfiguration(resourceOrEditor: EditorInput | URI | undefined): ICachedAutoSaveConfiguration {
325 const resource = this.toResource(resourceOrEditor);
326 if (resource) {
327 let resourceAutoSaveConfiguration = this.autoSaveConfigurationCache.get(resource);
328 if (!resourceAutoSaveConfiguration) {
329 resourceAutoSaveConfiguration = this.computeAutoSaveConfiguration(resource, this.textResourceConfigurationService.getValue<IFilesConfigurationNode>(resource, 'files'));
330 this.autoSaveConfigurationCache.set(resource, resourceAutoSaveConfiguration);
331 }
332
333 return resourceAutoSaveConfiguration;
334 }
335
336 return this.currentGlobalAutoSaveConfiguration;
337 }
339 > private computeAutoSaveConfiguration(resource: URI | undefined, filesConfiguration: IFilesConfigurationNode | undefined): ICachedAutoSaveConfiguration {
340 let autoSave: 'afterDelay' | 'onFocusChange' | 'onWindowChange' | undefined;
341 let autoSaveDelay: number | undefined;
342 let autoSaveWorkspaceFilesOnly: boolean | undefined;
343 let autoSaveWhenNoErrors: boolean | undefined;
344
345 let isOutOfWorkspace: boolean | undefined;
346 let isShortAutoSaveDelay: boolean | undefined;
347
348 switch (filesConfiguration?.autoSave ?? FilesConfigurationService.DEFAULT_AUTO_SAVE_MODE) {
349 case AutoSaveConfiguration.AFTER_DELAY: {
350 autoSave = 'afterDelay';
351 autoSaveDelay = typeof filesConfiguration?.autoSaveDelay === 'number' && filesConfiguration.autoSaveDelay >= 0 ? filesConfiguration.autoSaveDelay : FilesConfigurationService.DEFAULT_AUTO_SAVE_DELAY;
352 isShortAutoSaveDelay = autoSaveDelay <= FilesConfigurationService.DEFAULT_AUTO_SAVE_DELAY;
353 break;
354 }
355
356 case AutoSaveConfiguration.ON_FOCUS_CHANGE:
357 autoSave = 'onFocusChange';
358 break;
359
360 case AutoSaveConfiguration.ON_WINDOW_CHANGE:
361 autoSave = 'onWindowChange';
362 break;
363 }
364
365 if (filesConfiguration?.autoSaveWorkspaceFilesOnly === true) {
366 autoSaveWorkspaceFilesOnly = true;
367
368 if (resource && !this.contextService.isInsideWorkspace(resource)) {
369 isOutOfWorkspace = true;
370 isShortAutoSaveDelay = undefined; // out of workspace file are not auto saved with this configuration
371 }
372 }
373
374 if (filesConfiguration?.autoSaveWhenNoErrors === true) {
375 autoSaveWhenNoErrors = true;
376 isShortAutoSaveDelay = undefined; // this configuration disables short auto save delay
377 }
378
379 return {
380 autoSave,
381 autoSaveDelay,
382 autoSaveWorkspaceFilesOnly,
383 autoSaveWhenNoErrors,
384 isOutOfWorkspace,
385 isShortAutoSaveDelay
386 };
387 }
389 > private toResource(resourceOrEditor: EditorInput | URI | undefined): URI | undefined {
390 if (resourceOrEditor instanceof EditorInput) {
391 return EditorResourceAccessor.getOriginalUri(resourceOrEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
392 }
393
394 return resourceOrEditor;
395 }
397 > hasShortAutoSaveDelay(resourceOrEditor: EditorInput | URI | undefined): boolean {
398 const resource = this.toResource(resourceOrEditor);
399
400 if (resource && this.autoSaveAfterShortDelayOverrides.has(resource)) {
401 return true; // overridden to be enabled after short delay
402 }
403
404 if (this.getAutoSaveConfiguration(resource).isShortAutoSaveDelay) {
405 return !resource || !this.autoSaveDisabledOverrides.has(resource);
406 }
407
408 return false;
409 }
411 > getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined, saveReason?: SaveReason): IAutoSaveMode {
412 const resource = this.toResource(resourceOrEditor);
413 if (resource && this.autoSaveAfterShortDelayOverrides.has(resource)) {
414 return { mode: AutoSaveMode.AFTER_SHORT_DELAY }; // overridden to be enabled after short delay
415 }
416
417 if (resource && this.autoSaveDisabledOverrides.has(resource)) {
418 return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.DISABLED };
419 }
420
421 const autoSaveConfiguration = this.getAutoSaveConfiguration(resource);
422 if (typeof autoSaveConfiguration.autoSave === 'undefined') {
423 return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS };
424 }
425
426 if (typeof saveReason === 'number') {
427 if (
428 (autoSaveConfiguration.autoSave === 'afterDelay' && saveReason !== SaveReason.AUTO) ||
429 (autoSaveConfiguration.autoSave === 'onFocusChange' && saveReason !== SaveReason.FOCUS_CHANGE && saveReason !== SaveReason.WINDOW_CHANGE) ||
430 (autoSaveConfiguration.autoSave === 'onWindowChange' && saveReason !== SaveReason.WINDOW_CHANGE)
431 ) {
432 return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS };
433 }
434 }
435
436 if (resource) {
437 if (autoSaveConfiguration.autoSaveWorkspaceFilesOnly && autoSaveConfiguration.isOutOfWorkspace) {
438 return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.OUT_OF_WORKSPACE };
439 }
440
441 if (autoSaveConfiguration.autoSaveWhenNoErrors && this.markerService.read({ resource, take: 1, severities: MarkerSeverity.Error }).length > 0) {
442 return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.ERRORS };
443 }
444 }
445
446 switch (autoSaveConfiguration.autoSave) {
447 case 'afterDelay':
448 if (typeof autoSaveConfiguration.autoSaveDelay === 'number' && autoSaveConfiguration.autoSaveDelay <= FilesConfigurationService.DEFAULT_AUTO_SAVE_DELAY) {
449 // Explicitly mark auto save configurations as long running
450 // if they are configured to not run when there are errors.
451 // The rationale here is that errors may come in after auto
452 // save has been scheduled and then further delay the auto
453 // save until resolved.
454 return { mode: autoSaveConfiguration.autoSaveWhenNoErrors ? AutoSaveMode.AFTER_LONG_DELAY : AutoSaveMode.AFTER_SHORT_DELAY };
455 }
456 return { mode: AutoSaveMode.AFTER_LONG_DELAY };
457 case 'onFocusChange':
458 return { mode: AutoSaveMode.ON_FOCUS_CHANGE };
459 case 'onWindowChange':
460 return { mode: AutoSaveMode.ON_WINDOW_CHANGE };
461 }
462 }
464 > async toggleAutoSave(): Promise<void> {
465 const currentSetting = this.configurationService.getValue('files.autoSave');
466
467 let newAutoSaveValue: string;
468 if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(setting => setting === currentSetting)) {
469 newAutoSaveValue = AutoSaveConfiguration.OFF;
470 } else {
471 newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY;
472 }
473
474 return this.configurationService.updateValue('files.autoSave', newAutoSaveValue);
475 }
477 > enableAutoSaveAfterShortDelay(resourceOrEditor: EditorInput | URI): IDisposable {
478 const resource = this.toResource(resourceOrEditor);
479 if (!resource) {
480 return Disposable.None;
481 }
482
483 const counter = this.autoSaveAfterShortDelayOverrides.get(resource) ?? 0;
484 this.autoSaveAfterShortDelayOverrides.set(resource, counter + 1);
485
486 return toDisposable(() => {
487 const counter = this.autoSaveAfterShortDelayOverrides.get(resource) ?? 0;
488 if (counter <= 1) {
489 this.autoSaveAfterShortDelayOverrides.delete(resource);
490 } else {
491 this.autoSaveAfterShortDelayOverrides.set(resource, counter - 1);
492 }
493 });
494 }
496 > disableAutoSave(resourceOrEditor: EditorInput | URI): IDisposable {
497 const resource = this.toResource(resourceOrEditor);
498 if (!resource) {
499 return Disposable.None;
500 }
501
502 const counter = this.autoSaveDisabledOverrides.get(resource) ?? 0;
503 this.autoSaveDisabledOverrides.set(resource, counter + 1);
504
505 if (counter === 0) {
506 this._onDidChangeAutoSaveDisabled.fire(resource);
507 }
508
509 return toDisposable(() => {
510 const counter = this.autoSaveDisabledOverrides.get(resource) ?? 0;
511 if (counter <= 1) {
512 this.autoSaveDisabledOverrides.delete(resource);
513 this._onDidChangeAutoSaveDisabled.fire(resource);
514 } else {
515 this.autoSaveDisabledOverrides.set(resource, counter - 1);
516 }
517 });
518 }
520 > get isHotExitEnabled(): boolean {
521 if (this.contextService.getWorkspace().transient) {
522 // Transient workspace: hot exit is disabled because
523 // transient workspaces are not restored upon restart
524 return false;
525 }
526
527 return this.currentHotExitConfiguration !== HotExitConfiguration.OFF;
528 }
530 > get hotExitConfiguration(): string {
531 return this.currentHotExitConfiguration;
532 }
534 > preventSaveConflicts(resource: URI, language?: string): boolean {
535 return this.configurationService.getValue('files.saveConflictResolution', { resource, overrideIdentifier: language }) !== 'overwriteFileOnDisk';
536 }
538 >
539 > registerSingleton(IFilesConfigurationService, FilesConfigurationService, InstantiationType.Eager);