"Fossies" - the Fresh Open Source Software Archive

Member "vscode-1.49.1/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts" (16 Sep 2020, 15554 Bytes) of package /linux/misc/vscode-1.49.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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 { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
    7 import { URI } from 'vs/base/common/uri';
    8 import * as nls from 'vs/nls';
    9 import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
   10 import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
   11 import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION, isUntitledWorkspace, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces';
   12 import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
   13 import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
   14 import { Registry } from 'vs/platform/registry/common/platform';
   15 import { ICommandService } from 'vs/platform/commands/common/commands';
   16 import { distinct } from 'vs/base/common/arrays';
   17 import { isEqualAuthority, extUri } from 'vs/base/common/resources';
   18 import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
   19 import { IFileService } from 'vs/platform/files/common/files';
   20 import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
   21 import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
   22 import { mnemonicButtonLabel } from 'vs/base/common/labels';
   23 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
   24 import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
   25 import { IHostService } from 'vs/workbench/services/host/browser/host';
   26 import { Schemas } from 'vs/base/common/network';
   27 import { SaveReason } from 'vs/workbench/common/editor';
   28 import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
   29 
   30 export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditingService {
   31 
   32     declare readonly _serviceBrand: undefined;
   33 
   34     constructor(
   35         @IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
   36         @IWorkspaceContextService protected readonly contextService: WorkspaceService,
   37         @IConfigurationService private readonly configurationService: IConfigurationService,
   38         @INotificationService private readonly notificationService: INotificationService,
   39         @ICommandService private readonly commandService: ICommandService,
   40         @IFileService private readonly fileService: IFileService,
   41         @ITextFileService private readonly textFileService: ITextFileService,
   42         @IWorkspacesService protected readonly workspacesService: IWorkspacesService,
   43         @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
   44         @IFileDialogService private readonly fileDialogService: IFileDialogService,
   45         @IDialogService protected readonly dialogService: IDialogService,
   46         @IHostService protected readonly hostService: IHostService,
   47         @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService
   48     ) { }
   49 
   50     async pickNewWorkspacePath(): Promise<URI | undefined> {
   51         let workspacePath = await this.fileDialogService.showSaveDialog({
   52             saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")),
   53             title: nls.localize('saveWorkspace', "Save Workspace"),
   54             filters: WORKSPACE_FILTER,
   55             defaultUri: this.fileDialogService.defaultWorkspacePath()
   56         });
   57 
   58         if (!workspacePath) {
   59             return; // canceled
   60         }
   61 
   62         if (!hasWorkspaceFileExtension(workspacePath)) {
   63             // Always ensure we have workspace file extension
   64             // (see https://github.com/microsoft/vscode/issues/84818)
   65             workspacePath = workspacePath.with({ path: `${workspacePath.path}.${WORKSPACE_EXTENSION}` });
   66         }
   67 
   68         return workspacePath;
   69     }
   70 
   71     updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise<void> {
   72         const folders = this.contextService.getWorkspace().folders;
   73 
   74         let foldersToDelete: URI[] = [];
   75         if (typeof deleteCount === 'number') {
   76             foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri);
   77         }
   78 
   79         const wantsToDelete = foldersToDelete.length > 0;
   80         const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0;
   81 
   82         if (!wantsToAdd && !wantsToDelete) {
   83             return Promise.resolve(); // return early if there is nothing to do
   84         }
   85 
   86         // Add Folders
   87         if (wantsToAdd && !wantsToDelete && Array.isArray(foldersToAdd)) {
   88             return this.doAddFolders(foldersToAdd, index, donotNotifyError);
   89         }
   90 
   91         // Delete Folders
   92         if (wantsToDelete && !wantsToAdd) {
   93             return this.removeFolders(foldersToDelete);
   94         }
   95 
   96         // Add & Delete Folders
   97         else {
   98 
   99             // if we are in single-folder state and the folder is replaced with
  100             // other folders, we handle this specially and just enter workspace
  101             // mode with the folders that are being added.
  102             if (this.includesSingleFolderWorkspace(foldersToDelete)) {
  103                 return this.createAndEnterWorkspace(foldersToAdd!);
  104             }
  105 
  106             // if we are not in workspace-state, we just add the folders
  107             if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
  108                 return this.doAddFolders(foldersToAdd!, index, donotNotifyError);
  109             }
  110 
  111             // finally, update folders within the workspace
  112             return this.doUpdateFolders(foldersToAdd!, foldersToDelete, index, donotNotifyError);
  113         }
  114     }
  115 
  116     private async doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): Promise<void> {
  117         try {
  118             await this.contextService.updateFolders(foldersToAdd, foldersToDelete, index);
  119         } catch (error) {
  120             if (donotNotifyError) {
  121                 throw error;
  122             }
  123 
  124             this.handleWorkspaceConfigurationEditingError(error);
  125         }
  126     }
  127 
  128     addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): Promise<void> {
  129         return this.doAddFolders(foldersToAdd, undefined, donotNotifyError);
  130     }
  131 
  132     private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise<void> {
  133         const state = this.contextService.getWorkbenchState();
  134         const remoteAuthority = this.environmentService.configuration.remoteAuthority;
  135         if (remoteAuthority) {
  136             // https://github.com/microsoft/vscode/issues/94191
  137             foldersToAdd = foldersToAdd.filter(f => f.uri.scheme !== Schemas.file && (f.uri.scheme !== Schemas.vscodeRemote || isEqualAuthority(f.uri.authority, remoteAuthority)));
  138         }
  139 
  140         // If we are in no-workspace or single-folder workspace, adding folders has to
  141         // enter a workspace.
  142         if (state !== WorkbenchState.WORKSPACE) {
  143             let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri }));
  144             newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd);
  145             newWorkspaceFolders = distinct(newWorkspaceFolders, folder => this.uriIdentityService.extUri.getComparisonKey(folder.uri));
  146 
  147             if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
  148                 return; // return if the operation is a no-op for the current state
  149             }
  150 
  151             return this.createAndEnterWorkspace(newWorkspaceFolders);
  152         }
  153 
  154         // Delegate addition of folders to workspace service otherwise
  155         try {
  156             await this.contextService.addFolders(foldersToAdd, index);
  157         } catch (error) {
  158             if (donotNotifyError) {
  159                 throw error;
  160             }
  161 
  162             this.handleWorkspaceConfigurationEditingError(error);
  163         }
  164     }
  165 
  166     async removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): Promise<void> {
  167 
  168         // If we are in single-folder state and the opened folder is to be removed,
  169         // we create an empty workspace and enter it.
  170         if (this.includesSingleFolderWorkspace(foldersToRemove)) {
  171             return this.createAndEnterWorkspace([]);
  172         }
  173 
  174         // Delegate removal of folders to workspace service otherwise
  175         try {
  176             await this.contextService.removeFolders(foldersToRemove);
  177         } catch (error) {
  178             if (donotNotifyError) {
  179                 throw error;
  180             }
  181 
  182             this.handleWorkspaceConfigurationEditingError(error);
  183         }
  184     }
  185 
  186     private includesSingleFolderWorkspace(folders: URI[]): boolean {
  187         if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
  188             const workspaceFolder = this.contextService.getWorkspace().folders[0];
  189             return (folders.some(folder => this.uriIdentityService.extUri.isEqual(folder, workspaceFolder.uri)));
  190         }
  191 
  192         return false;
  193     }
  194 
  195     async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise<void> {
  196         if (path && !await this.isValidTargetWorkspacePath(path)) {
  197             return;
  198         }
  199 
  200         const remoteAuthority = this.environmentService.configuration.remoteAuthority;
  201         const untitledWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority);
  202         if (path) {
  203             try {
  204                 await this.saveWorkspaceAs(untitledWorkspace, path);
  205             } finally {
  206                 await this.workspacesService.deleteUntitledWorkspace(untitledWorkspace); // https://github.com/microsoft/vscode/issues/100276
  207             }
  208         } else {
  209             path = untitledWorkspace.configPath;
  210         }
  211 
  212         return this.enterWorkspace(path);
  213     }
  214 
  215     async saveAndEnterWorkspace(path: URI): Promise<void> {
  216         const workspaceIdentifier = this.getCurrentWorkspaceIdentifier();
  217         if (!workspaceIdentifier) {
  218             return;
  219         }
  220 
  221         // Allow to save the workspace of the current window
  222         if (extUri.isEqual(workspaceIdentifier.configPath, path)) {
  223             return this.saveWorkspace(workspaceIdentifier);
  224         }
  225 
  226         // From this moment on we require a valid target that is not opened already
  227         if (!await this.isValidTargetWorkspacePath(path)) {
  228             return;
  229         }
  230 
  231         await this.saveWorkspaceAs(workspaceIdentifier, path);
  232 
  233         return this.enterWorkspace(path);
  234     }
  235 
  236     async isValidTargetWorkspacePath(path: URI): Promise<boolean> {
  237         return true; // OK
  238     }
  239 
  240     protected async saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPathURI: URI): Promise<void> {
  241         const configPathURI = workspace.configPath;
  242 
  243         // Return early if target is same as source
  244         if (this.uriIdentityService.extUri.isEqual(configPathURI, targetConfigPathURI)) {
  245             return;
  246         }
  247 
  248         const isFromUntitledWorkspace = isUntitledWorkspace(configPathURI, this.environmentService);
  249 
  250         // Read the contents of the workspace file, update it to new location and save it.
  251         const raw = await this.fileService.readFile(configPathURI);
  252         const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, isFromUntitledWorkspace, targetConfigPathURI);
  253         await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true });
  254     }
  255 
  256     protected async saveWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
  257         const configPathURI = workspace.configPath;
  258 
  259         // First: try to save any existing model as it could be dirty
  260         const existingModel = this.textFileService.files.get(configPathURI);
  261         if (existingModel) {
  262             await existingModel.save({ force: true, reason: SaveReason.EXPLICIT });
  263             return;
  264         }
  265 
  266         // Second: if the file exists on disk, simply return
  267         const workspaceFileExists = await this.fileService.exists(configPathURI);
  268         if (workspaceFileExists) {
  269             return;
  270         }
  271 
  272         // Finally, we need to re-create the file as it was deleted
  273         const newWorkspace: IStoredWorkspace = { folders: [] };
  274         const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(JSON.stringify(newWorkspace, null, '\t'), configPathURI, false, configPathURI);
  275         await this.textFileService.create(configPathURI, newRawWorkspaceContents);
  276     }
  277 
  278     private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void {
  279         switch (error.code) {
  280             case JSONEditingErrorCode.ERROR_INVALID_FILE:
  281                 this.onInvalidWorkspaceConfigurationFileError();
  282                 break;
  283             case JSONEditingErrorCode.ERROR_FILE_DIRTY:
  284                 this.onWorkspaceConfigurationFileDirtyError();
  285                 break;
  286             default:
  287                 this.notificationService.error(error.message);
  288         }
  289     }
  290 
  291     private onInvalidWorkspaceConfigurationFileError(): void {
  292         const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again.");
  293         this.askToOpenWorkspaceConfigurationFile(message);
  294     }
  295 
  296     private onWorkspaceConfigurationFileDirtyError(): void {
  297         const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again.");
  298         this.askToOpenWorkspaceConfigurationFile(message);
  299     }
  300 
  301     private askToOpenWorkspaceConfigurationFile(message: string): void {
  302         this.notificationService.prompt(Severity.Error, message,
  303             [{
  304                 label: nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"),
  305                 run: () => this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile')
  306             }]
  307         );
  308     }
  309 
  310     abstract async enterWorkspace(path: URI): Promise<void>;
  311 
  312     protected async doEnterWorkspace(path: URI): Promise<IEnterWorkspaceResult | null> {
  313         if (!!this.environmentService.extensionTestsLocationURI) {
  314             throw new Error('Entering a new workspace is not possible in tests.');
  315         }
  316 
  317         const workspace = await this.workspacesService.getWorkspaceIdentifier(path);
  318 
  319         // Settings migration (only if we come from a folder workspace)
  320         if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
  321             await this.migrateWorkspaceSettings(workspace);
  322         }
  323 
  324         const workspaceImpl = this.contextService as WorkspaceService;
  325         await workspaceImpl.initialize(workspace);
  326 
  327         return this.workspacesService.enterWorkspace(path);
  328     }
  329 
  330     private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise<void> {
  331         return this.doCopyWorkspaceSettings(toWorkspace, setting => setting.scope === ConfigurationScope.WINDOW);
  332     }
  333 
  334     copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise<void> {
  335         return this.doCopyWorkspaceSettings(toWorkspace);
  336     }
  337 
  338     private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): Promise<void> {
  339         const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
  340         const targetWorkspaceConfiguration: any = {};
  341         for (const key of this.configurationService.keys().workspace) {
  342             if (configurationProperties[key]) {
  343                 if (filter && !filter(configurationProperties[key])) {
  344                     continue;
  345                 }
  346 
  347                 targetWorkspaceConfiguration[key] = this.configurationService.inspect(key).workspaceValue;
  348             }
  349         }
  350 
  351         return this.jsonEditingService.write(toWorkspace.configPath, [{ path: ['settings'], value: targetWorkspaceConfiguration }], true);
  352     }
  353 
  354     protected getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined {
  355         const workspace = this.contextService.getWorkspace();
  356         if (workspace?.configuration) {
  357             return { id: workspace.id, configPath: workspace.configuration };
  358         }
  359 
  360         return undefined;
  361     }
  362 }