in src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts [308:828]
content: localize('invalidTrust', "'{0}' is not valid and has too many path segments.", path)
};
}
}
return null;
}
acceptEdit(item: ITrustedUriItem, uri: URI) {
const trustedFolders = this.workspaceTrustManagementService.getTrustedUris();
const index = trustedFolders.findIndex(u => this.uriService.extUri.isEqual(u, item.uri));
if (index >= trustedFolders.length || index === -1) {
trustedFolders.push(uri);
} else {
trustedFolders[index] = uri;
}
this.workspaceTrustManagementService.setTrustedUris(trustedFolders);
this._onDidAcceptEdit.fire(item);
}
rejectEdit(item: ITrustedUriItem) {
this._onDidRejectEdit.fire(item);
}
async delete(item: ITrustedUriItem) {
await this.workspaceTrustManagementService.setUrisTrust([item.uri], false);
this._onDelete.fire(item);
}
async edit(item: ITrustedUriItem, usePickerIfPossible?: boolean) {
const canUseOpenDialog = item.uri.scheme === Schemas.file ||
(
item.uri.scheme === this.currentWorkspaceUri.scheme &&
this.uriService.extUri.isEqualAuthority(this.currentWorkspaceUri.authority, item.uri.authority) &&
!isVirtualResource(item.uri)
);
if (canUseOpenDialog && usePickerIfPossible) {
const uri = await this.fileDialogService.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
defaultUri: item.uri,
openLabel: localize('trustUri', "Trust Folder"),
title: localize('selectTrustedUri', "Select Folder To Trust")
});
if (uri) {
this.acceptEdit(item, uri[0]);
} else {
this.rejectEdit(item);
}
} else {
this.selectTrustedUriEntry(item);
this._onEdit.fire(item);
}
}
}
class TrustedUriTableVirtualDelegate implements ITableVirtualDelegate<ITrustedUriItem> {
static readonly HEADER_ROW_HEIGHT = 30;
static readonly ROW_HEIGHT = 24;
readonly headerRowHeight = TrustedUriTableVirtualDelegate.HEADER_ROW_HEIGHT;
getHeight(item: ITrustedUriItem) {
return TrustedUriTableVirtualDelegate.ROW_HEIGHT;
}
}
interface IActionsColumnTemplateData {
readonly actionBar: ActionBar;
}
class TrustedUriActionsColumnRenderer implements ITableRenderer<ITrustedUriItem, IActionsColumnTemplateData> {
static readonly TEMPLATE_ID = 'actions';
readonly templateId: string = TrustedUriActionsColumnRenderer.TEMPLATE_ID;
constructor(
private readonly table: WorkspaceTrustedUrisTable,
private readonly currentWorkspaceUri: URI,
@IUriIdentityService private readonly uriService: IUriIdentityService) { }
renderTemplate(container: HTMLElement): IActionsColumnTemplateData {
const element = container.appendChild($('.actions'));
const actionBar = new ActionBar(element, { animated: false });
return { actionBar };
}
renderElement(item: ITrustedUriItem, index: number, templateData: IActionsColumnTemplateData, height: number | undefined): void {
templateData.actionBar.clear();
const canUseOpenDialog = item.uri.scheme === Schemas.file ||
(
item.uri.scheme === this.currentWorkspaceUri.scheme &&
this.uriService.extUri.isEqualAuthority(this.currentWorkspaceUri.authority, item.uri.authority) &&
!isVirtualResource(item.uri)
);
const actions: IAction[] = [];
if (canUseOpenDialog) {
actions.push(this.createPickerAction(item));
}
actions.push(this.createEditAction(item));
actions.push(this.createDeleteAction(item));
templateData.actionBar.push(actions, { icon: true });
}
private createEditAction(item: ITrustedUriItem): IAction {
return <IAction>{
class: ThemeIcon.asClassName(settingsEditIcon),
enabled: true,
id: 'editTrustedUri',
tooltip: localize('editTrustedUri', "Edit Path"),
run: () => {
this.table.edit(item, false);
}
};
}
private createPickerAction(item: ITrustedUriItem): IAction {
return <IAction>{
class: ThemeIcon.asClassName(folderPickerIcon),
enabled: true,
id: 'pickerTrustedUri',
tooltip: localize('pickerTrustedUri', "Open File Picker"),
run: () => {
this.table.edit(item, true);
}
};
}
private createDeleteAction(item: ITrustedUriItem): IAction {
return <IAction>{
class: ThemeIcon.asClassName(settingsRemoveIcon),
enabled: true,
id: 'deleteTrustedUri',
tooltip: localize('deleteTrustedUri', "Delete Path"),
run: async () => {
await this.table.delete(item);
}
};
}
disposeTemplate(templateData: IActionsColumnTemplateData): void {
templateData.actionBar.dispose();
}
}
interface ITrustedUriPathColumnTemplateData {
element: HTMLElement;
pathLabel: HTMLElement;
pathInput: InputBox;
renderDisposables: DisposableStore;
disposables: DisposableStore;
}
class TrustedUriPathColumnRenderer implements ITableRenderer<ITrustedUriItem, ITrustedUriPathColumnTemplateData> {
static readonly TEMPLATE_ID = 'path';
readonly templateId: string = TrustedUriPathColumnRenderer.TEMPLATE_ID;
private currentItem?: ITrustedUriItem;
constructor(
private readonly table: WorkspaceTrustedUrisTable,
@IContextViewService private readonly contextViewService: IContextViewService,
@IThemeService private readonly themeService: IThemeService,
) {
}
renderTemplate(container: HTMLElement): ITrustedUriPathColumnTemplateData {
const element = container.appendChild($('.path'));
const pathLabel = element.appendChild($('div.path-label'));
const pathInput = new InputBox(element, this.contextViewService, {
validationOptions: {
validation: value => this.table.validateUri(value, this.currentItem)
}
});
const disposables = new DisposableStore();
disposables.add(attachInputBoxStyler(pathInput, this.themeService));
const renderDisposables = disposables.add(new DisposableStore());
return {
element,
pathLabel,
pathInput,
disposables,
renderDisposables
};
}
renderElement(item: ITrustedUriItem, index: number, templateData: ITrustedUriPathColumnTemplateData, height: number | undefined): void {
templateData.renderDisposables.clear();
this.currentItem = item;
templateData.renderDisposables.add(this.table.onEdit(async (e) => {
if (item === e) {
templateData.element.classList.add('input-mode');
templateData.pathInput.focus();
templateData.pathInput.select();
templateData.element.parentElement!.style.paddingLeft = '0px';
}
}));
// stop double click action from re-rendering the element on the table #125052
templateData.renderDisposables.add(addDisposableListener(templateData.pathInput.element, EventType.DBLCLICK, e => {
EventHelper.stop(e);
}));
const hideInputBox = () => {
templateData.element.classList.remove('input-mode');
templateData.element.parentElement!.style.paddingLeft = '5px';
};
const accept = () => {
hideInputBox();
const uri = item.uri.with({ path: templateData.pathInput.value });
templateData.pathLabel.innerText = templateData.pathInput.value;
if (uri) {
this.table.acceptEdit(item, uri);
}
};
const reject = () => {
hideInputBox();
templateData.pathInput.value = stringValue;
this.table.rejectEdit(item);
};
templateData.renderDisposables.add(addStandardDisposableListener(templateData.pathInput.inputElement, EventType.KEY_DOWN, e => {
let handled = false;
if (e.equals(KeyCode.Enter)) {
accept();
handled = true;
} else if (e.equals(KeyCode.Escape)) {
reject();
handled = true;
}
if (handled) {
e.preventDefault();
e.stopPropagation();
}
}));
templateData.renderDisposables.add((addDisposableListener(templateData.pathInput.inputElement, EventType.BLUR, () => {
reject();
})));
const stringValue = item.uri.scheme === Schemas.file ? URI.revive(item.uri).fsPath : item.uri.path;
templateData.pathInput.value = stringValue;
templateData.pathLabel.innerText = stringValue;
templateData.element.classList.toggle('current-workspace-parent', item.parentOfWorkspaceItem);
// templateData.pathLabel.style.display = '';
}
disposeTemplate(templateData: ITrustedUriPathColumnTemplateData): void {
templateData.disposables.dispose();
templateData.renderDisposables.dispose();
}
}
interface ITrustedUriHostColumnTemplateData {
element: HTMLElement;
hostContainer: HTMLElement;
buttonBarContainer: HTMLElement;
disposables: DisposableStore;
renderDisposables: DisposableStore;
}
function getHostLabel(labelService: ILabelService, item: ITrustedUriItem): string {
return item.uri.authority ? labelService.getHostLabel(item.uri.scheme, item.uri.authority) : localize('localAuthority', "Local");
}
class TrustedUriHostColumnRenderer implements ITableRenderer<ITrustedUriItem, ITrustedUriHostColumnTemplateData> {
static readonly TEMPLATE_ID = 'host';
readonly templateId: string = TrustedUriHostColumnRenderer.TEMPLATE_ID;
constructor(
@ILabelService private readonly labelService: ILabelService,
) { }
renderTemplate(container: HTMLElement): ITrustedUriHostColumnTemplateData {
const disposables = new DisposableStore();
const renderDisposables = disposables.add(new DisposableStore());
const element = container.appendChild($('.host'));
const hostContainer = element.appendChild($('div.host-label'));
const buttonBarContainer = element.appendChild($('div.button-bar'));
return {
element,
hostContainer,
buttonBarContainer,
disposables,
renderDisposables
};
}
renderElement(item: ITrustedUriItem, index: number, templateData: ITrustedUriHostColumnTemplateData, height: number | undefined): void {
templateData.renderDisposables.clear();
templateData.renderDisposables.add({ dispose: () => { clearNode(templateData.buttonBarContainer); } });
templateData.hostContainer.innerText = getHostLabel(this.labelService, item);
templateData.element.classList.toggle('current-workspace-parent', item.parentOfWorkspaceItem);
templateData.hostContainer.style.display = '';
templateData.buttonBarContainer.style.display = 'none';
}
disposeTemplate(templateData: ITrustedUriHostColumnTemplateData): void {
templateData.disposables.dispose();
}
}
export class WorkspaceTrustEditor extends EditorPane {
static readonly ID: string = 'workbench.editor.workspaceTrust';
private rootElement!: HTMLElement;
// Header Section
private headerContainer!: HTMLElement;
private headerTitleContainer!: HTMLElement;
private headerTitleIcon!: HTMLElement;
private headerTitleText!: HTMLElement;
private headerDescription!: HTMLElement;
private bodyScrollBar!: DomScrollableElement;
// Affected Features Section
private affectedFeaturesContainer!: HTMLElement;
// Settings Section
private configurationContainer!: HTMLElement;
private workspaceTrustedUrisTable!: WorkspaceTrustedUrisTable;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService
) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); }
protected createEditor(parent: HTMLElement): void {
this.rootElement = append(parent, $('.workspace-trust-editor', { tabindex: '0' }));
this.rootElement.style.visibility = 'hidden';
this.createHeaderElement(this.rootElement);
const scrollableContent = $('.workspace-trust-editor-body');
this.bodyScrollBar = this._register(new DomScrollableElement(scrollableContent, {
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto,
}));
append(this.rootElement, this.bodyScrollBar.getDomNode());
this.createAffectedFeaturesElement(scrollableContent);
this.createConfigurationElement(scrollableContent);
this._register(attachStylerCallback(this.themeService, { debugIconStartForeground, editorErrorForeground, buttonBackground, buttonSecondaryBackground }, colors => {
this.rootElement.style.setProperty('--workspace-trust-selected-color', colors.buttonBackground?.toString() || '');
this.rootElement.style.setProperty('--workspace-trust-unselected-color', colors.buttonSecondaryBackground?.toString() || '');
this.rootElement.style.setProperty('--workspace-trust-check-color', colors.debugIconStartForeground?.toString() || '');
this.rootElement.style.setProperty('--workspace-trust-x-color', colors.editorErrorForeground?.toString() || '');
}));
}
override focus() {
this.rootElement.focus();
}
override async setInput(input: WorkspaceTrustEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
await super.setInput(input, options, context, token);
if (token.isCancellationRequested) { return; }
await this.workspaceTrustManagementService.workspaceTrustInitialized;
this.registerListeners();
this.render();
}
private registerListeners(): void {
this._register(this.extensionWorkbenchService.onChange(() => this.render()));
this._register(this.configurationService.onDidChangeRestrictedSettings(() => this.render()));
this._register(this.workspaceTrustManagementService.onDidChangeTrust(() => this.render()));
this._register(this.workspaceTrustManagementService.onDidChangeTrustedFolders(() => this.render()));
}
private getHeaderContainerClass(trusted: boolean): string {
if (trusted) {
return 'workspace-trust-header workspace-trust-trusted';
}
return 'workspace-trust-header workspace-trust-untrusted';
}
private getHeaderTitleText(trusted: boolean): string {
if (trusted) {
if (this.workspaceTrustManagementService.isWorkspaceTrustForced()) {
return localize('trustedUnsettableWindow', "This window is trusted");
}
switch (this.workspaceService.getWorkbenchState()) {
case WorkbenchState.EMPTY:
return localize('trustedHeaderWindow', "You trust this window");
case WorkbenchState.FOLDER:
return localize('trustedHeaderFolder', "You trust this folder");
case WorkbenchState.WORKSPACE:
return localize('trustedHeaderWorkspace', "You trust this workspace");
}
}
return localize('untrustedHeader', "You are in Restricted Mode");
}
private getHeaderTitleIconClassNames(trusted: boolean): string[] {
return shieldIcon.classNamesArray;
}
private getFeaturesHeaderText(trusted: boolean): [string, string] {
let title: string = '';
let subTitle: string = '';
switch (this.workspaceService.getWorkbenchState()) {
case WorkbenchState.EMPTY: {
title = trusted ? localize('trustedWindow', "In a Trusted Window") : localize('untrustedWorkspace', "In Restricted Mode");
subTitle = trusted ? localize('trustedWindowSubtitle', "You trust the authors of the files in the current window. All features are enabled:") :
localize('untrustedWindowSubtitle', "You do not trust the authors of the files in the current window. The following features are disabled:");
break;
}
case WorkbenchState.FOLDER: {
title = trusted ? localize('trustedFolder', "In a Trusted Folder") : localize('untrustedWorkspace', "In Restricted Mode");
subTitle = trusted ? localize('trustedFolderSubtitle', "You trust the authors of the files in the current folder. All features are enabled:") :
localize('untrustedFolderSubtitle', "You do not trust the authors of the files in the current folder. The following features are disabled:");
break;
}
case WorkbenchState.WORKSPACE: {
title = trusted ? localize('trustedWorkspace', "In a Trusted Workspace") : localize('untrustedWorkspace', "In Restricted Mode");
subTitle = trusted ? localize('trustedWorkspaceSubtitle', "You trust the authors of the files in the current workspace. All features are enabled:") :
localize('untrustedWorkspaceSubtitle', "You do not trust the authors of the files in the current workspace. The following features are disabled:");
break;
}
}
return [title, subTitle];
}
private rendering = false;
private rerenderDisposables: DisposableStore = this._register(new DisposableStore());
@debounce(100)
private async render() {
if (this.rendering) {
return;
}
this.rendering = true;
this.rerenderDisposables.clear();
const isWorkspaceTrusted = this.workspaceTrustManagementService.isWorkspaceTrusted();
this.rootElement.classList.toggle('trusted', isWorkspaceTrusted);
this.rootElement.classList.toggle('untrusted', !isWorkspaceTrusted);
// Header Section
this.headerTitleText.innerText = this.getHeaderTitleText(isWorkspaceTrusted);
this.headerTitleIcon.className = 'workspace-trust-title-icon';
this.headerTitleIcon.classList.add(...this.getHeaderTitleIconClassNames(isWorkspaceTrusted));
this.headerDescription.innerText = '';
const headerDescriptionText = append(this.headerDescription, $('div'));
headerDescriptionText.innerText = isWorkspaceTrusted ?
localize('trustedDescription', "All features are enabled because trust has been granted to the workspace.") :
localize('untrustedDescription', "{0} is in a restricted mode intended for safe code browsing.", product.nameShort);
const headerDescriptionActions = append(this.headerDescription, $('div'));
const headerDescriptionActionsText = localize({ key: 'workspaceTrustEditorHeaderActions', comment: ['Please ensure the markdown link syntax is not broken up with whitespace [text block](link block)'] }, "[Configure your settings]({0}) or [learn more](https://aka.ms/vscode-workspace-trust).", `command:workbench.trust.configure`);
for (const node of parseLinkedText(headerDescriptionActionsText).nodes) {
if (typeof node === 'string') {
append(headerDescriptionActions, document.createTextNode(node));
} else {
const link = this.instantiationService.createInstance(Link, node, {});
append(headerDescriptionActions, link.el);
this.rerenderDisposables.add(link);
}
}
this.headerContainer.className = this.getHeaderContainerClass(isWorkspaceTrusted);
this.rootElement.setAttribute('aria-label', `${localize('root element label', "Manage Workspace Trust")}: ${this.headerContainer.innerText}`);
// Settings
const restrictedSettings = this.configurationService.restrictedSettings;
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const settingsRequiringTrustedWorkspaceCount = restrictedSettings.default.filter(key => {
const property = configurationRegistry.getConfigurationProperties()[key];
// cannot be configured in workspace
if (property.scope === ConfigurationScope.APPLICATION || property.scope === ConfigurationScope.MACHINE) {
return false;
}
// If deprecated include only those configured in the workspace
if (property.deprecationMessage || property.markdownDeprecationMessage) {
if (restrictedSettings.workspace?.includes(key)) {