Skip to content

Commit 1976534

Browse files
author
Benjamin Pasero
committed
readonly - make commands operate in session only
1 parent 350797f commit 1976534

9 files changed

Lines changed: 72 additions & 81 deletions

File tree

src/vs/workbench/contrib/files/browser/fileActions.contribution.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as nls from 'vs/nls';
7-
import { ToggleAutoSaveAction, FocusFilesExplorer, GlobalCompareResourcesAction, ShowActiveFileInExplorer, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow, UPLOAD_COMMAND_ID, UPLOAD_LABEL, CompareNewUntitledTextFilesAction, SetActiveEditorReadonly, SetActiveEditorWriteable, ToggleActiveEditorReadonly } from 'vs/workbench/contrib/files/browser/fileActions';
7+
import { ToggleAutoSaveAction, FocusFilesExplorer, GlobalCompareResourcesAction, ShowActiveFileInExplorer, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow, UPLOAD_COMMAND_ID, UPLOAD_LABEL, CompareNewUntitledTextFilesAction, SetActiveEditorReadonlyInSession, SetActiveEditorWriteableInSession, ToggleActiveEditorReadonlyInSession } from 'vs/workbench/contrib/files/browser/fileActions';
88
import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler';
99
import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
1010
import { ILocalizedString } from 'vs/platform/action/common/action';
@@ -37,9 +37,9 @@ registerAction2(CompareWithClipboardAction);
3737
registerAction2(CompareNewUntitledTextFilesAction);
3838
registerAction2(ToggleAutoSaveAction);
3939
registerAction2(ShowOpenedFileInNewWindow);
40-
registerAction2(SetActiveEditorReadonly);
41-
registerAction2(SetActiveEditorWriteable);
42-
registerAction2(ToggleActiveEditorReadonly);
40+
registerAction2(SetActiveEditorReadonlyInSession);
41+
registerAction2(SetActiveEditorWriteableInSession);
42+
registerAction2(ToggleActiveEditorReadonlyInSession);
4343

4444
// Commands
4545
CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand);

src/vs/workbench/contrib/files/browser/fileActions.ts

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
5858
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
5959
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
6060
import { ILocalizedString } from 'vs/platform/action/common/action';
61-
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
6261

6362
export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
6463
export const NEW_FILE_LABEL = nls.localize('newFile', "New File...");
@@ -1186,7 +1185,7 @@ export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) =
11861185
})));
11871186
};
11881187

1189-
class BaseSetActiveEditorReadonly extends Action2 {
1188+
class BaseSetActiveEditorReadonlyInSession extends Action2 {
11901189

11911190
constructor(
11921191
id: string,
@@ -1204,68 +1203,54 @@ class BaseSetActiveEditorReadonly extends Action2 {
12041203

12051204
override async run(accessor: ServicesAccessor): Promise<void> {
12061205
const editorService = accessor.get(IEditorService);
1207-
const dialogService = accessor.get(IDialogService);
12081206
const filesConfigurationService = accessor.get(IFilesConfigurationService);
1209-
const preferencesService = accessor.get(IPreferencesService);
12101207

12111208
const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
12121209
if (!fileResource) {
12131210
return;
12141211
}
12151212

1216-
const success = await filesConfigurationService.updateReadonly(fileResource, this.newReadonlyState);
1217-
if (!success) {
1218-
const { confirmed } = await dialogService.confirm({
1219-
type: Severity.Info,
1220-
message: nls.localize('readonlyMessage', "Unable to change readonly state of the active editor."),
1221-
detail: nls.localize('readonlyDetail', "Please review the configured settings to update readonly state manually."),
1222-
primaryButton: nls.localize({ key: 'openSettings', comment: ['&& denotes a mnemonic'] }, "&&Open Settings")
1223-
});
1224-
1225-
if (confirmed) {
1226-
await preferencesService.openSettings({ query: 'files.readonly' });
1227-
}
1228-
}
1213+
filesConfigurationService.updateReadonly(fileResource, this.newReadonlyState);
12291214
}
12301215
}
12311216

1232-
export class SetActiveEditorReadonly extends BaseSetActiveEditorReadonly {
1217+
export class SetActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession {
12331218

1234-
static readonly ID = 'workbench.action.files.setActiveEditorReadonly';
1235-
static readonly LABEL = nls.localize('setActiveEditorReadonly', "Set Active Editor Readonly");
1219+
static readonly ID = 'workbench.action.files.setActiveEditorReadonlyInSession';
1220+
static readonly LABEL = nls.localize('setActiveEditorReadonlyInSession', "Set Active Editor Readonly in Session");
12361221

12371222
constructor() {
12381223
super(
1239-
SetActiveEditorReadonly.ID,
1240-
{ value: SetActiveEditorReadonly.LABEL, original: 'Set Active Editor Readonly' },
1224+
SetActiveEditorReadonlyInSession.ID,
1225+
{ value: SetActiveEditorReadonlyInSession.LABEL, original: 'Set Active Editor Readonly in Session' },
12411226
true
12421227
);
12431228
}
12441229
}
12451230

1246-
export class SetActiveEditorWriteable extends BaseSetActiveEditorReadonly {
1231+
export class SetActiveEditorWriteableInSession extends BaseSetActiveEditorReadonlyInSession {
12471232

1248-
static readonly ID = 'workbench.action.files.setActiveEditorWriteable';
1249-
static readonly LABEL = nls.localize('setActiveEditorWriteable', "Set Active Editor Writeable");
1233+
static readonly ID = 'workbench.action.files.setActiveEditorWriteableInSession';
1234+
static readonly LABEL = nls.localize('setActiveEditorWriteableInSession', "Set Active Editor Writeable in Session");
12501235

12511236
constructor() {
12521237
super(
1253-
SetActiveEditorWriteable.ID,
1254-
{ value: SetActiveEditorWriteable.LABEL, original: 'Set Active Editor Writeable' },
1238+
SetActiveEditorWriteableInSession.ID,
1239+
{ value: SetActiveEditorWriteableInSession.LABEL, original: 'Set Active Editor Writeable in Session' },
12551240
false
12561241
);
12571242
}
12581243
}
12591244

1260-
export class ToggleActiveEditorReadonly extends BaseSetActiveEditorReadonly {
1245+
export class ToggleActiveEditorReadonlyInSession extends BaseSetActiveEditorReadonlyInSession {
12611246

1262-
static readonly ID = 'workbench.action.files.toggleActiveEditorReadonly';
1263-
static readonly LABEL = nls.localize('toggleActiveEditorReadonly', "Toggle Active Editor Readonly");
1247+
static readonly ID = 'workbench.action.files.toggleActiveEditorReadonlyInSession';
1248+
static readonly LABEL = nls.localize('toggleActiveEditorReadonlyInSession', "Toggle Active Editor Readonly in Session");
12641249

12651250
constructor() {
12661251
super(
1267-
ToggleActiveEditorReadonly.ID,
1268-
{ value: ToggleActiveEditorReadonly.LABEL, original: 'Toggle Active Editor Readonly' },
1252+
ToggleActiveEditorReadonlyInSession.ID,
1253+
{ value: ToggleActiveEditorReadonlyInSession.LABEL, original: 'Toggle Active Editor Readonly in Session' },
12691254
'toggle'
12701255
);
12711256
}

src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as assert from 'assert';
77
import { Event } from 'vs/base/common/event';
88
import { toResource } from 'vs/base/test/common/utils';
99
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
10-
import { TestFilesConfigurationService, workbenchInstantiationService, TestServiceAccessor, registerTestFileEditor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
10+
import { TestFilesConfigurationService, workbenchInstantiationService, TestServiceAccessor, registerTestFileEditor, createEditorPart, TestEnvironmentService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices';
1111
import { IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
1212
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
1313
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -22,6 +22,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe
2222
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
2323
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
2424
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
25+
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
2526

2627
suite('EditorAutoSave', () => {
2728

@@ -45,7 +46,9 @@ suite('EditorAutoSave', () => {
4546
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(
4647
<IContextKeyService>instantiationService.createInstance(MockContextKeyService),
4748
configurationService,
48-
new TestContextService(TestWorkspace)
49+
new TestContextService(TestWorkspace),
50+
TestEnvironmentService,
51+
new UriIdentityService(new TestFileService())
4952
));
5053

5154
const part = await createEditorPart(instantiationService, disposables);

src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event';
88
import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker';
99
import { toResource } from 'vs/base/test/common/utils';
1010
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
11-
import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, registerTestFileEditor, registerTestResourceEditor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
11+
import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, registerTestFileEditor, registerTestResourceEditor, createEditorPart, TestEnvironmentService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices';
1212
import { IResolvedTextFileEditorModel, snapshotToString, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
1313
import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
1414
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -30,6 +30,7 @@ import { TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspac
3030
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
3131
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
3232
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
33+
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
3334

3435
suite('Files - TextFileEditorTracker', () => {
3536

@@ -63,7 +64,9 @@ suite('Files - TextFileEditorTracker', () => {
6364
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(
6465
<IContextKeyService>instantiationService.createInstance(MockContextKeyService),
6566
configurationService,
66-
new TestContextService(TestWorkspace)
67+
new TestContextService(TestWorkspace),
68+
TestEnvironmentService,
69+
new UriIdentityService(new TestFileService())
6770
));
6871
}
6972

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

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import { Event, Emitter } from 'vs/base/common/event';
99
import { Disposable } from 'vs/base/common/lifecycle';
1010
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1111
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
12-
import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, IFileStatWithMetadata, IGlobPatterns } from 'vs/platform/files/common/files';
12+
import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration, FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG, IFileStatWithMetadata } from 'vs/platform/files/common/files';
1313
import { equals } from 'vs/base/common/objects';
1414
import { URI } from 'vs/base/common/uri';
1515
import { isWeb } from 'vs/base/common/platform';
1616
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
1717
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
1818
import { IdleValue } from 'vs/base/common/async';
19-
import { Schemas } from 'vs/base/common/network';
19+
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
20+
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
21+
import { ResourceSet } from 'vs/base/common/map';
2022

2123
export const AutoSaveAfterShortDelayContext = new RawContextKey<boolean>('autoSaveAfterShortDelayContext', false, true);
2224

@@ -54,11 +56,11 @@ export interface IFilesConfigurationService {
5456

5557
//#region Configured Readonly
5658

57-
readonly onReadonlyConfigurationChange: Event<void>;
59+
readonly onReadonlyChange: Event<void>;
5860

5961
isReadonly(resource: URI, stat?: IFileStatWithMetadata): boolean;
6062

61-
updateReadonly(resource: URI, readonly: true | false | 'toggle'): Promise<boolean>;
63+
updateReadonly(resource: URI, readonly: true | false | 'toggle'): void;
6264

6365
//#endregion
6466

@@ -84,7 +86,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
8486
readonly onFilesAssociationChange = this._onFilesAssociationChange.event;
8587

8688
private readonly _onReadonlyConfigurationChange = this._register(new Emitter<void>());
87-
readonly onReadonlyConfigurationChange = this._onReadonlyConfigurationChange.event;
89+
readonly onReadonlyChange = this._onReadonlyConfigurationChange.event;
8890

8991
private configuredAutoSaveDelay?: number;
9092
private configuredAutoSaveOnFocusChange: boolean | undefined;
@@ -100,10 +102,15 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
100102
private readonly readonlyExcludeMatcher = this._register(new IdleValue(() => this.createMatcher(FILES_READONLY_EXCLUDE_CONFIG)));
101103
private configuredReadonlyFromPermissions: boolean | undefined;
102104

105+
private readonly sessionReadonlyResources = new ResourceSet(resource => this.uriIdentityService.extUri.getComparisonKey(resource));
106+
private readonly sessionWriteableResources = new ResourceSet(resource => this.uriIdentityService.extUri.getComparisonKey(resource));
107+
103108
constructor(
104109
@IContextKeyService contextKeyService: IContextKeyService,
105110
@IConfigurationService private readonly configurationService: IConfigurationService,
106-
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
111+
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
112+
@IEnvironmentService private readonly environmentService: IEnvironmentService,
113+
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
107114
) {
108115
super();
109116

@@ -133,50 +140,40 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
133140
}
134141

135142
isReadonly(resource: URI, stat?: IFileStatWithMetadata): boolean {
143+
if (this.sessionReadonlyResources.has(resource)) {
144+
return true; // session override: readonly
145+
}
146+
147+
if (this.sessionWriteableResources.has(resource)) {
148+
return false; // session override: writeable
149+
}
150+
151+
if (this.uriIdentityService.extUri.isEqualOrParent(resource, this.environmentService.userRoamingDataHome)) {
152+
return false; // never turn configuration folder readonly
153+
}
154+
136155
if (this.configuredReadonlyFromPermissions && stat?.locked) {
137156
return true; // leverage file permissions if configured as such
138157
}
139158

140159
return this.readonlyIncludeMatcher.value.matches(resource) && !this.readonlyExcludeMatcher.value.matches(resource);
141160
}
142161

143-
async updateReadonly(resource: URI, readonly: true | false | 'toggle'): Promise<boolean> {
162+
updateReadonly(resource: URI, readonly: true | false | 'toggle'): void {
144163
if (readonly === 'toggle') {
145164
readonly = !this.isReadonly(resource);
146165
}
147166

148-
// Add to target setting
149-
if (readonly) {
150-
const targetSettingToAdd = FILES_READONLY_INCLUDE_CONFIG;
151-
const configurationToAdd = this.configurationService.inspect<IGlobPatterns | undefined>(targetSettingToAdd, { resource });
152-
153-
await this.configurationService.updateValue(targetSettingToAdd, {
154-
...configurationToAdd.user?.value,
155-
[this.uriToPath(resource)]: true
156-
});
157-
}
158-
159-
// Remove from other setting
160-
const targetSettingsToRemove = readonly ? [FILES_READONLY_EXCLUDE_CONFIG] : [FILES_READONLY_INCLUDE_CONFIG, FILES_READONLY_EXCLUDE_CONFIG];
161-
for (const targetSettingToRemove of targetSettingsToRemove) {
162-
const configurationToRemove = this.configurationService.inspect<IGlobPatterns | undefined>(targetSettingToRemove, { resource });
163-
if (configurationToRemove.user?.value) {
164-
const configurationClone = { ...configurationToRemove.user.value };
165-
delete configurationClone[this.uriToPath(resource)];
166-
167-
await this.configurationService.updateValue(targetSettingToRemove, configurationClone);
168-
}
169-
}
167+
this.sessionReadonlyResources.delete(resource);
168+
this.sessionWriteableResources.delete(resource);
170169

171-
return this.isReadonly(resource) === readonly;
172-
}
173-
174-
private uriToPath(uri: URI): string {
175-
if (uri.scheme === Schemas.file) {
176-
return uri.fsPath;
170+
if (readonly) {
171+
this.sessionReadonlyResources.add(resource);
172+
} else {
173+
this.sessionWriteableResources.add(resource);
177174
}
178175

179-
return uri.path;
176+
this._onReadonlyConfigurationChange.fire();
180177
}
181178

182179
private registerListeners(): void {

src/vs/workbench/services/textfile/common/textFileEditorModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
135135
private registerListeners(): void {
136136
this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));
137137
this._register(this.filesConfigurationService.onFilesAssociationChange(() => this.onFilesAssociationChange()));
138-
this._register(this.filesConfigurationService.onReadonlyConfigurationChange(() => this._onDidChangeReadonly.fire()));
138+
this._register(this.filesConfigurationService.onReadonlyChange(() => this._onDidChangeReadonly.fire()));
139139
}
140140

141141
private async onDidFilesChange(e: FileChangesEvent): Promise<void> {

src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
337337
}
338338

339339
private registerListeners(): void {
340-
this._register(this.filesConfigurationService.onReadonlyConfigurationChange(() => this._onDidChangeReadonly.fire()));
340+
this._register(this.filesConfigurationService.onReadonlyChange(() => this._onDidChangeReadonly.fire()));
341341
}
342342

343343
//#region Dirty

0 commit comments

Comments
 (0)