mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Trusted Workspaces Feature Branch Merge (#115961)
* draft trusted workspace service / model * renaming * add request model and action * err fix * add request handlers with mock actions * some quick fixes * adding badge icon to activity bar gear * Add Statusbar item to indicate trust * Cleanup code * Add background color * Use theme color for the status background color * adding basic editing experience * observe trust with startup tasks * Extension enablement * Add capability to provide a custom message * Remove old actions * explorer: if you can not undo, pass undo to editor fixes #111630 * Remove plug icon from ports view Part of https://github.com/microsoft/vscode-internalbacklog/issues/1689 * Fixed compilation error * Handle extension uninstall * Handle extension install * Ability to prompt when state is untrusted * Do not change state is the modal dialog is dismissed or the Cancel button is pressed * Refactored enablement code * Prompt when installing from VSIX * Prompt when installing from the Gallery * Move file into the browser folder * fixes and polish * restructure workspace contributions * restructure actions and use confirmations * Initial draft of the proposed APIs * Added stubs for the proposed api * Trusted Workspace proposed API * Fix a regression introduced by merge * status bar indicator improvements * remove helper command as we now have hooks * verbose messaging for the immediate request * add indication to global activity icon of pending request * try personal title * Add configuration setting * Add additional extension actions * Fix contributions * Removed context key that is not needed * Fixed issue with the dialog * Reduce arbitrary event limiter from 16ms down to 4.16666 (support for monitors up-to 240hz) #107016 * Fixes #115221: update emoji tests * Give a higher priority to language configuration set via API call (#114684) * debug console menu action polish * Avoid the CSS general sibling combinator ~ for perf reasons * more notebook todos * Use label as tooltip fallback properly Part of #115337 * Fixes microsoft/monaco-editor#2329: Move `registerThemingParticipant` call to `/editor/` * Fix port label not always getting set Part of microsoft/vscode-remote-release#4364 * simplify map creation, fyi @bpasero * Fix #114432: Multiple save dialogs appearing on Windows if Ctrl+S is pressed multiple times (#114450) * fix multiple save dialogs appearing on Windows when spamming Ctrl+S * remove old fix and instead keep track of windows with open dialogs in the dialogMainService * keep initialisation of activeWindowDialogs in constructor * remove unused variable * some changes * queue dialogs based on hash of options * simplify structure, fix comment typo * Apply suggestions from code review Co-authored-by: Benjamin Pasero <benjamin.pasero@gmail.com> * remove unnecessary async/await for aquireFileDialogLock method * don't acquire file dialog lock for message boxes * use MessageBoxReturnValue | SaveDialogReturnValue | OpenDialogReturnValue instead of any type for getWindowDialogQueue * Apply suggestions from code review Co-authored-by: Benjamin Pasero <benjamin.pasero@gmail.com> Co-authored-by: Benjamin Pasero <benjpas@microsoft.com> Co-authored-by: Benjamin Pasero <benjamin.pasero@gmail.com> * 💄 dialog main service locks * debt - adopt some ? operator * Better hiding of custom hover in icon label * Limit to 8ms (120fps) * more API todos for notebooks * 💄 * Update grammars * chore - group notebook specific api proposals together * added unreleased fixes to endgame notebook * Add changes back to the modal dialog * Add back the workspace trust proposed APIs * Adjust dialog buttons * Standardize on WorkspaceTrust name across interfaces, classes, variables * Renamed some of the missing keys * Add TestWorkspaceTrust stub and fix failing tests * Add requiresWorkspaceTrust property to fix test failure * remove notebook change Co-authored-by: Ladislau Szomoru <lszomoru@microsoft.com> Co-authored-by: isidor <inikolic@microsoft.com> Co-authored-by: Alex Ross <alros@microsoft.com> Co-authored-by: TacticalDan <gorksorf@gmail.com> Co-authored-by: Alexandru Dima <alexdima@microsoft.com> Co-authored-by: Johannes Rieken <johannes.rieken@gmail.com> Co-authored-by: Cameron <cameron532@gmail.com> Co-authored-by: Benjamin Pasero <benjpas@microsoft.com> Co-authored-by: Benjamin Pasero <benjamin.pasero@gmail.com>
This commit is contained in:
parent
01a3787cca
commit
afd102cbd2
|
@ -218,6 +218,10 @@
|
||||||
"name": "vs/workbench/contrib/webviewPanel",
|
"name": "vs/workbench/contrib/webviewPanel",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "vs/workbench/contrib/workspace",
|
||||||
|
"project": "vscode-workbench"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "vs/workbench/contrib/workspaces",
|
"name": "vs/workbench/contrib/workspaces",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"publisher": "vscode",
|
"publisher": "vscode",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"enableProposedApi": true,
|
"enableProposedApi": true,
|
||||||
|
"requiresWorkspaceTrust": "onDemand",
|
||||||
"private": true,
|
"private": true,
|
||||||
"activationEvents": [],
|
"activationEvents": [],
|
||||||
"main": "./out/extension",
|
"main": "./out/extension",
|
||||||
|
|
|
@ -136,6 +136,8 @@ export interface IExtensionContributions {
|
||||||
|
|
||||||
export type ExtensionKind = 'ui' | 'workspace' | 'web';
|
export type ExtensionKind = 'ui' | 'workspace' | 'web';
|
||||||
|
|
||||||
|
export type ExtensionWorkspaceTrustRequirement = false | 'onStart' | 'onDemand';
|
||||||
|
|
||||||
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
|
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
|
||||||
return thing
|
return thing
|
||||||
&& typeof thing === 'object'
|
&& typeof thing === 'object'
|
||||||
|
@ -190,6 +192,7 @@ export interface IExtensionManifest {
|
||||||
readonly enableProposedApi?: boolean;
|
readonly enableProposedApi?: boolean;
|
||||||
readonly api?: string;
|
readonly api?: string;
|
||||||
readonly scripts?: { [key: string]: string; };
|
readonly scripts?: { [key: string]: string; };
|
||||||
|
readonly requiresWorkspaceTrust?: ExtensionWorkspaceTrustRequirement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ExtensionType {
|
export const enum ExtensionType {
|
||||||
|
|
375
src/vs/platform/workspace/common/workspaceTrust.ts
Normal file
375
src/vs/platform/workspace/common/workspaceTrust.ts
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||||
|
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||||
|
|
||||||
|
export const WORKSPACE_TRUST_ENABLED = 'workspace.trustEnabled';
|
||||||
|
export const WORKSPACE_TRUST_URI = URI.parse('workspaceTrust:/Trusted Workspaces');
|
||||||
|
|
||||||
|
export enum WorkspaceTrustScope {
|
||||||
|
Local = 0,
|
||||||
|
Remote = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WorkspaceTrustState {
|
||||||
|
Untrusted = 0,
|
||||||
|
Trusted = 1,
|
||||||
|
Unknown = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export function workspaceTrustStateToString(trustState: WorkspaceTrustState) {
|
||||||
|
switch (trustState) {
|
||||||
|
case WorkspaceTrustState.Trusted:
|
||||||
|
return localize('trusted', "Trusted");
|
||||||
|
case WorkspaceTrustState.Untrusted:
|
||||||
|
return localize('untrusted', "Untrusted");
|
||||||
|
case WorkspaceTrustState.Unknown:
|
||||||
|
default:
|
||||||
|
return localize('unknown', "Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceTrustContext = {
|
||||||
|
PendingRequest: new RawContextKey<boolean>('workspaceTrustPendingRequest', false),
|
||||||
|
TrustState: new RawContextKey<WorkspaceTrustState>('workspaceTrustState', WorkspaceTrustState.Unknown)
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IWorkspaceTrustModel {
|
||||||
|
|
||||||
|
readonly onDidChangeTrustState: Event<void>;
|
||||||
|
|
||||||
|
setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void;
|
||||||
|
getFolderTrustState(folder: URI): WorkspaceTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceTrustRequest {
|
||||||
|
immediate: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceTrustRequestModel {
|
||||||
|
readonly trustRequest: IWorkspaceTrustRequest | undefined;
|
||||||
|
|
||||||
|
readonly onDidInitiateRequest: Event<void>;
|
||||||
|
readonly onDidCompleteRequest: Event<WorkspaceTrustState | undefined>;
|
||||||
|
|
||||||
|
initiateRequest(request?: IWorkspaceTrustRequest): void;
|
||||||
|
completeRequest(trustState?: WorkspaceTrustState): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceTrustStateChangeEvent {
|
||||||
|
previousTrustState: WorkspaceTrustState;
|
||||||
|
currentTrustState: WorkspaceTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkspaceTrustChangeEvent = Event<WorkspaceTrustStateChangeEvent>;
|
||||||
|
|
||||||
|
export const IWorkspaceTrustService = createDecorator<IWorkspaceTrustService>('workspaceTrustService');
|
||||||
|
|
||||||
|
export interface IWorkspaceTrustService {
|
||||||
|
readonly _serviceBrand: undefined;
|
||||||
|
|
||||||
|
readonly requestModel: IWorkspaceTrustRequestModel;
|
||||||
|
|
||||||
|
onDidChangeTrustState: WorkspaceTrustChangeEvent;
|
||||||
|
getWorkspaceTrustState(): WorkspaceTrustState;
|
||||||
|
isWorkspaceTrustEnabled(): boolean;
|
||||||
|
requireWorkspaceTrust(request: IWorkspaceTrustRequest): Promise<WorkspaceTrustState>;
|
||||||
|
resetWorkspaceTrust(): Promise<WorkspaceTrustState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWorkspaceTrustStateInfo {
|
||||||
|
localFolders: { uri: string, trustState: WorkspaceTrustState }[]
|
||||||
|
|
||||||
|
// Removing complexity of remote items
|
||||||
|
//trustedRemoteItems: { uri: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key';
|
||||||
|
|
||||||
|
export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustModel {
|
||||||
|
|
||||||
|
private storageKey = WORKSPACE_TRUST_STORAGE_KEY;
|
||||||
|
private trustStateInfo: IWorkspaceTrustStateInfo;
|
||||||
|
|
||||||
|
private readonly _onDidChangeTrustState = this._register(new Emitter<void>());
|
||||||
|
readonly onDidChangeTrustState = this._onDidChangeTrustState.event;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly storageService: IStorageService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.trustStateInfo = this.loadTrustInfo();
|
||||||
|
this._register(this.storageService.onDidChangeValue(changeEvent => {
|
||||||
|
if (changeEvent.key === this.storageKey) {
|
||||||
|
this.onDidStorageChange();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadTrustInfo(): IWorkspaceTrustStateInfo {
|
||||||
|
const infoAsString = this.storageService.get(this.storageKey, StorageScope.GLOBAL);
|
||||||
|
|
||||||
|
let result: IWorkspaceTrustStateInfo | undefined;
|
||||||
|
try {
|
||||||
|
if (infoAsString) {
|
||||||
|
result = JSON.parse(infoAsString);
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = {
|
||||||
|
localFolders: [],
|
||||||
|
//trustedRemoteItems: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.localFolders) {
|
||||||
|
result.localFolders = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!result.trustedRemoteItems) {
|
||||||
|
// result.trustedRemoteItems = [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveTrustInfo(): void {
|
||||||
|
this.storageService.store(this.storageKey, JSON.stringify(this.trustStateInfo), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDidStorageChange(): void {
|
||||||
|
this.trustStateInfo = this.loadTrustInfo();
|
||||||
|
|
||||||
|
this._onDidChangeTrustState.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void {
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
if (trustState === WorkspaceTrustState.Unknown) {
|
||||||
|
const before = this.trustStateInfo.localFolders.length;
|
||||||
|
this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(info => info.uri !== folder.toString());
|
||||||
|
|
||||||
|
if (this.trustStateInfo.localFolders.length !== before) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let found = false;
|
||||||
|
for (const trustInfo of this.trustStateInfo.localFolders) {
|
||||||
|
if (trustInfo.uri === folder.toString()) {
|
||||||
|
found = true;
|
||||||
|
if (trustInfo.trustState !== trustState) {
|
||||||
|
trustInfo.trustState = trustState;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
this.trustStateInfo.localFolders.push({ uri: folder.toString(), trustState });
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
this.saveTrustInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFolderTrustState(folder: URI): WorkspaceTrustState {
|
||||||
|
for (const trustInfo of this.trustStateInfo.localFolders) {
|
||||||
|
if (trustInfo.uri === folder.toString()) {
|
||||||
|
return trustInfo.trustState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return WorkspaceTrustState.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkspaceTrustRequestModel extends Disposable implements IWorkspaceTrustRequestModel {
|
||||||
|
trustRequest: IWorkspaceTrustRequest | undefined;
|
||||||
|
|
||||||
|
_onDidInitiateRequest = this._register(new Emitter<void>());
|
||||||
|
onDidInitiateRequest: Event<void> = this._onDidInitiateRequest.event;
|
||||||
|
|
||||||
|
_onDidCompleteRequest = this._register(new Emitter<WorkspaceTrustState | undefined>());
|
||||||
|
onDidCompleteRequest = this._onDidCompleteRequest.event;
|
||||||
|
|
||||||
|
initiateRequest(request: IWorkspaceTrustRequest): void {
|
||||||
|
if (this.trustRequest && (!request.immediate || this.trustRequest.immediate)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trustRequest = request;
|
||||||
|
this._onDidInitiateRequest.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
completeRequest(trustState?: WorkspaceTrustState): void {
|
||||||
|
this.trustRequest = undefined;
|
||||||
|
this._onDidCompleteRequest.fire(trustState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkspaceTrustService extends Disposable implements IWorkspaceTrustService {
|
||||||
|
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
private readonly dataModel: IWorkspaceTrustModel;
|
||||||
|
readonly requestModel: IWorkspaceTrustRequestModel;
|
||||||
|
|
||||||
|
private readonly _onDidChangeTrustState = this._register(new Emitter<WorkspaceTrustStateChangeEvent>());
|
||||||
|
readonly onDidChangeTrustState = this._onDidChangeTrustState.event;
|
||||||
|
|
||||||
|
private _currentTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown;
|
||||||
|
private _inFlightResolver?: (trustState: WorkspaceTrustState) => void;
|
||||||
|
private _trustRequestPromise?: Promise<WorkspaceTrustState>;
|
||||||
|
private _workspace: IWorkspace;
|
||||||
|
|
||||||
|
private readonly _ctxWorkspaceTrustState: IContextKey<WorkspaceTrustState>;
|
||||||
|
private readonly _ctxWorkspaceTrustPendingRequest: IContextKey<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IStorageService private readonly storageService: IStorageService,
|
||||||
|
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
|
||||||
|
@IConfigurationService readonly configurationService: IConfigurationService,
|
||||||
|
@IContextKeyService readonly contextKeyService: IContextKeyService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.dataModel = this._register(new WorkspaceTrustModel(this.storageService));
|
||||||
|
this.requestModel = this._register(new WorkspaceTrustRequestModel());
|
||||||
|
|
||||||
|
this._workspace = this.workspaceService.getWorkspace();
|
||||||
|
this._currentTrustState = this.calculateWorkspaceTrustState();
|
||||||
|
|
||||||
|
this._register(this.dataModel.onDidChangeTrustState(() => this.currentTrustState = this.calculateWorkspaceTrustState()));
|
||||||
|
this._register(this.requestModel.onDidCompleteRequest((trustState) => this.onTrustRequestCompleted(trustState)));
|
||||||
|
|
||||||
|
this._ctxWorkspaceTrustState = WorkspaceTrustContext.TrustState.bindTo(contextKeyService);
|
||||||
|
this._ctxWorkspaceTrustPendingRequest = WorkspaceTrustContext.PendingRequest.bindTo(contextKeyService);
|
||||||
|
this._ctxWorkspaceTrustState.set(this.currentTrustState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get currentTrustState(): WorkspaceTrustState {
|
||||||
|
return this._currentTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private set currentTrustState(trustState: WorkspaceTrustState) {
|
||||||
|
if (this._currentTrustState === trustState) { return; }
|
||||||
|
const previousState = this._currentTrustState;
|
||||||
|
this._currentTrustState = trustState;
|
||||||
|
|
||||||
|
this._onDidChangeTrustState.fire({ previousTrustState: previousState, currentTrustState: this._currentTrustState });
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateWorkspaceTrustState(): WorkspaceTrustState {
|
||||||
|
if (!this.isWorkspaceTrustEnabled()) {
|
||||||
|
return WorkspaceTrustState.Trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) {
|
||||||
|
return WorkspaceTrustState.Trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = undefined;
|
||||||
|
for (const folder of this._workspace.folders) {
|
||||||
|
const folderTrust = this.dataModel.getFolderTrustState(folder.uri);
|
||||||
|
|
||||||
|
switch (folderTrust) {
|
||||||
|
case WorkspaceTrustState.Untrusted:
|
||||||
|
return WorkspaceTrustState.Untrusted;
|
||||||
|
case WorkspaceTrustState.Unknown:
|
||||||
|
state = folderTrust;
|
||||||
|
break;
|
||||||
|
case WorkspaceTrustState.Trusted:
|
||||||
|
if (state === undefined) {
|
||||||
|
state = folderTrust;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state ?? WorkspaceTrustState.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTrustRequestCompleted(trustState?: WorkspaceTrustState): void {
|
||||||
|
if (this._inFlightResolver) {
|
||||||
|
this._inFlightResolver(trustState === undefined ? this.currentTrustState : trustState);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._inFlightResolver = undefined;
|
||||||
|
this._trustRequestPromise = undefined;
|
||||||
|
|
||||||
|
if (trustState === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._workspace.folders.forEach(folder => {
|
||||||
|
this.dataModel.setFolderTrustState(folder.uri, trustState);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._ctxWorkspaceTrustPendingRequest.set(false);
|
||||||
|
this._ctxWorkspaceTrustState.set(trustState);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkspaceTrustState(): WorkspaceTrustState {
|
||||||
|
return this.currentTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWorkspaceTrustEnabled(): boolean {
|
||||||
|
return this.configurationService.getValue<boolean>(WORKSPACE_TRUST_ENABLED) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async requireWorkspaceTrust(request?: IWorkspaceTrustRequest): Promise<WorkspaceTrustState> {
|
||||||
|
if (this.currentTrustState === WorkspaceTrustState.Trusted) {
|
||||||
|
return this.currentTrustState;
|
||||||
|
}
|
||||||
|
if (this.currentTrustState === WorkspaceTrustState.Untrusted && !request?.immediate) {
|
||||||
|
return this.currentTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._trustRequestPromise) {
|
||||||
|
if (request?.immediate &&
|
||||||
|
this.requestModel.trustRequest &&
|
||||||
|
!this.requestModel.trustRequest.immediate) {
|
||||||
|
this.requestModel.initiateRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._trustRequestPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._trustRequestPromise = new Promise(resolve => {
|
||||||
|
this._inFlightResolver = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.requestModel.initiateRequest(request);
|
||||||
|
this._ctxWorkspaceTrustPendingRequest.set(true);
|
||||||
|
|
||||||
|
return this._trustRequestPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetWorkspaceTrust(): Promise<WorkspaceTrustState> {
|
||||||
|
if (this.currentTrustState !== WorkspaceTrustState.Unknown) {
|
||||||
|
this._workspace.folders.forEach(folder => {
|
||||||
|
this.dataModel.setFolderTrustState(folder.uri, WorkspaceTrustState.Unknown);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(WorkspaceTrustState.Unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerSingleton(IWorkspaceTrustService, WorkspaceTrustService);
|
31
src/vs/platform/workspace/test/common/testWorkspaceTrust.ts
Normal file
31
src/vs/platform/workspace/test/common/testWorkspaceTrust.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
|
import { IWorkspaceTrustRequest, IWorkspaceTrustRequestModel, IWorkspaceTrustService, WorkspaceTrustChangeEvent, WorkspaceTrustRequestModel, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
|
export class TestWorkspaceTrustService implements IWorkspaceTrustService {
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
|
||||||
|
requestModel: IWorkspaceTrustRequestModel = new WorkspaceTrustRequestModel();
|
||||||
|
|
||||||
|
onDidChangeTrustState: WorkspaceTrustChangeEvent = Event.None;
|
||||||
|
|
||||||
|
getWorkspaceTrustState(): WorkspaceTrustState {
|
||||||
|
return WorkspaceTrustState.Trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWorkspaceTrustEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
requireWorkspaceTrust(request: IWorkspaceTrustRequest): Promise<WorkspaceTrustState> {
|
||||||
|
return Promise.resolve(WorkspaceTrustState.Trusted);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetWorkspaceTrust(): Promise<WorkspaceTrustState> {
|
||||||
|
return Promise.resolve(WorkspaceTrustState.Unknown);
|
||||||
|
}
|
||||||
|
}
|
56
src/vs/vscode.proposed.d.ts
vendored
56
src/vs/vscode.proposed.d.ts
vendored
|
@ -2615,4 +2615,60 @@ declare module 'vscode' {
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region https://github.com/microsoft/vscode/issues/106488
|
||||||
|
|
||||||
|
export enum WorkspaceTrustState {
|
||||||
|
/**
|
||||||
|
* The workspace is untrusted, and it will have limited functionality.
|
||||||
|
*/
|
||||||
|
Untrusted = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workspace is trusted, and all functionality will be available.
|
||||||
|
*/
|
||||||
|
Trusted = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial state of the workspace.
|
||||||
|
*
|
||||||
|
* If trust will be required, users will be prompted to make a choice.
|
||||||
|
*/
|
||||||
|
Unknown = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event data that is fired when the trust state of the workspace changes
|
||||||
|
*/
|
||||||
|
export interface WorkspaceTrustStateChangeEvent {
|
||||||
|
/**
|
||||||
|
* Previous trust state of the workspace
|
||||||
|
*/
|
||||||
|
previousTrustState: WorkspaceTrustState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current trust state of the workspace
|
||||||
|
*/
|
||||||
|
currentTrustState: WorkspaceTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace workspace {
|
||||||
|
/**
|
||||||
|
* The trust state of the current workspace
|
||||||
|
*/
|
||||||
|
export const trustState: WorkspaceTrustState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user to chose whether to trust the current workspace
|
||||||
|
* @param message Optional message which would be displayed in the prompt
|
||||||
|
*/
|
||||||
|
export function requireWorkspaceTrust(message?: string): Thenable<WorkspaceTrustState>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event that fires when the trust state of the current workspace changes
|
||||||
|
*/
|
||||||
|
export const onDidChangeWorkspaceTrustState: Event<WorkspaceTrustStateChangeEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||||
import { ILabelService } from 'vs/platform/label/common/label';
|
import { ILabelService } from 'vs/platform/label/common/label';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { IRequestService } from 'vs/platform/request/common/request';
|
import { IRequestService } from 'vs/platform/request/common/request';
|
||||||
|
import { WorkspaceTrustStateChangeEvent, IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||||
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||||
|
@ -45,19 +46,21 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
@ILabelService private readonly _labelService: ILabelService,
|
@ILabelService private readonly _labelService: ILabelService,
|
||||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||||
@IFileService fileService: IFileService
|
@IFileService fileService: IFileService,
|
||||||
|
@IWorkspaceTrustService private readonly _workspaceTrustService: IWorkspaceTrustService
|
||||||
) {
|
) {
|
||||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
|
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
|
||||||
const workspace = this._contextService.getWorkspace();
|
const workspace = this._contextService.getWorkspace();
|
||||||
// The workspace file is provided be a unknown file system provider. It might come
|
// The workspace file is provided be a unknown file system provider. It might come
|
||||||
// from the extension host. So initialize now knowing that `rootPath` is undefined.
|
// from the extension host. So initialize now knowing that `rootPath` is undefined.
|
||||||
if (workspace.configuration && !isNative && !fileService.canHandleResource(workspace.configuration)) {
|
if (workspace.configuration && !isNative && !fileService.canHandleResource(workspace.configuration)) {
|
||||||
this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace));
|
this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.getWorkspaceTrustState());
|
||||||
} else {
|
} else {
|
||||||
this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace)));
|
this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace), this.getWorkspaceTrustState()));
|
||||||
}
|
}
|
||||||
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
|
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
|
||||||
this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose);
|
this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose);
|
||||||
|
this._workspaceTrustService.onDidChangeTrustState(this._onDidChangeWorkspaceTrustState, this, this._toDispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
|
@ -202,4 +205,18 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||||
$resolveProxy(url: string): Promise<string | undefined> {
|
$resolveProxy(url: string): Promise<string | undefined> {
|
||||||
return this._requestService.resolveProxy(url);
|
return this._requestService.resolveProxy(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- trust ---
|
||||||
|
|
||||||
|
$requireWorkspaceTrust(message?: string): Promise<WorkspaceTrustState> {
|
||||||
|
return this._workspaceTrustService.requireWorkspaceTrust({ immediate: true, message });
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWorkspaceTrustState(): WorkspaceTrustState {
|
||||||
|
return this._workspaceTrustService.getWorkspaceTrustState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void {
|
||||||
|
this._proxy.$onDidChangeWorkspaceTrustState(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls';
|
||||||
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
|
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
|
||||||
import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
|
import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
|
||||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||||
import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
import { throwProposedApiError, checkProposedApiEnabled, checkRequiresWorkspaceTrust } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||||
import type * as vscode from 'vscode';
|
import type * as vscode from 'vscode';
|
||||||
|
@ -881,11 +881,23 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||||
onDidChangeTunnels: (listener, thisArg?, disposables?) => {
|
onDidChangeTunnels: (listener, thisArg?, disposables?) => {
|
||||||
checkProposedApiEnabled(extension);
|
checkProposedApiEnabled(extension);
|
||||||
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
|
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
|
||||||
|
|
||||||
},
|
},
|
||||||
registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => {
|
registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => {
|
||||||
checkProposedApiEnabled(extension);
|
checkProposedApiEnabled(extension);
|
||||||
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);
|
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);
|
||||||
|
},
|
||||||
|
get trustState() {
|
||||||
|
checkProposedApiEnabled(extension);
|
||||||
|
checkRequiresWorkspaceTrust(extension);
|
||||||
|
return extHostWorkspace.trustState;
|
||||||
|
},
|
||||||
|
requireWorkspaceTrust: (message?: string) => {
|
||||||
|
checkProposedApiEnabled(extension);
|
||||||
|
checkRequiresWorkspaceTrust(extension);
|
||||||
|
return extHostWorkspace.requireWorkspaceTrust(message);
|
||||||
|
},
|
||||||
|
onDidChangeWorkspaceTrustState: (listener, thisArgs?, disposables?) => {
|
||||||
|
return extHostWorkspace.onDidChangeWorkspaceTrustState(listener, thisArgs, disposables);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1284,6 +1296,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||||
// checkProposedApiEnabled(extension);
|
// checkProposedApiEnabled(extension);
|
||||||
return extHostTypes.TestState;
|
return extHostTypes.TestState;
|
||||||
},
|
},
|
||||||
|
get WorkspaceTrustState() {
|
||||||
|
// checkProposedApiEnabled(extension);
|
||||||
|
return extHostTypes.WorkspaceTrustState;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,11 +55,12 @@ import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, Notebook
|
||||||
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||||
import { Dto } from 'vs/base/common/types';
|
import { Dto } from 'vs/base/common/types';
|
||||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||||
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes';
|
import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
|
||||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||||
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||||
import { InternalTestItem, InternalTestResults, RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
import { InternalTestItem, InternalTestResults, RunTestForProviderRequest, RunTestsRequest, RunTestsResult, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||||
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||||
|
import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
export interface IEnvironment {
|
export interface IEnvironment {
|
||||||
isExtensionDevelopmentDebug: boolean;
|
isExtensionDevelopmentDebug: boolean;
|
||||||
|
@ -831,6 +832,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
|
||||||
$saveAll(includeUntitled?: boolean): Promise<boolean>;
|
$saveAll(includeUntitled?: boolean): Promise<boolean>;
|
||||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise<void>;
|
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise<void>;
|
||||||
$resolveProxy(url: string): Promise<string | undefined>;
|
$resolveProxy(url: string): Promise<string | undefined>;
|
||||||
|
$requireWorkspaceTrust(message?: string): Promise<WorkspaceTrustState>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFileChangeDto {
|
export interface IFileChangeDto {
|
||||||
|
@ -1093,9 +1095,10 @@ export interface ExtHostTreeViewsShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostWorkspaceShape {
|
export interface ExtHostWorkspaceShape {
|
||||||
$initializeWorkspace(workspace: IWorkspaceData | null): void;
|
$initializeWorkspace(workspace: IWorkspaceData | null, trustState: WorkspaceTrustState): void;
|
||||||
$acceptWorkspaceData(workspace: IWorkspaceData | null): void;
|
$acceptWorkspaceData(workspace: IWorkspaceData | null): void;
|
||||||
$handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void;
|
$handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void;
|
||||||
|
$onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostFileSystemInfoShape {
|
export interface ExtHostFileSystemInfoShape {
|
||||||
|
|
|
@ -3001,3 +3001,9 @@ export enum ExternalUriOpenerPriority {
|
||||||
Default = 2,
|
Default = 2,
|
||||||
Preferred = 3,
|
Preferred = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WorkspaceTrustState {
|
||||||
|
Untrusted = 0,
|
||||||
|
Trusted = 1,
|
||||||
|
Unknown = 2
|
||||||
|
}
|
||||||
|
|
|
@ -20,11 +20,12 @@ import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import { Severity } from 'vs/platform/notification/common/notification';
|
import { Severity } from 'vs/platform/notification/common/notification';
|
||||||
|
import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||||
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||||
import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes';
|
import { Range, RelativePattern, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
|
||||||
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||||
import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search';
|
import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
@ -168,6 +169,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||||
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
||||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||||
|
|
||||||
|
private readonly _onDidChangeWorkspaceTrustState = new Emitter<vscode.WorkspaceTrustStateChangeEvent>();
|
||||||
|
readonly onDidChangeWorkspaceTrustState: Event<vscode.WorkspaceTrustStateChangeEvent> = this._onDidChangeWorkspaceTrustState.event;
|
||||||
|
|
||||||
private readonly _logService: ILogService;
|
private readonly _logService: ILogService;
|
||||||
private readonly _requestIdProvider: Counter;
|
private readonly _requestIdProvider: Counter;
|
||||||
private readonly _barrier: Barrier;
|
private readonly _barrier: Barrier;
|
||||||
|
@ -181,6 +185,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||||
|
|
||||||
private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = [];
|
private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = [];
|
||||||
|
|
||||||
|
private _workspaceTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||||
|
@ -198,7 +204,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||||
this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)) : undefined;
|
this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled, uri => ignorePathCasing(uri, extHostFileSystemInfo)) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
$initializeWorkspace(data: IWorkspaceData | null): void {
|
$initializeWorkspace(data: IWorkspaceData | null, trustState: WorkspaceTrustState): void {
|
||||||
|
this._workspaceTrustState = trustState;
|
||||||
this.$acceptWorkspaceData(data);
|
this.$acceptWorkspaceData(data);
|
||||||
this._barrier.open();
|
this._barrier.open();
|
||||||
}
|
}
|
||||||
|
@ -549,6 +556,21 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||||
resolveProxy(url: string): Promise<string | undefined> {
|
resolveProxy(url: string): Promise<string | undefined> {
|
||||||
return this._proxy.$resolveProxy(url);
|
return this._proxy.$resolveProxy(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- trust ---
|
||||||
|
|
||||||
|
get trustState(): WorkspaceTrustState {
|
||||||
|
return this._workspaceTrustState;
|
||||||
|
}
|
||||||
|
|
||||||
|
requireWorkspaceTrust(message?: string): Promise<WorkspaceTrustState> {
|
||||||
|
return this._proxy.$requireWorkspaceTrust(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$onDidChangeWorkspaceTrustState(state: WorkspaceTrustStateChangeEvent): void {
|
||||||
|
this._workspaceTrustState = state.currentTrustState;
|
||||||
|
this._onDidChangeWorkspaceTrustState.fire(Object.freeze(state));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IExtHostWorkspace = createDecorator<IExtHostWorkspace>('IExtHostWorkspace');
|
export const IExtHostWorkspace = createDecorator<IExtHostWorkspace>('IExtHostWorkspace');
|
||||||
|
|
|
@ -12,9 +12,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||||
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||||
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
|
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
import { MenuRegistry, MenuId, SyncActionDescriptor, Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
import { EmptyWorkspaceSupportContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys';
|
import { EmptyWorkspaceSupportContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
@ -23,6 +23,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||||
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||||
|
import { WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
export class OpenFileAction extends Action {
|
export class OpenFileAction extends Action {
|
||||||
|
|
||||||
|
@ -250,6 +251,24 @@ export class DuplicateWorkspaceInNewWindowAction extends Action {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WorkspaceTrustManageAction extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.manageTrust',
|
||||||
|
title: { value: nls.localize('resetTrustAction', "Manage Trusted Workspaces"), original: 'Manage Trusted Workspaces' },
|
||||||
|
precondition: ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true),
|
||||||
|
f1: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(accessor: ServicesAccessor) {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
editorService.openEditor({ resource: WORKSPACE_TRUST_URI, mode: 'jsonc', options: { pinned: true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(WorkspaceTrustManageAction);
|
||||||
|
|
||||||
// --- Actions Registration
|
// --- Actions Registration
|
||||||
|
|
||||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||||
|
|
|
@ -156,6 +156,19 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: unset;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content::before {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Right aligned */
|
/* Right aligned */
|
||||||
|
|
||||||
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) {
|
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||||
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
|
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||||
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
@ -19,8 +19,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDropEffect } from 'vs/workbench/browser/dnd';
|
import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDropEffect } from 'vs/workbench/browser/dnd';
|
||||||
import { Color } from 'vs/base/common/color';
|
import { Color } from 'vs/base/common/color';
|
||||||
import { Codicon } from 'vs/base/common/codicons';
|
|
||||||
import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
|
||||||
export interface ICompositeActivity {
|
export interface ICompositeActivity {
|
||||||
badge: IBadge;
|
badge: IBadge;
|
||||||
|
@ -288,8 +288,10 @@ export class ActivityActionViewItem extends BaseActionViewItem {
|
||||||
dom.show(this.badge);
|
dom.show(this.badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text
|
// Icon
|
||||||
else if (badge instanceof IconBadge) {
|
else if (badge instanceof IconBadge) {
|
||||||
|
const clazzList = ThemeIcon.asClassNameArray(badge.icon);
|
||||||
|
this.badgeContent.classList.add(...clazzList);
|
||||||
dom.show(this.badge);
|
dom.show(this.badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@ import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/ext
|
||||||
import { timeout } from 'vs/base/common/async';
|
import { timeout } from 'vs/base/common/async';
|
||||||
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
|
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||||
import { OS } from 'vs/base/common/platform';
|
import { OS } from 'vs/base/common/platform';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
|
||||||
|
|
||||||
interface ExperimentSettings {
|
interface ExperimentSettings {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
@ -94,6 +96,7 @@ suite('Experiment Service', () => {
|
||||||
instantiationService.stub(IConfigurationService, testConfigurationService);
|
instantiationService.stub(IConfigurationService, testConfigurationService);
|
||||||
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
||||||
instantiationService.stub(IStorageService, <Partial<IStorageService>>{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } });
|
instantiationService.stub(IStorageService, <Partial<IStorageService>>{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } });
|
||||||
|
instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService());
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
instantiationService.stub(IProductService, {});
|
instantiationService.stub(IProductService, {});
|
||||||
|
|
|
@ -58,7 +58,8 @@ import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOpti
|
||||||
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
|
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
|
||||||
import { infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
import { infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
function getRelativeDateLabel(date: Date): string {
|
function getRelativeDateLabel(date: Date): string {
|
||||||
const delta = new Date().getTime() - date.getTime();
|
const delta = new Date().getTime() - date.getTime();
|
||||||
|
@ -2112,6 +2113,7 @@ export class SystemDisabledWarningAction extends ExtensionAction {
|
||||||
private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} system-disable`;
|
private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} system-disable`;
|
||||||
private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(warningIcon)}`;
|
private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(warningIcon)}`;
|
||||||
private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(infoIcon)}`;
|
private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(infoIcon)}`;
|
||||||
|
private static readonly TRUST_CLASS = `${SystemDisabledWarningAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`;
|
||||||
|
|
||||||
updateWhenCounterExtensionChanges: boolean = true;
|
updateWhenCounterExtensionChanges: boolean = true;
|
||||||
private _runningExtensions: IExtensionDescription[] | null = null;
|
private _runningExtensions: IExtensionDescription[] | null = null;
|
||||||
|
@ -2123,6 +2125,7 @@ export class SystemDisabledWarningAction extends ExtensionAction {
|
||||||
@IExtensionService private readonly extensionService: IExtensionService,
|
@IExtensionService private readonly extensionService: IExtensionService,
|
||||||
@IProductService private readonly productService: IProductService,
|
@IProductService private readonly productService: IProductService,
|
||||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
|
@IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService
|
||||||
) {
|
) {
|
||||||
super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false);
|
super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false);
|
||||||
this._register(this.labelService.onDidChangeFormatters(() => this.update(), this));
|
this._register(this.labelService.onDidChangeFormatters(() => this.update(), this));
|
||||||
|
@ -2188,6 +2191,11 @@ export class SystemDisabledWarningAction extends ExtensionAction {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.workspaceTrustService.isWorkspaceTrustEnabled() && this.extension.enablementState === EnablementState.DisabledByTrustRequirement) {
|
||||||
|
this.class = `${SystemDisabledWarningAction.TRUST_CLASS}`;
|
||||||
|
this.tooltip = localize('extension disabled because of trust requirement', "This extension has been disabled as it requires a trusted workspace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
run(): Promise<any> {
|
run(): Promise<any> {
|
||||||
|
|
|
@ -31,3 +31,4 @@ export const starEmptyIcon = registerIcon('extensions-star-empty', Codicon.starE
|
||||||
|
|
||||||
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
|
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
|
||||||
export const infoIcon = registerIcon('extensions-info-message', Codicon.info, localize('infoIcon', 'Icon shown with an info message in the extensions editor.'));
|
export const infoIcon = registerIcon('extensions-info-message', Codicon.info, localize('infoIcon', 'Icon shown with an info message in the extensions editor.'));
|
||||||
|
export const trustIcon = registerIcon('extension-trust-message', Codicon.shield, localize('trustIcon', 'Icon shown with a message in the extension editor.'));
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { FileAccess } from 'vs/base/common/network';
|
||||||
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||||
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
interface IExtensionStateProvider<T> {
|
interface IExtensionStateProvider<T> {
|
||||||
(extension: Extension): T;
|
(extension: Extension): T;
|
||||||
|
@ -524,7 +525,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||||
@IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService,
|
@IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService,
|
||||||
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
||||||
@IProductService private readonly productService: IProductService,
|
@IProductService private readonly productService: IProductService,
|
||||||
@IContextKeyService contextKeyService: IContextKeyService
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService);
|
this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService);
|
||||||
|
@ -1031,33 +1033,54 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||||
|
|
||||||
private async installFromVSIX(vsix: URI): Promise<IExtension> {
|
private async installFromVSIX(vsix: URI): Promise<IExtension> {
|
||||||
const manifest = await this.extensionManagementService.getManifest(vsix);
|
const manifest = await this.extensionManagementService.getManifest(vsix);
|
||||||
const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) }));
|
return this.promptForTrustIfNeededAndInstall(manifest, async () => {
|
||||||
const { identifier } = await this.extensionManagementService.install(vsix);
|
const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) }));
|
||||||
|
const { identifier } = await this.extensionManagementService.install(vsix);
|
||||||
|
|
||||||
if (existingExtension && existingExtension.latestVersion !== manifest.version) {
|
if (existingExtension && existingExtension.latestVersion !== manifest.version) {
|
||||||
this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(identifier, manifest.version));
|
this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(identifier, manifest.version));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.local.filter(local => areSameExtensions(local.identifier, identifier))[0];
|
return this.local.filter(local => areSameExtensions(local.identifier, identifier))[0];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallOptions): Promise<IExtension> {
|
private async installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallOptions): Promise<IExtension> {
|
||||||
this.installing.push(extension);
|
const manifest = await extension.getManifest(CancellationToken.None);
|
||||||
this._onChange.fire(extension);
|
|
||||||
try {
|
if (manifest) {
|
||||||
if (extension.state === ExtensionState.Installed && extension.local) {
|
this.promptForTrustIfNeededAndInstall(manifest, async () => {
|
||||||
await this.extensionManagementService.updateFromGallery(gallery, extension.local);
|
this.installing.push(extension);
|
||||||
} else {
|
this._onChange.fire(extension);
|
||||||
await this.extensionManagementService.installFromGallery(gallery, installOptions);
|
try {
|
||||||
}
|
if (extension.state === ExtensionState.Installed && extension.local) {
|
||||||
const ids: string[] | undefined = extension.identifier.uuid ? [extension.identifier.uuid] : undefined;
|
await this.extensionManagementService.updateFromGallery(gallery, extension.local);
|
||||||
const names: string[] | undefined = extension.identifier.uuid ? undefined : [extension.identifier.id];
|
} else {
|
||||||
this.queryGallery({ names, ids, pageSize: 1 }, CancellationToken.None);
|
await this.extensionManagementService.installFromGallery(gallery, installOptions);
|
||||||
return this.local.filter(local => areSameExtensions(local.identifier, gallery.identifier))[0];
|
}
|
||||||
} finally {
|
const ids: string[] | undefined = extension.identifier.uuid ? [extension.identifier.uuid] : undefined;
|
||||||
this.installing = this.installing.filter(e => e !== extension);
|
const names: string[] | undefined = extension.identifier.uuid ? undefined : [extension.identifier.id];
|
||||||
this._onChange.fire(this.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]);
|
this.queryGallery({ names, ids, pageSize: 1 }, CancellationToken.None);
|
||||||
|
return this.local.filter(local => areSameExtensions(local.identifier, gallery.identifier))[0];
|
||||||
|
} finally {
|
||||||
|
this.installing = this.installing.filter(e => e !== extension);
|
||||||
|
this._onChange.fire(this.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async promptForTrustIfNeededAndInstall<T>(manifest: IExtensionManifest, installTask: () => Promise<T>): Promise<T> {
|
||||||
|
if (manifest.requiresWorkspaceTrust === 'onStart') {
|
||||||
|
const trustState = await this.workspaceTrustService.requireWorkspaceTrust(
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
message: 'Installing this extension requires you to trust the contents of this workspace.'
|
||||||
|
});
|
||||||
|
return trustState === WorkspaceTrustState.Trusted ? installTask() : Promise.reject();
|
||||||
|
}
|
||||||
|
return installTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<any> {
|
private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise<any> {
|
||||||
|
|
|
@ -55,6 +55,8 @@ import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementServ
|
||||||
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
||||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
|
||||||
|
|
||||||
let instantiationService: TestInstantiationService;
|
let instantiationService: TestInstantiationService;
|
||||||
let installEvent: Emitter<InstallExtensionEvent>,
|
let installEvent: Emitter<InstallExtensionEvent>,
|
||||||
|
@ -131,6 +133,7 @@ async function setupTest() {
|
||||||
instantiationService.stub(IUserDataSyncResourceEnablementService, instantiationService.createInstance(UserDataSyncResourceEnablementService));
|
instantiationService.stub(IUserDataSyncResourceEnablementService, instantiationService.createInstance(UserDataSyncResourceEnablementService));
|
||||||
|
|
||||||
instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
|
instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
|
||||||
|
instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQui
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import { once } from 'vs/base/common/functional';
|
import { once } from 'vs/base/common/functional';
|
||||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history';
|
const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history';
|
||||||
const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt';
|
const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt';
|
||||||
|
@ -255,6 +256,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||||
|
@IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService,
|
||||||
@ILogService private readonly logService: ILogService
|
@ILogService private readonly logService: ILogService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -911,7 +913,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
|
||||||
}).then((value) => {
|
}).then((value) => {
|
||||||
if (runSource === TaskRunSource.User) {
|
if (runSource === TaskRunSource.User) {
|
||||||
this.getWorkspaceTasks().then(workspaceTasks => {
|
this.getWorkspaceTasks().then(workspaceTasks => {
|
||||||
RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, workspaceTasks);
|
RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, this.workspaceTrustService, workspaceTasks);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -14,16 +14,19 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
|
||||||
import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||||
import { Action2 } from 'vs/platform/actions/common/actions';
|
import { Action2 } from 'vs/platform/actions/common/actions';
|
||||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
|
const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
|
||||||
|
|
||||||
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
|
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
|
||||||
constructor(
|
constructor(
|
||||||
@ITaskService private readonly taskService: ITaskService,
|
@ITaskService private readonly taskService: ITaskService,
|
||||||
@IStorageService storageService: IStorageService) {
|
@IStorageService storageService: IStorageService,
|
||||||
|
@IWorkspaceTrustService workspaceTrustService: IWorkspaceTrustService) {
|
||||||
super();
|
super();
|
||||||
const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
|
const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
|
||||||
this.tryRunTasks(isFolderAutomaticAllowed);
|
const isWorkspaceTrusted = workspaceTrustService.getWorkspaceTrustState() === WorkspaceTrustState.Trusted;
|
||||||
|
this.tryRunTasks(isFolderAutomaticAllowed && isWorkspaceTrusted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryRunTasks(isAllowed: boolean | undefined) {
|
private tryRunTasks(isAllowed: boolean | undefined) {
|
||||||
|
@ -84,8 +87,13 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
|
||||||
return { tasks, taskNames };
|
return { tasks, taskNames };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService,
|
public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustService: IWorkspaceTrustService,
|
||||||
workspaceTaskResult: Map<string, WorkspaceFolderTaskResult>) {
|
workspaceTaskResult: Map<string, WorkspaceFolderTaskResult>) {
|
||||||
|
const isWorkspaceTrusted = await workspaceTrustService.requireWorkspaceTrust({ immediate: false }) === WorkspaceTrustState.Trusted;
|
||||||
|
if (!isWorkspaceTrusted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
|
const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
|
||||||
if (isFolderAutomaticAllowed !== undefined) {
|
if (isFolderAutomaticAllowed !== undefined) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
interface WorkspaceFolderConfigurationResult {
|
interface WorkspaceFolderConfigurationResult {
|
||||||
workspaceFolder: IWorkspaceFolder;
|
workspaceFolder: IWorkspaceFolder;
|
||||||
|
@ -85,6 +86,7 @@ export class TaskService extends AbstractTaskService {
|
||||||
@ITextModelService textModelResolverService: ITextModelService,
|
@ITextModelService textModelResolverService: ITextModelService,
|
||||||
@IPreferencesService preferencesService: IPreferencesService,
|
@IPreferencesService preferencesService: IPreferencesService,
|
||||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||||
|
@IWorkspaceTrustService workspaceTrustService: IWorkspaceTrustService,
|
||||||
@ILogService logService: ILogService) {
|
@ILogService logService: ILogService) {
|
||||||
super(configurationService,
|
super(configurationService,
|
||||||
markerService,
|
markerService,
|
||||||
|
@ -115,6 +117,7 @@ export class TaskService extends AbstractTaskService {
|
||||||
textModelResolverService,
|
textModelResolverService,
|
||||||
preferencesService,
|
preferencesService,
|
||||||
viewDescriptorService,
|
viewDescriptorService,
|
||||||
|
workspaceTrustService,
|
||||||
logService);
|
logService);
|
||||||
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
|
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,335 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
|
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
|
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { Severity } from 'vs/platform/notification/common/notification';
|
||||||
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
import { IWorkspaceTrustService, WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI, WorkspaceTrustState, WorkspaceTrustStateChangeEvent, workspaceTrustStateToString } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||||
|
import { IActivityService, IconBadge } from 'vs/workbench/services/activity/common/activity';
|
||||||
|
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||||
|
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
import { ThemeColor } from 'vs/workbench/api/common/extHostTypes';
|
||||||
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { WorkspaceTrustFileSystemProvider } from 'vs/workbench/contrib/workspace/common/workspaceTrustFileSystemProvider';
|
||||||
|
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
|
||||||
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
|
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||||
|
|
||||||
|
const workspaceTrustIcon = registerIcon('workspace-trust-icon', Codicon.shield, localize('workspaceTrustIcon', "Icon for workspace trust badge."));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trust Request UX Handler
|
||||||
|
*/
|
||||||
|
export class WorkspaceTrustRequestHandler extends Disposable implements IWorkbenchContribution {
|
||||||
|
private readonly requestModel = this.workspaceTrustService.requestModel;
|
||||||
|
private readonly badgeDisposable = this._register(new MutableDisposable());
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IHostService private readonly hostService: IHostService,
|
||||||
|
@IDialogService private readonly dialogService: IDialogService,
|
||||||
|
@IActivityService private readonly activityService: IActivityService,
|
||||||
|
@ICommandService private readonly commandService: ICommandService,
|
||||||
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
|
@IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleRequestBadge(visible: boolean): void {
|
||||||
|
this.badgeDisposable.clear();
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
this.badgeDisposable.value = this.activityService.showGlobalActivity({
|
||||||
|
badge: new IconBadge(workspaceTrustIcon, () => localize('requestTrustIconText', "Some features require workspace trust.")),
|
||||||
|
priority: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerListeners(): void {
|
||||||
|
this._register(this.requestModel.onDidInitiateRequest(async () => {
|
||||||
|
if (this.requestModel.trustRequest) {
|
||||||
|
this.toggleRequestBadge(true);
|
||||||
|
|
||||||
|
if (this.requestModel.trustRequest.immediate) {
|
||||||
|
const result = await this.dialogService.show(
|
||||||
|
Severity.Warning,
|
||||||
|
localize('immediateTrustRequestTitle', "Do you trust the files in this folder?"),
|
||||||
|
[
|
||||||
|
localize('grantWorkspaceTrustButton', "Trust"),
|
||||||
|
localize('denyWorkspaceTrustButton', "Don't Trust"),
|
||||||
|
localize('manageWorkspaceTrustButton', "Manage"),
|
||||||
|
localize('cancelWorkspaceTrustButton', "Cancel"),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
cancelId: 3,
|
||||||
|
detail: localize('immediateTrustRequestDetail', "A feature you are trying to use may be a security risk if you do not trust the source of the files or folders you currently have open.\n\nYou should only trust this workspace if you trust its source. Otherwise, features will be enabled that may compromise your device or personal information."),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (result.choice) {
|
||||||
|
case 0: // Trust
|
||||||
|
this.requestModel.completeRequest(WorkspaceTrustState.Trusted);
|
||||||
|
break;
|
||||||
|
case 1: // Don't Trust
|
||||||
|
this.requestModel.completeRequest(WorkspaceTrustState.Untrusted);
|
||||||
|
break;
|
||||||
|
case 2: // Manage
|
||||||
|
this.requestModel.completeRequest(undefined);
|
||||||
|
await this.commandService.executeCommand('workbench.trust.manage');
|
||||||
|
break;
|
||||||
|
default: // Cancel
|
||||||
|
this.requestModel.completeRequest(undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this.requestModel.onDidCompleteRequest(trustState => {
|
||||||
|
if (trustState !== undefined && trustState !== WorkspaceTrustState.Unknown) {
|
||||||
|
this.toggleRequestBadge(false);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||||
|
if (e.affectsConfiguration(WORKSPACE_TRUST_ENABLED)) {
|
||||||
|
const isEnabled = this.configurationService.getValue<boolean>(WORKSPACE_TRUST_ENABLED);
|
||||||
|
if (!isEnabled || typeof isEnabled === 'boolean') {
|
||||||
|
this.dialogService.confirm({
|
||||||
|
message: localize('trustConfigurationChangeMessage', "In order for this change to take effect, the window needs to be reloaded. Do you want to reload the window now?")
|
||||||
|
}).then(result => {
|
||||||
|
if (result.confirmed) {
|
||||||
|
this.hostService.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustRequestHandler, LifecyclePhase.Ready);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Status Bar Entry
|
||||||
|
*/
|
||||||
|
class WorkspaceTrustStatusbarItem extends Disposable implements IWorkbenchContribution {
|
||||||
|
private static readonly ID = 'status.workspaceTrust';
|
||||||
|
private readonly statusBarEntryAccessor: MutableDisposable<IStatusbarEntryAccessor>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
||||||
|
@IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.statusBarEntryAccessor = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||||
|
|
||||||
|
if (this.workspaceTrustService.isWorkspaceTrustEnabled()) {
|
||||||
|
const entry = this.getStatusbarEntry(this.workspaceTrustService.getWorkspaceTrustState());
|
||||||
|
this.statusBarEntryAccessor.value = this.statusbarService.addEntry(entry, WorkspaceTrustStatusbarItem.ID, localize('status.WorkspaceTrust', "Workspace Trust"), StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */);
|
||||||
|
this._register(this.workspaceTrustService.onDidChangeTrustState(trustState => this.updateStatusbarEntry(trustState)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStatusbarEntry(state: WorkspaceTrustState): IStatusbarEntry {
|
||||||
|
const text = workspaceTrustStateToString(state);
|
||||||
|
const backgroundColor = state === WorkspaceTrustState.Trusted ?
|
||||||
|
'transparent' : new ThemeColor('statusBarItem.prominentBackground');
|
||||||
|
const color = state === WorkspaceTrustState.Trusted ? '#00dd3b' : '#ff5462';
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: state === WorkspaceTrustState.Trusted ? `$(shield)` : `$(shield) ${text}`,
|
||||||
|
ariaLabel: localize('status.WorkspaceTrust', "Workspace Trust"),
|
||||||
|
tooltip: localize('status.WorkspaceTrust', "Workspace Trust"),
|
||||||
|
command: 'workbench.trust.manage',
|
||||||
|
backgroundColor,
|
||||||
|
color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateStatusbarEntry(trustState: WorkspaceTrustStateChangeEvent): void {
|
||||||
|
this.statusBarEntryAccessor.value?.update(this.getStatusbarEntry(trustState.currentTrustState));
|
||||||
|
this.statusbarService.updateEntryVisibility(WorkspaceTrustStatusbarItem.ID, trustState.currentTrustState !== WorkspaceTrustState.Unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
|
||||||
|
WorkspaceTrustStatusbarItem,
|
||||||
|
LifecyclePhase.Starting
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trusted Workspace JSON Editor
|
||||||
|
*/
|
||||||
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
|
||||||
|
WorkspaceTrustFileSystemProvider,
|
||||||
|
LifecyclePhase.Ready
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Actions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Grant Workspace Trust
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.trust.grant',
|
||||||
|
title: {
|
||||||
|
original: 'Grant Workspace Trust',
|
||||||
|
value: localize('grantWorkspaceTrust', "Grant Workspace Trust")
|
||||||
|
},
|
||||||
|
category: localize('workspacesCategory', "Workspaces"),
|
||||||
|
f1: true,
|
||||||
|
precondition: WorkspaceTrustContext.TrustState.isEqualTo(WorkspaceTrustState.Trusted).negate(),
|
||||||
|
menu: {
|
||||||
|
id: MenuId.GlobalActivity,
|
||||||
|
when: WorkspaceTrustContext.PendingRequest,
|
||||||
|
group: '7_trust',
|
||||||
|
order: 10
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor) {
|
||||||
|
const dialogService = accessor.get(IDialogService);
|
||||||
|
const workspaceTrustService = accessor.get(IWorkspaceTrustService);
|
||||||
|
|
||||||
|
const result = await dialogService.confirm({
|
||||||
|
message: localize('grantWorkspaceTrust', "Grant Workspace Trust"),
|
||||||
|
detail: localize('confirmGrantWorkspaceTrust', "Granting trust to the workspace will enable features that may pose a security risk if the contents of the workspace cannot be trusted. Are you sure you want to trust this workspace?"),
|
||||||
|
primaryButton: localize('yes', 'Yes'),
|
||||||
|
secondaryButton: localize('no', 'No')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.confirmed) {
|
||||||
|
workspaceTrustService.requestModel.completeRequest(WorkspaceTrustState.Trusted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deny Workspace Trust
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.trust.deny',
|
||||||
|
title: {
|
||||||
|
original: 'Deny Workspace Trust',
|
||||||
|
value: localize('denyWorkspaceTrust', "Deny Workspace Trust")
|
||||||
|
},
|
||||||
|
category: localize('workspacesCategory', "Workspaces"),
|
||||||
|
f1: true,
|
||||||
|
precondition: WorkspaceTrustContext.TrustState.isEqualTo(WorkspaceTrustState.Untrusted).negate(),
|
||||||
|
menu: {
|
||||||
|
id: MenuId.GlobalActivity,
|
||||||
|
when: WorkspaceTrustContext.PendingRequest,
|
||||||
|
group: '7_trust',
|
||||||
|
order: 20
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor) {
|
||||||
|
const dialogService = accessor.get(IDialogService);
|
||||||
|
const workspaceTrustService = accessor.get(IWorkspaceTrustService);
|
||||||
|
|
||||||
|
const result = await dialogService.confirm({
|
||||||
|
message: localize('denyWorkspaceTrust', "Deny Workspace Trust"),
|
||||||
|
detail: localize('confirmDenyWorkspaceTrust', "Denying trust to the workspace will disable features that may pose a security risk if the contents of the workspace cannot be trusted. Are you sure you want to deny trust to this workspace?"),
|
||||||
|
primaryButton: localize('yes', 'Yes'),
|
||||||
|
secondaryButton: localize('no', 'No')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.confirmed) {
|
||||||
|
workspaceTrustService.requestModel.completeRequest(WorkspaceTrustState.Untrusted);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset Workspace Trust
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.trust.reset',
|
||||||
|
title: {
|
||||||
|
original: 'Reset Workspace Trust',
|
||||||
|
value: localize('reset', "Reset Workspace Trust")
|
||||||
|
},
|
||||||
|
category: localize('workspacesCategory', "Workspaces"),
|
||||||
|
f1: true,
|
||||||
|
precondition: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('empty').negate(), WorkspaceTrustContext.TrustState.isEqualTo(WorkspaceTrustState.Unknown).negate())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor) {
|
||||||
|
const dialogService = accessor.get(IDialogService);
|
||||||
|
const workspaceTrustService = accessor.get(IWorkspaceTrustService);
|
||||||
|
|
||||||
|
const result = await dialogService.confirm({
|
||||||
|
message: localize('reset', "Reset Workspace Trust"),
|
||||||
|
detail: localize('confirmResetWorkspaceTrust', "Resetting workspace trust to the workspace will disable features that may pose a security risk if the contents of the workspace cannot be trusted. Are you sure you want to reset trust this workspace?"),
|
||||||
|
primaryButton: localize('yesGrant', 'Yes'),
|
||||||
|
secondaryButton: localize('noGrant', 'No')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.confirmed) {
|
||||||
|
workspaceTrustService.resetWorkspaceTrust();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manage Workspace Trust
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.trust.manage',
|
||||||
|
title: {
|
||||||
|
original: 'Manage Trusted Workspaces',
|
||||||
|
value: localize('manageWorkspaceTrust', "Manage Trusted Workspaces")
|
||||||
|
},
|
||||||
|
category: localize('workspacesCategory', "Workspaces"),
|
||||||
|
menu: {
|
||||||
|
id: MenuId.GlobalActivity,
|
||||||
|
group: '7_trust',
|
||||||
|
order: 40,
|
||||||
|
when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest.negate())
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(accessor: ServicesAccessor) {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
editorService.openEditor({ resource: WORKSPACE_TRUST_URI, mode: 'jsonc', options: { pinned: true } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||||
|
command: {
|
||||||
|
id: 'workbench.trust.manage',
|
||||||
|
title: localize('manageWorkspaceTrustPending', "Manage Trusted Workspaces (1)"),
|
||||||
|
},
|
||||||
|
group: '7_trust',
|
||||||
|
order: 40,
|
||||||
|
when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest)
|
||||||
|
});
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IStat, IWatchOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files';
|
||||||
|
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||||
|
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||||
|
import { VSBuffer } from 'vs/base/common/buffer';
|
||||||
|
import { WORKSPACE_TRUST_STORAGE_KEY } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
|
const WORKSPACE_TRUST_SCHEMA = 'workspaceTrust';
|
||||||
|
|
||||||
|
const TRUSTED_WORKSPACES_STAT: IStat = {
|
||||||
|
type: FileType.File,
|
||||||
|
ctime: Date.now(),
|
||||||
|
mtime: Date.now(),
|
||||||
|
size: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const PREPENDED_TEXT = `// The following file is a placeholder UX for managing trusted workspaces. It will be replaced by a rich editor and provide
|
||||||
|
// additonal information about what trust means. e.g. enabling trust will unblock automatic tasks on startup and list the tasks. It will enable certain extensions
|
||||||
|
// and list the extensions with associated functionality.
|
||||||
|
`;
|
||||||
|
|
||||||
|
export class WorkspaceTrustFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IWorkbenchContribution {
|
||||||
|
readonly capabilities = FileSystemProviderCapabilities.FileReadWrite;
|
||||||
|
|
||||||
|
readonly onDidChangeCapabilities = Event.None;
|
||||||
|
readonly onDidChangeFile = Event.None;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IFileService private readonly fileService: IFileService,
|
||||||
|
@IStorageService private readonly storageService: IStorageService,
|
||||||
|
) {
|
||||||
|
this.fileService.registerProvider(WORKSPACE_TRUST_SCHEMA, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
stat(resource: URI): Promise<IStat> {
|
||||||
|
return Promise.resolve(TRUSTED_WORKSPACES_STAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(resource: URI): Promise<Uint8Array> {
|
||||||
|
let workspacesTrustContent = this.storageService.get(WORKSPACE_TRUST_STORAGE_KEY, StorageScope.GLOBAL);
|
||||||
|
|
||||||
|
let objectForm = {};
|
||||||
|
try {
|
||||||
|
objectForm = JSON.parse(workspacesTrustContent || '{}');
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
const buffer = VSBuffer.fromString(PREPENDED_TEXT + JSON.stringify(objectForm, undefined, 2)).buffer;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
|
||||||
|
try {
|
||||||
|
const workspacesTrustContent = VSBuffer.wrap(content).toString().replace(PREPENDED_TEXT, '');
|
||||||
|
this.storageService.store(WORKSPACE_TRUST_STORAGE_KEY, workspacesTrustContent, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||||
|
} catch (err) { }
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(resource: URI, opts: IWatchOptions): IDisposable {
|
||||||
|
return {
|
||||||
|
dispose() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
mkdir(resource: URI): Promise<void> {
|
||||||
|
return Promise.resolve(undefined!);
|
||||||
|
}
|
||||||
|
readdir(resource: URI): Promise<[string, FileType][]> {
|
||||||
|
return Promise.resolve(undefined!);
|
||||||
|
}
|
||||||
|
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
||||||
|
return Promise.resolve(undefined!);
|
||||||
|
}
|
||||||
|
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||||
|
return Promise.resolve(undefined!);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
export interface IActivity {
|
export interface IActivity {
|
||||||
readonly badge: IBadge;
|
readonly badge: IBadge;
|
||||||
|
@ -75,8 +76,7 @@ export class TextBadge extends BaseBadge {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IconBadge extends BaseBadge {
|
export class IconBadge extends BaseBadge {
|
||||||
|
constructor(public readonly icon: ThemeIcon, descriptorFn: () => string) {
|
||||||
constructor(descriptorFn: () => string) {
|
|
||||||
super(descriptorFn);
|
super(descriptorFn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionIdentifier, IGlobalExtensionEnablementService, ENABLED_EXTENSIONS_STORAGE_PATH, DISABLED_EXTENSIONS_STORAGE_PATH } from 'vs/platform/extensionManagement/common/extensionManagement';
|
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionIdentifier, IGlobalExtensionEnablementService, ENABLED_EXTENSIONS_STORAGE_PATH, DISABLED_EXTENSIONS_STORAGE_PATH, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||||
|
@ -25,6 +25,7 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc
|
||||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect';
|
import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect';
|
||||||
|
import { WorkspaceTrustStateChangeEvent, IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
import { Promises } from 'vs/base/common/async';
|
import { Promises } from 'vs/base/common/async';
|
||||||
|
|
||||||
const SOURCE = 'IWorkbenchExtensionEnablementService';
|
const SOURCE = 'IWorkbenchExtensionEnablementService';
|
||||||
|
@ -37,6 +38,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
public readonly onEnablementChanged: Event<readonly IExtension[]> = this._onEnablementChanged.event;
|
public readonly onEnablementChanged: Event<readonly IExtension[]> = this._onEnablementChanged.event;
|
||||||
|
|
||||||
private readonly storageManger: StorageManager;
|
private readonly storageManger: StorageManager;
|
||||||
|
private extensionsDisabledByTrustRequirement: IExtension[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IStorageService storageService: IStorageService,
|
@IStorageService storageService: IStorageService,
|
||||||
|
@ -51,13 +53,24 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
|
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
|
||||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||||
@INotificationService private readonly notificationService: INotificationService,
|
@INotificationService private readonly notificationService: INotificationService,
|
||||||
@IHostService hostService: IHostService,
|
@IHostService private readonly hostService: IHostService,
|
||||||
@IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService,
|
@IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService,
|
||||||
|
@IWorkspaceTrustService private readonly workspaceTrustService: IWorkspaceTrustService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.storageManger = this._register(new StorageManager(storageService));
|
this.storageManger = this._register(new StorageManager(storageService));
|
||||||
this._register(this.globalExtensionEnablementService.onDidChangeEnablement(({ extensions, source }) => this.onDidChangeExtensions(extensions, source)));
|
this._register(this.globalExtensionEnablementService.onDidChangeEnablement(({ extensions, source }) => this.onDidChangeExtensions(extensions, source)));
|
||||||
|
this._register(extensionManagementService.onDidInstallExtension(this._onDidInstallExtension, this));
|
||||||
this._register(extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this));
|
this._register(extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this));
|
||||||
|
this._register(this.workspaceTrustService.onDidChangeTrustState(this._onDidChangeTrustState, this));
|
||||||
|
|
||||||
|
// Trusted extensions notification
|
||||||
|
// TODO: Confirm that this is the right lifecycle phase
|
||||||
|
this.lifecycleService.when(LifecyclePhase.Eventually).then(() => {
|
||||||
|
if (this.extensionsDisabledByTrustRequirement.length > 0) {
|
||||||
|
this.workspaceTrustService.requireWorkspaceTrust({ immediate: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// delay notification for extensions disabled until workbench restored
|
// delay notification for extensions disabled until workbench restored
|
||||||
if (this.allUserExtensionsDisabled) {
|
if (this.allUserExtensionsDisabled) {
|
||||||
|
@ -88,6 +101,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
if (this._isDisabledByExtensionKind(extension)) {
|
if (this._isDisabledByExtensionKind(extension)) {
|
||||||
return EnablementState.DisabledByExtensionKind;
|
return EnablementState.DisabledByExtensionKind;
|
||||||
}
|
}
|
||||||
|
if (this._isEnabled(extension) && this._isDisabledByTrustRequirement(extension)) {
|
||||||
|
return EnablementState.DisabledByTrustRequirement;
|
||||||
|
}
|
||||||
return this._getEnablementState(extension.identifier);
|
return this._getEnablementState(extension.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +163,23 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await Promises.settled(extensions.map(e => this._setEnablement(e, newState)));
|
const result = await Promises.settled(extensions.map(e => {
|
||||||
|
if (this._isDisabledByTrustRequirement(e)) {
|
||||||
|
return this.workspaceTrustService.requireWorkspaceTrust({
|
||||||
|
immediate: true,
|
||||||
|
message: 'Enabling this extension requires you to trust the contents of this workspace.'
|
||||||
|
}).then(trustState => {
|
||||||
|
if (trustState === WorkspaceTrustState.Trusted) {
|
||||||
|
return this._setEnablement(e, newState);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this._setEnablement(e, newState);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const changedExtensions = extensions.filter((e, index) => result[index]);
|
const changedExtensions = extensions.filter((e, index) => result[index]);
|
||||||
if (changedExtensions.length) {
|
if (changedExtensions.length) {
|
||||||
this._onEnablementChanged.fire(changedExtensions);
|
this._onEnablementChanged.fire(changedExtensions);
|
||||||
|
@ -186,6 +218,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
return enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;
|
return enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _isEnabled(extension: IExtension): boolean {
|
||||||
|
const enablementState = this._getEnablementState(extension.identifier);
|
||||||
|
return enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;
|
||||||
|
}
|
||||||
|
|
||||||
isDisabledGlobally(extension: IExtension): boolean {
|
isDisabledGlobally(extension: IExtension): boolean {
|
||||||
return this._isDisabledGlobally(extension.identifier);
|
return this._isDisabledGlobally(extension.identifier);
|
||||||
}
|
}
|
||||||
|
@ -234,6 +271,18 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _isDisabledByTrustRequirement(extension: IExtension): boolean {
|
||||||
|
const workspaceTrustState = this.workspaceTrustService.getWorkspaceTrustState();
|
||||||
|
|
||||||
|
if (extension.manifest.requiresWorkspaceTrust === 'onStart') {
|
||||||
|
if (workspaceTrustState !== WorkspaceTrustState.Trusted) {
|
||||||
|
this._addToWorkspaceDisabledExtensionsByTrustRequirement(extension);
|
||||||
|
}
|
||||||
|
return workspaceTrustState !== WorkspaceTrustState.Trusted;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private _getEnablementState(identifier: IExtensionIdentifier): EnablementState {
|
private _getEnablementState(identifier: IExtensionIdentifier): EnablementState {
|
||||||
if (this.hasWorkspace) {
|
if (this.hasWorkspace) {
|
||||||
if (this._getWorkspaceEnabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {
|
if (this._getWorkspaceEnabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {
|
||||||
|
@ -334,6 +383,26 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addToWorkspaceDisabledExtensionsByTrustRequirement(extension: IExtension): void {
|
||||||
|
if (this.extensionsDisabledByTrustRequirement.every(e => !areSameExtensions(extension.identifier, e.identifier))) {
|
||||||
|
this.extensionsDisabledByTrustRequirement.push(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeFromWorkspaceDisabledExtensionsByTrustRequirement(identifier: IExtensionIdentifier): void {
|
||||||
|
let index = -1;
|
||||||
|
for (let i = 0; i < this.extensionsDisabledByTrustRequirement.length; i++) {
|
||||||
|
const disabledExtension = this.extensionsDisabledByTrustRequirement[i];
|
||||||
|
if (areSameExtensions(disabledExtension.identifier, identifier)) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index !== -1) {
|
||||||
|
this.extensionsDisabledByTrustRequirement.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected _getWorkspaceEnabledExtensions(): IExtensionIdentifier[] {
|
protected _getWorkspaceEnabledExtensions(): IExtensionIdentifier[] {
|
||||||
return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH);
|
return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH);
|
||||||
}
|
}
|
||||||
|
@ -369,6 +438,30 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _onDidChangeTrustState(state: WorkspaceTrustStateChangeEvent): Promise<void> {
|
||||||
|
if (state.previousTrustState === WorkspaceTrustState.Trusted && (
|
||||||
|
state.currentTrustState === WorkspaceTrustState.Untrusted ||
|
||||||
|
state.currentTrustState === WorkspaceTrustState.Unknown)) {
|
||||||
|
// Reload window
|
||||||
|
this.hostService.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.currentTrustState === WorkspaceTrustState.Trusted) {
|
||||||
|
// Enable extensions
|
||||||
|
this._onEnablementChanged.fire(this.extensionsDisabledByTrustRequirement);
|
||||||
|
this.extensionsDisabledByTrustRequirement = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDidInstallExtension({ local, error }: DidInstallExtensionEvent): void {
|
||||||
|
if (local && !error && this._isDisabledByTrustRequirement(local)) {
|
||||||
|
this.workspaceTrustService.requireWorkspaceTrust({
|
||||||
|
immediate: true,
|
||||||
|
message: 'Enabling this extension requires you to trust the contents of this workspace.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
|
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
this._reset(identifier);
|
this._reset(identifier);
|
||||||
|
@ -378,6 +471,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||||
private _reset(extension: IExtensionIdentifier) {
|
private _reset(extension: IExtensionIdentifier) {
|
||||||
this._removeFromWorkspaceDisabledExtensions(extension);
|
this._removeFromWorkspaceDisabledExtensions(extension);
|
||||||
this._removeFromWorkspaceEnabledExtensions(extension);
|
this._removeFromWorkspaceEnabledExtensions(extension);
|
||||||
|
this._removeFromWorkspaceDisabledExtensionsByTrustRequirement(extension);
|
||||||
this.globalExtensionEnablementService.enableExtension(extension);
|
this.globalExtensionEnablementService.enableExtension(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export interface IWorkbenchExtensioManagementService extends IExtensionManagemen
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum EnablementState {
|
export const enum EnablementState {
|
||||||
|
DisabledByTrustRequirement,
|
||||||
DisabledByExtensionKind,
|
DisabledByExtensionKind,
|
||||||
DisabledByEnvironment,
|
DisabledByEnvironment,
|
||||||
DisabledGlobally,
|
DisabledGlobally,
|
||||||
|
|
|
@ -30,6 +30,8 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
|
||||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { mock } from 'vs/base/test/common/mock';
|
import { mock } from 'vs/base/test/common/mock';
|
||||||
import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect';
|
import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
|
||||||
|
|
||||||
function createStorageService(instantiationService: TestInstantiationService): IStorageService {
|
function createStorageService(instantiationService: TestInstantiationService): IStorageService {
|
||||||
let service = instantiationService.get(IStorageService);
|
let service = instantiationService.get(IStorageService);
|
||||||
|
@ -64,7 +66,8 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
|
||||||
instantiationService.get(ILifecycleService) || instantiationService.stub(ILifecycleService, new TestLifecycleService()),
|
instantiationService.get(ILifecycleService) || instantiationService.stub(ILifecycleService, new TestLifecycleService()),
|
||||||
instantiationService.get(INotificationService) || instantiationService.stub(INotificationService, new TestNotificationService()),
|
instantiationService.get(INotificationService) || instantiationService.stub(INotificationService, new TestNotificationService()),
|
||||||
instantiationService.get(IHostService),
|
instantiationService.get(IHostService),
|
||||||
new class extends mock<IExtensionBisectService>() { isDisabledByBisect() { return false; } }
|
new class extends mock<IExtensionBisectService>() { isDisabledByBisect() { return false; } },
|
||||||
|
instantiationService.get(IWorkspaceTrustService) || instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,12 +91,17 @@ suite('ExtensionEnablementService Test', () => {
|
||||||
let instantiationService: TestInstantiationService;
|
let instantiationService: TestInstantiationService;
|
||||||
let testObject: IWorkbenchExtensionEnablementService;
|
let testObject: IWorkbenchExtensionEnablementService;
|
||||||
|
|
||||||
|
const didInstallEvent = new Emitter<DidInstallExtensionEvent>();
|
||||||
const didUninstallEvent = new Emitter<DidUninstallExtensionEvent>();
|
const didUninstallEvent = new Emitter<DidUninstallExtensionEvent>();
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
instantiationService = new TestInstantiationService();
|
instantiationService = new TestInstantiationService();
|
||||||
instantiationService.stub(IConfigurationService, new TestConfigurationService());
|
instantiationService.stub(IConfigurationService, new TestConfigurationService());
|
||||||
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve([] as ILocalExtension[]) } as IExtensionManagementService);
|
instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
||||||
|
onDidInstallExtension: didInstallEvent.event,
|
||||||
|
onDidUninstallExtension: didUninstallEvent.event,
|
||||||
|
getInstalled: () => Promise.resolve([] as ILocalExtension[])
|
||||||
|
});
|
||||||
instantiationService.stub(IExtensionManagementServerService, <IExtensionManagementServerService>{
|
instantiationService.stub(IExtensionManagementServerService, <IExtensionManagementServerService>{
|
||||||
localExtensionManagementServer: {
|
localExtensionManagementServer: {
|
||||||
extensionManagementService: instantiationService.get(IExtensionManagementService)
|
extensionManagementService: instantiationService.get(IExtensionManagementService)
|
||||||
|
@ -455,7 +463,11 @@ suite('ExtensionEnablementService Test', () => {
|
||||||
test('test extension is disabled when disabled in environment', async () => {
|
test('test extension is disabled when disabled in environment', async () => {
|
||||||
const extension = aLocalExtension('pub.a');
|
const extension = aLocalExtension('pub.a');
|
||||||
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService);
|
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService);
|
||||||
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService);
|
instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
||||||
|
onDidInstallExtension: didInstallEvent.event,
|
||||||
|
onDidUninstallExtension: didUninstallEvent.event,
|
||||||
|
getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')])
|
||||||
|
});
|
||||||
testObject = new TestExtensionEnablementService(instantiationService);
|
testObject = new TestExtensionEnablementService(instantiationService);
|
||||||
assert.ok(!testObject.isEnabled(extension));
|
assert.ok(!testObject.isEnabled(extension));
|
||||||
assert.deepEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment);
|
assert.deepEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment);
|
||||||
|
|
|
@ -270,6 +270,16 @@ export function throwProposedApiError(extension: IExtensionDescription): never {
|
||||||
throw new Error(`[${extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
|
throw new Error(`[${extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkRequiresWorkspaceTrust(extension: IExtensionDescription): void {
|
||||||
|
if (!extension.requiresWorkspaceTrust) {
|
||||||
|
throwRequiresWorkspaceTrustError(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function throwRequiresWorkspaceTrustError(extension: IExtensionDescription): void {
|
||||||
|
throw new Error(`[${extension.identifier.value}]: This API is only available when the "requiresWorkspaceTrust" is set to "onStart" or "onDemand" in the extension's package.json.`);
|
||||||
|
}
|
||||||
|
|
||||||
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
|
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
|
||||||
return {
|
return {
|
||||||
type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
|
type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
||||||
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||||
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||||
import { isLinux } from 'vs/base/common/platform';
|
import { isLinux } from 'vs/base/common/platform';
|
||||||
|
import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
suite('ExtHostConfiguration', function () {
|
suite('ExtHostConfiguration', function () {
|
||||||
|
|
||||||
|
@ -318,7 +319,7 @@ suite('ExtHostConfiguration', function () {
|
||||||
'id': 'foo',
|
'id': 'foo',
|
||||||
'folders': [aWorkspaceFolder(URI.file('foo'), 0)],
|
'folders': [aWorkspaceFolder(URI.file('foo'), 0)],
|
||||||
'name': 'foo'
|
'name': 'foo'
|
||||||
});
|
}, WorkspaceTrustState.Trusted);
|
||||||
const testObject = new ExtHostConfigProvider(
|
const testObject = new ExtHostConfigProvider(
|
||||||
new class extends mock<MainThreadConfigurationShape>() { },
|
new class extends mock<MainThreadConfigurationShape>() { },
|
||||||
extHostWorkspace,
|
extHostWorkspace,
|
||||||
|
@ -394,7 +395,7 @@ suite('ExtHostConfiguration', function () {
|
||||||
'id': 'foo',
|
'id': 'foo',
|
||||||
'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)],
|
'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)],
|
||||||
'name': 'foo'
|
'name': 'foo'
|
||||||
});
|
}, WorkspaceTrustState.Trusted);
|
||||||
const testObject = new ExtHostConfigProvider(
|
const testObject = new ExtHostConfigProvider(
|
||||||
new class extends mock<MainThreadConfigurationShape>() { },
|
new class extends mock<MainThreadConfigurationShape>() { },
|
||||||
extHostWorkspace,
|
extHostWorkspace,
|
||||||
|
@ -497,7 +498,7 @@ suite('ExtHostConfiguration', function () {
|
||||||
'id': 'foo',
|
'id': 'foo',
|
||||||
'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)],
|
'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)],
|
||||||
'name': 'foo'
|
'name': 'foo'
|
||||||
});
|
}, WorkspaceTrustState.Trusted);
|
||||||
const testObject = new ExtHostConfigProvider(
|
const testObject = new ExtHostConfigProvider(
|
||||||
new class extends mock<MainThreadConfigurationShape>() { },
|
new class extends mock<MainThreadConfigurationShape>() { },
|
||||||
extHostWorkspace,
|
extHostWorkspace,
|
||||||
|
@ -675,7 +676,7 @@ suite('ExtHostConfiguration', function () {
|
||||||
'id': 'foo',
|
'id': 'foo',
|
||||||
'folders': [workspaceFolder],
|
'folders': [workspaceFolder],
|
||||||
'name': 'foo'
|
'name': 'foo'
|
||||||
});
|
}, WorkspaceTrustState.Trusted);
|
||||||
const testObject = new ExtHostConfigProvider(
|
const testObject = new ExtHostConfigProvider(
|
||||||
new class extends mock<MainThreadConfigurationShape>() { },
|
new class extends mock<MainThreadConfigurationShape>() { },
|
||||||
extHostWorkspace,
|
extHostWorkspace,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { IPatternInfo } from 'vs/workbench/services/search/common/search';
|
||||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||||
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
|
||||||
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||||
|
import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
|
||||||
function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace {
|
function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace {
|
||||||
const result = new ExtHostWorkspace(
|
const result = new ExtHostWorkspace(
|
||||||
|
@ -31,7 +32,7 @@ function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData,
|
||||||
new class extends mock<IExtHostFileSystemInfo>() { getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } },
|
new class extends mock<IExtHostFileSystemInfo>() { getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } },
|
||||||
logService,
|
logService,
|
||||||
);
|
);
|
||||||
result.$initializeWorkspace(data);
|
result.$initializeWorkspace(data, WorkspaceTrustState.Trusted);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,8 @@ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorIn
|
||||||
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
|
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
|
||||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||||
import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||||
|
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||||
|
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
|
||||||
|
|
||||||
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||||
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined);
|
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined);
|
||||||
|
@ -223,6 +225,7 @@ export function workbenchInstantiationService(
|
||||||
instantiationService.stub(IListService, new TestListService());
|
instantiationService.stub(IListService, new TestListService());
|
||||||
instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService)));
|
instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService)));
|
||||||
instantiationService.stub(IWorkspacesService, new TestWorkspacesService());
|
instantiationService.stub(IWorkspacesService, new TestWorkspacesService());
|
||||||
|
instantiationService.stub(IWorkspaceTrustService, new TestWorkspaceTrustService());
|
||||||
|
|
||||||
return instantiationService;
|
return instantiationService;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ import 'vs/workbench/browser/parts/views/viewsService';
|
||||||
|
|
||||||
//#region --- workbench services
|
//#region --- workbench services
|
||||||
|
|
||||||
|
import 'vs/platform/workspace/common/workspaceTrust';
|
||||||
import 'vs/platform/undoRedo/common/undoRedoService';
|
import 'vs/platform/undoRedo/common/undoRedoService';
|
||||||
import 'vs/workbench/services/extensions/browser/extensionUrlHandler';
|
import 'vs/workbench/services/extensions/browser/extensionUrlHandler';
|
||||||
import 'vs/workbench/services/keybinding/common/keybindingEditing';
|
import 'vs/workbench/services/keybinding/common/keybindingEditing';
|
||||||
|
@ -309,6 +310,9 @@ import 'vs/workbench/contrib/welcome/common/viewsWelcome.contribution';
|
||||||
// Timeline
|
// Timeline
|
||||||
import 'vs/workbench/contrib/timeline/browser/timeline.contribution';
|
import 'vs/workbench/contrib/timeline/browser/timeline.contribution';
|
||||||
|
|
||||||
|
// Workspace
|
||||||
|
import 'vs/workbench/contrib/workspace/browser/workspace.contribution';
|
||||||
|
|
||||||
// Workspaces
|
// Workspaces
|
||||||
import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution';
|
import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue