Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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
108
//#endregion
109
110
readonly onDidChangeFilesAssociation: Event<void>;
111
112
readonly isHotExitEnabled: boolean;
113
114
readonly hotExitConfiguration: string | undefined;
115
116
preventSaveConflicts(resource: URI, language?: string): boolean;
117
}
118
119
export class FilesConfigurationService extends Disposable implements IFilesConfigurationService {
120
121
declare readonly _serviceBrand: undefined;
122
123
private static readonly DEFAULT_AUTO_SAVE_MODE = isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF;
124
private static readonly DEFAULT_AUTO_SAVE_DELAY = 1000;
125
126
private static readonly READONLY_MESSAGES = {
127
providerReadonly: { value: localize('providerReadonly', "Editor is read-only because the file system of the file is read-only."), isTrusted: true },
128
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 },
129
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 },
130
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 },
131
fileReadonly: { value: localize('fileReadonly', "Editor is read-only because the file is read-only."), isTrusted: true }
132
};
133
134
private readonly _onDidChangeAutoSaveConfiguration = this._register(new Emitter<void>());
135
readonly onDidChangeAutoSaveConfiguration = this._onDidChangeAutoSaveConfiguration.event;
136
137
private readonly _onDidChangeAutoSaveDisabled = this._register(new Emitter<URI>());
138
readonly onDidChangeAutoSaveDisabled = this._onDidChangeAutoSaveDisabled.event;
139
140
private readonly _onDidChangeFilesAssociation = this._register(new Emitter<void>());
141
readonly onDidChangeFilesAssociation = this._onDidChangeFilesAssociation.event;
142
143
private readonly _onDidChangeReadonly = this._register(new Emitter<void>());
144
readonly onDidChangeReadonly = this._onDidChangeReadonly.event;
145
146
private currentGlobalAutoSaveConfiguration: IAutoSaveConfiguration;
147
private currentFilesAssociationConfiguration: IStringDictionary<string> | undefined;
148
private currentHotExitConfiguration: string;
149
150
private readonly autoSaveConfigurationCache = new LRUCache<URI, ICachedAutoSaveConfiguration>(1000);
151
152
private readonly autoSaveAfterShortDelayOverrides = new ResourceMap<number /* counter */>();
153
private readonly autoSaveDisabledOverrides = new ResourceMap<number /* counter */>();
154
155
private readonly autoSaveAfterShortDelayContext: IContextKey<boolean>;
156
157
private readonly readonlyIncludeMatcher = this._register(new GlobalIdleValue(() => this.createReadonlyMatcher(FILES_READONLY_INCLUDE_CONFIG)));
158
private readonly readonlyExcludeMatcher = this._register(new GlobalIdleValue(() => this.createReadonlyMatcher(FILES_READONLY_EXCLUDE_CONFIG)));
159
private configuredReadonlyFromPermissions: boolean | undefined;
160
161
private readonly sessionReadonlyOverrides = new ResourceMap<boolean>(resource => this.uriIdentityService.extUri.getComparisonKey(resource));
162
163
constructor(
164
@IContextKeyService contextKeyService: IContextKeyService,
165
@IConfigurationService private readonly configurationService: IConfigurationService,
166
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
167
@IEnvironmentService private readonly environmentService: IEnvironmentService,
168
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
169
@IFileService private readonly fileService: IFileService,
170
@IMarkerService private readonly markerService: IMarkerService,
171
@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService
172
) {
173
super();
174
175
this.autoSaveAfterShortDelayContext = AutoSaveAfterShortDelayContext.bindTo(contextKeyService);
176
177
const configuration = configurationService.getValue<IFilesConfiguration>();
178
179
this.currentGlobalAutoSaveConfiguration = this.computeAutoSaveConfiguration(undefined, configuration.files);
180
this.currentFilesAssociationConfiguration = configuration?.files?.associations;
181
this.currentHotExitConfiguration = configuration?.files?.hotExit || HotExitConfiguration.ON_EXIT;
182
183
this.onFilesConfigurationChange(configuration, false);
184
185
this.registerListeners();
186
}
187
188
private createReadonlyMatcher(config: string) {
189
const matcher = this._register(new ResourceGlobMatcher(
190
resource => this.configurationService.getValue(config, { resource }),
191
event => event.affectsConfiguration(config),
192
this.contextService,
193
this.configurationService
194
));
195
196
this._register(matcher.onExpressionChange(() => this._onDidChangeReadonly.fire()));
197
198
return matcher;
199
}
200
201
isReadonly(resource: URI, stat?: IBaseFileStat): boolean | IMarkdownString {
202
203
// if the entire file system provider is readonly, we respect that
204
// and do not allow to change readonly. we take this as a hint that
205
// the provider has no capabilities of writing.
206
const provider = this.fileService.getProvider(resource.scheme);
207
if (provider && hasReadonlyCapability(provider)) {
208
return provider.readOnlyMessage ?? FilesConfigurationService.READONLY_MESSAGES.providerReadonly;
209
}
210
211
// session override always wins over the others
212
const sessionReadonlyOverride = this.sessionReadonlyOverrides.get(resource);
213
if (typeof sessionReadonlyOverride === 'boolean') {
214
return sessionReadonlyOverride === true ? FilesConfigurationService.READONLY_MESSAGES.sessionReadonly : false;
215
}
216
217
if (
218
this.uriIdentityService.extUri.isEqualOrParent(resource, this.environmentService.userRoamingDataHome) ||
219
this.uriIdentityService.extUri.isEqual(resource, this.contextService.getWorkspace().configuration ?? undefined)
220
) {
221
return false; // explicitly exclude some paths from readonly that we need for configuration
222
}
223
224
// configured glob patterns win over stat information
225
if (this.readonlyIncludeMatcher.value.matches(resource)) {
226
return !this.readonlyExcludeMatcher.value.matches(resource) ? FilesConfigurationService.READONLY_MESSAGES.configuredReadonly : false;
227
}
228
229
// check if file is locked and configured to treat as readonly
230
if (this.configuredReadonlyFromPermissions && stat?.locked) {
231
return FilesConfigurationService.READONLY_MESSAGES.fileLocked;
232
}
233
234
// check if file is marked readonly from the file system provider
235
if (stat?.readonly) {
236
return FilesConfigurationService.READONLY_MESSAGES.fileReadonly;
237
}
238
239
return false;
240
}
241
242
async updateReadonly(resource: URI, readonly: true | false | 'toggle' | 'reset'): Promise<void> {
243
if (readonly === 'toggle') {
244
let stat: IFileStatWithMetadata | undefined = undefined;
245
try {
246
stat = await this.fileService.resolve(resource, { resolveMetadata: true });
247
} catch (error) {
248
// ignore
249
}
250
251
readonly = !this.isReadonly(resource, stat);
252
}
253
254
if (readonly === 'reset') {
255
this.sessionReadonlyOverrides.delete(resource);
256
} else {
257
this.sessionReadonlyOverrides.set(resource, readonly);
258
}
259
260
this._onDidChangeReadonly.fire();
261
}
262
263
private registerListeners(): void {
264
265
// Files configuration changes
266
this._register(this.configurationService.onDidChangeConfiguration(e => {
267
if (e.affectsConfiguration('files')) {
268
this.onFilesConfigurationChange(this.configurationService.getValue<IFilesConfiguration>(), true);
269
}
270
}));
271
}
272
273
protected onFilesConfigurationChange(configuration: IFilesConfiguration, fromEvent: boolean): void {
274
275
// Auto Save
276
this.currentGlobalAutoSaveConfiguration = this.computeAutoSaveConfiguration(undefined, configuration.files);
277
this.autoSaveConfigurationCache.clear();
278
this.autoSaveAfterShortDelayContext.set(this.getAutoSaveMode(undefined).mode === AutoSaveMode.AFTER_SHORT_DELAY);
279
if (fromEvent) {
280
this._onDidChangeAutoSaveConfiguration.fire();
281
}
282
283
// Check for change in files associations
284
const filesAssociation = configuration?.files?.associations;
285
if (!equals(this.currentFilesAssociationConfiguration, filesAssociation)) {
286
this.currentFilesAssociationConfiguration = filesAssociation;
287
if (fromEvent) {
288
this._onDidChangeFilesAssociation.fire();
289
}
290
}
291
292
// Hot exit
293
const hotExitMode = configuration?.files?.hotExit;
294
if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
295
this.currentHotExitConfiguration = hotExitMode;
296
} else {
297
this.currentHotExitConfiguration = HotExitConfiguration.ON_EXIT;
298
}
299
300
// Readonly
301
const readonlyFromPermissions = Boolean(configuration?.files?.readonlyFromPermissions);
302
if (readonlyFromPermissions !== Boolean(this.configuredReadonlyFromPermissions)) {
303
this.configuredReadonlyFromPermissions = readonlyFromPermissions;
304
if (fromEvent) {
305
this._onDidChangeReadonly.fire();
306
}
307
}
308
}
309
310
getAutoSaveConfiguration(resourceOrEditor: EditorInput | URI | undefined): ICachedAutoSaveConfiguration {
311
const resource = this.toResource(resourceOrEditor);
312
if (resource) {
313
let resourceAutoSaveConfiguration = this.autoSaveConfigurationCache.get(resource);
314
if (!resourceAutoSaveConfiguration) {
315
resourceAutoSaveConfiguration = this.computeAutoSaveConfiguration(resource, this.textResourceConfigurationService.getValue<IFilesConfigurationNode>(resource, 'files'));
316
this.autoSaveConfigurationCache.set(resource, resourceAutoSaveConfiguration);
317
}
318
319
return resourceAutoSaveConfiguration;
320
}
321
322
return this.currentGlobalAutoSaveConfiguration;
323
}
324
325
private computeAutoSaveConfiguration(resource: URI | undefined, filesConfiguration: IFilesConfigurationNode | undefined): ICachedAutoSaveConfiguration {
326
let autoSave: 'afterDelay' | 'onFocusChange' | 'onWindowChange' | undefined;
327
let autoSaveDelay: number | undefined;
328
let autoSaveWorkspaceFilesOnly: boolean | undefined;
329
let autoSaveWhenNoErrors: boolean | undefined;
330
331
let isOutOfWorkspace: boolean | undefined;
332
let isShortAutoSaveDelay: boolean | undefined;
333
334
switch (filesConfiguration?.autoSave ?? FilesConfigurationService.DEFAULT_AUTO_SAVE_MODE) {
335
case AutoSaveConfiguration.AFTER_DELAY: {
336
autoSave = 'afterDelay';
337
autoSaveDelay = typeof filesConfiguration?.autoSaveDelay === 'number' && filesConfiguration.autoSaveDelay >= 0 ? filesConfiguration.autoSaveDelay : FilesConfigurationService.DEFAULT_AUTO_SAVE_DELAY;
338
isShortAutoSaveDelay = autoSaveDelay <= FilesConfigurationService.DEFAULT_AUTO_SAVE_DELAY;
339
break;
340
}
341
342
case AutoSaveConfiguration.ON_FOCUS_CHANGE:
343
autoSave = 'onFocusChange';
344
break;
345
346
case AutoSaveConfiguration.ON_WINDOW_CHANGE:
347
autoSave = 'onWindowChange';
348
break;
349
}
350
351
if (filesConfiguration?.autoSaveWorkspaceFilesOnly === true) {
352
autoSaveWorkspaceFilesOnly = true;
353
354
if (resource && !this.contextService.isInsideWorkspace(resource)) {
355
isOutOfWorkspace = true;
356
isShortAutoSaveDelay = undefined; // out of workspace file are not auto saved with this configuration
357
}
358
}
359
360
if (filesConfiguration?.autoSaveWhenNoErrors === true) {
361
autoSaveWhenNoErrors = true;
362
isShortAutoSaveDelay = undefined; // this configuration disables short auto save delay
363
}
364
365
return {
366
autoSave,
367
autoSaveDelay,
368
autoSaveWorkspaceFilesOnly,
369
autoSaveWhenNoErrors,
370
isOutOfWorkspace,
371
isShortAutoSaveDelay
372
};
373
}
374
375
private toResource(resourceOrEditor: EditorInput | URI | undefined): URI | undefined {
376
if (resourceOrEditor instanceof EditorInput) {
377
return EditorResourceAccessor.getOriginalUri(resourceOrEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
378
}
379
380
return resourceOrEditor;
381
}
382
383
hasShortAutoSaveDelay(resourceOrEditor: EditorInput | URI | undefined): boolean {
384
const resource = this.toResource(resourceOrEditor);
385
386
if (resource && this.autoSaveAfterShortDelayOverrides.has(resource)) {
387
return true; // overridden to be enabled after short delay
388
}
389
390
if (this.getAutoSaveConfiguration(resource).isShortAutoSaveDelay) {
391
return !resource || !this.autoSaveDisabledOverrides.has(resource);
392
}
393
394
return false;
395
}
396
397
getAutoSaveMode(resourceOrEditor: EditorInput | URI | undefined, saveReason?: SaveReason): IAutoSaveMode {
398
const resource = this.toResource(resourceOrEditor);
399
if (resource && this.autoSaveAfterShortDelayOverrides.has(resource)) {
400
return { mode: AutoSaveMode.AFTER_SHORT_DELAY }; // overridden to be enabled after short delay
401
}
402
403
if (resource && this.autoSaveDisabledOverrides.has(resource)) {
404
return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.DISABLED };
405
}
406
407
const autoSaveConfiguration = this.getAutoSaveConfiguration(resource);
408
if (typeof autoSaveConfiguration.autoSave === 'undefined') {
409
return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS };
410
}
411
412
if (typeof saveReason === 'number') {
413
if (
414
(autoSaveConfiguration.autoSave === 'afterDelay' && saveReason !== SaveReason.AUTO) ||
415
(autoSaveConfiguration.autoSave === 'onFocusChange' && saveReason !== SaveReason.FOCUS_CHANGE && saveReason !== SaveReason.WINDOW_CHANGE) ||
416
(autoSaveConfiguration.autoSave === 'onWindowChange' && saveReason !== SaveReason.WINDOW_CHANGE)
417
) {
418
return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.SETTINGS };
419
}
420
}
421
422
if (resource) {
423
if (autoSaveConfiguration.autoSaveWorkspaceFilesOnly && autoSaveConfiguration.isOutOfWorkspace) {
424
return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.OUT_OF_WORKSPACE };
425
}
426
427
if (autoSaveConfiguration.autoSaveWhenNoErrors && this.markerService.read({ resource, take: 1, severities: MarkerSeverity.Error }).length > 0) {
428
return { mode: AutoSaveMode.OFF, reason: AutoSaveDisabledReason.ERRORS };
429
}
430
}
431
432
switch (autoSaveConfiguration.autoSave) {
433
case 'afterDelay':
434
if (typeof autoSaveConfiguration.autoSaveDelay === 'number' && autoSaveConfiguration.autoSaveDelay <= FilesConfigurationService.DEFAULT_AUTO_SAVE_DELAY) {
435
// Explicitly mark auto save configurations as long running
436
// if they are configured to not run when there are errors.
437
// The rationale here is that errors may come in after auto
438
// save has been scheduled and then further delay the auto
439
// save until resolved.
440
return { mode: autoSaveConfiguration.autoSaveWhenNoErrors ? AutoSaveMode.AFTER_LONG_DELAY : AutoSaveMode.AFTER_SHORT_DELAY };
441
}
442
return { mode: AutoSaveMode.AFTER_LONG_DELAY };
443
case 'onFocusChange':
444
return { mode: AutoSaveMode.ON_FOCUS_CHANGE };
445
case 'onWindowChange':
446
return { mode: AutoSaveMode.ON_WINDOW_CHANGE };
447
}
448
}
449
450
async toggleAutoSave(): Promise<void> {
451
const currentSetting = this.configurationService.getValue('files.autoSave');
452
453
let newAutoSaveValue: string;
454
if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(setting => setting === currentSetting)) {
455
newAutoSaveValue = AutoSaveConfiguration.OFF;
456
} else {
457
newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY;
458
}
459
460
return this.configurationService.updateValue('files.autoSave', newAutoSaveValue);
461
}
462
463
enableAutoSaveAfterShortDelay(resourceOrEditor: EditorInput | URI): IDisposable {
464
const resource = this.toResource(resourceOrEditor);
465
if (!resource) {
466
return Disposable.None;
467
}
468
469
const counter = this.autoSaveAfterShortDelayOverrides.get(resource) ?? 0;
470
this.autoSaveAfterShortDelayOverrides.set(resource, counter + 1);
471
472
return toDisposable(() => {
473
const counter = this.autoSaveAfterShortDelayOverrides.get(resource) ?? 0;
474
if (counter <= 1) {
475
this.autoSaveAfterShortDelayOverrides.delete(resource);
476
} else {
477
this.autoSaveAfterShortDelayOverrides.set(resource, counter - 1);
478
}
479
});
480
}
481
482
disableAutoSave(resourceOrEditor: EditorInput | URI): IDisposable {
483
const resource = this.toResource(resourceOrEditor);
484
if (!resource) {
485
return Disposable.None;
486
}
487
488
const counter = this.autoSaveDisabledOverrides.get(resource) ?? 0;
489
this.autoSaveDisabledOverrides.set(resource, counter + 1);
490
491
if (counter === 0) {
492
this._onDidChangeAutoSaveDisabled.fire(resource);
493
}
494
495
return toDisposable(() => {
496
const counter = this.autoSaveDisabledOverrides.get(resource) ?? 0;
497
if (counter <= 1) {
498
this.autoSaveDisabledOverrides.delete(resource);
499
this._onDidChangeAutoSaveDisabled.fire(resource);
500
} else {
501
this.autoSaveDisabledOverrides.set(resource, counter - 1);
502
}
503
});
504
}
505
506
get isHotExitEnabled(): boolean {
507
if (this.contextService.getWorkspace().transient) {
508
// Transient workspace: hot exit is disabled because
509
// transient workspaces are not restored upon restart
510
return false;
511
}
512
513
return this.currentHotExitConfiguration !== HotExitConfiguration.OFF;
514
}
515
516
get hotExitConfiguration(): string {
517
return this.currentHotExitConfiguration;
518
}
519
520
preventSaveConflicts(resource: URI, language?: string): boolean {
521
return this.configurationService.getValue('files.saveConflictResolution', { resource, overrideIdentifier: language }) !== 'overwriteFileOnDisk';
522
}
523
}
524
525
registerSingleton(IFilesConfigurationService, FilesConfigurationService, InstantiationType.Eager);
526
527