mirror of
https://github.com/coder/code-server.git
synced 2026-05-09 13:57:26 +02:00
chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
@@ -8,25 +8,21 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
|
||||
import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
|
||||
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor, ViewContainerLocationToString } from 'vs/workbench/common/views';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
|
||||
@@ -125,54 +121,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
order: 3
|
||||
});
|
||||
|
||||
// --- Toggle Editor Layout
|
||||
|
||||
export class ToggleEditorLayoutAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.toggleEditorGroupLayout';
|
||||
static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout");
|
||||
|
||||
private readonly toDispose = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.class = Codicon.editorLayout.classNames;
|
||||
this.updateEnablement();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.add(this.editorGroupService.onDidAddGroup(() => this.updateEnablement()));
|
||||
this.toDispose.add(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
this.enabled = this.editorGroupService.count > 1;
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL;
|
||||
this.editorGroupService.setGroupOrientation(newOrientation);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorLayoutAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', CATEGORIES.View.value);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: 'z_flip',
|
||||
command: {
|
||||
id: ToggleEditorLayoutAction.ID,
|
||||
title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
// --- Toggle Sidebar Position
|
||||
|
||||
export class ToggleSidebarPositionAction extends Action {
|
||||
@@ -203,7 +151,64 @@ export class ToggleSidebarPositionAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarPositionAction), 'View: Toggle Side Bar Position', CATEGORIES.View.value);
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: { value: nls.localize('toggleSidebarPosition', "Toggle Side Bar Position"), original: 'Toggle Side Bar Position' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
accessor.get(IInstantiationService).createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL).run();
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItems([{
|
||||
id: MenuId.ViewContainerTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar right', "Move Side Bar Right")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar right', "Move Side Bar Right")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewContainerTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar left', "Move Side Bar Left")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize('move sidebar left', "Move Side Bar Left")
|
||||
},
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 1
|
||||
}
|
||||
}]);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '3_workbench_layout_move',
|
||||
@@ -256,27 +261,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
order: 5
|
||||
});
|
||||
|
||||
export class ToggleSidebarVisibilityAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.toggleSidebarVisibility';
|
||||
static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART);
|
||||
this.layoutService.setSideBarHidden(hideSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarVisibilityAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', CATEGORIES.View.value);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '2_appearance',
|
||||
title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"),
|
||||
@@ -284,10 +268,53 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
order: 1
|
||||
});
|
||||
|
||||
export const TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID = 'workbench.action.toggleSidebarVisibility';
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: { value: nls.localize('toggleSidebar', "Toggle Side Bar Visibility"), original: 'Toggle Side Bar Visibility' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_B
|
||||
}
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
const layoutService = accessor.get(IWorkbenchLayoutService);
|
||||
layoutService.setSideBarHidden(layoutService.isVisible(Parts.SIDEBAR_PART));
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItems([{
|
||||
id: MenuId.ViewContainerTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
},
|
||||
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 2
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
},
|
||||
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
|
||||
order: 2
|
||||
}
|
||||
}]);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: ToggleSidebarVisibilityAction.ID,
|
||||
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
|
||||
title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"),
|
||||
toggled: SideBarVisibleContext
|
||||
},
|
||||
@@ -413,32 +440,16 @@ export class ToggleMenuBarAction extends Action {
|
||||
static readonly ID = 'workbench.action.toggleMenuBar';
|
||||
static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar");
|
||||
|
||||
private static readonly menuBarVisibilityKey = 'window.menuBarVisibility';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
|
||||
if (typeof currentVisibilityValue !== 'string') {
|
||||
currentVisibilityValue = 'default';
|
||||
}
|
||||
|
||||
let newVisibilityValue: string;
|
||||
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
|
||||
newVisibilityValue = 'toggle';
|
||||
} else if (currentVisibilityValue === 'compact') {
|
||||
newVisibilityValue = 'hidden';
|
||||
} else {
|
||||
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
|
||||
}
|
||||
|
||||
return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER);
|
||||
async run(): Promise<void> {
|
||||
this.layoutService.toggleMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,19 +490,18 @@ export class ResetViewLocationsAction extends Action {
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetViewLocationsAction), 'View: Reset View Locations', CATEGORIES.View.value);
|
||||
|
||||
// --- Toggle View with Command
|
||||
export abstract class ToggleViewAction extends Action {
|
||||
export class ToggleViewAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private readonly viewId: string,
|
||||
protected viewsService: IViewsService,
|
||||
protected viewDescriptorService: IViewDescriptorService,
|
||||
protected contextKeyService: IContextKeyService,
|
||||
private layoutService: IWorkbenchLayoutService,
|
||||
cssClass?: string
|
||||
@IViewsService protected viewsService: IViewsService,
|
||||
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService,
|
||||
) {
|
||||
super(id, label, cssClass);
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
||||
@@ -17,7 +17,6 @@ import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/c
|
||||
import { Direction } from 'vs/base/browser/ui/grid/grid';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isAncestor } from 'vs/base/browser/dom';
|
||||
|
||||
abstract class BaseNavigationAction extends Action {
|
||||
|
||||
@@ -215,7 +214,8 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part
|
||||
}
|
||||
|
||||
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void {
|
||||
const currentlyFocusedPart = isActiveElementInNotebookEditor(editorService) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
|
||||
const editorFocused = editorService.activeEditorPane?.hasFocus();
|
||||
const currentlyFocusedPart = editorFocused ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
|
||||
layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
|
||||
let partToFocus = Parts.EDITOR_PART;
|
||||
if (currentlyFocusedPart) {
|
||||
@@ -225,17 +225,6 @@ function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorS
|
||||
layoutService.focusPart(partToFocus);
|
||||
}
|
||||
|
||||
function isActiveElementInNotebookEditor(editorService: IEditorService): boolean {
|
||||
const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
const control = editorService.activeEditorPane?.getControl() as { getDomNode(): HTMLElement; getOverflowContainerDomNode(): HTMLElement; };
|
||||
const activeElement = document.activeElement;
|
||||
return isAncestor(activeElement, control.getDomNode()) || isAncestor(activeElement, control.getOverflowContainerDomNode());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export class FocusNextPart extends Action {
|
||||
static readonly ID = 'workbench.action.focusNextPart';
|
||||
static readonly LABEL = nls.localize('focusNextPart', "Focus Next Part");
|
||||
|
||||
@@ -76,23 +76,26 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo
|
||||
|
||||
// Context menu support in input/textarea
|
||||
this.layoutService.container.addEventListener('contextmenu', e => this.onContextMenu(e));
|
||||
|
||||
}
|
||||
|
||||
private onContextMenu(e: MouseEvent): void {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
const target = <HTMLElement>e.target;
|
||||
if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e,
|
||||
getActions: () => this.textInputActions,
|
||||
getActionsContext: () => target,
|
||||
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
|
||||
});
|
||||
}
|
||||
if (e.defaultPrevented) {
|
||||
return; // make sure to not show these actions by accident if component indicated to prevent
|
||||
}
|
||||
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLElement) || (target.nodeName.toLowerCase() !== 'input' && target.nodeName.toLowerCase() !== 'textarea')) {
|
||||
return; // only for inputs or textareas
|
||||
}
|
||||
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e,
|
||||
getActions: () => this.textInputActions,
|
||||
getActionsContext: () => target,
|
||||
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ResourceMap } from 'vs/base/common/map';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { isHTMLElement } from 'vs/base/browser/dom';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export const inRecentFilesPickerContextKey = 'inRecentFilesPicker';
|
||||
|
||||
@@ -49,12 +49,17 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
tooltip: nls.localize('remove', "Remove from Recently Opened")
|
||||
};
|
||||
|
||||
private readonly dirtyRecentlyOpened: IQuickInputButton = {
|
||||
private readonly dirtyRecentlyOpenedFolder: IQuickInputButton = {
|
||||
iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames,
|
||||
tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"),
|
||||
tooltip: nls.localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"),
|
||||
alwaysVisible: true
|
||||
};
|
||||
|
||||
private readonly dirtyRecentlyOpenedWorkspace: IQuickInputButton = {
|
||||
...this.dirtyRecentlyOpenedFolder,
|
||||
tooltip: nls.localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"),
|
||||
};
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@@ -77,7 +82,9 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
|
||||
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
|
||||
|
||||
// Identify all folders and workspaces with dirty files
|
||||
let hasWorkspaces = false;
|
||||
|
||||
// Identify all folders and workspaces with unsaved files
|
||||
const dirtyFolders = new ResourceMap<boolean>();
|
||||
const dirtyWorkspaces = new ResourceMap<IWorkspaceIdentifier>();
|
||||
for (const dirtyWorkspace of dirtyWorkspacesAndFolders) {
|
||||
@@ -85,6 +92,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
dirtyFolders.set(dirtyWorkspace, true);
|
||||
} else {
|
||||
dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace);
|
||||
hasWorkspaces = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +104,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
recentFolders.set(recent.folderUri, true);
|
||||
} else {
|
||||
recentWorkspaces.set(recent.workspace.configPath, recent.workspace);
|
||||
hasWorkspaces = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +133,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
|
||||
let keyMods: IKeyMods | undefined;
|
||||
|
||||
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") };
|
||||
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? nls.localize('workspacesAndFolders', "folders & workspaces") : nls.localize('folders', "folders") };
|
||||
const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") };
|
||||
const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks];
|
||||
|
||||
@@ -143,13 +152,14 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
context.removeItem();
|
||||
}
|
||||
|
||||
// Dirty Workspace
|
||||
else if (context.button === this.dirtyRecentlyOpened) {
|
||||
// Dirty Folder/Workspace
|
||||
else if (context.button === this.dirtyRecentlyOpenedFolder || context.button === this.dirtyRecentlyOpenedWorkspace) {
|
||||
const isDirtyWorkspace = context.button === this.dirtyRecentlyOpenedWorkspace;
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'question',
|
||||
title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"),
|
||||
message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"),
|
||||
detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.")
|
||||
title: isDirtyWorkspace ? nls.localize('dirtyWorkspace', "Workspace with Unsaved Files") : nls.localize('dirtyFolder', "Folder with Unsaved Files"),
|
||||
message: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : nls.localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"),
|
||||
detail: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : nls.localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.")
|
||||
});
|
||||
|
||||
if (result.confirmed) {
|
||||
@@ -170,6 +180,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
let iconClasses: string[];
|
||||
let fullLabel: string | undefined;
|
||||
let resource: URI | undefined;
|
||||
let isWorkspace = false;
|
||||
|
||||
// Folder
|
||||
if (isRecentFolder(recent)) {
|
||||
@@ -185,6 +196,7 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER);
|
||||
openable = { workspaceUri: resource };
|
||||
fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
|
||||
isWorkspace = true;
|
||||
}
|
||||
|
||||
// File
|
||||
@@ -200,9 +212,9 @@ abstract class BaseOpenRecentAction extends Action {
|
||||
return {
|
||||
iconClasses,
|
||||
label: name,
|
||||
ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name,
|
||||
ariaLabel: isDirty ? isWorkspace ? nls.localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : nls.localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name,
|
||||
description: parentPath,
|
||||
buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened],
|
||||
buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened],
|
||||
openable,
|
||||
resource
|
||||
};
|
||||
@@ -405,7 +417,7 @@ CommandsRegistry.registerCommand('workbench.action.toggleConfirmBeforeClose', ac
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const setting = configurationService.inspect<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose').userValue;
|
||||
|
||||
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never', ConfigurationTarget.USER);
|
||||
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never');
|
||||
});
|
||||
|
||||
// --- Menu Registration
|
||||
|
||||
@@ -114,7 +114,7 @@ export class CloseWorkspaceAction extends Action {
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
|
||||
this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close."));
|
||||
this.notificationService.info(nls.localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ export class SaveWorkspaceAsAction extends Action {
|
||||
export class DuplicateWorkspaceInNewWindowAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow';
|
||||
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window");
|
||||
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -259,7 +259,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(AddRootFolderAction),
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalRemoveRootFolderAction), 'Workspaces: Remove Folder from Workspace...', workspacesCategory);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseWorkspaceAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory, EmptyWorkspaceSupportContext);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveWorkspaceAsAction), 'Workspaces: Save Workspace As...', workspacesCategory, EmptyWorkspaceSupportContext);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate As Workspace in New Window', workspacesCategory);
|
||||
|
||||
// --- Menu Registration
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -15,11 +15,121 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
|
||||
export interface IRangeHighlightDecoration {
|
||||
resource: URI;
|
||||
range: IRange;
|
||||
isWholeLine?: boolean;
|
||||
}
|
||||
|
||||
export class RangeHighlightDecorations extends Disposable {
|
||||
|
||||
private readonly _onHighlightRemoved = this._register(new Emitter<void>());
|
||||
readonly onHighlightRemoved = this._onHighlightRemoved.event;
|
||||
|
||||
private rangeHighlightDecorationId: string | null = null;
|
||||
private editor: ICodeEditor | null = null;
|
||||
private readonly editorDisposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(@IEditorService private readonly editorService: IEditorService) {
|
||||
super();
|
||||
}
|
||||
|
||||
removeHighlightRange() {
|
||||
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
|
||||
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
|
||||
this._onHighlightRemoved.fire();
|
||||
}
|
||||
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
highlightRange(range: IRangeHighlightDecoration, editor?: any) {
|
||||
editor = editor ?? this.getEditor(range);
|
||||
if (isCodeEditor(editor)) {
|
||||
this.doHighlightRange(editor, range);
|
||||
} else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) {
|
||||
this.doHighlightRange(editor.activeCodeEditor, range);
|
||||
}
|
||||
}
|
||||
|
||||
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
|
||||
this.removeHighlightRange();
|
||||
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
|
||||
});
|
||||
|
||||
this.setEditor(editor);
|
||||
}
|
||||
|
||||
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
const resource = activeEditor && activeEditor.resource;
|
||||
if (resource && isEqual(resource, resourceRange.resource)) {
|
||||
return this.editorService.activeTextEditorControl as ICodeEditor;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private setEditor(editor: ICodeEditor) {
|
||||
if (this.editor !== editor) {
|
||||
this.editorDisposables.clear();
|
||||
this.editor = editor;
|
||||
this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
if (
|
||||
e.reason === CursorChangeReason.NotSet
|
||||
|| e.reason === CursorChangeReason.Explicit
|
||||
|| e.reason === CursorChangeReason.Undo
|
||||
|| e.reason === CursorChangeReason.Redo
|
||||
) {
|
||||
this.removeHighlightRange();
|
||||
}
|
||||
}));
|
||||
this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); }));
|
||||
this.editorDisposables.add(this.editor.onDidDispose(() => {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
});
|
||||
|
||||
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight'
|
||||
});
|
||||
|
||||
private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions {
|
||||
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.editor && this.editor.getModel()) {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FloatingClickWidget extends Widget implements IOverlayWidget {
|
||||
|
||||
@@ -56,15 +56,26 @@ export abstract class Composite extends Component implements IComposite {
|
||||
return this._onDidBlur.event;
|
||||
}
|
||||
|
||||
private _hasFocus = false;
|
||||
hasFocus(): boolean {
|
||||
return this._hasFocus;
|
||||
}
|
||||
|
||||
private registerFocusTrackEvents(): { onDidFocus: Emitter<void>, onDidBlur: Emitter<void> } {
|
||||
const container = assertIsDefined(this.getContainer());
|
||||
const focusTracker = this._register(trackFocus(container));
|
||||
|
||||
const onDidFocus = this._onDidFocus = this._register(new Emitter<void>());
|
||||
this._register(focusTracker.onDidFocus(() => onDidFocus.fire()));
|
||||
this._register(focusTracker.onDidFocus(() => {
|
||||
this._hasFocus = true;
|
||||
onDidFocus.fire();
|
||||
}));
|
||||
|
||||
const onDidBlur = this._onDidBlur = this._register(new Emitter<void>());
|
||||
this._register(focusTracker.onDidBlur(() => onDidBlur.fire()));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
this._hasFocus = false;
|
||||
onDidBlur.fire();
|
||||
}));
|
||||
|
||||
return { onDidFocus, onDidBlur };
|
||||
}
|
||||
@@ -234,7 +245,6 @@ export abstract class CompositeDescriptor<T extends Composite> {
|
||||
readonly cssClass?: string,
|
||||
readonly order?: number,
|
||||
readonly requestedIndex?: number,
|
||||
readonly keybindingId?: string,
|
||||
) { }
|
||||
|
||||
instantiate(instantiationService: IInstantiationService): T {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/
|
||||
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
|
||||
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { PanelPositionContext } from 'vs/workbench/common/panel';
|
||||
import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
|
||||
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { isNative } from 'vs/base/common/platform';
|
||||
@@ -64,6 +64,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
private sideBarVisibleContext: IContextKey<boolean>;
|
||||
private editorAreaVisibleContext: IContextKey<boolean>;
|
||||
private panelPositionContext: IContextKey<string>;
|
||||
private panelVisibleContext: IContextKey<boolean>;
|
||||
private panelMaximizedContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@@ -146,9 +148,13 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
// Sidebar
|
||||
this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService);
|
||||
|
||||
// Panel Position
|
||||
// Panel
|
||||
this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService);
|
||||
this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition()));
|
||||
this.panelVisibleContext = PanelVisibleContext.bindTo(this.contextKeyService);
|
||||
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
|
||||
this.panelMaximizedContext = PanelMaximizedContext.bindTo(this.contextKeyService);
|
||||
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -182,7 +188,11 @@ export class WorkbenchContextKeysHandler extends Disposable {
|
||||
this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys()));
|
||||
this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys()));
|
||||
|
||||
this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART))));
|
||||
this._register(this.layoutService.onPartVisibilityChange(() => {
|
||||
this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART));
|
||||
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
|
||||
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
|
||||
}));
|
||||
|
||||
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty)));
|
||||
}
|
||||
|
||||
@@ -737,7 +737,7 @@ export class CompositeDragAndDropObserver extends Disposable {
|
||||
if (callbacks.onDragEnd) {
|
||||
this._onDragEnd.event(e => {
|
||||
callbacks.onDragEnd!(e);
|
||||
});
|
||||
}, this, disposableStore);
|
||||
}
|
||||
return this._register(disposableStore);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { insert } from 'vs/base/common/arrays';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IEditorDescriptor {
|
||||
|
||||
getId(): string;
|
||||
getName(): string;
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ export const DEFAULT_LABELS_CONTAINER: IResourceLabelsContainer = {
|
||||
};
|
||||
|
||||
export class ResourceLabels extends Disposable {
|
||||
|
||||
private _onDidChangeDecorations = this._register(new Emitter<void>());
|
||||
readonly onDidChangeDecorations = this._onDidChangeDecorations.event;
|
||||
|
||||
private widgets: ResourceLabelWidget[] = [];
|
||||
private labels: IResourceLabel[] = [];
|
||||
|
||||
@@ -148,7 +152,18 @@ export class ResourceLabels extends Disposable {
|
||||
}));
|
||||
|
||||
// notify when file decoration changes
|
||||
this._register(this.decorationsService.onDidChangeDecorations(e => this.widgets.forEach(widget => widget.notifyFileDecorationsChanges(e))));
|
||||
this._register(this.decorationsService.onDidChangeDecorations(e => {
|
||||
let notifyDidChangeDecorations = false;
|
||||
this.widgets.forEach(widget => {
|
||||
if (widget.notifyFileDecorationsChanges(e)) {
|
||||
notifyDidChangeDecorations = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (notifyDidChangeDecorations) {
|
||||
this._onDidChangeDecorations.fire();
|
||||
}
|
||||
}));
|
||||
|
||||
// notify when theme changes
|
||||
this._register(this.themeService.onDidColorThemeChange(() => this.widgets.forEach(widget => widget.notifyThemeChange())));
|
||||
@@ -311,19 +326,21 @@ class ResourceLabelWidget extends IconLabel {
|
||||
}
|
||||
}
|
||||
|
||||
notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void {
|
||||
notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): boolean {
|
||||
if (!this.options) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const resource = toResource(this.label);
|
||||
if (!resource) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.options.fileDecorations && e.affectsResource(resource)) {
|
||||
this.render(false);
|
||||
return this.render(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
notifyExtensionsRegistered(): void {
|
||||
@@ -465,7 +482,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
this.setLabel('');
|
||||
}
|
||||
|
||||
private render(clearIconCache: boolean): void {
|
||||
private render(clearIconCache: boolean): boolean {
|
||||
if (this.isHidden) {
|
||||
if (!this.needsRedraw) {
|
||||
this.needsRedraw = clearIconCache ? Redraw.Full : Redraw.Basic;
|
||||
@@ -475,7 +492,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
this.needsRedraw = Redraw.Full;
|
||||
}
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.label) {
|
||||
@@ -492,7 +509,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
}
|
||||
|
||||
if (!this.label) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.renderDisposables.clear();
|
||||
@@ -558,6 +575,8 @@ class ResourceLabelWidget extends IconLabel {
|
||||
this.setLabel(label || '', this.label.description, iconLabelOptions);
|
||||
|
||||
this._onDidRender.fire();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -78,7 +78,9 @@ enum Storage {
|
||||
|
||||
GRID_LAYOUT = 'workbench.grid.layout',
|
||||
GRID_WIDTH = 'workbench.grid.width',
|
||||
GRID_HEIGHT = 'workbench.grid.height'
|
||||
GRID_HEIGHT = 'workbench.grid.height',
|
||||
|
||||
MENU_VISIBILITY = 'window.menuBarVisibility'
|
||||
}
|
||||
|
||||
enum Classes {
|
||||
@@ -321,9 +323,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) {
|
||||
// Propagate to grid
|
||||
this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART));
|
||||
|
||||
this.layout();
|
||||
}
|
||||
|
||||
// Move layout call to any time the menubar
|
||||
// is toggled to update consumers of offset
|
||||
// see issue #115267
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +627,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return this._openedDefaultEditors;
|
||||
}
|
||||
|
||||
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined {
|
||||
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined {
|
||||
const defaultLayout = this.environmentService.options?.defaultLayout;
|
||||
if (defaultLayout?.editors?.length && this.storageService.isNew(StorageScope.WORKSPACE)) {
|
||||
this._openedDefaultEditors = true;
|
||||
@@ -652,7 +657,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
// Restore editors
|
||||
restorePromises.push((async () => {
|
||||
mark('willRestoreEditors');
|
||||
mark('code/willRestoreEditors');
|
||||
|
||||
// first ensure the editor part is restored
|
||||
await this.editorGroupService.whenRestored;
|
||||
@@ -669,17 +674,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
await this.editorService.openEditors(editors);
|
||||
}
|
||||
|
||||
mark('didRestoreEditors');
|
||||
mark('code/didRestoreEditors');
|
||||
})());
|
||||
|
||||
// Restore default views
|
||||
const restoreDefaultViewsPromise = (async () => {
|
||||
if (this.state.views.defaults?.length) {
|
||||
mark('willOpenDefaultViews');
|
||||
mark('code/willOpenDefaultViews');
|
||||
|
||||
let locationsRestored: { id: string; order: number }[] = [];
|
||||
let locationsRestored: { id: string; order: number; }[] = [];
|
||||
|
||||
const tryOpenView = (view: { id: string; order: number }): boolean => {
|
||||
const tryOpenView = (view: { id: string; order: number; }): boolean => {
|
||||
const location = this.viewDescriptorService.getViewLocationById(view.id);
|
||||
if (location !== null) {
|
||||
const container = this.viewDescriptorService.getViewContainerByViewId(view.id);
|
||||
@@ -733,7 +738,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
this.state.panel.panelToRestore = locationsRestored[ViewContainerLocation.Panel].id;
|
||||
}
|
||||
|
||||
mark('didOpenDefaultViews');
|
||||
mark('code/didOpenDefaultViews');
|
||||
}
|
||||
})();
|
||||
restorePromises.push(restoreDefaultViewsPromise);
|
||||
@@ -748,14 +753,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return;
|
||||
}
|
||||
|
||||
mark('willRestoreViewlet');
|
||||
mark('code/willRestoreViewlet');
|
||||
|
||||
const viewlet = await this.viewletService.openViewlet(this.state.sideBar.viewletToRestore);
|
||||
if (!viewlet) {
|
||||
await this.viewletService.openViewlet(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); // fallback to default viewlet as needed
|
||||
}
|
||||
|
||||
mark('didRestoreViewlet');
|
||||
mark('code/didRestoreViewlet');
|
||||
})());
|
||||
|
||||
// Restore Panel
|
||||
@@ -768,14 +773,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return;
|
||||
}
|
||||
|
||||
mark('willRestorePanel');
|
||||
mark('code/willRestorePanel');
|
||||
|
||||
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!);
|
||||
if (!panel) {
|
||||
await this.panelService.openPanel(Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed
|
||||
}
|
||||
|
||||
mark('didRestorePanel');
|
||||
mark('code/didRestorePanel');
|
||||
})());
|
||||
|
||||
// Restore Zen Mode
|
||||
@@ -1128,7 +1133,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
[Parts.STATUSBAR_PART]: this.statusBarPartView
|
||||
};
|
||||
|
||||
const fromJSON = ({ type }: { type: Parts }) => viewMap[type];
|
||||
const fromJSON = ({ type }: { type: Parts; }) => viewMap[type];
|
||||
const workbenchGrid = SerializableGrid.deserialize(
|
||||
this.createGridDescriptor(),
|
||||
{ fromJSON },
|
||||
@@ -1410,7 +1415,29 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
// If panel part becomes visible, show last active panel or default panel
|
||||
else if (!hidden && !this.panelService.getActivePanel()) {
|
||||
const panelToOpen = this.panelService.getLastActivePanelId();
|
||||
let panelToOpen: string | undefined = this.panelService.getLastActivePanelId();
|
||||
const hasViews = (id: string): boolean => {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
|
||||
if (!viewContainer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (!viewContainerModel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return viewContainerModel.activeViewDescriptors.length >= 1;
|
||||
};
|
||||
|
||||
// verify that the panel we try to open has views before we default to it
|
||||
// otherwise fall back to any view that has views still refs #111463
|
||||
if (!panelToOpen || !hasViews(panelToOpen)) {
|
||||
panelToOpen = this.viewDescriptorService
|
||||
.getViewContainersByLocation(ViewContainerLocation.Panel)
|
||||
.find(viewContainer => hasViews(viewContainer.id))?.id;
|
||||
}
|
||||
|
||||
if (panelToOpen) {
|
||||
const focus = !skipLayout;
|
||||
this.panelService.openPanel(panelToOpen, focus);
|
||||
@@ -1529,6 +1556,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return this.state.menuBar.visibility;
|
||||
}
|
||||
|
||||
toggleMenuBar(): void {
|
||||
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
|
||||
if (typeof currentVisibilityValue !== 'string') {
|
||||
currentVisibilityValue = 'default';
|
||||
}
|
||||
|
||||
let newVisibilityValue: string;
|
||||
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
|
||||
newVisibilityValue = 'toggle';
|
||||
} else if (currentVisibilityValue === 'compact') {
|
||||
newVisibilityValue = 'hidden';
|
||||
} else {
|
||||
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
|
||||
}
|
||||
|
||||
this.configurationService.updateValue(Storage.MENU_VISIBILITY, newVisibilityValue);
|
||||
}
|
||||
|
||||
getPanelPosition(): Position {
|
||||
return this.state.panel.position;
|
||||
}
|
||||
|
||||
99
lib/vscode/src/vs/workbench/browser/menuActions.ts
Normal file
99
lib/vscode/src/vs/workbench/browser/menuActions.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
||||
class MenuActions extends Disposable {
|
||||
|
||||
private readonly menu: IMenu;
|
||||
|
||||
private _primaryActions: IAction[] = [];
|
||||
get primaryActions() { return this._primaryActions; }
|
||||
|
||||
private _secondaryActions: IAction[] = [];
|
||||
get secondaryActions() { return this._secondaryActions; }
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private disposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
menuId: MenuId,
|
||||
private readonly options: IMenuActionOptions | undefined,
|
||||
private readonly menuService: IMenuService,
|
||||
private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
this.menu = this._register(menuService.createMenu(menuId, contextKeyService));
|
||||
this._register(this.menu.onDidChange(() => this.updateActions()));
|
||||
this.updateActions();
|
||||
}
|
||||
|
||||
private updateActions(): void {
|
||||
this.disposables.clear();
|
||||
this._primaryActions = [];
|
||||
this._secondaryActions = [];
|
||||
this.disposables.add(createAndFillInActionBarActions(this.menu, this.options, { primary: this._primaryActions, secondary: this._secondaryActions }));
|
||||
this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {}));
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private updateSubmenus(actions: readonly IAction[], submenus: { [id: number]: IMenu }): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
for (const action of actions) {
|
||||
if (action instanceof SubmenuItemAction && !submenus[action.item.submenu.id]) {
|
||||
const menu = submenus[action.item.submenu.id] = disposables.add(this.menuService.createMenu(action.item.submenu, this.contextKeyService));
|
||||
disposables.add(menu.onDidChange(() => this.updateActions()));
|
||||
disposables.add(this.updateSubmenus(action.actions, submenus));
|
||||
}
|
||||
}
|
||||
return disposables;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeMenuActions extends Disposable {
|
||||
|
||||
private readonly menuActions: MenuActions;
|
||||
private readonly contextMenuActionsDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
constructor(
|
||||
menuId: MenuId,
|
||||
private readonly contextMenuId: MenuId | undefined,
|
||||
private readonly options: IMenuActionOptions | undefined,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
) {
|
||||
super();
|
||||
this.menuActions = this._register(new MenuActions(menuId, this.options, menuService, contextKeyService));
|
||||
this._register(this.menuActions.onDidChange(() => this._onDidChange.fire()));
|
||||
}
|
||||
|
||||
getPrimaryActions(): IAction[] {
|
||||
return this.menuActions.primaryActions;
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menuActions.secondaryActions;
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
if (this.contextMenuId) {
|
||||
const menu = this.menuService.createMenu(this.contextMenuId, this.contextKeyService);
|
||||
this.contextMenuActionsDisposable.value = createAndFillInActionBarActions(menu, this.options, { primary: [], secondary: actions });
|
||||
menu.dispose();
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
||||
import { ViewPaneContainer } from './parts/views/viewPaneContainer';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions';
|
||||
import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
export class PaneComposite extends Composite implements IPaneComposite {
|
||||
|
||||
private menuActions: ViewContainerMenuActions;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
protected readonly viewPaneContainer: ViewPaneContainer,
|
||||
@@ -35,8 +31,6 @@ export class PaneComposite extends Composite implements IPaneComposite {
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, telemetryService, themeService, storageService);
|
||||
|
||||
this.menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.getId(), MenuId.ViewContainerTitleContext));
|
||||
this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea()));
|
||||
}
|
||||
|
||||
@@ -71,22 +65,36 @@ export class PaneComposite extends Composite implements IPaneComposite {
|
||||
|
||||
getContextMenuActions(): ReadonlyArray<IAction> {
|
||||
const result = [];
|
||||
result.push(...this.menuActions.getContextMenuActions());
|
||||
result.push(...this.viewPaneContainer.getContextMenuActions2());
|
||||
|
||||
if (result.length) {
|
||||
const otherActions = this.viewPaneContainer.getContextMenuActions();
|
||||
|
||||
if (otherActions.length) {
|
||||
result.push(new Separator());
|
||||
result.push(...otherActions);
|
||||
}
|
||||
|
||||
result.push(...this.viewPaneContainer.getContextMenuActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
getActions(): ReadonlyArray<IAction> {
|
||||
return this.viewPaneContainer.getActions();
|
||||
const result = [];
|
||||
result.push(...this.viewPaneContainer.getActions2());
|
||||
result.push(...this.viewPaneContainer.getActions());
|
||||
return result;
|
||||
}
|
||||
|
||||
getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return this.viewPaneContainer.getSecondaryActions();
|
||||
const menuActions = this.viewPaneContainer.getSecondaryActions2();
|
||||
const viewPaneContainerActions = this.viewPaneContainer.getSecondaryActions();
|
||||
if (menuActions.length && viewPaneContainerActions.length) {
|
||||
return [
|
||||
...menuActions,
|
||||
new Separator(),
|
||||
...viewPaneContainerActions
|
||||
];
|
||||
}
|
||||
return menuActions.length ? menuActions : viewPaneContainerActions;
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
|
||||
@@ -6,23 +6,74 @@
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConstructorSignature0, BrandedService, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
|
||||
import { IAction, Separator } from 'vs/base/common/actions';
|
||||
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export abstract class Panel extends PaneComposite implements IPanel { }
|
||||
export abstract class Panel extends PaneComposite implements IPanel {
|
||||
|
||||
private readonly panelActions: CompositeMenuActions;
|
||||
|
||||
constructor(id: string,
|
||||
viewPaneContainer: ViewPaneContainer,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
) {
|
||||
super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
this.panelActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, MenuId.PanelTitle, undefined, undefined));
|
||||
this._register(this.panelActions.onDidChange(() => this.updateTitleArea()));
|
||||
}
|
||||
|
||||
getActions(): ReadonlyArray<IAction> {
|
||||
return [...super.getActions(), ...this.panelActions.getPrimaryActions()];
|
||||
}
|
||||
|
||||
getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions());
|
||||
}
|
||||
|
||||
getContextMenuActions(): ReadonlyArray<IAction> {
|
||||
return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions());
|
||||
}
|
||||
|
||||
private mergeSecondaryActions(actions: ReadonlyArray<IAction>, panelActions: IAction[]): ReadonlyArray<IAction> {
|
||||
if (panelActions.length && actions.length) {
|
||||
return [
|
||||
...actions,
|
||||
new Separator(),
|
||||
...panelActions,
|
||||
];
|
||||
}
|
||||
return panelActions.length ? panelActions : actions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
|
||||
*/
|
||||
export class PanelDescriptor extends CompositeDescriptor<Panel> {
|
||||
|
||||
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string): PanelDescriptor {
|
||||
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, requestedIndex, _commandId);
|
||||
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number): PanelDescriptor {
|
||||
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, requestedIndex);
|
||||
}
|
||||
|
||||
private constructor(ctor: IConstructorSignature0<Panel>, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string) {
|
||||
super(ctor, id, name, cssClass, order, requestedIndex, _commandId);
|
||||
private constructor(ctor: IConstructorSignature0<Panel>, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number) {
|
||||
super(ctor, id, name, cssClass, order, requestedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ class PartLayout {
|
||||
if (this.options && this.options.hasTitle) {
|
||||
titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT));
|
||||
} else {
|
||||
titleSize = new Dimension(0, 0);
|
||||
titleSize = Dimension.None;
|
||||
}
|
||||
|
||||
let contentWidth = width;
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/activityaction';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { EventType, addDisposableListener, EventHelper } from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
|
||||
import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
@@ -29,12 +29,12 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform';
|
||||
import { getCurrentAuthenticationSessionInfo, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { AuthenticationSession } from 'vs/editor/common/modes';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class ViewContainerActivityAction extends ActivityAction {
|
||||
|
||||
@@ -97,10 +97,10 @@ export class ViewContainerActivityAction extends ActivityAction {
|
||||
|
||||
private logAction(action: string) {
|
||||
type ActivityBarActionClassification = {
|
||||
viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
|
||||
};
|
||||
this.telemetryService.publicLog2<{ viewletId: String, action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
|
||||
this.telemetryService.publicLog2<{ viewletId: String, action: String; }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
|
||||
constructor(
|
||||
private readonly menuId: MenuId,
|
||||
action: ActivityAction,
|
||||
private contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IMenuService protected readonly menuService: IMenuService,
|
||||
@@ -126,30 +127,35 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
|
||||
// Context menus are triggered on mouse down so that an item can be picked
|
||||
// and executed with releasing the mouse over it
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
EventHelper.stop(e, true);
|
||||
this.showContextMenu(e);
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
EventHelper.stop(e, true);
|
||||
this.showContextMenu();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
|
||||
EventHelper.stop(e, true);
|
||||
this.showContextMenu();
|
||||
}));
|
||||
}
|
||||
|
||||
protected async showContextMenu(e?: MouseEvent): Promise<void> {
|
||||
private async showContextMenu(e?: MouseEvent): Promise<void> {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
|
||||
const actions = await this.resolveActions(menu, disposables);
|
||||
let actions: IAction[];
|
||||
if (e?.button !== 2) {
|
||||
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
|
||||
actions = await this.resolveMainMenuActions(menu, disposables);
|
||||
} else {
|
||||
actions = await this.resolveContextMenuActions(disposables);
|
||||
}
|
||||
|
||||
const isUsingCustomMenu = isWeb || (getTitleBarStyle(this.configurationService) !== 'native' && !isMacintosh); // see #40262
|
||||
const position = this.configurationService.getValue('workbench.sideBar.location');
|
||||
@@ -163,13 +169,17 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
|
||||
});
|
||||
}
|
||||
|
||||
protected async resolveActions(menu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
protected async resolveMainMenuActions(menu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
disposables.add(createAndFillInActionBarActions(menu, undefined, { primary: [], secondary: actions }));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
|
||||
return this.contextMenuActionsProvider();
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@@ -179,6 +189,7 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
constructor(
|
||||
private readonly goHomeHref: string,
|
||||
action: ActivityAction,
|
||||
contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@@ -188,27 +199,32 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
super(MenuId.MenubarHomeMenu, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
super(MenuId.MenubarHomeMenu, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
}
|
||||
|
||||
protected async resolveActions(homeMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
protected async resolveMainMenuActions(homeMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions = [];
|
||||
|
||||
// Go Home
|
||||
actions.push(disposables.add(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, async () => window.location.href = this.goHomeHref)));
|
||||
actions.push(disposables.add(new Separator()));
|
||||
actions.push(toAction({ id: 'goHome', label: localize('goHome', "Go Home"), run: () => window.location.href = this.goHomeHref }));
|
||||
|
||||
// Contributed
|
||||
const contributedActions = await super.resolveActions(homeMenu, disposables);
|
||||
actions.push(...contributedActions);
|
||||
|
||||
// Hide
|
||||
if (contributedActions.length > 0) {
|
||||
const contributedActions = await super.resolveMainMenuActions(homeMenu, disposables);
|
||||
if (contributedActions.length) {
|
||||
actions.push(disposables.add(new Separator()));
|
||||
actions.push(...contributedActions);
|
||||
}
|
||||
actions.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => {
|
||||
this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
})));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions = await super.resolveContextMenuActions(disposables);
|
||||
|
||||
actions.unshift(...[
|
||||
toAction({ id: 'hideHomeButton', label: localize('hideHomeButton', "Hide Home Button"), run: () => this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER) }),
|
||||
new Separator()
|
||||
]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
@@ -220,6 +236,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -227,15 +244,15 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
super(MenuId.AccountsContext, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
}
|
||||
|
||||
protected async resolveActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
await super.resolveActions(accountsMenu, disposables);
|
||||
protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
|
||||
await super.resolveMainMenuActions(accountsMenu, disposables);
|
||||
|
||||
const otherCommands = accountsMenu.getActions();
|
||||
const providers = this.authenticationService.getProviderIds();
|
||||
@@ -243,7 +260,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
try {
|
||||
const sessions = await this.authenticationService.getSessions(providerId);
|
||||
|
||||
const groupedSessions: { [label: string]: AuthenticationSession[] } = {};
|
||||
const groupedSessions: { [label: string]: AuthenticationSession[]; } = {};
|
||||
sessions.forEach(session => {
|
||||
if (groupedSessions[session.account.label]) {
|
||||
groupedSessions[session.account.label].push(session);
|
||||
@@ -266,11 +283,11 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
|
||||
if (sessionInfo.sessions) {
|
||||
Object.keys(sessionInfo.sessions).forEach(accountName => {
|
||||
const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => {
|
||||
const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => {
|
||||
return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName);
|
||||
}));
|
||||
|
||||
const signOutAction = disposables.add(new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, () => {
|
||||
const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), '', true, () => {
|
||||
return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName);
|
||||
}));
|
||||
|
||||
@@ -285,7 +302,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
menus.push(providerSubMenu);
|
||||
});
|
||||
} else {
|
||||
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', nls.localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)));
|
||||
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)));
|
||||
menus.push(providerUnavailableAction);
|
||||
}
|
||||
});
|
||||
@@ -302,22 +319,26 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
}
|
||||
});
|
||||
|
||||
if (menus.length) {
|
||||
menus.push(disposables.add(new Separator()));
|
||||
}
|
||||
|
||||
menus.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => {
|
||||
this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
})));
|
||||
|
||||
return menus;
|
||||
}
|
||||
|
||||
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
|
||||
const actions = await super.resolveContextMenuActions(disposables);
|
||||
|
||||
actions.unshift(...[
|
||||
toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER) }),
|
||||
new Separator()
|
||||
]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@@ -326,7 +347,7 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
super(MenuId.GlobalActivity, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +400,7 @@ registerAction2(
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.previousSideBarView',
|
||||
title: { value: nls.localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' },
|
||||
title: { value: localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
}, -1);
|
||||
@@ -392,7 +413,7 @@ registerAction2(
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.nextSideBarView',
|
||||
title: { value: nls.localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' },
|
||||
title: { value: localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
}, 1);
|
||||
@@ -400,7 +421,7 @@ registerAction2(
|
||||
}
|
||||
);
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const activityBarBackgroundColor = theme.getColor(ACTIVITY_BAR_BACKGROUND);
|
||||
if (activityBarBackgroundColor) {
|
||||
collector.addRule(`
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
@@ -13,7 +13,7 @@ import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activ
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
@@ -23,33 +23,34 @@ import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { assertIsDefined, isString } from 'vs/base/common/types';
|
||||
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { isNative, isWeb } from 'vs/base/common/platform';
|
||||
import { Before2D } from 'vs/workbench/browser/dnd';
|
||||
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
|
||||
import { Action, Separator } from 'vs/base/common/actions';
|
||||
import { IAction, Separator, toAction } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { StringSHA1 } from 'vs/base/common/hash';
|
||||
|
||||
interface IPlaceholderViewContainer {
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly iconUrl?: UriComponents;
|
||||
readonly themeIcon?: ThemeIcon;
|
||||
readonly views?: { when?: string }[];
|
||||
readonly isBuiltin?: boolean;
|
||||
readonly views?: { when?: string; }[];
|
||||
}
|
||||
|
||||
interface IPinnedViewContainer {
|
||||
@@ -66,12 +67,10 @@ interface ICachedViewContainer {
|
||||
readonly pinned: boolean;
|
||||
readonly order?: number;
|
||||
visible: boolean;
|
||||
views?: { when?: string }[];
|
||||
isBuiltin?: boolean;
|
||||
views?: { when?: string; }[];
|
||||
}
|
||||
|
||||
const settingsViewBarIcon = registerIcon('settings-view-bar-icon', Codicon.settingsGear, nls.localize('settingsViewBarIcon', 'Settings icon in the view bar.'));
|
||||
const accountsViewBarIcon = registerIcon('accounts-view-bar-icon', Codicon.account, nls.localize('accountsViewBarIcon', 'Accounts icon in the view bar.'));
|
||||
|
||||
export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
@@ -81,6 +80,9 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
private static readonly ACTION_HEIGHT = 48;
|
||||
private static readonly ACCOUNTS_ACTION_INDEX = 0;
|
||||
|
||||
private static readonly GEAR_ICON = registerIcon('settings-view-bar-icon', Codicon.settingsGear, localize('settingsViewBarIcon', "Settings icon in the view bar."));
|
||||
private static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar."));
|
||||
|
||||
//#region IView
|
||||
|
||||
readonly minimumWidth: number = 48;
|
||||
@@ -110,12 +112,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private readonly accountsActivity: ICompositeActivity[] = [];
|
||||
|
||||
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction }>();
|
||||
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; }>();
|
||||
private readonly viewContainerDisposables = new Map<string, IDisposable>();
|
||||
|
||||
private readonly keyboardNavigationDisposables = this._register(new DisposableStore());
|
||||
|
||||
private readonly location = ViewContainerLocation.Sidebar;
|
||||
private hasExtensionsRegistered: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -132,14 +135,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
|
||||
for (const cachedViewContainer of this.cachedViewContainers) {
|
||||
if (
|
||||
environmentService.remoteAuthority || // In remote window, hide activity bar entries until registered
|
||||
this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer)
|
||||
) {
|
||||
cachedViewContainer.visible = false;
|
||||
}
|
||||
cachedViewContainer.visible = !this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer);
|
||||
}
|
||||
|
||||
this.compositeBar = this.createCompositeBar();
|
||||
|
||||
this.onDidRegisterViewContainers(this.getViewContainers());
|
||||
@@ -161,53 +158,66 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
icon: true,
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
preventLoopNavigation: true,
|
||||
openComposite: (compositeId: string) => this.viewsService.openViewContainer(compositeId, true),
|
||||
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)),
|
||||
getContextMenuActions: () => {
|
||||
const actions = [];
|
||||
openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true),
|
||||
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: compositeId => toAction({ id: compositeId, label: '', run: async () => this.viewsService.isViewContainerVisible(compositeId) ? this.viewsService.closeViewContainer(compositeId) : this.viewsService.openViewContainer(compositeId) }),
|
||||
fillExtraContextMenuActions: actions => {
|
||||
|
||||
// Home
|
||||
const topActions: IAction[] = [];
|
||||
if (this.homeBarContainer) {
|
||||
actions.push(new Action(
|
||||
'toggleHomeBarAction',
|
||||
this.homeBarVisibilityPreference ? nls.localize('hideHomeBar', "Hide Home Button") : nls.localize('showHomeBar', "Show Home Button"),
|
||||
undefined,
|
||||
true,
|
||||
async () => { this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference; }
|
||||
));
|
||||
topActions.push({
|
||||
id: 'toggleHomeBarAction',
|
||||
label: localize('homeButton', "Home Button"),
|
||||
class: undefined,
|
||||
tooltip: localize('homeButton', "Home Button"),
|
||||
checked: this.homeBarVisibilityPreference,
|
||||
enabled: true,
|
||||
run: async () => this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference,
|
||||
dispose: () => { }
|
||||
});
|
||||
}
|
||||
|
||||
// Menu
|
||||
const menuBarVisibility = getMenuBarVisibility(this.configurationService);
|
||||
if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) {
|
||||
actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu")));
|
||||
topActions.push({
|
||||
id: 'toggleMenuVisibility',
|
||||
label: localize('menu', "Menu"),
|
||||
class: undefined,
|
||||
tooltip: localize('menu', "Menu"),
|
||||
checked: menuBarVisibility === 'compact',
|
||||
enabled: true,
|
||||
run: async () => this.layoutService.toggleMenuBar(),
|
||||
dispose: () => { }
|
||||
});
|
||||
}
|
||||
|
||||
if (topActions.length) {
|
||||
actions.unshift(...topActions, new Separator());
|
||||
}
|
||||
|
||||
// Accounts
|
||||
actions.push(new Action(
|
||||
'toggleAccountsVisibility',
|
||||
this.accountsVisibilityPreference ? nls.localize('hideAccounts', "Hide Accounts") : nls.localize('showAccounts', "Show Accounts"),
|
||||
undefined,
|
||||
true,
|
||||
async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; }
|
||||
));
|
||||
actions.push(new Separator());
|
||||
actions.push({
|
||||
id: 'toggleAccountsVisibility',
|
||||
label: localize('accounts', "Accounts"),
|
||||
class: undefined,
|
||||
tooltip: localize('accounts', "Accounts"),
|
||||
checked: this.accountsVisibilityPreference,
|
||||
enabled: true,
|
||||
run: async () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference,
|
||||
dispose: () => { }
|
||||
});
|
||||
|
||||
actions.push(new Separator());
|
||||
|
||||
// Toggle Sidebar
|
||||
actions.push(this.instantiationService.createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService)));
|
||||
|
||||
// Toggle Activity Bar
|
||||
actions.push(new Action(
|
||||
ToggleActivityBarVisibilityAction.ID,
|
||||
nls.localize('hideActivitBar', "Hide Activity Bar"),
|
||||
undefined,
|
||||
true,
|
||||
async () => { this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)); }
|
||||
));
|
||||
|
||||
return actions;
|
||||
actions.push(toAction({ id: ToggleActivityBarVisibilityAction.ID, label: localize('hideActivitBar', "Hide Activity Bar"), run: async () => this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)) }));
|
||||
},
|
||||
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
|
||||
getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)!.id,
|
||||
@@ -223,24 +233,20 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}));
|
||||
}
|
||||
|
||||
private getContextMenuActionsForComposite(compositeId: string): Action[] {
|
||||
const actions = [];
|
||||
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
|
||||
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
|
||||
if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) {
|
||||
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
|
||||
this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation);
|
||||
}));
|
||||
actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) }));
|
||||
} else {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (viewContainerModel.allViewDescriptors.length === 1) {
|
||||
const viewToReset = viewContainerModel.allViewDescriptors[0];
|
||||
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
|
||||
if (defaultContainer !== viewContainer) {
|
||||
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
|
||||
this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer);
|
||||
}));
|
||||
actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,7 +284,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}));
|
||||
}
|
||||
|
||||
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>) {
|
||||
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) {
|
||||
removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
|
||||
this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container));
|
||||
}
|
||||
@@ -310,7 +316,22 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
|
||||
private onDidRegisterExtensions(): void {
|
||||
this.removeNotExistingComposites();
|
||||
this.hasExtensionsRegistered = true;
|
||||
|
||||
// show/hide/remove composites
|
||||
for (const { id } of this.cachedViewContainers) {
|
||||
const viewContainer = this.getViewContainer(id);
|
||||
if (viewContainer) {
|
||||
this.showOrHideViewContainer(viewContainer);
|
||||
} else {
|
||||
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
|
||||
this.removeComposite(id);
|
||||
} else {
|
||||
this.hideComposite(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.saveCachedViewContainers();
|
||||
}
|
||||
|
||||
@@ -322,7 +343,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
this.compositeBar.activateComposite(viewContainer.id);
|
||||
|
||||
if (viewContainer.hideIfEmpty) {
|
||||
if (this.shouldBeHidden(viewContainer)) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (viewContainerModel.activeViewDescriptors.length === 0) {
|
||||
// Update the composite bar by hiding
|
||||
@@ -460,7 +481,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
// Home action bar
|
||||
const homeIndicator = this.environmentService.options?.homeIndicator;
|
||||
if (homeIndicator) {
|
||||
// TODO @sbatten remove the fake setting and associated code
|
||||
if (homeIndicator && this.configurationService.getValue<boolean>('window.showHomeIndicator')) {
|
||||
let codicon = iconRegistry.get(homeIndicator.icon);
|
||||
if (!codicon) {
|
||||
codicon = Codicon.code;
|
||||
@@ -556,14 +578,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private createHomeBar(href: string, icon: Codicon): void {
|
||||
this.homeBarContainer = document.createElement('div');
|
||||
this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home"));
|
||||
this.homeBarContainer.setAttribute('aria-label', localize('homeIndicator', "Home"));
|
||||
this.homeBarContainer.setAttribute('role', 'toolbar');
|
||||
this.homeBarContainer.classList.add('home-bar');
|
||||
|
||||
this.homeBar = this._register(new ActionBar(this.homeBarContainer, {
|
||||
actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)),
|
||||
actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('home', "Home"),
|
||||
ariaLabel: localize('home', "Home"),
|
||||
animated: false,
|
||||
preventLoopNavigation: true,
|
||||
ignoreOrientationForPreviousAndNextKey: true
|
||||
@@ -575,7 +597,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.homeBar.push(this._register(new ActivityAction({
|
||||
id: 'workbench.actions.home',
|
||||
name: nls.localize('home', "Home"),
|
||||
name: localize('home', "Home"),
|
||||
cssClass: icon.classNames
|
||||
})));
|
||||
|
||||
@@ -587,17 +609,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.globalActivityActionBar = this._register(new ActionBar(container, {
|
||||
actionViewItemProvider: action => {
|
||||
if (action.id === 'workbench.actions.manage') {
|
||||
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
}
|
||||
|
||||
if (action.id === 'workbench.actions.accounts') {
|
||||
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
|
||||
}
|
||||
|
||||
throw new Error(`No view item for action '${action.id}'`);
|
||||
},
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('manage', "Manage"),
|
||||
ariaLabel: localize('manage', "Manage"),
|
||||
animated: false,
|
||||
preventLoopNavigation: true,
|
||||
ignoreOrientationForPreviousAndNextKey: true
|
||||
@@ -605,15 +627,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.globalActivityAction = this._register(new ActivityAction({
|
||||
id: 'workbench.actions.manage',
|
||||
name: nls.localize('manage', "Manage"),
|
||||
cssClass: ThemeIcon.asClassName(settingsViewBarIcon)
|
||||
name: localize('manage', "Manage"),
|
||||
cssClass: ThemeIcon.asClassName(ActivitybarPart.GEAR_ICON)
|
||||
}));
|
||||
|
||||
if (this.accountsVisibilityPreference) {
|
||||
this.accountsActivityAction = this._register(new ActivityAction({
|
||||
id: 'workbench.actions.accounts',
|
||||
name: nls.localize('accounts', "Accounts"),
|
||||
cssClass: ThemeIcon.asClassName(accountsViewBarIcon)
|
||||
name: localize('accounts', "Accounts"),
|
||||
cssClass: ThemeIcon.asClassName(ActivitybarPart.ACCOUNTS_ICON)
|
||||
}));
|
||||
|
||||
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
|
||||
@@ -630,7 +652,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
} else {
|
||||
this.accountsActivityAction = this._register(new ActivityAction({
|
||||
id: 'workbench.actions.accounts',
|
||||
name: nls.localize('accounts', "Accounts"),
|
||||
name: localize('accounts', "Accounts"),
|
||||
cssClass: Codicon.account.classNames
|
||||
}));
|
||||
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
|
||||
@@ -640,7 +662,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.updateGlobalActivity(ACCOUNTS_ACTIVITY_ID);
|
||||
}
|
||||
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } {
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; } {
|
||||
let compositeActions = this.compositeActions.get(compositeId);
|
||||
if (!compositeActions) {
|
||||
const viewContainer = this.getViewContainer(compositeId);
|
||||
@@ -666,32 +688,27 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private onDidRegisterViewContainers(viewContainers: ReadonlyArray<ViewContainer>): void {
|
||||
for (const viewContainer of viewContainers) {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
|
||||
// Pin it by default if it is new
|
||||
const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];
|
||||
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
|
||||
const isActive = visibleViewContainer?.id === viewContainer.id;
|
||||
|
||||
if (isActive || !this.shouldBeHidden(viewContainer.id, cachedViewContainer)) {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
|
||||
// Pin it by default if it is new
|
||||
if (!cachedViewContainer) {
|
||||
this.compositeBar.pin(viewContainer.id);
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
this.compositeBar.activateComposite(viewContainer.id);
|
||||
}
|
||||
if (!cachedViewContainer) {
|
||||
this.compositeBar.pin(viewContainer.id);
|
||||
}
|
||||
|
||||
// Active
|
||||
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
|
||||
if (visibleViewContainer?.id === viewContainer.id) {
|
||||
this.compositeBar.activateComposite(viewContainer.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const viewContainer of viewContainers) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
this.updateActivity(viewContainer, viewContainerModel);
|
||||
this.onDidChangeActiveViews(viewContainer, viewContainerModel);
|
||||
this.showOrHideViewContainer(viewContainer);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel)));
|
||||
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel)));
|
||||
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer)));
|
||||
|
||||
this.viewContainerDisposables.set(viewContainer.id, disposables);
|
||||
}
|
||||
@@ -728,12 +745,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
let iconUrl: URI | undefined = undefined;
|
||||
if (URI.isUri(icon)) {
|
||||
iconUrl = icon;
|
||||
cssClass = `activity-${id.replace(/\./g, '-')}`;
|
||||
const cssUrl = asCSSUrl(icon);
|
||||
const hash = new StringSHA1();
|
||||
hash.update(cssUrl);
|
||||
cssClass = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`;
|
||||
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
|
||||
createCSSRule(iconClass, `
|
||||
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
mask: ${cssUrl} no-repeat 50% 50%;
|
||||
mask-size: 24px;
|
||||
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
-webkit-mask: ${cssUrl} no-repeat 50% 50%;
|
||||
-webkit-mask-size: 24px;
|
||||
`);
|
||||
} else if (ThemeIcon.isThemeIcon(icon)) {
|
||||
@@ -743,36 +763,43 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
return { id, name, cssClass, iconUrl, keybindingId };
|
||||
}
|
||||
|
||||
private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
|
||||
if (viewContainerModel.activeViewDescriptors.length) {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
} else if (viewContainer.hideIfEmpty) {
|
||||
private showOrHideViewContainer(viewContainer: ViewContainer): void {
|
||||
if (this.shouldBeHidden(viewContainer)) {
|
||||
this.hideComposite(viewContainer.id);
|
||||
} else {
|
||||
this.compositeBar.addComposite(viewContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private shouldBeHidden(viewContainerId: string, cachedViewContainer?: ICachedViewContainer): boolean {
|
||||
const viewContainer = this.getViewContainer(viewContainerId);
|
||||
if (!viewContainer || !viewContainer.hideIfEmpty) {
|
||||
return false;
|
||||
}
|
||||
private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean {
|
||||
const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId;
|
||||
const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id;
|
||||
|
||||
return cachedViewContainer?.views && cachedViewContainer.views.length
|
||||
? cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)))
|
||||
: viewContainerId === TEST_VIEW_CONTAINER_ID /* Hide Test view container for the first time or it had no views registered before */;
|
||||
}
|
||||
|
||||
private removeNotExistingComposites(): void {
|
||||
const viewContainers = this.getViewContainers();
|
||||
for (const { id } of this.cachedViewContainers) {
|
||||
if (viewContainers.every(viewContainer => viewContainer.id !== id)) {
|
||||
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
|
||||
this.removeComposite(id);
|
||||
} else {
|
||||
this.hideComposite(id);
|
||||
if (viewContainer) {
|
||||
if (viewContainer.hideIfEmpty) {
|
||||
if (this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.length > 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window
|
||||
if (!this.hasExtensionsRegistered && !(this.environmentService.remoteAuthority && isNative)) {
|
||||
cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);
|
||||
|
||||
// Show builtin ViewContainer if not registered yet
|
||||
if (!viewContainer && cachedViewContainer?.isBuiltin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cachedViewContainer?.views?.length) {
|
||||
return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private hideComposite(compositeId: string): void {
|
||||
@@ -864,7 +891,6 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private getViewContainer(id: string): ViewContainer | undefined {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
|
||||
|
||||
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
|
||||
}
|
||||
|
||||
@@ -918,22 +944,22 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
const viewContainer = this.getViewContainer(compositeItem.id);
|
||||
if (viewContainer) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
const views: { when: string | undefined }[] = [];
|
||||
const views: { when: string | undefined; }[] = [];
|
||||
for (const { when } of viewContainerModel.allViewDescriptors) {
|
||||
views.push({ when: when ? when.serialize() : undefined });
|
||||
}
|
||||
const cacheIcon = URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon.scheme === Schemas.file : true;
|
||||
state.push({
|
||||
id: compositeItem.id,
|
||||
name: viewContainerModel.title,
|
||||
icon: cacheIcon ? viewContainerModel.icon : undefined,
|
||||
icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority && isNative ? undefined : viewContainerModel.icon, /* Donot cache uri icons in desktop with remote connection */
|
||||
views,
|
||||
pinned: compositeItem.pinned,
|
||||
order: compositeItem.order,
|
||||
visible: compositeItem.visible
|
||||
visible: compositeItem.visible,
|
||||
isBuiltin: !viewContainer.extensionId
|
||||
});
|
||||
} else {
|
||||
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false });
|
||||
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,9 +974,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
|
||||
if (cachedViewContainer) {
|
||||
cachedViewContainer.name = placeholderViewContainer.name;
|
||||
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? ThemeIcon.revive(placeholderViewContainer.themeIcon) :
|
||||
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon :
|
||||
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
|
||||
cachedViewContainer.views = placeholderViewContainer.views;
|
||||
cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -966,11 +993,12 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
order
|
||||
})));
|
||||
|
||||
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => (<IPlaceholderViewContainer>{
|
||||
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => (<IPlaceholderViewContainer>{
|
||||
id,
|
||||
iconUrl: URI.isUri(icon) ? icon : undefined,
|
||||
themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined,
|
||||
name,
|
||||
isBuiltin,
|
||||
views
|
||||
})));
|
||||
}
|
||||
@@ -1067,7 +1095,7 @@ class FocusActivityBarAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.focusActivityBar',
|
||||
title: { value: nls.localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' },
|
||||
title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' },
|
||||
category: CATEGORIES.View,
|
||||
f1: true
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import { IAction, toAction } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -149,10 +149,10 @@ export interface ICompositeBarOptions {
|
||||
readonly preventLoopNavigation?: boolean;
|
||||
|
||||
getActivityAction: (compositeId: string) => ActivityAction;
|
||||
getCompositePinnedAction: (compositeId: string) => Action;
|
||||
getOnCompositeClickAction: (compositeId: string) => Action;
|
||||
getContextMenuActions: () => Action[];
|
||||
getContextMenuActionsForComposite: (compositeId: string) => Action[];
|
||||
getCompositePinnedAction: (compositeId: string) => IAction;
|
||||
getOnCompositeClickAction: (compositeId: string) => IAction;
|
||||
fillExtraContextMenuActions: (actions: IAction[]) => void;
|
||||
getContextMenuActionsForComposite: (compositeId: string) => IAction[];
|
||||
openComposite: (compositeId: string) => Promise<IComposite | null>;
|
||||
getDefaultCompositeId: () => string;
|
||||
hidePart: () => void;
|
||||
@@ -208,15 +208,15 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
const actionBarDiv = parent.appendChild($('.composite-bar'));
|
||||
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
actionViewItemProvider: action => {
|
||||
if (action instanceof CompositeOverflowActivityAction) {
|
||||
return this.compositeOverflowActionViewItem;
|
||||
}
|
||||
const item = this.model.findItem(action.id);
|
||||
return item && this.instantiationService.createInstance(
|
||||
CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
|
||||
(compositeId: string) => this.options.getContextMenuActionsForComposite(compositeId),
|
||||
() => this.getContextMenuActions() as Action[],
|
||||
compositeId => this.options.getContextMenuActionsForComposite(compositeId),
|
||||
() => this.getContextMenuActions(),
|
||||
this.options.colors,
|
||||
this.options.icon,
|
||||
this.options.dndHandler,
|
||||
@@ -319,7 +319,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number }): void {
|
||||
addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number; }): void {
|
||||
// Add to the model
|
||||
if (this.model.add(id, name, order, requestedIndex)) {
|
||||
this.computeSizes([this.model.findItem(id)]);
|
||||
@@ -596,7 +596,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.compositeOverflowAction,
|
||||
() => this.getOverflowingComposites(),
|
||||
() => this.model.activeItem ? this.model.activeItem.id : undefined,
|
||||
(compositeId: string) => {
|
||||
compositeId => {
|
||||
const item = this.model.findItem(compositeId);
|
||||
return item?.activity[0]?.badge;
|
||||
},
|
||||
@@ -610,7 +610,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private getOverflowingComposites(): { id: string, name?: string }[] {
|
||||
private getOverflowingComposites(): { id: string, name?: string; }[] {
|
||||
let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id);
|
||||
|
||||
// Show the active composite even if it is not pinned
|
||||
@@ -631,9 +631,9 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
});
|
||||
}
|
||||
|
||||
private getContextMenuActions(): IAction[] {
|
||||
getContextMenuActions(): IAction[] {
|
||||
const actions: IAction[] = this.model.visibleItems
|
||||
.map(({ id, name, activityAction }) => (<IAction>{
|
||||
.map(({ id, name, activityAction }) => (toAction({
|
||||
id,
|
||||
label: this.getAction(id).label || name || id,
|
||||
checked: this.isPinned(id),
|
||||
@@ -645,19 +645,17 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.pin(id, true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
const otherActions = this.options.getContextMenuActions();
|
||||
if (otherActions.length) {
|
||||
actions.push(new Separator());
|
||||
actions.push(...otherActions);
|
||||
}
|
||||
})));
|
||||
|
||||
this.options.fillExtraContextMenuActions(actions);
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
interface ICompositeBarModelItem extends ICompositeBarItem {
|
||||
activityAction: ActivityAction;
|
||||
pinnedAction: Action;
|
||||
pinnedAction: IAction;
|
||||
activity: ICompositeActivity[];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action, Separator } from 'vs/base/common/actions';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -374,14 +374,14 @@ export class CompositeOverflowActivityAction extends ActivityAction {
|
||||
}
|
||||
|
||||
export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem {
|
||||
private actions: Action[] = [];
|
||||
private actions: IAction[] = [];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingComposites: () => { id: string, name?: string }[],
|
||||
private getActiveCompositeId: () => string | undefined,
|
||||
private getBadge: (compositeId: string) => IBadge,
|
||||
private getCompositeOpenAction: (compositeId: string) => Action,
|
||||
private getCompositeOpenAction: (compositeId: string) => IAction,
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@@ -404,7 +404,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): Action[] {
|
||||
private getActions(): IAction[] {
|
||||
return this.getOverflowingComposites().map(composite => {
|
||||
const action = this.getCompositeOpenAction(composite.id);
|
||||
action.checked = this.getActiveCompositeId() === action.id;
|
||||
@@ -457,9 +457,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
|
||||
constructor(
|
||||
private compositeActivityAction: ActivityAction,
|
||||
private toggleCompositePinnedAction: Action,
|
||||
private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray<Action>,
|
||||
private contextMenuActionsProvider: () => ReadonlyArray<Action>,
|
||||
private toggleCompositePinnedAction: IAction,
|
||||
private compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
|
||||
private contextMenuActionsProvider: () => IAction[],
|
||||
colors: (theme: IColorTheme) => ICompositeBarColors,
|
||||
icon: boolean,
|
||||
private dndHandler: ICompositeDragAndDrop,
|
||||
@@ -500,9 +500,14 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
return this.compositeActivity;
|
||||
}
|
||||
|
||||
private getActivtyName(): string {
|
||||
private getActivtyName(skipKeybinding = false): string {
|
||||
let name = this.compositeActivityAction.activity.name;
|
||||
if (skipKeybinding) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null;
|
||||
return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding.getLabel()) : this.compositeActivityAction.activity.name;
|
||||
return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
@@ -601,7 +606,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [this.toggleCompositePinnedAction];
|
||||
const actions: IAction[] = [this.toggleCompositePinnedAction];
|
||||
|
||||
const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id);
|
||||
if (compositeContextMenuActions.length) {
|
||||
@@ -615,10 +620,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
|
||||
const isPinned = this.compositeBar.isPinned(this.activity.id);
|
||||
if (isPinned) {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide");
|
||||
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide '{0}'", this.getActivtyName(true));
|
||||
this.toggleCompositePinnedAction.checked = false;
|
||||
} else {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep");
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep '{0}'", this.getActivtyName(true));
|
||||
}
|
||||
|
||||
const otherActions = this.contextMenuActionsProvider();
|
||||
|
||||
@@ -327,10 +327,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
const primaryActions: IAction[] = composite?.getActions().slice(0) || [];
|
||||
const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || [];
|
||||
|
||||
// From Part
|
||||
primaryActions.push(...this.getActions());
|
||||
secondaryActions.push(...this.getSecondaryActions());
|
||||
|
||||
// Update context
|
||||
const toolBar = assertIsDefined(this.toolBar);
|
||||
toolBar.context = this.actionsContextProvider();
|
||||
@@ -471,14 +467,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return compositeItem ? compositeItem.progress : undefined;
|
||||
}
|
||||
|
||||
protected getActions(): ReadonlyArray<IAction> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
|
||||
return AnchorAlignment.RIGHT;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution {
|
||||
private impl: IDialogHandler;
|
||||
private readonly model: IDialogsModel;
|
||||
private readonly impl: IDialogHandler;
|
||||
|
||||
private model: IDialogsModel;
|
||||
private currentDialog: IDialogViewItem | undefined;
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -6,19 +6,13 @@
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { extUri } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { SymbolKinds } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
@@ -28,35 +22,92 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { IListService, WorkbenchDataTree, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { BreadcrumbsModel, FileElement, OutlineElement2 } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { IEditorPartOptions, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { onDidChangeZoomLevel } from 'vs/base/browser/browser';
|
||||
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IOutline } from 'vs/workbench/services/outline/browser/outline';
|
||||
|
||||
class Item extends BreadcrumbsItem {
|
||||
class OutlineItem extends BreadcrumbsItem {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
readonly element: BreadcrumbElement,
|
||||
readonly model: BreadcrumbsModel,
|
||||
readonly element: OutlineElement2,
|
||||
readonly options: IBreadcrumbsControlOptions
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
equals(other: BreadcrumbsItem): boolean {
|
||||
if (!(other instanceof OutlineItem)) {
|
||||
return false;
|
||||
}
|
||||
return this.element === other.element &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
const { element, outline } = this.element;
|
||||
|
||||
if (element === outline) {
|
||||
const element = dom.$('span', undefined, '…');
|
||||
container.appendChild(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const templateId = outline.config.delegate.getTemplateId(element);
|
||||
const renderer = outline.config.renderers.find(renderer => renderer.templateId === templateId);
|
||||
if (!renderer) {
|
||||
container.innerText = '<<NO RENDERER>>';
|
||||
return;
|
||||
}
|
||||
|
||||
const template = renderer.renderTemplate(container);
|
||||
renderer.renderElement(<ITreeNode<any, any>>{
|
||||
element,
|
||||
children: [],
|
||||
depth: 0,
|
||||
visibleChildrenCount: 0,
|
||||
visibleChildIndex: 0,
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
visible: true,
|
||||
filterData: undefined
|
||||
}, 0, template, undefined);
|
||||
|
||||
this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); }));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileItem extends BreadcrumbsItem {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
readonly model: BreadcrumbsModel,
|
||||
readonly element: FileElement,
|
||||
readonly options: IBreadcrumbsControlOptions,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
@@ -68,59 +119,26 @@ class Item extends BreadcrumbsItem {
|
||||
}
|
||||
|
||||
equals(other: BreadcrumbsItem): boolean {
|
||||
if (!(other instanceof Item)) {
|
||||
if (!(other instanceof FileItem)) {
|
||||
return false;
|
||||
}
|
||||
if (this.element instanceof FileElement && other.element instanceof FileElement) {
|
||||
return (extUri.isEqual(this.element.uri, other.element.uri) &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons);
|
||||
}
|
||||
if (this.element instanceof TreeElement && other.element instanceof TreeElement) {
|
||||
return this.element.id === other.element.id;
|
||||
}
|
||||
return false;
|
||||
return (extUri.isEqual(this.element.uri, other.element.uri) &&
|
||||
this.options.showFileIcons === other.options.showFileIcons &&
|
||||
this.options.showSymbolIcons === other.options.showSymbolIcons);
|
||||
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
if (this.element instanceof FileElement) {
|
||||
// file/folder
|
||||
let label = this._instantiationService.createInstance(ResourceLabel, container, {});
|
||||
label.element.setFile(this.element.uri, {
|
||||
hidePath: true,
|
||||
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
|
||||
fileKind: this.element.kind,
|
||||
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
|
||||
});
|
||||
container.classList.add(FileKind[this.element.kind].toLowerCase());
|
||||
this._disposables.add(label);
|
||||
|
||||
} else if (this.element instanceof OutlineModel) {
|
||||
// has outline element but not in one
|
||||
let label = document.createElement('div');
|
||||
label.innerText = '\u2026';
|
||||
label.className = 'hint-more';
|
||||
container.appendChild(label);
|
||||
|
||||
} else if (this.element instanceof OutlineGroup) {
|
||||
// provider
|
||||
let label = new IconLabel(container);
|
||||
label.setLabel(this.element.label);
|
||||
this._disposables.add(label);
|
||||
|
||||
} else if (this.element instanceof OutlineElement) {
|
||||
// symbol
|
||||
if (this.options.showSymbolIcons) {
|
||||
let icon = document.createElement('div');
|
||||
icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind);
|
||||
container.appendChild(icon);
|
||||
container.classList.add('shows-symbol-icon');
|
||||
}
|
||||
let label = new IconLabel(container);
|
||||
let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE');
|
||||
label.setLabel(title);
|
||||
this._disposables.add(label);
|
||||
}
|
||||
// file/folder
|
||||
let label = this._instantiationService.createInstance(ResourceLabel, container, {});
|
||||
label.element.setFile(this.element.uri, {
|
||||
hidePath: true,
|
||||
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
|
||||
fileKind: this.element.kind,
|
||||
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
|
||||
});
|
||||
container.classList.add(FileKind[this.element.kind].toLowerCase());
|
||||
this._disposables.add(label);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,26 +188,23 @@ export class BreadcrumbsControl {
|
||||
private readonly _editorGroup: IEditorGroupView,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
|
||||
) {
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.classList.add('breadcrumbs-control');
|
||||
dom.append(container, this.domNode);
|
||||
|
||||
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService);
|
||||
this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService);
|
||||
this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(_configurationService);
|
||||
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(configurationService);
|
||||
this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(configurationService);
|
||||
this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService);
|
||||
|
||||
const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default';
|
||||
this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing]);
|
||||
@@ -256,14 +271,11 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsVisible.set(true);
|
||||
this._ckBreadcrumbsPossible.set(true);
|
||||
|
||||
const editor = this._getActiveCodeEditor();
|
||||
const model = new EditorBreadcrumbsModel(
|
||||
const model = this._instantiationService.createInstance(BreadcrumbsModel,
|
||||
fileInfoUri ?? uri,
|
||||
uri, editor,
|
||||
this._configurationService,
|
||||
this._textResourceConfigurationService,
|
||||
this._workspaceService
|
||||
this._editorGroup.activeEditorPane
|
||||
);
|
||||
|
||||
this.domNode.classList.toggle('relative-path', model.isRelative());
|
||||
this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\');
|
||||
|
||||
@@ -274,7 +286,7 @@ export class BreadcrumbsControl {
|
||||
showFileIcons: this._options.showFileIcons && showIcons,
|
||||
showSymbolIcons: this._options.showSymbolIcons && showIcons
|
||||
};
|
||||
const items = model.getElements().map(element => new Item(element, options, this._instantiationService));
|
||||
const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options));
|
||||
this._widget.setItems(items);
|
||||
this._widget.reveal(items[items.length - 1]);
|
||||
};
|
||||
@@ -298,7 +310,7 @@ export class BreadcrumbsControl {
|
||||
this._breadcrumbsDisposables.add({
|
||||
dispose: () => {
|
||||
if (this._breadcrumbsPickerShowing) {
|
||||
this._contextViewService.hideContextView(this);
|
||||
this._contextViewService.hideContextView({ source: this });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -306,20 +318,6 @@ export class BreadcrumbsControl {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _getActiveCodeEditor(): ICodeEditor | undefined {
|
||||
if (!this._editorGroup.activeEditorPane) {
|
||||
return undefined;
|
||||
}
|
||||
let control = this._editorGroup.activeEditorPane.getControl();
|
||||
let editor: ICodeEditor | undefined;
|
||||
if (isCodeEditor(control)) {
|
||||
editor = control as ICodeEditor;
|
||||
} else if (isDiffEditor(control)) {
|
||||
editor = control.getModifiedEditor();
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
|
||||
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
|
||||
if (event.item && this._breadcrumbsPickerShowing) {
|
||||
this._breadcrumbsPickerIgnoreOnceItem = undefined;
|
||||
@@ -339,13 +337,12 @@ export class BreadcrumbsControl {
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = event.item as Item;
|
||||
const { element } = event.item as FileItem | OutlineItem;
|
||||
this._editorGroup.focus();
|
||||
|
||||
type BreadcrumbSelectClassification = {
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
this._telemetryService.publicLog2<{ type: string }, BreadcrumbSelectClassification>('breadcrumbs/select', { type: element instanceof TreeElement ? 'symbol' : 'file' });
|
||||
type BreadcrumbSelect = { type: string };
|
||||
type BreadcrumbSelectClassification = { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; };
|
||||
this._telemetryService.publicLog2<BreadcrumbSelect, BreadcrumbSelectClassification>('breadcrumbs/select', { type: event.item instanceof OutlineItem ? 'symbol' : 'file' });
|
||||
|
||||
const group = this._getEditorGroup(event.payload);
|
||||
if (group !== undefined) {
|
||||
@@ -360,64 +357,31 @@ export class BreadcrumbsControl {
|
||||
// using quick pick
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
this._quickInputService.quickAccess.show(element instanceof TreeElement ? '@' : '');
|
||||
this._quickInputService.quickAccess.show(element instanceof OutlineElement2 ? '@' : '');
|
||||
return;
|
||||
}
|
||||
|
||||
// show picker
|
||||
let picker: BreadcrumbsPicker;
|
||||
let pickerAnchor: { x: number; y: number };
|
||||
let editor = this._getActiveCodeEditor();
|
||||
let editorDecorations: string[] = [];
|
||||
let editorViewState: ICodeEditorViewState | undefined;
|
||||
|
||||
interface IHideData { didPick?: boolean, source?: BreadcrumbsControl }
|
||||
|
||||
this._contextViewService.showContextView({
|
||||
render: (parent: HTMLElement) => {
|
||||
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
|
||||
let selectListener = picker.onDidPickElement(data => {
|
||||
if (data.target) {
|
||||
editorViewState = undefined;
|
||||
}
|
||||
this._contextViewService.hideContextView(this);
|
||||
if (event.item instanceof FileItem) {
|
||||
picker = this._instantiationService.createInstance(BreadcrumbsFilePicker, parent, event.item.model.resource);
|
||||
} else if (event.item instanceof OutlineItem) {
|
||||
picker = this._instantiationService.createInstance(BreadcrumbsOutlinePicker, parent, event.item.model.resource);
|
||||
}
|
||||
|
||||
const group = (picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).metaKey) || (!picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).altKey)
|
||||
? SIDE_GROUP
|
||||
: ACTIVE_GROUP;
|
||||
|
||||
this._revealInEditor(event, data.target, group, (data.browserEvent as MouseEvent).button === 1);
|
||||
/* __GDPR__
|
||||
"breadcrumbs/open" : {
|
||||
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('breadcrumbs/open', { type: !data ? 'nothing' : data.target instanceof TreeElement ? 'symbol' : 'file' });
|
||||
});
|
||||
let focusListener = picker.onDidFocusElement(data => {
|
||||
if (!editor || !(data.target instanceof OutlineElement)) {
|
||||
return;
|
||||
}
|
||||
if (!editorViewState) {
|
||||
editorViewState = withNullAsUndefined(editor.saveViewState());
|
||||
}
|
||||
const { symbol } = data.target;
|
||||
editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth);
|
||||
editorDecorations = editor.deltaDecorations(editorDecorations, [{
|
||||
range: symbol.range,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
}]);
|
||||
});
|
||||
|
||||
let zoomListener = onDidChangeZoomLevel(() => {
|
||||
this._contextViewService.hideContextView(this);
|
||||
});
|
||||
let selectListener = picker.onWillPickElement(() => this._contextViewService.hideContextView({ source: this, didPick: true }));
|
||||
let zoomListener = onDidChangeZoomLevel(() => this._contextViewService.hideContextView({ source: this }));
|
||||
|
||||
let focusTracker = dom.trackFocus(parent);
|
||||
let blurListener = focusTracker.onDidBlur(() => {
|
||||
this._breadcrumbsPickerIgnoreOnceItem = this._widget.isDOMFocused() ? event.item : undefined;
|
||||
this._contextViewService.hideContextView(this);
|
||||
this._contextViewService.hideContextView({ source: this });
|
||||
});
|
||||
|
||||
this._breadcrumbsPickerShowing = true;
|
||||
@@ -426,7 +390,6 @@ export class BreadcrumbsControl {
|
||||
return combinedDisposable(
|
||||
picker,
|
||||
selectListener,
|
||||
focusListener,
|
||||
zoomListener,
|
||||
focusTracker,
|
||||
blurListener
|
||||
@@ -465,19 +428,17 @@ export class BreadcrumbsControl {
|
||||
}
|
||||
return pickerAnchor;
|
||||
},
|
||||
onHide: (data) => {
|
||||
if (editor) {
|
||||
editor.deltaDecorations(editorDecorations, []);
|
||||
if (editorViewState) {
|
||||
editor.restoreViewState(editorViewState);
|
||||
}
|
||||
onHide: (data?: IHideData) => {
|
||||
if (!data?.didPick) {
|
||||
picker.restoreViewState();
|
||||
}
|
||||
this._breadcrumbsPickerShowing = false;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
if (data === this) {
|
||||
if (data?.source === this) {
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
}
|
||||
picker.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -487,11 +448,11 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsActive.set(value);
|
||||
}
|
||||
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
|
||||
private async _revealInEditor(event: IBreadcrumbsItemEvent, element: FileElement | OutlineElement2, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): Promise<void> {
|
||||
|
||||
if (element instanceof FileElement) {
|
||||
if (element.kind === FileKind.FILE) {
|
||||
// open file in any editor
|
||||
this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group);
|
||||
await this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group);
|
||||
} else {
|
||||
// show next picker
|
||||
let items = this._widget.getItems();
|
||||
@@ -499,20 +460,8 @@ export class BreadcrumbsControl {
|
||||
this._widget.setFocused(items[idx + 1]);
|
||||
this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick);
|
||||
}
|
||||
|
||||
} else if (element instanceof OutlineElement) {
|
||||
// open symbol in code editor
|
||||
const model = OutlineModel.get(element);
|
||||
if (model) {
|
||||
this._codeEditorService.openCodeEditor({
|
||||
resource: model.uri,
|
||||
options: {
|
||||
selection: Range.collapseToStart(element.symbol.selectionRange),
|
||||
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,
|
||||
pinned
|
||||
}
|
||||
}, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP);
|
||||
}
|
||||
} else {
|
||||
element.outline.reveal(element, { pinned }, group === SIDE_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,29 +693,29 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
handler(accessor) {
|
||||
const editors = accessor.get(IEditorService);
|
||||
const lists = accessor.get(IListService);
|
||||
const element = lists.lastFocusedList ? <OutlineElement | IFileStat>lists.lastFocusedList.getFocus()[0] : undefined;
|
||||
if (element instanceof OutlineElement) {
|
||||
const outlineElement = OutlineModel.get(element);
|
||||
if (!outlineElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// open symbol in editor
|
||||
return editors.openEditor({
|
||||
resource: outlineElement.uri,
|
||||
options: { selection: Range.collapseToStart(element.symbol.selectionRange), pinned: true }
|
||||
}, SIDE_GROUP);
|
||||
const tree = lists.lastFocusedList;
|
||||
if (!(tree instanceof WorkbenchDataTree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (element && URI.isUri(element.resource)) {
|
||||
// open file in editor
|
||||
const element = <IFileStat | unknown>tree.getFocus()[0];
|
||||
|
||||
if (URI.isUri((<IFileStat>element)?.resource)) {
|
||||
// IFileStat: open file in editor
|
||||
return editors.openEditor({
|
||||
resource: element.resource,
|
||||
resource: (<IFileStat>element).resource,
|
||||
options: { pinned: true }
|
||||
}, SIDE_GROUP);
|
||||
}
|
||||
|
||||
} else {
|
||||
// ignore
|
||||
return undefined;
|
||||
// IOutline: check if this the outline and iff so reveal element
|
||||
const input = tree.getInput();
|
||||
if (input && typeof (<IOutline<any>>input).outlineKind === 'string') {
|
||||
return (<IOutline<any>>input).reveal(element, {
|
||||
pinned: true,
|
||||
preserveFocus: false
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,27 +3,20 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isEqual, dirname } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
|
||||
import { IEditorPane } from 'vs/workbench/common/editor';
|
||||
|
||||
export class FileElement {
|
||||
constructor(
|
||||
@@ -32,11 +25,16 @@ export class FileElement {
|
||||
) { }
|
||||
}
|
||||
|
||||
export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement;
|
||||
|
||||
type FileInfo = { path: FileElement[], folder?: IWorkspaceFolder };
|
||||
|
||||
export class EditorBreadcrumbsModel {
|
||||
export class OutlineElement2 {
|
||||
constructor(
|
||||
readonly element: IOutline<any> | any,
|
||||
readonly outline: IOutline<any>
|
||||
) { }
|
||||
}
|
||||
|
||||
export class BreadcrumbsModel {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _fileInfo: FileInfo;
|
||||
@@ -45,28 +43,31 @@ export class EditorBreadcrumbsModel {
|
||||
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
|
||||
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
|
||||
|
||||
private _outlineElements: Array<OutlineModel | OutlineGroup | OutlineElement> = [];
|
||||
private _outlineDisposables = new DisposableStore();
|
||||
private readonly _currentOutline = new MutableDisposable<IOutline<any>>();
|
||||
private readonly _outlineDisposables = new DisposableStore();
|
||||
|
||||
private readonly _onDidUpdate = new Emitter<this>();
|
||||
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
|
||||
|
||||
constructor(
|
||||
fileInfoUri: URI,
|
||||
private readonly _uri: URI,
|
||||
private readonly _editor: ICodeEditor | undefined,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
|
||||
readonly resource: URI,
|
||||
editor: IEditorPane | undefined,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IOutlineService private readonly _outlineService: IOutlineService,
|
||||
) {
|
||||
this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(_configurationService);
|
||||
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService);
|
||||
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService);
|
||||
this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(configurationService);
|
||||
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
|
||||
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);
|
||||
|
||||
this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
|
||||
this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
|
||||
this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(fileInfoUri, workspaceService);
|
||||
this._bindToEditor();
|
||||
this._fileInfo = this._initFilePathInfo(resource);
|
||||
|
||||
if (editor) {
|
||||
this._bindToEditor(editor);
|
||||
this._disposables.add(_outlineService.onDidChange(() => this._bindToEditor(editor)));
|
||||
}
|
||||
this._onDidUpdate.fire(this);
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ export class EditorBreadcrumbsModel {
|
||||
this._cfgEnabled.dispose();
|
||||
this._cfgFilePath.dispose();
|
||||
this._cfgSymbolPath.dispose();
|
||||
this._currentOutline.dispose();
|
||||
this._outlineDisposables.dispose();
|
||||
this._disposables.dispose();
|
||||
this._onDidUpdate.dispose();
|
||||
@@ -83,8 +85,8 @@ export class EditorBreadcrumbsModel {
|
||||
return Boolean(this._fileInfo.folder);
|
||||
}
|
||||
|
||||
getElements(): ReadonlyArray<BreadcrumbElement> {
|
||||
let result: BreadcrumbElement[] = [];
|
||||
getElements(): ReadonlyArray<FileElement | OutlineElement2> {
|
||||
let result: (FileElement | OutlineElement2)[] = [];
|
||||
|
||||
// file path elements
|
||||
if (this._cfgFilePath.getValue() === 'on') {
|
||||
@@ -93,17 +95,27 @@ export class EditorBreadcrumbsModel {
|
||||
result = result.concat(this._fileInfo.path.slice(-1));
|
||||
}
|
||||
|
||||
// symbol path elements
|
||||
if (this._cfgSymbolPath.getValue() === 'on') {
|
||||
result = result.concat(this._outlineElements);
|
||||
} else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) {
|
||||
result = result.concat(this._outlineElements.slice(-1));
|
||||
if (this._cfgSymbolPath.getValue() === 'off') {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!this._currentOutline.value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements();
|
||||
for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {
|
||||
result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value));
|
||||
}
|
||||
|
||||
if (breadcrumbsElements.length === 0 && !this._currentOutline.value.isEmpty) {
|
||||
result.push(new OutlineElement2(this._currentOutline.value, this._currentOutline.value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
|
||||
private _initFilePathInfo(uri: URI): FileInfo {
|
||||
|
||||
if (uri.scheme === Schemas.untitled) {
|
||||
return {
|
||||
@@ -113,7 +125,7 @@ export class EditorBreadcrumbsModel {
|
||||
}
|
||||
|
||||
let info: FileInfo = {
|
||||
folder: withNullAsUndefined(workspaceService.getWorkspaceFolder(uri)),
|
||||
folder: withNullAsUndefined(this._workspaceService.getWorkspaceFolder(uri)),
|
||||
path: []
|
||||
};
|
||||
|
||||
@@ -130,181 +142,33 @@ export class EditorBreadcrumbsModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
if (info.folder && this._workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private _bindToEditor(): void {
|
||||
if (!this._editor) {
|
||||
return;
|
||||
}
|
||||
// update as language, model, providers changes
|
||||
this._disposables.add(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline()));
|
||||
this._disposables.add(this._editor.onDidChangeModel(_ => this._updateOutline()));
|
||||
this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._updateOutline()));
|
||||
|
||||
// update when config changes (re-render)
|
||||
this._disposables.add(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (!this._cfgEnabled.getValue()) {
|
||||
// breadcrumbs might be disabled (also via a setting/config) and that is
|
||||
// something we must check before proceeding.
|
||||
return;
|
||||
}
|
||||
if (e.affectsConfiguration('breadcrumbs')) {
|
||||
this._updateOutline(true);
|
||||
return;
|
||||
}
|
||||
if (this._editor && this._editor.getModel()) {
|
||||
const editorModel = this._editor.getModel() as ITextModel;
|
||||
const languageName = editorModel.getLanguageIdentifier().language;
|
||||
|
||||
// Checking for changes in the current language override config.
|
||||
// We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path
|
||||
if (e.affectsConfiguration(`[${languageName}]`)) {
|
||||
this._updateOutline(true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// update soon'ish as model content change
|
||||
const updateSoon = new TimeoutTimer();
|
||||
this._disposables.add(updateSoon);
|
||||
this._disposables.add(this._editor.onDidChangeModelContent(_ => {
|
||||
const timeout = OutlineModel.getRequestDelay(this._editor!.getModel());
|
||||
updateSoon.cancelAndSet(() => this._updateOutline(true), timeout);
|
||||
}));
|
||||
this._updateOutline();
|
||||
|
||||
// stop when editor dies
|
||||
this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear()));
|
||||
}
|
||||
|
||||
private _updateOutline(didChangeContent?: boolean): void {
|
||||
|
||||
private _bindToEditor(editor: IEditorPane): void {
|
||||
const newCts = new CancellationTokenSource();
|
||||
this._currentOutline.clear();
|
||||
this._outlineDisposables.clear();
|
||||
if (!didChangeContent) {
|
||||
this._updateOutlineElements([]);
|
||||
}
|
||||
this._outlineDisposables.add(toDisposable(() => newCts.dispose(true)));
|
||||
|
||||
const editor = this._editor!;
|
||||
|
||||
const buffer = editor.getModel();
|
||||
if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const source = new CancellationTokenSource();
|
||||
const versionIdThen = buffer.getVersionId();
|
||||
const timeout = new TimeoutTimer();
|
||||
|
||||
this._outlineDisposables.add({
|
||||
dispose: () => {
|
||||
source.dispose(true);
|
||||
timeout.dispose();
|
||||
this._outlineService.createOutline(editor, OutlineTarget.Breadcrumbs, newCts.token).then(outline => {
|
||||
if (newCts.token.isCancellationRequested) {
|
||||
// cancelled: dispose new outline and reset
|
||||
outline?.dispose();
|
||||
outline = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
OutlineModel.create(buffer, source.token).then(model => {
|
||||
if (source.token.isCancellationRequested) {
|
||||
// cancelled -> do nothing
|
||||
return;
|
||||
this._currentOutline.value = outline;
|
||||
this._onDidUpdate.fire(this);
|
||||
if (outline) {
|
||||
this._outlineDisposables.add(outline.onDidChange(() => this._onDidUpdate.fire(this)));
|
||||
}
|
||||
if (TreeElement.empty(model)) {
|
||||
// empty -> no outline elements
|
||||
this._updateOutlineElements([]);
|
||||
|
||||
} else {
|
||||
// copy the model
|
||||
model = model.adopt();
|
||||
|
||||
this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition()));
|
||||
this._outlineDisposables.add(editor.onDidChangeCursorPosition(_ => {
|
||||
timeout.cancelAndSet(() => {
|
||||
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && editor.getModel()) {
|
||||
this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition()));
|
||||
}
|
||||
}, 150);
|
||||
}));
|
||||
}
|
||||
}).catch(err => {
|
||||
this._updateOutlineElements([]);
|
||||
this._onDidUpdate.fire(this);
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
}
|
||||
|
||||
private _getOutlineElements(model: OutlineModel, position: IPosition | null): Array<OutlineModel | OutlineGroup | OutlineElement> {
|
||||
if (!model || !position) {
|
||||
return [];
|
||||
}
|
||||
let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position);
|
||||
if (!item) {
|
||||
return this._getOutlineElementsRoot(model);
|
||||
}
|
||||
let chain: Array<OutlineGroup | OutlineElement> = [];
|
||||
while (item) {
|
||||
chain.push(item);
|
||||
let parent: any = item.parent;
|
||||
if (parent instanceof OutlineModel) {
|
||||
break;
|
||||
}
|
||||
if (parent instanceof OutlineGroup && parent.parent && parent.parent.children.size === 1) {
|
||||
break;
|
||||
}
|
||||
item = parent;
|
||||
}
|
||||
let result: Array<OutlineGroup | OutlineElement> = [];
|
||||
for (let i = chain.length - 1; i >= 0; i--) {
|
||||
let element = chain[i];
|
||||
if (this._isFiltered(element)) {
|
||||
break;
|
||||
}
|
||||
result.push(element);
|
||||
}
|
||||
if (result.length === 0) {
|
||||
return this._getOutlineElementsRoot(model);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _getOutlineElementsRoot(model: OutlineModel): (OutlineModel | OutlineGroup | OutlineElement)[] {
|
||||
for (const child of model.children.values()) {
|
||||
if (!this._isFiltered(child)) {
|
||||
return [model];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private _isFiltered(element: TreeElement): boolean {
|
||||
if (element instanceof OutlineElement) {
|
||||
const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`;
|
||||
let uri: URI | undefined;
|
||||
if (this._editor && this._editor.getModel()) {
|
||||
const model = this._editor.getModel() as ITextModel;
|
||||
uri = model.uri;
|
||||
}
|
||||
return !this._textResourceConfigurationService.getValue<boolean>(uri, key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _updateOutlineElements(elements: Array<OutlineModel | OutlineGroup | OutlineElement>): void {
|
||||
if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
|
||||
this._outlineElements = elements;
|
||||
this._onDidUpdate.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
} else if (!a || !b) {
|
||||
return false;
|
||||
} else {
|
||||
return a.id === b.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,34 +8,30 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { posix } from 'vs/base/common/path';
|
||||
import { basename, dirname, isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { isWorkspace, isWorkspaceFolder, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { OutlineElement2, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
|
||||
return element instanceof FileElement
|
||||
? instantiationService.createInstance(BreadcrumbsFilePicker, parent)
|
||||
: instantiationService.createInstance(BreadcrumbsOutlinePicker, parent);
|
||||
}
|
||||
import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/browser/outline';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
|
||||
interface ILayoutInfo {
|
||||
maxHeight: number;
|
||||
@@ -62,17 +58,18 @@ export abstract class BreadcrumbsPicker {
|
||||
protected _fakeEvent = new UIEvent('fakeEvent');
|
||||
protected _layoutInfo!: ILayoutInfo;
|
||||
|
||||
private readonly _onDidPickElement = new Emitter<SelectEvent>();
|
||||
readonly onDidPickElement: Event<SelectEvent> = this._onDidPickElement.event;
|
||||
protected readonly _onWillPickElement = new Emitter<void>();
|
||||
readonly onWillPickElement: Event<void> = this._onWillPickElement.event;
|
||||
|
||||
private readonly _onDidFocusElement = new Emitter<SelectEvent>();
|
||||
readonly onDidFocusElement: Event<SelectEvent> = this._onDidFocusElement.event;
|
||||
private readonly _previewDispoables = new MutableDisposable();
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
protected resource: URI,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService protected readonly _themeService: IThemeService,
|
||||
@IConfigurationService protected readonly _configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
|
||||
@@ -81,12 +78,13 @@ export abstract class BreadcrumbsPicker {
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
this._onDidPickElement.dispose();
|
||||
this._onDidFocusElement.dispose();
|
||||
this._tree.dispose();
|
||||
this._previewDispoables.dispose();
|
||||
this._onWillPickElement.dispose();
|
||||
this._domNode.remove();
|
||||
setTimeout(() => this._tree.dispose(), 0); // tree cannot be disposed while being opened...
|
||||
}
|
||||
|
||||
show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void {
|
||||
async show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise<void> {
|
||||
|
||||
const theme = this._themeService.getColorTheme();
|
||||
const color = theme.getColor(breadcrumbsPickerBackground);
|
||||
@@ -103,33 +101,33 @@ export abstract class BreadcrumbsPicker {
|
||||
this._domNode.appendChild(this._treeContainer);
|
||||
|
||||
this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 };
|
||||
this._tree = this._createTree(this._treeContainer);
|
||||
this._tree = this._createTree(this._treeContainer, input);
|
||||
|
||||
this._disposables.add(this._tree.onDidChangeSelection(e => {
|
||||
if (e.browserEvent !== this._fakeEvent) {
|
||||
const target = this._getTargetFromEvent(e.elements[0]);
|
||||
if (target) {
|
||||
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
|
||||
this._onDidPickElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') });
|
||||
}, 0);
|
||||
}
|
||||
this._disposables.add(this._tree.onDidOpen(async e => {
|
||||
const { element, editorOptions, sideBySide } = e;
|
||||
const didReveal = await this._revealElement(element, { ...editorOptions, preserveFocus: false }, sideBySide);
|
||||
if (!didReveal) {
|
||||
return;
|
||||
}
|
||||
// send telemetry
|
||||
interface OpenEvent { type: string }
|
||||
interface OpenEventGDPR { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } }
|
||||
this._telemetryService.publicLog2<OpenEvent, OpenEventGDPR>('breadcrumbs/open', { type: element instanceof OutlineElement2 ? 'symbol' : 'file' });
|
||||
}));
|
||||
this._disposables.add(this._tree.onDidChangeFocus(e => {
|
||||
const target = this._getTargetFromEvent(e.elements[0]);
|
||||
if (target) {
|
||||
this._onDidFocusElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') });
|
||||
}
|
||||
this._previewDispoables.value = this._previewElement(e.elements[0]);
|
||||
}));
|
||||
this._disposables.add(this._tree.onDidChangeContentHeight(() => {
|
||||
this._layout();
|
||||
}));
|
||||
|
||||
this._domNode.focus();
|
||||
|
||||
this._setInput(input).then(() => {
|
||||
try {
|
||||
await this._setInput(input);
|
||||
this._layout();
|
||||
}).catch(onUnexpectedError);
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
}
|
||||
|
||||
protected _layout(): void {
|
||||
@@ -146,16 +144,15 @@ export abstract class BreadcrumbsPicker {
|
||||
this._treeContainer.style.height = `${treeHeight}px`;
|
||||
this._treeContainer.style.width = `${this._layoutInfo.width}px`;
|
||||
this._tree.layout(treeHeight, this._layoutInfo.width);
|
||||
|
||||
}
|
||||
|
||||
get useAltAsMultipleSelectionModifier() {
|
||||
return this._tree.useAltAsMultipleSelectionModifier;
|
||||
}
|
||||
restoreViewState(): void { }
|
||||
|
||||
protected abstract _setInput(element: FileElement | OutlineElement2): Promise<void>;
|
||||
protected abstract _createTree(container: HTMLElement, input: any): Tree<any, any>;
|
||||
protected abstract _previewElement(element: any): IDisposable;
|
||||
protected abstract _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean>;
|
||||
|
||||
protected abstract _setInput(element: BreadcrumbElement): Promise<void>;
|
||||
protected abstract _createTree(container: HTMLElement): Tree<any, any>;
|
||||
protected abstract _getTargetFromEvent(element: any): any | undefined;
|
||||
}
|
||||
|
||||
//#region - Files
|
||||
@@ -173,9 +170,9 @@ class FileIdentityProvider implements IIdentityProvider<IWorkspace | IWorkspaceF
|
||||
getId(element: IWorkspace | IWorkspaceFolder | IFileStat | URI): { toString(): string; } {
|
||||
if (URI.isUri(element)) {
|
||||
return element.toString();
|
||||
} else if (IWorkspace.isIWorkspace(element)) {
|
||||
} else if (isWorkspace(element)) {
|
||||
return element.id;
|
||||
} else if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
} else if (isWorkspaceFolder(element)) {
|
||||
return element.uri.toString();
|
||||
} else {
|
||||
return element.resource.toString();
|
||||
@@ -186,43 +183,31 @@ class FileIdentityProvider implements IIdentityProvider<IWorkspace | IWorkspaceF
|
||||
|
||||
class FileDataSource implements IAsyncDataSource<IWorkspace | URI, IWorkspaceFolder | IFileStat> {
|
||||
|
||||
private readonly _parents = new WeakMap<object, IWorkspaceFolder | IFileStat>();
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
) { }
|
||||
|
||||
hasChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): boolean {
|
||||
return URI.isUri(element)
|
||||
|| IWorkspace.isIWorkspace(element)
|
||||
|| IWorkspaceFolder.isIWorkspaceFolder(element)
|
||||
|| isWorkspace(element)
|
||||
|| isWorkspaceFolder(element)
|
||||
|| element.isDirectory;
|
||||
}
|
||||
|
||||
getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> {
|
||||
|
||||
if (IWorkspace.isIWorkspace(element)) {
|
||||
return Promise.resolve(element.folders).then(folders => {
|
||||
for (let child of folders) {
|
||||
this._parents.set(element, child);
|
||||
}
|
||||
return folders;
|
||||
});
|
||||
async getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> {
|
||||
if (isWorkspace(element)) {
|
||||
return element.folders;
|
||||
}
|
||||
let uri: URI;
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
if (isWorkspaceFolder(element)) {
|
||||
uri = element.uri;
|
||||
} else if (URI.isUri(element)) {
|
||||
uri = element;
|
||||
} else {
|
||||
uri = element.resource;
|
||||
}
|
||||
return this._fileService.resolve(uri).then(stat => {
|
||||
for (const child of stat.children || []) {
|
||||
this._parents.set(stat, child);
|
||||
}
|
||||
return stat.children || [];
|
||||
});
|
||||
const stat = await this._fileService.resolve(uri);
|
||||
return stat.children ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +230,7 @@ class FileRenderer implements ITreeRenderer<IFileStat | IWorkspaceFolder, FuzzyS
|
||||
const { element } = node;
|
||||
let resource: URI;
|
||||
let fileKind: FileKind;
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
if (isWorkspaceFolder(element)) {
|
||||
resource = element.uri;
|
||||
fileKind = FileKind.ROOT_FOLDER;
|
||||
} else {
|
||||
@@ -327,7 +312,7 @@ class FileFilter implements ITreeFilter<IWorkspaceFolder | IFileStat> {
|
||||
}
|
||||
|
||||
filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
if (isWorkspaceFolder(element)) {
|
||||
// not a file
|
||||
return true;
|
||||
}
|
||||
@@ -345,7 +330,7 @@ class FileFilter implements ITreeFilter<IWorkspaceFolder | IFileStat> {
|
||||
|
||||
export class FileSorter implements ITreeSorter<IFileStat | IWorkspaceFolder> {
|
||||
compare(a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) {
|
||||
if (isWorkspaceFolder(a) && isWorkspaceFolder(b)) {
|
||||
return a.index - b.index;
|
||||
}
|
||||
if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) {
|
||||
@@ -363,12 +348,15 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
protected resource: URI,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(parent, instantiationService, themeService, configService);
|
||||
super(parent, resource, instantiationService, themeService, configService, telemetryService);
|
||||
}
|
||||
|
||||
_createTree(container: HTMLElement) {
|
||||
@@ -406,7 +394,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
});
|
||||
}
|
||||
|
||||
_setInput(element: BreadcrumbElement): Promise<void> {
|
||||
async _setInput(element: FileElement | OutlineElement2): Promise<void> {
|
||||
const { uri, kind } = (element as FileElement);
|
||||
let input: IWorkspace | URI;
|
||||
if (kind === FileKind.ROOT_FOLDER) {
|
||||
@@ -416,115 +404,120 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
}
|
||||
|
||||
const tree = this._tree as WorkbenchAsyncDataTree<IWorkspace | URI, IWorkspaceFolder | IFileStat, FuzzyScore>;
|
||||
return tree.setInput(input).then(() => {
|
||||
let focusElement: IWorkspaceFolder | IFileStat | undefined;
|
||||
for (const { element } of tree.getNode().children) {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element) && isEqual(element.uri, uri)) {
|
||||
focusElement = element;
|
||||
break;
|
||||
} else if (isEqual((element as IFileStat).resource, uri)) {
|
||||
focusElement = element as IFileStat;
|
||||
break;
|
||||
}
|
||||
await tree.setInput(input);
|
||||
let focusElement: IWorkspaceFolder | IFileStat | undefined;
|
||||
for (const { element } of tree.getNode().children) {
|
||||
if (isWorkspaceFolder(element) && isEqual(element.uri, uri)) {
|
||||
focusElement = element;
|
||||
break;
|
||||
} else if (isEqual((element as IFileStat).resource, uri)) {
|
||||
focusElement = element as IFileStat;
|
||||
break;
|
||||
}
|
||||
if (focusElement) {
|
||||
tree.reveal(focusElement, 0.5);
|
||||
tree.setFocus([focusElement], this._fakeEvent);
|
||||
}
|
||||
tree.domFocus();
|
||||
});
|
||||
}
|
||||
if (focusElement) {
|
||||
tree.reveal(focusElement, 0.5);
|
||||
tree.setFocus([focusElement], this._fakeEvent);
|
||||
}
|
||||
tree.domFocus();
|
||||
}
|
||||
|
||||
protected _getTargetFromEvent(element: any): any | undefined {
|
||||
if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) {
|
||||
return new FileElement((element as IFileStat).resource, FileKind.FILE);
|
||||
protected _previewElement(_element: any): IDisposable {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
async _revealElement(element: IFileStat | IWorkspaceFolder, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
|
||||
let resource: URI | undefined;
|
||||
if (isWorkspaceFolder(element)) {
|
||||
resource = element.uri;
|
||||
} else if (!element.isDirectory) {
|
||||
resource = element.resource;
|
||||
}
|
||||
if (resource) {
|
||||
this._onWillPickElement.fire();
|
||||
await this._editorService.openEditor({ resource, options }, sideBySide ? SIDE_GROUP : undefined);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region - Symbols
|
||||
//#region - Outline
|
||||
|
||||
class OutlineTreeSorter<E> implements ITreeSorter<E> {
|
||||
|
||||
private _order: 'name' | 'type' | 'position';
|
||||
|
||||
constructor(
|
||||
private comparator: IOutlineComparator<E>,
|
||||
uri: URI | undefined,
|
||||
@ITextResourceConfigurationService configService: ITextResourceConfigurationService,
|
||||
) {
|
||||
this._order = configService.getValue(uri, 'breadcrumbs.symbolSortOrder');
|
||||
}
|
||||
|
||||
compare(a: E, b: E): number {
|
||||
if (this._order === 'name') {
|
||||
return this.comparator.compareByName(a, b);
|
||||
} else if (this._order === 'type') {
|
||||
return this.comparator.compareByType(a, b);
|
||||
} else {
|
||||
return this.comparator.compareByPosition(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
|
||||
|
||||
protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>;
|
||||
protected _outlineComparator: OutlineItemComparator;
|
||||
protected _createTree(container: HTMLElement, input: OutlineElement2) {
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
) {
|
||||
super(parent, instantiationService, themeService, configurationService);
|
||||
this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService);
|
||||
this._outlineComparator = new OutlineItemComparator();
|
||||
}
|
||||
const { config } = input.outline;
|
||||
|
||||
protected _createTree(container: HTMLElement) {
|
||||
return <WorkbenchDataTree<OutlineModel, any, FuzzyScore>>this._instantiationService.createInstance(
|
||||
return <WorkbenchDataTree<IOutline<any>, any, FuzzyScore>>this._instantiationService.createInstance(
|
||||
WorkbenchDataTree,
|
||||
'BreadcrumbsOutlinePicker',
|
||||
container,
|
||||
new OutlineVirtualDelegate(),
|
||||
[new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)],
|
||||
new OutlineDataSource(),
|
||||
config.delegate,
|
||||
config.renderers,
|
||||
config.treeDataSource,
|
||||
{
|
||||
...config.options,
|
||||
sorter: this._instantiationService.createInstance(OutlineTreeSorter, config.comparator, undefined),
|
||||
collapseByDefault: true,
|
||||
expandOnlyOnTwistieClick: true,
|
||||
multipleSelectionSupport: false,
|
||||
sorter: this._outlineComparator,
|
||||
identityProvider: new OutlineIdentityProvider(),
|
||||
keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(),
|
||||
accessibilityProvider: new OutlineAccessibilityProvider(localize('breadcrumbs', "Breadcrumbs")),
|
||||
filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._symbolSortOrder.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
protected _setInput(input: OutlineElement2): Promise<void> {
|
||||
|
||||
protected _setInput(input: BreadcrumbElement): Promise<void> {
|
||||
const element = input as TreeElement;
|
||||
const model = OutlineModel.get(element)!;
|
||||
const tree = this._tree as WorkbenchDataTree<OutlineModel, any, FuzzyScore>;
|
||||
const viewState = input.outline.captureViewState();
|
||||
this.restoreViewState = () => { viewState.dispose(); };
|
||||
|
||||
const overrideConfiguration = {
|
||||
resource: model.uri,
|
||||
overrideIdentifier: this._modeService.getModeIdByFilepathOrFirstLine(model.uri)
|
||||
};
|
||||
this._outlineComparator.type = this._getOutlineItemCompareType(overrideConfiguration);
|
||||
const tree = this._tree as WorkbenchDataTree<IOutline<any>, any, FuzzyScore>;
|
||||
|
||||
tree.setInput(model);
|
||||
if (element !== model) {
|
||||
tree.reveal(element, 0.5);
|
||||
tree.setFocus([element], this._fakeEvent);
|
||||
tree.setInput(input.outline);
|
||||
if (input.element !== input.outline) {
|
||||
tree.reveal(input.element, 0.5);
|
||||
tree.setFocus([input.element], this._fakeEvent);
|
||||
}
|
||||
tree.domFocus();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected _getTargetFromEvent(element: any): any | undefined {
|
||||
if (element instanceof OutlineElement) {
|
||||
return element;
|
||||
}
|
||||
protected _previewElement(element: any): IDisposable {
|
||||
const outline: IOutline<any> = this._tree.getInput();
|
||||
return outline.preview(element);
|
||||
}
|
||||
|
||||
private _getOutlineItemCompareType(overrideConfiguration?: IConfigurationOverrides): OutlineSortOrder {
|
||||
switch (this._symbolSortOrder.getValue(overrideConfiguration)) {
|
||||
case 'name':
|
||||
return OutlineSortOrder.ByName;
|
||||
case 'type':
|
||||
return OutlineSortOrder.ByKind;
|
||||
case 'position':
|
||||
default:
|
||||
return OutlineSortOrder.ByPosition;
|
||||
}
|
||||
async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
|
||||
this._onWillPickElement.fire();
|
||||
const outline: IOutline<any> = this._tree.getInput();
|
||||
await outline.reveal(element, options, sideBySide);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
|
||||
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction,
|
||||
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction
|
||||
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -42,7 +42,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
|
||||
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/codeeditor';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { toLocalResource } from 'vs/base/common/resources';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
@@ -351,6 +351,10 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupLeftAction,
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupRightAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupUpAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupDownAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupLeftAction), 'View: Duplicate Editor Group Left', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupRightAction), 'View: Duplicate Editor Group Right', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupUpAction), 'View: Duplicate Editor Group Up', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupDownAction), 'View: Duplicate Editor Group Down', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToPreviousGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToNextGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', CATEGORIES.View.value);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToFirstGroupAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', CATEGORIES.View.value);
|
||||
|
||||
@@ -23,6 +23,7 @@ import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecent
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/services/editor/common/editorOpenWith';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ExecuteCommandAction extends Action {
|
||||
|
||||
@@ -746,12 +747,13 @@ export class CloseEditorInAllGroupsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseMoveGroupAction extends Action {
|
||||
class BaseMoveCopyGroupAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private direction: GroupDirection,
|
||||
private isMove: boolean,
|
||||
private editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label);
|
||||
@@ -766,9 +768,18 @@ export class BaseMoveGroupAction extends Action {
|
||||
}
|
||||
|
||||
if (sourceGroup) {
|
||||
const targetGroup = this.findTargetGroup(sourceGroup);
|
||||
if (targetGroup) {
|
||||
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
let resultGroup: IEditorGroup | undefined = undefined;
|
||||
if (this.isMove) {
|
||||
const targetGroup = this.findTargetGroup(sourceGroup);
|
||||
if (targetGroup) {
|
||||
resultGroup = this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
}
|
||||
} else {
|
||||
resultGroup = this.editorGroupService.copyGroup(sourceGroup, sourceGroup, this.direction);
|
||||
}
|
||||
|
||||
if (resultGroup) {
|
||||
this.editorGroupService.activateGroup(resultGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -801,6 +812,18 @@ export class BaseMoveGroupAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
class BaseMoveGroupAction extends BaseMoveCopyGroupAction {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
direction: GroupDirection,
|
||||
editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, direction, true, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveGroupLeftAction extends BaseMoveGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.moveActiveEditorGroupLeft';
|
||||
@@ -857,6 +880,74 @@ export class MoveGroupDownAction extends BaseMoveGroupAction {
|
||||
}
|
||||
}
|
||||
|
||||
class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
direction: GroupDirection,
|
||||
editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, direction, false, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.LEFT, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupRightAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupRight', "Duplicate Editor Group Right");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.RIGHT, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupUpAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupUp', "Duplicate Editor Group Up");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.UP, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateGroupDownAction extends BaseDuplicateGroupAction {
|
||||
|
||||
static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown';
|
||||
static readonly LABEL = nls.localize('duplicateActiveGroupDown', "Duplicate Editor Group Down");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, GroupDirection.DOWN, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MinimizeOtherGroupsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.minimizeOtherEditors';
|
||||
@@ -1803,9 +1894,8 @@ export class ReopenResourcesAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
@@ -1823,7 +1913,7 @@ export class ReopenResourcesAction extends Action {
|
||||
|
||||
const options = activeEditorPane.options;
|
||||
const group = activeEditorPane.group;
|
||||
await openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService);
|
||||
await this.instantiationService.invokeFunction(openEditorWith, activeInput, undefined, options, group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith';
|
||||
@@ -484,7 +483,6 @@ function registerOpenEditorAPICommands(): void {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const editorGroupsService = accessor.get(IEditorGroupsService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
|
||||
const [columnArg, optionsArg] = columnAndOptions ?? [];
|
||||
let group: IEditorGroup | undefined = undefined;
|
||||
@@ -504,7 +502,7 @@ function registerOpenEditorAPICommands(): void {
|
||||
const textOptions: ITextEditorOptions = optionsArg ? { ...optionsArg, override: false } : { override: false };
|
||||
|
||||
const input = editorService.createEditorInput({ resource: URI.revive(resource) });
|
||||
return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService);
|
||||
return openEditorWith(accessor, input, id, textOptions, group);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -902,24 +900,10 @@ function registerOtherEditorCommands(): void {
|
||||
id: TOGGLE_KEEP_EDITORS_COMMAND_ID,
|
||||
handler: accessor => {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
|
||||
// Update setting
|
||||
const currentSetting = configurationService.getValue<boolean>('workbench.editor.enablePreview');
|
||||
const newSetting = currentSetting === true ? false : true;
|
||||
configurationService.updateValue('workbench.editor.enablePreview', newSetting);
|
||||
|
||||
// Inform user
|
||||
notificationService.prompt(
|
||||
Severity.Info,
|
||||
newSetting ?
|
||||
nls.localize('enablePreview', "Preview editors have been enabled in settings.") :
|
||||
nls.localize('disablePreview', "Preview editors have been disabled in settings."),
|
||||
[{
|
||||
label: nls.localize('learnMode', "Learn More"), run: () => openerService.open('https://go.microsoft.com/fwlink/?linkid=2147473')
|
||||
}]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -283,7 +283,8 @@ class DropOverlay extends Themable {
|
||||
// Open in target group
|
||||
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({
|
||||
pinned: true, // always pin dropped editor
|
||||
sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state
|
||||
sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state
|
||||
override: false, // Use `draggedEditor.editor` as is. If it is already a custom editor, it will stay so.
|
||||
}));
|
||||
const copyEditor = this.isCopyOperation(event, draggedEditor);
|
||||
targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
|
||||
|
||||
@@ -322,8 +322,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
private createContainerContextMenu(): void {
|
||||
const menu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroupContext, this.contextKeyService));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, event => this.onShowContainerContextMenu(menu, event)));
|
||||
this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, event => this.onShowContainerContextMenu(menu)));
|
||||
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => this.onShowContainerContextMenu(menu, e)));
|
||||
this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, () => this.onShowContainerContextMenu(menu)));
|
||||
}
|
||||
|
||||
private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void {
|
||||
@@ -1704,13 +1704,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
layout(width: number, height: number): void {
|
||||
this.dimension = new Dimension(width, height);
|
||||
|
||||
// Ensure editor container gets height as CSS depending on the preferred height of the title control
|
||||
const titleHeight = this.titleDimensions.height;
|
||||
const editorHeight = Math.max(0, height - titleHeight);
|
||||
this.editorContainer.style.height = `${editorHeight}px`;
|
||||
// Layout the title area first to receive the size it occupies
|
||||
const titleAreaSize = this.titleAreaControl.layout({
|
||||
container: this.dimension,
|
||||
available: new Dimension(width, height - this.editorControl.minimumHeight)
|
||||
});
|
||||
|
||||
// Forward to controls
|
||||
this.titleAreaControl.layout(new Dimension(width, titleHeight));
|
||||
// Pass the container width and remaining height to the editor layout
|
||||
const editorHeight = Math.max(0, height - titleAreaSize.height);
|
||||
this.editorContainer.style.height = `${editorHeight}px`;
|
||||
this.editorControl.layout(new Dimension(width, editorHeight));
|
||||
}
|
||||
|
||||
@@ -1769,7 +1771,7 @@ export interface EditorReplacement {
|
||||
readonly options?: EditorOptions;
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector, environment) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
// Letterpress
|
||||
const letterpress = `./media/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/editorstatus';
|
||||
import * as nls from 'vs/nls';
|
||||
import { localize } from 'vs/nls';
|
||||
import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { format, compare, splitLines } from 'vs/base/common/strings';
|
||||
import { extname, basename, isEqual } from 'vs/base/common/resources';
|
||||
@@ -283,14 +283,15 @@ class State {
|
||||
}
|
||||
}
|
||||
|
||||
const nlsSingleSelectionRange = nls.localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
|
||||
const nlsSingleSelection = nls.localize('singleSelection', "Ln {0}, Col {1}");
|
||||
const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selections ({1} characters selected)");
|
||||
const nlsMultiSelection = nls.localize('multiSelection', "{0} selections");
|
||||
const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF");
|
||||
const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
|
||||
const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}");
|
||||
const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)");
|
||||
const nlsMultiSelection = localize('multiSelection', "{0} selections");
|
||||
const nlsEOLLF = localize('endOfLineLineFeed', "LF");
|
||||
const nlsEOLCRLF = localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
|
||||
export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private readonly tabFocusModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly columnSelectionModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
private readonly screenRedearModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
|
||||
@@ -342,14 +343,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
if (!this.screenReaderNotification) {
|
||||
this.screenReaderNotification = this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"),
|
||||
localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"),
|
||||
[{
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
label: localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on');
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
label: localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off');
|
||||
}
|
||||
@@ -364,11 +365,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private async showIndentationPicker(): Promise<unknown> {
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
if (this.editorService.activeEditor?.isReadonly()) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
return this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const picks: QuickPickInput<IQuickPickItem & { run(): void; }>[] = [
|
||||
@@ -390,25 +391,25 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
};
|
||||
});
|
||||
|
||||
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
|
||||
picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
|
||||
picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") });
|
||||
picks.unshift({ type: 'separator', label: localize('indentView', "change view") });
|
||||
|
||||
const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
const action = await this.quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
return action?.run();
|
||||
}
|
||||
|
||||
private updateTabFocusModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.tabFocusModeElement.value) {
|
||||
const text = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
|
||||
const text = localize('tabFocusModeEnabled', "Tab Moves Focus");
|
||||
this.tabFocusModeElement.value = this.statusbarService.addEntry({
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('disableTabMode', "Disable Accessibility Mode"),
|
||||
tooltip: localize('disableTabMode', "Disable Accessibility Mode"),
|
||||
command: 'editor.action.toggleTabFocusMode',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.tabFocusMode', nls.localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
|
||||
}, 'status.editor.tabFocusMode', localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
|
||||
}
|
||||
} else {
|
||||
this.tabFocusModeElement.clear();
|
||||
@@ -418,15 +419,15 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private updateColumnSelectionModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.columnSelectionModeElement.value) {
|
||||
const text = nls.localize('columnSelectionModeEnabled', "Column Selection");
|
||||
const text = localize('columnSelectionModeEnabled', "Column Selection");
|
||||
this.columnSelectionModeElement.value = this.statusbarService.addEntry({
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
|
||||
tooltip: localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
|
||||
command: 'editor.action.toggleColumnSelection',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
|
||||
}, 'status.editor.columnSelectionMode', localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
|
||||
}
|
||||
} else {
|
||||
this.columnSelectionModeElement.clear();
|
||||
@@ -436,14 +437,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private updateScreenReaderModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.screenRedearModeElement.value) {
|
||||
const text = nls.localize('screenReaderDetected', "Screen Reader Optimized");
|
||||
const text = localize('screenReaderDetected', "Screen Reader Optimized");
|
||||
this.screenRedearModeElement.value = this.statusbarService.addEntry({
|
||||
text,
|
||||
ariaLabel: text,
|
||||
command: 'showEditorScreenReaderNotification',
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
|
||||
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
|
||||
}, 'status.editor.screenReaderMode', nls.localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
|
||||
}, 'status.editor.screenReaderMode', localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
|
||||
}
|
||||
} else {
|
||||
this.screenRedearModeElement.clear();
|
||||
@@ -459,11 +460,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('gotoLine', "Go to Line/Column"),
|
||||
tooltip: localize('gotoLine', "Go to Line/Column"),
|
||||
command: 'workbench.action.gotoLine'
|
||||
};
|
||||
|
||||
this.updateElement(this.selectionElement, props, 'status.editor.selection', nls.localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
|
||||
this.updateElement(this.selectionElement, props, 'status.editor.selection', localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
|
||||
}
|
||||
|
||||
private updateIndentationElement(text: string | undefined): void {
|
||||
@@ -475,11 +476,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectIndentation', "Select Indentation"),
|
||||
tooltip: localize('selectIndentation', "Select Indentation"),
|
||||
command: 'changeEditorIndentation'
|
||||
};
|
||||
|
||||
this.updateElement(this.indentationElement, props, 'status.editor.indentation', nls.localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
|
||||
this.updateElement(this.indentationElement, props, 'status.editor.indentation', localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
|
||||
}
|
||||
|
||||
private updateEncodingElement(text: string | undefined): void {
|
||||
@@ -491,11 +492,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectEncoding', "Select Encoding"),
|
||||
tooltip: localize('selectEncoding', "Select Encoding"),
|
||||
command: 'workbench.action.editor.changeEncoding'
|
||||
};
|
||||
|
||||
this.updateElement(this.encodingElement, props, 'status.editor.encoding', nls.localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
|
||||
this.updateElement(this.encodingElement, props, 'status.editor.encoding', localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
|
||||
}
|
||||
|
||||
private updateEOLElement(text: string | undefined): void {
|
||||
@@ -507,11 +508,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectEOL', "Select End of Line Sequence"),
|
||||
tooltip: localize('selectEOL', "Select End of Line Sequence"),
|
||||
command: 'workbench.action.editor.changeEOL'
|
||||
};
|
||||
|
||||
this.updateElement(this.eolElement, props, 'status.editor.eol', nls.localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
|
||||
this.updateElement(this.eolElement, props, 'status.editor.eol', localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
|
||||
}
|
||||
|
||||
private updateModeElement(text: string | undefined): void {
|
||||
@@ -523,11 +524,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('selectLanguageMode', "Select Language Mode"),
|
||||
tooltip: localize('selectLanguageMode', "Select Language Mode"),
|
||||
command: 'workbench.action.editor.changeLanguageMode'
|
||||
};
|
||||
|
||||
this.updateElement(this.modeElement, props, 'status.editor.mode', nls.localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
|
||||
this.updateElement(this.modeElement, props, 'status.editor.mode', localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
|
||||
}
|
||||
|
||||
private updateMetadataElement(text: string | undefined): void {
|
||||
@@ -539,10 +540,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const props: IStatusbarEntry = {
|
||||
text,
|
||||
ariaLabel: text,
|
||||
tooltip: nls.localize('fileInfo', "File Information")
|
||||
tooltip: localize('fileInfo', "File Information")
|
||||
};
|
||||
|
||||
this.updateElement(this.metadataElement, props, 'status.editor.info', nls.localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
|
||||
this.updateElement(this.metadataElement, props, 'status.editor.info', localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
|
||||
}
|
||||
|
||||
private updateElement(element: MutableDisposable<IStatusbarEntryAccessor>, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number) {
|
||||
@@ -730,8 +731,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
const modelOpts = model.getOptions();
|
||||
update.indentation = (
|
||||
modelOpts.insertSpaces
|
||||
? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
|
||||
: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
|
||||
? localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
|
||||
: localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -766,7 +767,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
if (editorWidget) {
|
||||
const screenReaderDetected = this.accessibilityService.isScreenReaderOptimized();
|
||||
if (screenReaderDetected) {
|
||||
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
|
||||
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor')?.accessibilitySupport;
|
||||
if (screenReaderConfiguration === 'auto') {
|
||||
if (!this.promptedScreenReader) {
|
||||
this.promptedScreenReader = true;
|
||||
@@ -921,7 +922,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
const line = splitLines(this.currentMarker.message)[0];
|
||||
const text = `${this.getType(this.currentMarker)} ${line}`;
|
||||
if (!this.statusBarEntryAccessor.value) {
|
||||
this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT);
|
||||
this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT);
|
||||
}
|
||||
this.statusBarEntryAccessor.value.update({ text, ariaLabel: text });
|
||||
} else {
|
||||
@@ -934,9 +935,11 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!currentMarker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!previousMarker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return IMarkerData.makeKey(previousMarker) !== IMarkerData.makeKey(currentMarker);
|
||||
}
|
||||
|
||||
@@ -946,6 +949,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
case MarkerSeverity.Warning: return '$(warning)';
|
||||
case MarkerSeverity.Info: return '$(info)';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -953,17 +957,21 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!this.configurationService.getValue<boolean>('problems.showCurrentInStatus')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const position = this.editor.getPosition();
|
||||
if (!position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.markers.find(marker => Range.containsPosition(marker, position)) || null;
|
||||
}
|
||||
|
||||
@@ -971,13 +979,16 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model && !changedResources.some(r => isEqual(model.uri, r))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateMarkers();
|
||||
}
|
||||
|
||||
@@ -985,10 +996,12 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model) {
|
||||
this.markers = this.markerService.read({
|
||||
resource: model.uri,
|
||||
@@ -998,6 +1011,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
|
||||
} else {
|
||||
this.markers = [];
|
||||
}
|
||||
|
||||
this.updateStatus();
|
||||
}
|
||||
}
|
||||
@@ -1007,9 +1021,11 @@ function compareMarker(a: IMarker, b: IMarker): number {
|
||||
if (res === 0) {
|
||||
res = MarkerSeverity.compare(a.severity, b.severity);
|
||||
}
|
||||
|
||||
if (res === 0) {
|
||||
res = Range.compareRangesUsingStarts(a, b);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1022,7 +1038,7 @@ export class ShowLanguageExtensionsAction extends Action {
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService
|
||||
) {
|
||||
super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));
|
||||
super(ShowLanguageExtensionsAction.ID, localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));
|
||||
|
||||
this.enabled = galleryService.isEnabled();
|
||||
}
|
||||
@@ -1035,7 +1051,7 @@ export class ShowLanguageExtensionsAction extends Action {
|
||||
export class ChangeModeAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeLanguageMode';
|
||||
static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
|
||||
static readonly LABEL = localize('changeMode', "Change Language Mode");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
@@ -1054,14 +1070,14 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
async run(): Promise<void> {
|
||||
const activeEditorPane = this.editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
|
||||
if (activeEditorPane?.isNotebookEditor) {
|
||||
if (activeEditorPane?.isNotebookEditor) { // TODO@rebornix TODO@jrieken debt: https://github.com/microsoft/vscode/issues/114554
|
||||
// it's inside notebook editor
|
||||
return this.commandService.executeCommand('notebook.cell.changeLanguage');
|
||||
}
|
||||
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1085,22 +1101,24 @@ export class ChangeModeAction extends Action {
|
||||
const languages = this.modeService.getRegisteredLanguageNames();
|
||||
const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
|
||||
const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown';
|
||||
const extensions = this.modeService.getExtensions(lang).join(' ');
|
||||
let description: string;
|
||||
if (currentLanguageId === lang) {
|
||||
description = nls.localize('languageDescription', "({0}) - Configured Language", modeId);
|
||||
description = localize('languageDescription', "({0}) - Configured Language", modeId);
|
||||
} else {
|
||||
description = nls.localize('languageDescriptionConfigured', "({0})", modeId);
|
||||
description = localize('languageDescriptionConfigured', "({0})", modeId);
|
||||
}
|
||||
|
||||
return {
|
||||
label: lang,
|
||||
meta: extensions,
|
||||
iconClasses: getIconClassesForModeId(modeId),
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
if (hasLanguageSupport) {
|
||||
picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") });
|
||||
picks.unshift({ type: 'separator', label: localize('languagesPicks', "languages (identifier)") });
|
||||
}
|
||||
|
||||
// Offer action to configure via settings
|
||||
@@ -1115,22 +1133,22 @@ export class ChangeModeAction extends Action {
|
||||
picks.unshift(galleryAction);
|
||||
}
|
||||
|
||||
configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) };
|
||||
configureModeSettings = { label: localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) };
|
||||
picks.unshift(configureModeSettings);
|
||||
configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
|
||||
configureModeAssociations = { label: localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
|
||||
picks.unshift(configureModeAssociations);
|
||||
}
|
||||
|
||||
// Offer to "Auto Detect"
|
||||
const autoDetectMode: IQuickPickItem = {
|
||||
label: nls.localize('autoDetect', "Auto Detect")
|
||||
label: localize('autoDetect', "Auto Detect")
|
||||
};
|
||||
|
||||
if (hasLanguageSupport) {
|
||||
picks.unshift(autoDetectMode);
|
||||
}
|
||||
|
||||
const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
|
||||
const pick = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
@@ -1178,6 +1196,8 @@ export class ChangeModeAction extends Action {
|
||||
modeSupport.setMode(languageSelection.languageIdentifier.language);
|
||||
}
|
||||
}
|
||||
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1194,12 +1214,12 @@ export class ChangeModeAction extends Action {
|
||||
id,
|
||||
label: lang,
|
||||
iconClasses: getIconClassesForModeId(id),
|
||||
description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined
|
||||
description: (id === currentAssociation) ? localize('currentAssociation', "Current Association") : undefined
|
||||
};
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
const language = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
|
||||
const language = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
|
||||
if (language) {
|
||||
const fileAssociationsConfig = this.configurationService.inspect<{}>(FILES_ASSOCIATIONS_CONFIG);
|
||||
|
||||
@@ -1233,7 +1253,7 @@ export interface IChangeEOLEntry extends IQuickPickItem {
|
||||
export class ChangeEOLAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeEOL';
|
||||
static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
|
||||
static readonly LABEL = localize('changeEndOfLine', "Change End of Line Sequence");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
@@ -1247,12 +1267,12 @@ export class ChangeEOLAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editorService.activeEditor?.isReadonly()) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1265,7 +1285,7 @@ export class ChangeEOLAction extends Action {
|
||||
|
||||
const selectedIndex = (textModel?.getEOL() === '\n') ? 0 : 1;
|
||||
|
||||
const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
|
||||
const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
|
||||
if (eol) {
|
||||
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) {
|
||||
@@ -1275,13 +1295,15 @@ export class ChangeEOLAction extends Action {
|
||||
textModel.pushStackElement();
|
||||
}
|
||||
}
|
||||
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeEncodingAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeEncoding';
|
||||
static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
|
||||
static readonly LABEL = localize('changeEncoding', "Change File Encoding");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
@@ -1296,25 +1318,26 @@ export class ChangeEncodingAction extends Action {
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!getCodeEditor(this.editorService.activeTextEditorControl)) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
|
||||
if (!activeTextEditorControl) {
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane;
|
||||
if (!activeEditorPane) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeEditorPane.input);
|
||||
if (!encodingSupport) {
|
||||
await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
|
||||
await this.quickInputService.pick([{ label: localize('noFileEditor', "No file active at this time") }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
|
||||
const reopenWithEncodingPick: IQuickPickItem = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
|
||||
const saveWithEncodingPick: IQuickPickItem = { label: localize('saveWithEncoding', "Save with Encoding") };
|
||||
const reopenWithEncodingPick: IQuickPickItem = { label: localize('reopenWithEncoding', "Reopen with Encoding") };
|
||||
|
||||
if (!Language.isDefaultVariant()) {
|
||||
const saveWithEncodingAlias = 'Save with Encoding';
|
||||
@@ -1334,7 +1357,7 @@ export class ChangeEncodingAction extends Action {
|
||||
} else if (activeEditorPane.input.isReadonly()) {
|
||||
action = reopenWithEncodingPick;
|
||||
} else {
|
||||
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
}
|
||||
|
||||
if (!action) {
|
||||
@@ -1394,11 +1417,11 @@ export class ChangeEncodingAction extends Action {
|
||||
// If we have a guessed encoding, show it first unless it matches the configured encoding
|
||||
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
|
||||
picks.unshift({ type: 'separator' });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: localize('guessedEncoding', "Guessed from content") });
|
||||
}
|
||||
|
||||
const encoding = await this.quickInputService.pick(picks, {
|
||||
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
placeHolder: isReopenWithEncoding ? localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
|
||||
});
|
||||
|
||||
@@ -1414,5 +1437,7 @@ export class ChangeEncodingAction extends Action {
|
||||
if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) {
|
||||
activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
|
||||
}
|
||||
|
||||
activeTextEditorControl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-label {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -22,16 +22,16 @@
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label > .monaco-icon-label-container {
|
||||
flex: none; /* helps to show decorations right next to the label and not at the end */
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
|
||||
flex: 1 50%;
|
||||
overflow: hidden;
|
||||
@@ -62,13 +62,11 @@
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before,
|
||||
.monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:nth-child(2)::before {
|
||||
/* workspace folder, item following workspace folder, or relative path -> hide first seperator */
|
||||
display: none;
|
||||
display: none; /* workspace folder, item following workspace folder, or relative path -> hide first seperator */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
|
||||
/* use dot separator for workspace folder */
|
||||
content: '\00a0•\00a0';
|
||||
content: '\00a0•\00a0'; /* use dot separator for workspace folder */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -80,13 +78,18 @@
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child {
|
||||
display: none; /* hides chevrons when no tabs visible and when last items */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child {
|
||||
display: none; /* hides chevrons when no tabs visible */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
|
||||
height: 18px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
/* Editor Actions Toolbar (via title actions) */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions {
|
||||
display: flex;
|
||||
flex: initial;
|
||||
opacity: 0.5;
|
||||
@@ -94,6 +97,6 @@
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .title-actions {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container.active > .title > .title-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,45 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
################################### z-index explainer ###################################
|
||||
|
||||
Tabs have various levels of z-index depending on state, typically:
|
||||
- scrollbar should be above all
|
||||
- sticky (compact, shrink) tabs need to be above non-sticky tabs for scroll under effect
|
||||
including non-sticky tabs-top borders, otherwise these borders would not scroll under
|
||||
(https://github.com/microsoft/vscode/issues/111641)
|
||||
- bottom-border needs to be above tabs bottom border to win but also support sticky tabs
|
||||
(https://github.com/microsoft/vscode/issues/99084) <- this currently cannot be done and
|
||||
is still broken. putting sticky-tabs above tabs bottom border would not render this
|
||||
border at all for sticky tabs.
|
||||
|
||||
On top of that there is 2 borders with a z-index for a general border below tabs
|
||||
- tabs bottom border
|
||||
- editor title bottom border (when breadcrumbs are disabled, this border will appear
|
||||
same as tabs bottom border)
|
||||
|
||||
The following tabls shows the current stacking order:
|
||||
|
||||
[z-index] [kind]
|
||||
7 scrollbar
|
||||
6 active-tab border-bottom
|
||||
5 tabs, title border bottom
|
||||
4 sticky-tab
|
||||
2 active/dirty-tab border top
|
||||
0 tab
|
||||
|
||||
##########################################################################################
|
||||
*/
|
||||
|
||||
/* Title Container */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
|
||||
display: flex;
|
||||
position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.tabs-border-bottom::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -25,12 +53,12 @@
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 3; /* on top of tabs */
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 7;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -46,6 +74,13 @@
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container {
|
||||
|
||||
/* Enable wrapping via flex layout and dynamic height */
|
||||
height: auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome + Safari: hide scrollbar */
|
||||
}
|
||||
@@ -62,6 +97,10 @@
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
|
||||
margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */
|
||||
@@ -74,6 +113,10 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit {
|
||||
flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
|
||||
min-width: 80px;
|
||||
flex-basis: 0; /* all tabs are even */
|
||||
@@ -89,7 +132,7 @@
|
||||
|
||||
/** Sticky compact/shrink tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
z-index: 4;
|
||||
|
||||
/** Sticky compact/shrink tabs are even and never grow */
|
||||
flex-basis: 0;
|
||||
@@ -118,9 +161,7 @@
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink {
|
||||
|
||||
/** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */
|
||||
position: static;
|
||||
position: static; /** disable sticky positions for sticky compact/shrink tabs if the available space is too little */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label {
|
||||
@@ -132,7 +173,7 @@
|
||||
content: '';
|
||||
display: flex;
|
||||
flex: 0;
|
||||
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
|
||||
width: 5px; /* reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left {
|
||||
@@ -167,24 +208,26 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 6; /* over possible title border */
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
height: 1px;
|
||||
background-color: var(--tab-border-top-color);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
|
||||
z-index: 6;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background-color: var(--tab-border-bottom-color);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
background-color: var(--tab-dirty-border-top-color);
|
||||
@@ -203,13 +246,16 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
|
||||
content: '';
|
||||
content: ''; /* enables a linear gradient to overlay the end of the label when tabs overflow */
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
opacity: 1;
|
||||
padding: 0;
|
||||
/* the rules below ensure that the gradient does not impact top/bottom borders (https://github.com/microsoft/vscode/issues/115129) */
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
height: calc(100% - 2px);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:focus > .tab-label::after {
|
||||
@@ -243,7 +289,7 @@
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions {
|
||||
flex: 0;
|
||||
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */
|
||||
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions,
|
||||
@@ -301,7 +347,7 @@
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* No Tab Actions */
|
||||
/* Tab Actions: Off */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off {
|
||||
padding-right: 10px; /* give a little bit more room if tab actions is off */
|
||||
@@ -324,15 +370,6 @@
|
||||
pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */
|
||||
}
|
||||
|
||||
/* Editor Actions */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding: 0 8px 0 4px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
|
||||
@@ -350,11 +387,17 @@
|
||||
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .outline-element-icon {
|
||||
padding-right: 3px;
|
||||
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
@@ -362,6 +405,27 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child {
|
||||
display: none; /* hides chevrons when last item */
|
||||
}
|
||||
|
||||
/* Editor Actions Toolbar */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding: 0 8px 0 4px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions {
|
||||
|
||||
/* When tabs are wrapped, position the editor actions at the end of the very last row */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after,
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs .monaco-icon-label::after {
|
||||
padding-right: 0; /* by default the icon label has a padding right and this isn't wanted when not showing tabs and not showing breadcrumbs */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label:not(span),
|
||||
@@ -59,13 +68,12 @@
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Drag Cursor */
|
||||
/* Drag and Drop */
|
||||
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
/* Drag and Drop Feedback */
|
||||
|
||||
.monaco-editor-group-drag-image {
|
||||
display: inline-block;
|
||||
padding: 1px 7px;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./media/notabstitlecontrol';
|
||||
import { EditorResourceAccessor, Verbosity, IEditorInput, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
@@ -15,6 +15,7 @@ import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/edito
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types';
|
||||
import { IEditorGroupTitleDimensions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
|
||||
interface IRenderedEditorLabel {
|
||||
editor?: IEditorInput;
|
||||
@@ -50,7 +51,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
// Breadcrumbs
|
||||
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
|
||||
titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl));
|
||||
this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // import to remove because the container is a shared dom node
|
||||
this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // important to remove because the container is a shared dom node
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
@@ -67,16 +68,16 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.enableGroupDragging(titleContainer);
|
||||
|
||||
// Pin on double click
|
||||
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, e => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, (e: MouseEvent) => this.onTitleAuxClick(e)));
|
||||
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, e => this.onTitleAuxClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleTap(e)));
|
||||
|
||||
// Context Menu
|
||||
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => {
|
||||
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, e => {
|
||||
if (this.group.activeEditor) {
|
||||
this.onContextMenu(this.group.activeEditor, e, titleContainer);
|
||||
}
|
||||
@@ -188,7 +189,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
|
||||
if (oldOptions.labelFormat !== newOptions.labelFormat) {
|
||||
if (oldOptions.labelFormat !== newOptions.labelFormat || !equals(oldOptions.decorations, newOptions.decorations)) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
@@ -236,6 +237,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
private redraw(): void {
|
||||
const editor = withNullAsUndefined(this.group.activeEditor);
|
||||
const options = this.accessor.partOptions;
|
||||
|
||||
const isEditorPinned = editor ? this.group.isPinned(editor) : false;
|
||||
const isGroupActive = this.accessor.activeGroup === this.group;
|
||||
@@ -291,14 +293,18 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
{
|
||||
title,
|
||||
italic: !isEditorPinned,
|
||||
extraClasses: ['no-tabs', 'title-label']
|
||||
extraClasses: ['no-tabs', 'title-label'],
|
||||
fileDecorations: {
|
||||
colors: Boolean(options.decorations?.colors),
|
||||
badges: Boolean(options.decorations?.badges)
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (isGroupActive) {
|
||||
editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || '';
|
||||
titleContainer.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || '';
|
||||
} else {
|
||||
editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || '';
|
||||
titleContainer.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || '';
|
||||
}
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
@@ -333,9 +339,11 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
};
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
layout(dimensions: ITitleControlDimensions): Dimension {
|
||||
if (this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.layout(undefined);
|
||||
}
|
||||
|
||||
return new Dimension(dimensions.container.width, this.getDimensions().height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ICodeEditor, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
export interface IRangeHighlightDecoration {
|
||||
resource: URI;
|
||||
range: IRange;
|
||||
isWholeLine?: boolean;
|
||||
}
|
||||
|
||||
export class RangeHighlightDecorations extends Disposable {
|
||||
|
||||
private rangeHighlightDecorationId: string | null = null;
|
||||
private editor: ICodeEditor | null = null;
|
||||
private readonly editorDisposables = this._register(new DisposableStore());
|
||||
|
||||
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onHighlightRemoved = this._onHighlightRemoved.event;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
removeHighlightRange() {
|
||||
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
|
||||
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
|
||||
this._onHighlightRemoved.fire();
|
||||
}
|
||||
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
highlightRange(range: IRangeHighlightDecoration, editor?: any) {
|
||||
editor = editor ?? this.getEditor(range);
|
||||
if (isCodeEditor(editor)) {
|
||||
this.doHighlightRange(editor, range);
|
||||
} else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) {
|
||||
this.doHighlightRange(editor.activeCodeEditor, range);
|
||||
}
|
||||
}
|
||||
|
||||
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
|
||||
this.removeHighlightRange();
|
||||
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
|
||||
});
|
||||
|
||||
this.setEditor(editor);
|
||||
}
|
||||
|
||||
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
const resource = activeEditor && activeEditor.resource;
|
||||
if (resource && isEqual(resource, resourceRange.resource)) {
|
||||
return this.editorService.activeTextEditorControl as ICodeEditor;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private setEditor(editor: ICodeEditor) {
|
||||
if (this.editor !== editor) {
|
||||
this.editorDisposables.clear();
|
||||
this.editor = editor;
|
||||
this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
|
||||
if (
|
||||
e.reason === CursorChangeReason.NotSet
|
||||
|| e.reason === CursorChangeReason.Explicit
|
||||
|| e.reason === CursorChangeReason.Undo
|
||||
|| e.reason === CursorChangeReason.Redo
|
||||
) {
|
||||
this.removeHighlightRange();
|
||||
}
|
||||
}));
|
||||
this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); }));
|
||||
this.editorDisposables.add(this.editor.onDidDispose(() => {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
});
|
||||
|
||||
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight'
|
||||
});
|
||||
|
||||
private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions {
|
||||
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.editor && this.editor.getModel()) {
|
||||
this.removeHighlightRange();
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,19 +18,18 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { ITitleControlDimensions, TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -48,6 +47,7 @@ import { IPath, win32, posix } from 'vs/base/common/path';
|
||||
import { insert } from 'vs/base/common/arrays';
|
||||
import { ColorScheme } from 'vs/platform/theme/common/theme';
|
||||
import { isSafari } from 'vs/base/browser/browser';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
|
||||
interface IEditorInputLabel {
|
||||
name?: string;
|
||||
@@ -73,6 +73,9 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
private static readonly TAB_HEIGHT = 35;
|
||||
|
||||
private static readonly MOUSE_WHEEL_EVENT_THRESHOLD = 150;
|
||||
private static readonly MOUSE_WHEEL_DISTANCE_THRESHOLD = 1.5;
|
||||
|
||||
private titleContainer: HTMLElement | undefined;
|
||||
private tabsAndActionsContainer: HTMLElement | undefined;
|
||||
private tabsContainer: HTMLElement | undefined;
|
||||
@@ -87,12 +90,18 @@ export class TabsTitleControl extends TitleControl {
|
||||
private tabActionBars: ActionBar[] = [];
|
||||
private tabDisposables: IDisposable[] = [];
|
||||
|
||||
private dimension: Dimension | undefined;
|
||||
private dimensions: ITitleControlDimensions & { used?: Dimension } = {
|
||||
container: Dimension.None,
|
||||
available: Dimension.None
|
||||
};
|
||||
|
||||
private readonly layoutScheduled = this._register(new MutableDisposable());
|
||||
private blockRevealActiveTab: boolean | undefined;
|
||||
|
||||
private path: IPath = isWindows ? win32 : posix;
|
||||
|
||||
private lastMouseWheelEventTime = 0;
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
accessor: IEditorGroupsAccessor,
|
||||
@@ -106,19 +115,21 @@ export class TabsTitleControl extends TitleControl {
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IQuickInputService quickInputService: IQuickInputService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEditorService private readonly editorService: EditorServiceImpl,
|
||||
@IPathService private readonly pathService: IPathService,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService);
|
||||
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, configurationService, fileService);
|
||||
|
||||
// Resolve the correct path library for the OS we are on
|
||||
// If we are connected to remote, this accounts for the
|
||||
// remote OS.
|
||||
(async () => this.path = await this.pathService.path)();
|
||||
|
||||
// React to decorations changing for our resource labels
|
||||
this._register(this.tabResourceLabels.onDidChangeDecorations(() => this.doHandleDecorationsChange()));
|
||||
}
|
||||
|
||||
protected create(parent: HTMLElement): void {
|
||||
@@ -151,7 +162,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Editor Actions Toolbar
|
||||
this.createEditorActionsToolBar(this.editorToolbarContainer);
|
||||
|
||||
// Breadcrumbs (are on a separate row below tabs and actions)
|
||||
// Breadcrumbs
|
||||
const breadcrumbsContainer = document.createElement('div');
|
||||
breadcrumbsContainer.classList.add('tabs-breadcrumbs');
|
||||
this.titleContainer.appendChild(breadcrumbsContainer);
|
||||
@@ -242,7 +253,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
});
|
||||
|
||||
// Prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690)
|
||||
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, e => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -337,8 +348,27 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out scrolling direction
|
||||
const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + (e.deltaX < 0 || e.deltaY < 0 /* scrolling up */ ? -1 : 1));
|
||||
// Ignore event if the last one happened too recently (https://github.com/microsoft/vscode/issues/96409)
|
||||
// The restriction is relaxed according to the absolute value of `deltaX` and `deltaY`
|
||||
// to support discrete (mouse wheel) and contiguous scrolling (touchpad) equally well
|
||||
const now = Date.now();
|
||||
if (now - this.lastMouseWheelEventTime < TabsTitleControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastMouseWheelEventTime = now;
|
||||
|
||||
// Figure out scrolling direction but ignore it if too subtle
|
||||
let tabSwitchDirection: number;
|
||||
if (e.deltaX + e.deltaY < - TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) {
|
||||
tabSwitchDirection = -1;
|
||||
} else if (e.deltaX + e.deltaY > TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) {
|
||||
tabSwitchDirection = 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + tabSwitchDirection);
|
||||
if (!nextEditor) {
|
||||
return;
|
||||
}
|
||||
@@ -351,12 +381,19 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
}
|
||||
|
||||
private doHandleDecorationsChange(): void {
|
||||
|
||||
// A change to decorations potentially has an impact on the size of tabs
|
||||
// so we need to trigger a layout in that case to adjust things
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
protected updateEditorActionsToolbar(): void {
|
||||
super.updateEditorActionsToolbar();
|
||||
|
||||
// Changing the actions in the toolbar can have an impact on the size of the
|
||||
// tab container, so we need to layout the tabs to make sure the active is visible
|
||||
this.layout(this.dimension);
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
openEditor(editor: IEditorInput): void {
|
||||
@@ -439,7 +476,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
});
|
||||
|
||||
// Moving an editor requires a layout to keep the active editor visible
|
||||
this.layout(this.dimension);
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
pinEditor(editor: IEditorInput): void {
|
||||
@@ -466,7 +503,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
});
|
||||
|
||||
// A change to the sticky state requires a layout to keep the active editor visible
|
||||
this.layout(this.dimension);
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
setActive(isGroupActive: boolean): void {
|
||||
@@ -478,7 +515,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Activity has an impact on the toolbar, so we need to update and layout
|
||||
this.updateEditorActionsToolbar();
|
||||
this.layout(this.dimension);
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0));
|
||||
@@ -504,7 +541,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
});
|
||||
|
||||
// A change to a label requires a layout to keep the active editor visible
|
||||
this.layout(this.dimension);
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
updateEditorDirty(editor: IEditorInput): void {
|
||||
@@ -531,7 +568,9 @@ export class TabsTitleControl extends TitleControl {
|
||||
oldOptions.pinnedTabSizing !== newOptions.pinnedTabSizing ||
|
||||
oldOptions.showIcons !== newOptions.showIcons ||
|
||||
oldOptions.hasIcons !== newOptions.hasIcons ||
|
||||
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs
|
||||
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs ||
|
||||
oldOptions.wrapTabs !== newOptions.wrapTabs ||
|
||||
!equals(oldOptions.decorations, newOptions.decorations)
|
||||
) {
|
||||
this.redraw();
|
||||
}
|
||||
@@ -648,7 +687,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
};
|
||||
|
||||
// Open on Click / Touch
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, e => handleClickOrTouch(e)));
|
||||
disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
|
||||
|
||||
// Touch Scroll Support
|
||||
@@ -657,14 +696,14 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Prevent flicker of focus outline on tab until editor got focus
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, e => {
|
||||
EventHelper.stop(e);
|
||||
|
||||
tab.blur();
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
disposables.add(addDisposableListener(tab, EventType.AUXCLICK, (e: MouseEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.AUXCLICK, e => {
|
||||
if (e.button === 1 /* Middle Button*/) {
|
||||
EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */);
|
||||
|
||||
@@ -674,7 +713,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Context menu on Shift+F10
|
||||
disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.shiftKey && event.keyCode === KeyCode.F10) {
|
||||
showContextMenu(e);
|
||||
@@ -687,7 +726,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Keyboard accessibility
|
||||
disposables.add(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.KEY_UP, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let handled = false;
|
||||
|
||||
@@ -750,7 +789,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
});
|
||||
|
||||
// Context menu
|
||||
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, e => {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const input = this.group.getEditorByIndex(index);
|
||||
@@ -760,7 +799,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */));
|
||||
|
||||
// Drag support
|
||||
disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.DRAG_START, e => {
|
||||
const editor = this.group.getEditorByIndex(index);
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -1022,7 +1061,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.updateEditorActionsToolbar();
|
||||
|
||||
// Ensure the active tab is always revealed
|
||||
this.layout(this.dimension);
|
||||
this.layout(this.dimensions);
|
||||
}
|
||||
|
||||
private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void {
|
||||
@@ -1092,12 +1131,14 @@ export class TabsTitleControl extends TitleControl {
|
||||
// or their first character of the name otherwise
|
||||
let name: string | undefined;
|
||||
let forceLabel = false;
|
||||
let forceDisableBadgeDecorations = false;
|
||||
let description: string;
|
||||
if (options.pinnedTabSizing === 'compact' && this.group.isSticky(index)) {
|
||||
const isShowingIcons = options.showIcons && options.hasIcons;
|
||||
name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase();
|
||||
description = '';
|
||||
forceLabel = true;
|
||||
forceDisableBadgeDecorations = true; // not enough space when sticky tabs are compact
|
||||
} else {
|
||||
name = tabLabel.name;
|
||||
description = tabLabel.description || '';
|
||||
@@ -1116,7 +1157,16 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Label
|
||||
tabLabelWidget.setResource(
|
||||
{ name, description, resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }) },
|
||||
{ title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel }
|
||||
{
|
||||
title,
|
||||
extraClasses: ['tab-label'],
|
||||
italic: !this.group.isPinned(editor),
|
||||
forceLabel,
|
||||
fileDecorations: {
|
||||
colors: Boolean(options.decorations?.colors),
|
||||
badges: forceDisableBadgeDecorations ? false : Boolean(options.decorations?.badges)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Tests helper
|
||||
@@ -1234,62 +1284,179 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
getDimensions(): IEditorGroupTitleDimensions {
|
||||
let height = TabsTitleControl.TAB_HEIGHT;
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
height += BreadcrumbsControl.HEIGHT;
|
||||
let height: number;
|
||||
|
||||
// Wrap: we need to ask `offsetHeight` to get
|
||||
// the real height of the title area with wrapping.
|
||||
if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
|
||||
height = this.tabsAndActionsContainer.offsetHeight;
|
||||
} else {
|
||||
height = TabsTitleControl.TAB_HEIGHT;
|
||||
}
|
||||
|
||||
return {
|
||||
height,
|
||||
offset: TabsTitleControl.TAB_HEIGHT
|
||||
};
|
||||
const offset = height;
|
||||
|
||||
// Account for breadcrumbs if visible
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
height += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible
|
||||
}
|
||||
|
||||
return { height, offset };
|
||||
}
|
||||
|
||||
layout(dimension: Dimension | undefined): void {
|
||||
this.dimension = dimension;
|
||||
layout(dimensions: ITitleControlDimensions): Dimension {
|
||||
|
||||
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
|
||||
if (!activeTabAndIndex || !this.dimension) {
|
||||
return;
|
||||
}
|
||||
// Remember dimensions that we get
|
||||
Object.assign(this.dimensions, dimensions);
|
||||
|
||||
// The layout of tabs can be an expensive operation because we access DOM properties
|
||||
// that can result in the browser doing a full page layout to validate them. To buffer
|
||||
// this a little bit we try at least to schedule this work on the next animation frame.
|
||||
if (!this.layoutScheduled.value) {
|
||||
this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => {
|
||||
const dimension = assertIsDefined(this.dimension);
|
||||
this.doLayout(dimension);
|
||||
this.doLayout(this.dimensions);
|
||||
|
||||
this.layoutScheduled.clear();
|
||||
});
|
||||
}
|
||||
|
||||
// First time layout: compute the dimensions and store it
|
||||
if (!this.dimensions.used) {
|
||||
this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height);
|
||||
}
|
||||
|
||||
return this.dimensions.used;
|
||||
}
|
||||
|
||||
private doLayout(dimension: Dimension): void {
|
||||
private doLayout(dimensions: ITitleControlDimensions): void {
|
||||
|
||||
// Only layout if we have valid tab index and dimensions
|
||||
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
|
||||
if (!activeTabAndIndex) {
|
||||
return; // nothing to do if not editor opened
|
||||
if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) {
|
||||
|
||||
// Breadcrumbs
|
||||
this.doLayoutBreadcrumbs(dimensions);
|
||||
|
||||
// Tabs
|
||||
const [activeTab, activeIndex] = activeTabAndIndex;
|
||||
this.doLayoutTabs(activeTab, activeIndex, dimensions);
|
||||
}
|
||||
|
||||
// Breadcrumbs
|
||||
this.doLayoutBreadcrumbs(dimension);
|
||||
// Remember the dimensions used in the control so that we can
|
||||
// return it fast from the `layout` call without having to
|
||||
// compute it over and over again
|
||||
const oldDimension = this.dimensions.used;
|
||||
const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height);
|
||||
|
||||
// Tabs
|
||||
const [activeTab, activeIndex] = activeTabAndIndex;
|
||||
this.doLayoutTabs(activeTab, activeIndex);
|
||||
// In case the height of the title control changed from before
|
||||
// (currently only possible if wrapping changed on/off), we need
|
||||
// to signal this to the outside via a `relayout` call so that
|
||||
// e.g. the editor control can be adjusted accordingly.
|
||||
if (oldDimension && oldDimension.height !== newDimension.height) {
|
||||
this.group.relayout();
|
||||
}
|
||||
}
|
||||
|
||||
private doLayoutBreadcrumbs(dimension: Dimension): void {
|
||||
private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void {
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
const tabsScrollbar = assertIsDefined(this.tabsScrollbar);
|
||||
|
||||
this.breadcrumbsControl.layout(new Dimension(dimension.width, BreadcrumbsControl.HEIGHT));
|
||||
tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
|
||||
this.breadcrumbsControl.layout(new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT));
|
||||
}
|
||||
}
|
||||
|
||||
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void {
|
||||
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void {
|
||||
|
||||
// Always first layout tabs with wrapping support even if wrapping
|
||||
// is disabled. The result indicates if tabs wrap and if not, we
|
||||
// need to proceed with the layout without wrapping because even
|
||||
// if wrapping is enabled in settings, there are cases where
|
||||
// wrapping is disabled (e.g. due to space constraints)
|
||||
const tabsWrapMultiLine = this.doLayoutTabsWrapping(dimensions);
|
||||
if (!tabsWrapMultiLine) {
|
||||
this.doLayoutTabsNonWrapping(activeTab, activeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private doLayoutTabsWrapping(dimensions: ITitleControlDimensions): boolean {
|
||||
const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar);
|
||||
|
||||
// Handle wrapping tabs according to setting:
|
||||
// - enabled: only add class if tabs wrap and don't exceed available dimensions
|
||||
// - disabled: remove class and margin-right variable
|
||||
|
||||
const didTabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping');
|
||||
let tabsWrapMultiLine = didTabsWrapMultiLine;
|
||||
|
||||
function updateTabsWrapping(enabled: boolean): void {
|
||||
tabsWrapMultiLine = enabled;
|
||||
|
||||
// Toggle the `wrapped` class to enable wrapping
|
||||
tabsAndActionsContainer.classList.toggle('wrapping', tabsWrapMultiLine);
|
||||
|
||||
// Update `last-tab-margin-right` CSS variable to account for the absolute
|
||||
// positioned editor actions container when tabs wrap. The margin needs to
|
||||
// be the width of the editor actions container to avoid screen cheese.
|
||||
tabsContainer.style.setProperty('--last-tab-margin-right', tabsWrapMultiLine ? `${editorToolbarContainer.offsetWidth}px` : '0');
|
||||
}
|
||||
|
||||
// Setting enabled: selectively enable wrapping if possible
|
||||
if (this.accessor.partOptions.wrapTabs) {
|
||||
const visibleTabsWidth = tabsContainer.offsetWidth;
|
||||
const allTabsWidth = tabsContainer.scrollWidth;
|
||||
const lastTabFitsWrapped = () => {
|
||||
const lastTab = this.getLastTab();
|
||||
if (!lastTab) {
|
||||
return true; // no tab always fits
|
||||
}
|
||||
|
||||
return lastTab.offsetWidth <= (dimensions.available.width - editorToolbarContainer.offsetWidth);
|
||||
};
|
||||
|
||||
// If tabs wrap or should start to wrap (when width exceeds visible width)
|
||||
// we must trigger `updateWrapping` to set the `last-tab-margin-right`
|
||||
// accordingly based on the number of actions. The margin is important to
|
||||
// properly position the last tab apart from the actions
|
||||
//
|
||||
// We already check here if the last tab would fit when wrapped given the
|
||||
// editor toolbar will also show right next to it. This ensures we are not
|
||||
// enabling wrapping only to disable it again in the code below (this fixes
|
||||
// flickering issue https://github.com/microsoft/vscode/issues/115050)
|
||||
if (tabsWrapMultiLine || (allTabsWidth > visibleTabsWidth && lastTabFitsWrapped())) {
|
||||
updateTabsWrapping(true);
|
||||
}
|
||||
|
||||
// Tabs wrap multiline: remove wrapping under certain size constraint conditions
|
||||
if (tabsWrapMultiLine) {
|
||||
if (
|
||||
(tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height
|
||||
(allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) || // if wrapping is not needed anymore
|
||||
(!lastTabFitsWrapped()) // if last tab does not fit anymore
|
||||
) {
|
||||
updateTabsWrapping(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setting disabled: remove CSS traces only if tabs did wrap
|
||||
else if (didTabsWrapMultiLine) {
|
||||
updateTabsWrapping(false);
|
||||
}
|
||||
|
||||
// If we transitioned from non-wrapping to wrapping, we need
|
||||
// to update the scrollbar to have an equal `width` and
|
||||
// `scrollWidth`. Otherwise a scrollbar would appear which is
|
||||
// never desired when wrapping.
|
||||
if (tabsWrapMultiLine && !didTabsWrapMultiLine) {
|
||||
const visibleTabsWidth = tabsContainer.offsetWidth;
|
||||
tabsScrollbar.setScrollDimensions({
|
||||
width: visibleTabsWidth,
|
||||
scrollWidth: visibleTabsWidth
|
||||
});
|
||||
}
|
||||
|
||||
return tabsWrapMultiLine;
|
||||
}
|
||||
|
||||
private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number): void {
|
||||
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
|
||||
|
||||
//
|
||||
@@ -1308,7 +1475,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// [-- Sticky Tabs Width --]
|
||||
//
|
||||
|
||||
const visibleTabsContainerWidth = tabsContainer.offsetWidth;
|
||||
const visibleTabsWidth = tabsContainer.offsetWidth;
|
||||
const allTabsWidth = tabsContainer.scrollWidth;
|
||||
|
||||
// Compute width of sticky tabs depending on pinned tab sizing
|
||||
@@ -1331,17 +1498,17 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
// Figure out if active tab is positioned static which has an
|
||||
// impact on wether to reveal the tab or not later
|
||||
// impact on whether to reveal the tab or not later
|
||||
let activeTabPositionStatic = this.accessor.partOptions.pinnedTabSizing !== 'normal' && this.group.isSticky(activeIndex);
|
||||
|
||||
// Special case: we have sticky tabs but the available space for showing tabs
|
||||
// is little enough that we need to disable sticky tabs sticky positioning
|
||||
// so that tabs can be scrolled at naturally.
|
||||
let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth;
|
||||
let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth;
|
||||
if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) {
|
||||
tabsContainer.classList.add('disable-sticky-tabs');
|
||||
|
||||
availableTabsContainerWidth = visibleTabsContainerWidth;
|
||||
availableTabsContainerWidth = visibleTabsWidth;
|
||||
stickyTabsWidth = 0;
|
||||
activeTabPositionStatic = false;
|
||||
} else {
|
||||
@@ -1358,7 +1525,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Update scrollbar
|
||||
tabsScrollbar.setScrollDimensions({
|
||||
width: visibleTabsContainerWidth,
|
||||
width: visibleTabsWidth,
|
||||
scrollWidth: allTabsWidth
|
||||
});
|
||||
|
||||
@@ -1426,15 +1593,28 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
private getTabAndIndex(editor: IEditorInput): [HTMLElement, number /* index */] | undefined {
|
||||
const editorIndex = this.group.getIndexOfEditor(editor);
|
||||
if (editorIndex >= 0) {
|
||||
const tabsContainer = assertIsDefined(this.tabsContainer);
|
||||
|
||||
return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex];
|
||||
const tab = this.getTabAtIndex(editorIndex);
|
||||
if (tab) {
|
||||
return [tab, editorIndex];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTabAtIndex(editorIndex: number): HTMLElement | undefined {
|
||||
if (editorIndex >= 0) {
|
||||
const tabsContainer = assertIsDefined(this.tabsContainer);
|
||||
|
||||
return tabsContainer.children[editorIndex] as HTMLElement | undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getLastTab(): HTMLElement | undefined {
|
||||
return this.getTabAtIndex(this.group.count - 1);
|
||||
}
|
||||
|
||||
private blockRevealActiveTabOnce(): void {
|
||||
|
||||
// When closing tabs through the tab close button or gesture, the user
|
||||
@@ -1527,16 +1707,28 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
// Add border between tabs and breadcrumbs in high contrast mode.
|
||||
if (theme.type === ColorScheme.HIGH_CONTRAST) {
|
||||
const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder));
|
||||
if (borderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add bottom border to tabs when wrapping
|
||||
const borderColor = theme.getColor(TAB_BORDER);
|
||||
if (borderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
}
|
||||
`);
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab {
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { isFunction, isObject, isArray, assertIsDefined } from 'vs/base/common/types';
|
||||
import { isFunction, isObject, isArray, assertIsDefined, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
@@ -103,7 +103,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
|
||||
}
|
||||
|
||||
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
|
||||
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
|
||||
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {});
|
||||
}
|
||||
|
||||
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||
@@ -132,8 +132,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
|
||||
|
||||
// Set Editor Model
|
||||
const diffEditor = assertIsDefined(this.getControl());
|
||||
const resolvedDiffEditorModel = <TextDiffEditorModel>resolvedModel;
|
||||
diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel);
|
||||
const resolvedDiffEditorModel = resolvedModel as TextDiffEditorModel;
|
||||
diffEditor.setModel(withUndefinedAsNull(resolvedDiffEditorModel.textDiffEditorModel));
|
||||
|
||||
// Apply Options from TextOptions
|
||||
let optionsGotApplied = false;
|
||||
|
||||
@@ -233,7 +233,6 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
protected saveTextEditorViewState(resource: URI, cleanUpOnDispose?: IEditorInput): void {
|
||||
const editorViewState = this.retrieveTextEditorViewState(resource);
|
||||
if (!editorViewState || !this.group) {
|
||||
|
||||
@@ -9,14 +9,13 @@ import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ExecuteCommandAction, IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -26,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
@@ -34,7 +33,6 @@ import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/bro
|
||||
import { IEditorGroupsAccessor, IEditorGroupTitleDimensions, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, ActiveEditorPinnedContext, ActiveEditorStickyContext } from 'vs/workbench/common/editor';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types';
|
||||
@@ -46,6 +44,20 @@ export interface IToolbarActions {
|
||||
secondary: IAction[];
|
||||
}
|
||||
|
||||
export interface ITitleControlDimensions {
|
||||
|
||||
/**
|
||||
* The size of the parent container the title control is layed out in.
|
||||
*/
|
||||
container: Dimension;
|
||||
|
||||
/**
|
||||
* The maximum size the title control is allowed to consume based on
|
||||
* other controls that are positioned inside the container.
|
||||
*/
|
||||
available: Dimension;
|
||||
}
|
||||
|
||||
export abstract class TitleControl extends Themable {
|
||||
|
||||
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
@@ -53,9 +65,6 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined;
|
||||
|
||||
private currentPrimaryEditorActionIds: string[] = [];
|
||||
private currentSecondaryEditorActionIds: string[] = [];
|
||||
|
||||
private editorActionsToolbar: ToolBar | undefined;
|
||||
|
||||
private resourceContext: ResourceContextKey;
|
||||
@@ -79,7 +88,6 @@ export abstract class TitleControl extends Themable {
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IQuickInputService protected quickInputService: IQuickInputService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
@@ -92,13 +100,6 @@ export abstract class TitleControl extends Themable {
|
||||
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
|
||||
|
||||
this.create(parent);
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected registerListeners(): void {
|
||||
|
||||
// Update actions toolbar when extension register that may contribute them
|
||||
this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar()));
|
||||
}
|
||||
|
||||
protected abstract create(parent: HTMLElement): void;
|
||||
@@ -147,7 +148,7 @@ export abstract class TitleControl extends Themable {
|
||||
this.editorActionsToolbar.context = context;
|
||||
|
||||
// Action Run Handling
|
||||
this._register(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
|
||||
this._register(this.editorActionsToolbar.actionRunner.onDidRun(e => {
|
||||
|
||||
// Notify for Error
|
||||
this.notificationService.error(e.error);
|
||||
@@ -172,35 +173,14 @@ export abstract class TitleControl extends Themable {
|
||||
}
|
||||
|
||||
// Check extensions
|
||||
if (action instanceof MenuItemAction) {
|
||||
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
|
||||
} else if (action instanceof SubmenuItemAction) {
|
||||
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return createActionViewItem(this.instantiationService, action);
|
||||
}
|
||||
|
||||
protected updateEditorActionsToolbar(): void {
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
const { primaryEditorActions, secondaryEditorActions } = this.prepareEditorActions(this.getEditorActions());
|
||||
|
||||
// Only update if something actually has changed
|
||||
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
|
||||
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
|
||||
if (
|
||||
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
|
||||
!arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) ||
|
||||
primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments
|
||||
secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/microsoft/vscode/issues/16298
|
||||
) {
|
||||
const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar);
|
||||
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions);
|
||||
|
||||
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
|
||||
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
|
||||
}
|
||||
const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar);
|
||||
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions);
|
||||
}
|
||||
|
||||
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[]; secondaryEditorActions: IAction[]; } {
|
||||
@@ -251,18 +231,13 @@ export abstract class TitleControl extends Themable {
|
||||
}
|
||||
|
||||
protected clearEditorActionsToolbar(): void {
|
||||
if (this.editorActionsToolbar) {
|
||||
this.editorActionsToolbar.setActions([], []);
|
||||
}
|
||||
|
||||
this.currentPrimaryEditorActionIds = [];
|
||||
this.currentSecondaryEditorActionIds = [];
|
||||
this.editorActionsToolbar?.setActions([], []);
|
||||
}
|
||||
|
||||
protected enableGroupDragging(element: HTMLElement): void {
|
||||
|
||||
// Drag start
|
||||
this._register(addDisposableListener(element, EventType.DRAG_START, (e: DragEvent) => {
|
||||
this._register(addDisposableListener(element, EventType.DRAG_START, e => {
|
||||
if (e.target !== element) {
|
||||
return; // only if originating from tabs container
|
||||
}
|
||||
@@ -354,7 +329,7 @@ export abstract class TitleControl extends Themable {
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) }),
|
||||
getKeyBinding: (action) => this.getKeybinding(action),
|
||||
getKeyBinding: action => this.getKeybinding(action),
|
||||
onHide: () => {
|
||||
|
||||
// restore previous contexts
|
||||
@@ -407,7 +382,7 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
abstract updateStyles(): void;
|
||||
|
||||
abstract layout(dimension: Dimension): void;
|
||||
abstract layout(dimensions: ITitleControlDimensions): Dimension;
|
||||
|
||||
abstract getDimensions(): IEditorGroupTitleDimensions;
|
||||
|
||||
@@ -419,7 +394,7 @@ export abstract class TitleControl extends Themable {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
// Drag Feedback
|
||||
const dragImageBackground = theme.getColor(listActiveSelectionBackground);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./media/notificationsCenter';
|
||||
import 'vs/css!./media/notificationsActions';
|
||||
import { NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
@@ -313,7 +313,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER);
|
||||
if (notificationBorderColor) {
|
||||
collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IListOptions } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { INotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer';
|
||||
@@ -278,7 +278,7 @@ export class NotificationsList extends Themable {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const linkColor = theme.getColor(NOTIFICATIONS_LINKS);
|
||||
if (linkColor) {
|
||||
collector.addRule(`.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`);
|
||||
|
||||
@@ -470,7 +470,9 @@ export class NotificationTemplateRenderer extends Disposable {
|
||||
: buttonToolbar.addButton(buttonOptions));
|
||||
button.label = action.label;
|
||||
this.inputDisposables.add(button.onDidClick(e => {
|
||||
EventHelper.stop(e, true);
|
||||
if (e) {
|
||||
EventHelper.stop(e, true);
|
||||
}
|
||||
actionRunner.run(action);
|
||||
}));
|
||||
|
||||
|
||||
@@ -5,45 +5,26 @@
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ActivityAction, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel';
|
||||
import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ActivePanelContext, PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
|
||||
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { ViewContainerLocationToString, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
|
||||
const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, nls.localize('maximizeIcon', 'Icon to maximize a panel.'));
|
||||
const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, nls.localize('restoreIcon', 'Icon to restore a panel.'));
|
||||
const closeIcon = registerIcon('panel-close', Codicon.close, nls.localize('closeIcon', 'Icon to close a panel.'));
|
||||
|
||||
export class ClosePanelAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.closePanel';
|
||||
static readonly LABEL = nls.localize('closePanel', "Close Panel");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, name, ThemeIcon.asClassName(closeIcon));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
this.layoutService.setPanelHidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class TogglePanelAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.togglePanel';
|
||||
@@ -91,46 +72,6 @@ class FocusPanelAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ToggleMaximizedPanelAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.toggleMaximizedPanel';
|
||||
static readonly LABEL = nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel");
|
||||
|
||||
private static readonly MAXIMIZE_LABEL = nls.localize('maximizePanel', "Maximize Panel Size");
|
||||
private static readonly RESTORE_LABEL = nls.localize('minimizePanel', "Restore Panel Size");
|
||||
|
||||
private readonly toDispose = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IEditorGroupsService editorGroupsService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, layoutService.isPanelMaximized() ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon));
|
||||
|
||||
this.toDispose.add(editorGroupsService.onDidLayout(() => {
|
||||
const maximized = this.layoutService.isPanelMaximized();
|
||||
this.class = maximized ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon);
|
||||
this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL;
|
||||
}));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
|
||||
this.layoutService.setPanelHidden(false);
|
||||
// If the panel is not already maximized, maximize it
|
||||
if (!this.layoutService.isPanelMaximized()) {
|
||||
this.layoutService.toggleMaximizedPanel();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.layoutService.toggleMaximizedPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PositionPanelActionId = {
|
||||
LEFT: 'workbench.action.positionPanelLeft',
|
||||
RIGHT: 'workbench.action.positionPanelRight',
|
||||
@@ -218,7 +159,6 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SwitchPanelViewAction extends Action {
|
||||
|
||||
constructor(
|
||||
@@ -287,35 +227,117 @@ export class NextPanelViewAction extends SwitchPanelViewAction {
|
||||
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TogglePanelAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', CATEGORIES.View.value);
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPanelAction), 'View: Focus into Panel', CATEGORIES.View.value);
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Maximized Panel', CATEGORIES.View.value);
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClosePanelAction), 'View: Close Panel', CATEGORIES.View.value);
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(PreviousPanelViewAction), 'View: Previous Panel View', CATEGORIES.View.value);
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NextPanelViewAction), 'View: Next Panel View', CATEGORIES.View.value);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: TogglePanelAction.ID,
|
||||
title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"),
|
||||
toggled: ActivePanelContext
|
||||
},
|
||||
order: 5
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.toggleMaximizedPanel',
|
||||
title: { value: nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel"), original: 'Toggle Maximized Panel' },
|
||||
tooltip: nls.localize('maximizePanel', "Maximize Panel Size"),
|
||||
category: CATEGORIES.View,
|
||||
f1: true,
|
||||
icon: maximizeIcon,
|
||||
toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: nls.localize('minimizePanel', "Restore Panel Size") },
|
||||
menu: [{
|
||||
id: MenuId.PanelTitle,
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
}]
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
const layoutService = accessor.get(IWorkbenchLayoutService);
|
||||
if (!layoutService.isVisible(Parts.PANEL_PART)) {
|
||||
layoutService.setPanelHidden(false);
|
||||
// If the panel is not already maximized, maximize it
|
||||
if (!layoutService.isPanelMaximized()) {
|
||||
layoutService.toggleMaximizedPanel();
|
||||
}
|
||||
}
|
||||
else {
|
||||
layoutService.toggleMaximizedPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.closePanel',
|
||||
title: { value: nls.localize('closePanel', "Close Panel"), original: 'Close Panel' },
|
||||
category: CATEGORIES.View,
|
||||
icon: closeIcon,
|
||||
menu: [{
|
||||
id: MenuId.CommandPalette,
|
||||
when: PanelVisibleContext,
|
||||
}, {
|
||||
id: MenuId.PanelTitle,
|
||||
group: 'navigation',
|
||||
order: 2
|
||||
}]
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
accessor.get(IWorkbenchLayoutService).setPanelHidden(true);
|
||||
}
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItems([
|
||||
{
|
||||
id: MenuId.MenubarAppearanceMenu,
|
||||
item: {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: TogglePanelAction.ID,
|
||||
title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"),
|
||||
toggled: ActivePanelContext
|
||||
},
|
||||
order: 5
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TogglePanelAction.ID,
|
||||
title: { value: nls.localize('hidePanel', "Hide Panel"), original: 'Hide Panel' },
|
||||
},
|
||||
when: ContextKeyExpr.and(PanelVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))),
|
||||
order: 2
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
function registerPositionPanelActionById(config: PanelActionConfig<Position>) {
|
||||
const { id, label, alias, when } = config;
|
||||
// register the workbench action
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetPanelPositionAction, id, label), alias, CATEGORIES.View.value, when);
|
||||
// register as a menu item
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id,
|
||||
title: label
|
||||
},
|
||||
when,
|
||||
order: 5
|
||||
});
|
||||
MenuRegistry.appendMenuItems([{
|
||||
id: MenuId.MenubarAppearanceMenu,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id,
|
||||
title: label
|
||||
},
|
||||
when,
|
||||
order: 5
|
||||
}
|
||||
}, {
|
||||
id: MenuId.ViewTitleContext,
|
||||
item: {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: id,
|
||||
title: label,
|
||||
},
|
||||
when: ContextKeyExpr.and(when, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))),
|
||||
order: 1
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
// register each position panel action
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction, Separator, toAction } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -18,8 +19,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { PanelActivityAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
|
||||
@@ -27,15 +28,12 @@ import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/composit
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Dimension, trackFocus, EventHelper } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ViewMenuActions, ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
@@ -46,7 +44,7 @@ interface ICachedPanel {
|
||||
pinned: boolean;
|
||||
order?: number;
|
||||
visible: boolean;
|
||||
views?: { when?: string }[];
|
||||
views?: { when?: string; }[];
|
||||
}
|
||||
|
||||
interface IPlaceholderViewContainer {
|
||||
@@ -148,24 +146,27 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), {
|
||||
icon: false,
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
openComposite: (compositeId: string) => this.openPanel(compositeId, true).then(panel => panel || null),
|
||||
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
|
||||
getContextMenuActions: () => [
|
||||
...PositionPanelActionConfigs
|
||||
// show the contextual menu item if it is not in that position
|
||||
.filter(({ when }) => contextKeyService.contextMatchesRules(when))
|
||||
.map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)),
|
||||
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
|
||||
] as Action[],
|
||||
getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[],
|
||||
openComposite: compositeId => this.openPanel(compositeId, true).then(panel => panel || null),
|
||||
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: compositeId => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
|
||||
fillExtraContextMenuActions: actions => {
|
||||
actions.push(...[
|
||||
new Separator(),
|
||||
...PositionPanelActionConfigs
|
||||
// show the contextual menu item if it is not in that position
|
||||
.filter(({ when }) => contextKeyService.contextMatchesRules(when))
|
||||
.map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)),
|
||||
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
|
||||
]);
|
||||
},
|
||||
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
|
||||
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
|
||||
hidePart: () => this.layoutService.setPanelHidden(true),
|
||||
dndHandler: this.dndHandler,
|
||||
compositeSize: 0,
|
||||
overflowActionSize: 44,
|
||||
colors: (theme: IColorTheme) => ({
|
||||
colors: theme => ({
|
||||
activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action
|
||||
inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action
|
||||
activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER),
|
||||
@@ -184,20 +185,21 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
this.onDidRegisterPanels([...this.getPanels()]);
|
||||
}
|
||||
|
||||
private getContextMenuActionsForComposite(compositeId: string): readonly IAction[] {
|
||||
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
const container = this.getViewContainer(compositeId);
|
||||
if (container) {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(container);
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
|
||||
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
|
||||
if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) {
|
||||
result.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) }));
|
||||
} else {
|
||||
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (viewContainerModel.allViewDescriptors.length === 1) {
|
||||
const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewContainerModel.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext);
|
||||
result.push(...viewMenuActions.getContextMenuActions());
|
||||
viewMenuActions.dispose();
|
||||
const viewToReset = viewContainerModel.allViewDescriptors[0];
|
||||
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
|
||||
if (defaultContainer !== viewContainer) {
|
||||
result.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) }));
|
||||
}
|
||||
}
|
||||
|
||||
const viewContainerMenuActions = this.instantiationService.createInstance(ViewContainerMenuActions, container.id, MenuId.ViewContainerTitleContext);
|
||||
result.push(...viewContainerMenuActions.getContextMenuActions());
|
||||
viewContainerMenuActions.dispose();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -534,13 +536,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
.sort((p1, p2) => pinnedCompositeIds.indexOf(p1.id) - pinnedCompositeIds.indexOf(p2.id));
|
||||
}
|
||||
|
||||
protected getActions(): ReadonlyArray<IAction> {
|
||||
return [
|
||||
this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
|
||||
this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL)
|
||||
];
|
||||
}
|
||||
|
||||
getActivePanel(): IPanel | undefined {
|
||||
return this.getActiveComposite();
|
||||
}
|
||||
@@ -803,7 +798,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
// Panel Background: since panels can host editors, we apply a background rule if the panel background
|
||||
// color is different from the editor background color. This is a bit of a hack though. The better way
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Removed to allow progress bar positioning to escape */
|
||||
/* .monaco-workbench .sidebar > .content {
|
||||
overflow: hidden;
|
||||
} */
|
||||
|
||||
.monaco-workbench.nosidebar > .part.sidebar {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import 'vs/css!./media/sidebarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { CompositePart } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
@@ -288,8 +287,8 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
|
||||
const anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => contextMenuActions,
|
||||
getActionViewItem: action => this.actionViewItemProvider(action as Action),
|
||||
getActions: () => contextMenuActions.slice(),
|
||||
getActionViewItem: action => this.actionViewItemProvider(action),
|
||||
actionRunner: activeViewlet.getActionRunner()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./media/statusbarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
|
||||
import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -15,13 +15,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, appendChildren } from 'vs/base/browser/dom';
|
||||
import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, append } from 'vs/base/browser/dom';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
@@ -38,7 +37,8 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ColorScheme } from 'vs/platform/theme/common/theme';
|
||||
import { renderCodicon, renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { syncing } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
||||
interface IPendingStatusbarEntry {
|
||||
id: string;
|
||||
@@ -610,7 +610,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
}
|
||||
|
||||
private getContextMenuActions(event: StandardMouseEvent): IAction[] {
|
||||
const actions: Action[] = [];
|
||||
const actions: IAction[] = [];
|
||||
|
||||
// Provide an action to hide the status bar at last
|
||||
actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar")));
|
||||
@@ -704,9 +704,9 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
}
|
||||
}
|
||||
|
||||
class StatusBarCodiconLabel extends CodiconLabel {
|
||||
class StatusBarCodiconLabel extends SimpleIconLabel {
|
||||
|
||||
private readonly progressCodicon = renderCodicon('sync', 'spin');
|
||||
private readonly progressCodicon = renderIcon(syncing);
|
||||
|
||||
private currentText = '';
|
||||
private currentShowProgress = false;
|
||||
@@ -749,7 +749,7 @@ class StatusBarCodiconLabel extends CodiconLabel {
|
||||
}
|
||||
|
||||
// Append new elements
|
||||
appendChildren(this.container, ...renderCodicons(textContent));
|
||||
append(this.container, ...renderLabelWithIcons(textContent));
|
||||
}
|
||||
|
||||
// No Progress: no special handling
|
||||
@@ -868,12 +868,8 @@ class StatusbarEntryItem extends Disposable {
|
||||
|
||||
// Update: Background
|
||||
if (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {
|
||||
if (entry.backgroundColor) {
|
||||
this.applyColor(this.container, entry.backgroundColor, true);
|
||||
this.container.classList.add('has-background-color');
|
||||
} else {
|
||||
this.container.classList.remove('has-background-color');
|
||||
}
|
||||
this.container.classList.toggle('has-background-color', !!entry.backgroundColor);
|
||||
this.applyColor(this.container, entry.backgroundColor, true);
|
||||
}
|
||||
|
||||
// Remember for next round
|
||||
@@ -893,7 +889,7 @@ class StatusbarEntryItem extends Disposable {
|
||||
}
|
||||
|
||||
private applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {
|
||||
let colorResult: string | null = null;
|
||||
let colorResult: string | undefined = undefined;
|
||||
|
||||
if (isBackground) {
|
||||
this.backgroundListener.clear();
|
||||
@@ -903,15 +899,15 @@ class StatusbarEntryItem extends Disposable {
|
||||
|
||||
if (color) {
|
||||
if (isThemeColor(color)) {
|
||||
colorResult = (this.themeService.getColorTheme().getColor(color.id) || Color.transparent).toString();
|
||||
colorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();
|
||||
|
||||
const listener = this.themeService.onDidColorThemeChange(theme => {
|
||||
const colorValue = (theme.getColor(color.id) || Color.transparent).toString();
|
||||
const colorValue = theme.getColor(color.id)?.toString();
|
||||
|
||||
if (isBackground) {
|
||||
container.style.backgroundColor = colorValue;
|
||||
container.style.backgroundColor = colorValue ?? '';
|
||||
} else {
|
||||
container.style.color = colorValue;
|
||||
container.style.color = colorValue ?? '';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -926,9 +922,9 @@ class StatusbarEntryItem extends Disposable {
|
||||
}
|
||||
|
||||
if (isBackground) {
|
||||
container.style.backgroundColor = colorResult || '';
|
||||
container.style.backgroundColor = colorResult ?? '';
|
||||
} else {
|
||||
container.style.color = colorResult || '';
|
||||
container.style.color = colorResult ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,7 +938,7 @@ class StatusbarEntryItem extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
if (theme.type !== ColorScheme.HIGH_CONTRAST) {
|
||||
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
|
||||
if (statusBarItemHoverBackground) {
|
||||
|
||||
@@ -85,11 +85,34 @@
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 3000;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar > .window-appicon:not(.codicon) {
|
||||
background-image: url('../../../media/code-icon.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar .window-appicon > .home-bar-icon-badge {
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
bottom: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
z-index: 1; /* on top of home indicator */
|
||||
background-image: url('../../../media/code-icon.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 8px;
|
||||
pointer-events: none;
|
||||
border-top: 1px solid transparent;
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.titlebar > .window-appicon.codicon {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.monaco-workbench.fullscreen .part.titlebar > .window-appicon {
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -37,6 +37,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
export abstract class MenubarControl extends Disposable {
|
||||
|
||||
@@ -91,7 +92,8 @@ export abstract class MenubarControl extends Disposable {
|
||||
protected readonly preferencesService: IPreferencesService,
|
||||
protected readonly environmentService: IWorkbenchEnvironmentService,
|
||||
protected readonly accessibilityService: IAccessibilityService,
|
||||
protected readonly hostService: IHostService
|
||||
protected readonly hostService: IHostService,
|
||||
protected readonly commandService: ICommandService
|
||||
) {
|
||||
|
||||
super();
|
||||
@@ -308,23 +310,10 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IHostService protected readonly hostService: IHostService
|
||||
@IHostService protected readonly hostService: IHostService,
|
||||
@ICommandService commandService: ICommandService
|
||||
) {
|
||||
super(
|
||||
menuService,
|
||||
workspacesService,
|
||||
contextKeyService,
|
||||
keybindingService,
|
||||
configurationService,
|
||||
labelService,
|
||||
updateService,
|
||||
storageService,
|
||||
notificationService,
|
||||
preferencesService,
|
||||
environmentService,
|
||||
accessibilityService,
|
||||
hostService
|
||||
);
|
||||
super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);
|
||||
|
||||
this._onVisibilityChange = this._register(new Emitter<boolean>());
|
||||
this._onFocusStateChange = this._register(new Emitter<boolean>());
|
||||
@@ -337,7 +326,17 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
|
||||
this.registerActions();
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
// Register web menu actions to the file menu when its in the title
|
||||
this.getWebNavigationMenuItemActions().forEach(actionItem => {
|
||||
this._register(MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
||||
command: actionItem.item,
|
||||
title: actionItem.item.title,
|
||||
group: 'z_Web',
|
||||
when: ContextKeyExpr.and(IsWebContext, ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact'))
|
||||
}));
|
||||
});
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
|
||||
if (menubarActiveWindowFgColor) {
|
||||
collector.addRule(`
|
||||
@@ -356,7 +355,6 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
color: ${activityBarInactiveFgColor};
|
||||
}
|
||||
`);
|
||||
|
||||
}
|
||||
|
||||
const activityBarFgColor = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
@@ -383,7 +381,6 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
const menubarSelectedFgColor = theme.getColor(MENUBAR_SELECTION_FOREGROUND);
|
||||
if (menubarSelectedFgColor) {
|
||||
collector.addRule(`
|
||||
@@ -608,6 +605,12 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
|
||||
for (let action of actions) {
|
||||
this.insertActionsBefore(action, target);
|
||||
|
||||
// use mnemonicTitle whenever possible
|
||||
const title = typeof action.item.title === 'string'
|
||||
? action.item.title
|
||||
: action.item.title.mnemonicTitle ?? action.item.title.value;
|
||||
|
||||
if (action instanceof SubmenuItemAction) {
|
||||
let submenu = this.menus[action.item.submenu.id];
|
||||
if (!submenu) {
|
||||
@@ -625,10 +628,12 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
|
||||
const submenuActions: SubmenuAction[] = [];
|
||||
updateActions(submenu, submenuActions, topLevelTitle);
|
||||
target.push(new SubmenuAction(action.id, mnemonicMenuLabel(action.label), submenuActions));
|
||||
target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions));
|
||||
} else {
|
||||
action.label = mnemonicMenuLabel(this.calculateActionLabel(action));
|
||||
target.push(action);
|
||||
const newAction = new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id));
|
||||
newAction.tooltip = action.tooltip;
|
||||
newAction.checked = action.checked;
|
||||
target.push(newAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,6 +672,27 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
}
|
||||
}
|
||||
|
||||
private getWebNavigationMenuItemActions(): MenuItemAction[] {
|
||||
if (!isWeb) {
|
||||
return []; // only for web
|
||||
}
|
||||
|
||||
const webNavigationActions = [];
|
||||
const webNavigationMenu = this.menuService.createMenu(MenuId.MenubarHomeMenu, this.contextKeyService);
|
||||
for (const groups of webNavigationMenu.getActions()) {
|
||||
const [, actions] = groups;
|
||||
for (const action of actions) {
|
||||
if (action instanceof MenuItemAction) {
|
||||
webNavigationActions.push(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webNavigationMenu.dispose();
|
||||
|
||||
return webNavigationActions;
|
||||
}
|
||||
|
||||
private getMenuBarOptions(): IMenuBarOptions {
|
||||
return {
|
||||
enableMnemonics: this.currentEnableMenuBarMnemonics,
|
||||
@@ -674,7 +700,35 @@ export class CustomMenubarControl extends MenubarControl {
|
||||
visibility: this.currentMenubarVisibility,
|
||||
getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id),
|
||||
alwaysOnMnemonics: this.alwaysOnMnemonics,
|
||||
compactMode: this.currentCompactMenuMode
|
||||
compactMode: this.currentCompactMenuMode,
|
||||
getCompactMenuActions: () => {
|
||||
if (!isWeb) {
|
||||
return []; // only for web
|
||||
}
|
||||
|
||||
const webNavigationActions: IAction[] = [];
|
||||
const href = this.environmentService.options?.homeIndicator?.href;
|
||||
if (href) {
|
||||
webNavigationActions.push(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true,
|
||||
async (event?: MouseEvent) => {
|
||||
if ((!isMacintosh && event?.ctrlKey) || (isMacintosh && event?.metaKey)) {
|
||||
window.open(href, '_blank');
|
||||
} else {
|
||||
window.location.href = href;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const otherActions = this.getWebNavigationMenuItemActions().map(action => {
|
||||
const title = typeof action.item.title === 'string'
|
||||
? action.item.title
|
||||
: action.item.title.mnemonicTitle ?? action.item.title.value;
|
||||
return new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id));
|
||||
});
|
||||
|
||||
webNavigationActions.push(...otherActions);
|
||||
return webNavigationActions;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/titlebarpart';
|
||||
import { localize } from 'vs/nls';
|
||||
import { dirname, basename } from 'vs/base/common/resources';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
|
||||
@@ -15,17 +16,16 @@ import { IAction } from 'vs/base/common/actions';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as nls from 'vs/nls';
|
||||
import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend } from 'vs/base/browser/dom';
|
||||
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { template } from 'vs/base/common/labels';
|
||||
@@ -41,12 +41,13 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
|
||||
|
||||
export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
|
||||
private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
|
||||
private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
|
||||
private static readonly NLS_UNSUPPORTED = localize('patchedWindowTitle', "[Unsupported]");
|
||||
private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]");
|
||||
private static readonly NLS_EXTENSION_HOST = localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
|
||||
private static readonly TITLE_DIRTY = '\u25cf ';
|
||||
|
||||
//#region IView
|
||||
@@ -65,6 +66,8 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
protected title!: HTMLElement;
|
||||
protected customMenubar: CustomMenubarControl | undefined;
|
||||
protected appIcon: HTMLElement | undefined;
|
||||
private appIconBadge: HTMLElement | undefined;
|
||||
protected menubar?: HTMLElement;
|
||||
protected lastLayoutDimensions: Dimension | undefined;
|
||||
private titleBarStyle: 'native' | 'custom';
|
||||
@@ -86,7 +89,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IInstantiationService protected readonly instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@@ -341,6 +344,28 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.element = parent;
|
||||
|
||||
// App Icon (Native Windows/Linux and Web)
|
||||
if (!isMacintosh || isWeb) {
|
||||
this.appIcon = prepend(this.element, $('a.window-appicon'));
|
||||
|
||||
// Web-only home indicator and menu
|
||||
if (isWeb) {
|
||||
const homeIndicator = this.environmentService.options?.homeIndicator;
|
||||
if (homeIndicator) {
|
||||
let codicon = iconRegistry.get(homeIndicator.icon);
|
||||
if (!codicon) {
|
||||
codicon = Codicon.code;
|
||||
}
|
||||
|
||||
this.appIcon.setAttribute('href', homeIndicator.href);
|
||||
this.appIcon.classList.add(...codicon.classNamesArray);
|
||||
this.appIconBadge = document.createElement('div');
|
||||
this.appIconBadge.classList.add('home-bar-icon-badge');
|
||||
this.appIcon.appendChild(this.appIconBadge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Menubar: install a custom menu bar depending on configuration
|
||||
// and when not in activity bar
|
||||
if (this.titleBarStyle !== 'native'
|
||||
@@ -407,6 +432,11 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme));
|
||||
}) || '';
|
||||
this.element.style.backgroundColor = titleBackground;
|
||||
|
||||
if (this.appIconBadge) {
|
||||
this.appIconBadge.style.backgroundColor = titleBackground;
|
||||
}
|
||||
|
||||
if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
|
||||
this.element.classList.add('light');
|
||||
} else {
|
||||
@@ -441,7 +471,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
protected adjustTitleMarginToCenter(): void {
|
||||
if (this.customMenubar && this.menubar) {
|
||||
const leftMarker = this.menubar.clientWidth + 10;
|
||||
const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10;
|
||||
const rightMarker = this.element.clientWidth - 10;
|
||||
|
||||
// Not enough space to center the titlebar within window,
|
||||
@@ -497,7 +527,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
|
||||
if (titlebarActiveFg) {
|
||||
collector.addRule(`
|
||||
|
||||
@@ -8,19 +8,19 @@ import { toDisposable, IDisposable, Disposable, DisposableStore } from 'vs/base/
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { MenuId, IMenuService, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { MenuId, IMenuService, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions';
|
||||
import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { createAndFillInContextMenuActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -52,6 +52,8 @@ import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel
|
||||
import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer';
|
||||
import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
|
||||
export class TreeViewPane extends ViewPane {
|
||||
|
||||
@@ -453,15 +455,7 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
|
||||
private createTree() {
|
||||
const actionViewItemProvider = (action: IAction) => {
|
||||
if (action instanceof MenuItemAction) {
|
||||
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
|
||||
} else if (action instanceof SubmenuItemAction) {
|
||||
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService);
|
||||
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
|
||||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
|
||||
@@ -534,12 +528,13 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
}));
|
||||
this.tree.setInput(this.root).then(() => this.updateContentAreas());
|
||||
|
||||
this._register(this.tree.onDidOpen(e => {
|
||||
this._register(this.tree.onDidOpen(async (e) => {
|
||||
if (!e.browserEvent) {
|
||||
return;
|
||||
}
|
||||
const selection = this.tree!.getSelection();
|
||||
const command = selection.length === 1 ? selection[0].command : undefined;
|
||||
const command = await this.resolveCommand(selection.length === 1 ? selection[0] : undefined);
|
||||
|
||||
if (command) {
|
||||
let args = command.arguments || [];
|
||||
if (command.id === API_OPEN_EDITOR_COMMAND_ID || command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) {
|
||||
@@ -554,6 +549,17 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
|
||||
}
|
||||
|
||||
private async resolveCommand(element: ITreeItem | undefined): Promise<Command | undefined> {
|
||||
let command = element?.command;
|
||||
if (element && !command) {
|
||||
if ((element instanceof ResolvableTreeItem) && element.hasResolve) {
|
||||
await element.resolve(new CancellationTokenSource().token);
|
||||
command = element.command;
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>, actionRunner: MultipleSelectionActionRunner): void {
|
||||
this.hoverService.hideHover();
|
||||
const node: ITreeItem | null = treeEvent.element;
|
||||
@@ -776,10 +782,17 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
|
||||
}
|
||||
|
||||
async getChildren(element: ITreeItem): Promise<ITreeItem[]> {
|
||||
let result: ITreeItem[] = [];
|
||||
if (this.treeView.dataProvider) {
|
||||
return this.withProgress(this.treeView.dataProvider.getChildren(element));
|
||||
try {
|
||||
result = await this.withProgress(this.treeView.dataProvider.getChildren(element));
|
||||
} catch (e) {
|
||||
if (!(<string>e.message).startsWith('Bad progress location:')) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,6 +853,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
this._hoverDelegate = {
|
||||
showHover: (options: IHoverDelegateOptions): IDisposable | undefined => {
|
||||
return this.hoverService.showHover(options);
|
||||
},
|
||||
hideHover: () => {
|
||||
return this.hoverService.hideHover();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -868,7 +884,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
|
||||
private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | IIconLabelMarkdownString | undefined {
|
||||
if (!(node instanceof ResolvableTreeItem) || !node.hasResolve) {
|
||||
if (resource) {
|
||||
if (resource && !node.tooltip) {
|
||||
return undefined;
|
||||
} else if (!node.tooltip) {
|
||||
return label;
|
||||
@@ -880,13 +896,13 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
}
|
||||
|
||||
return {
|
||||
markdown: (): Promise<IMarkdownString | string | undefined> => {
|
||||
markdown: (token: CancellationToken): Promise<IMarkdownString | string | undefined> => {
|
||||
return new Promise<IMarkdownString | string | undefined>(async (resolve) => {
|
||||
await node.resolve();
|
||||
await node.resolve(token);
|
||||
resolve(node.tooltip);
|
||||
});
|
||||
},
|
||||
markdownNotSupportedFallback: resource ? undefined : '' // Passing undefined as the fallback for a resource falls back to the old native hover
|
||||
markdownNotSupportedFallback: resource ? undefined : label ?? '' // Passing undefined as the fallback for a resource falls back to the old native hover
|
||||
};
|
||||
}
|
||||
|
||||
@@ -928,7 +944,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
templateData.resourceLabel.setResource({ name: label, description, resource: labelResource }, {
|
||||
fileKind: this.getFileKind(node),
|
||||
title,
|
||||
hideIcon: !!iconUrl,
|
||||
hideIcon: !!iconUrl || (!!node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)),
|
||||
fileDecorations,
|
||||
extraClasses: ['custom-view-tree-node-item-resourceLabel'],
|
||||
matches: matches ? matches : createMatches(element.filterData),
|
||||
@@ -1168,4 +1184,3 @@ export class CustomTreeView extends TreeView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
||||
export class ViewMenuActions extends Disposable {
|
||||
|
||||
private primaryActions: IAction[] = [];
|
||||
private readonly titleActionsDisposable = this._register(new MutableDisposable());
|
||||
private secondaryActions: IAction[] = [];
|
||||
private contextMenuActions: IAction[] = [];
|
||||
|
||||
private _onDidChangeTitle = this._register(new Emitter<void>());
|
||||
readonly onDidChangeTitle: Event<void> = this._onDidChangeTitle.event;
|
||||
|
||||
constructor(
|
||||
viewId: string,
|
||||
menuId: MenuId,
|
||||
contextMenuId: MenuId,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('view', viewId);
|
||||
|
||||
const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService));
|
||||
const updateActions = () => {
|
||||
this.primaryActions = [];
|
||||
this.secondaryActions = [];
|
||||
this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary: this.primaryActions, secondary: this.secondaryActions });
|
||||
this._onDidChangeTitle.fire();
|
||||
};
|
||||
this._register(menu.onDidChange(updateActions));
|
||||
updateActions();
|
||||
|
||||
const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService));
|
||||
const updateContextMenuActions = () => {
|
||||
this.contextMenuActions = [];
|
||||
this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions });
|
||||
};
|
||||
this._register(contextMenu.onDidChange(updateContextMenuActions));
|
||||
updateContextMenuActions();
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
this.primaryActions = [];
|
||||
this.secondaryActions = [];
|
||||
this.contextMenuActions = [];
|
||||
}));
|
||||
}
|
||||
|
||||
getPrimaryActions(): IAction[] {
|
||||
return this.primaryActions;
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.secondaryActions;
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.contextMenuActions;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewContainerMenuActions extends Disposable {
|
||||
|
||||
private readonly titleActionsDisposable = this._register(new MutableDisposable());
|
||||
private contextMenuActions: IAction[] = [];
|
||||
|
||||
constructor(
|
||||
containerId: string,
|
||||
contextMenuId: MenuId,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('container', containerId);
|
||||
|
||||
const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService));
|
||||
const updateContextMenuActions = () => {
|
||||
this.contextMenuActions = [];
|
||||
this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions });
|
||||
};
|
||||
this._register(contextMenu.onDidChange(updateContextMenuActions));
|
||||
updateContextMenuActions();
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
this.contextMenuActions = [];
|
||||
}));
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.contextMenuActions;
|
||||
}
|
||||
}
|
||||
642
lib/vscode/src/vs/workbench/browser/parts/views/viewPane.ts
Normal file
642
lib/vscode/src/vs/workbench/browser/parts/views/viewPane.ts
Normal file
@@ -0,0 +1,642 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/paneviewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId, Action2, IAction2Options, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { parseLinkedText } from 'vs/base/common/linkedText';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Link } from 'vs/platform/opener/browser/link';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
|
||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
|
||||
|
||||
export interface IViewPaneOptions extends IPaneOptions {
|
||||
id: string;
|
||||
showActionsAlways?: boolean;
|
||||
titleMenuId?: MenuId;
|
||||
}
|
||||
|
||||
type WelcomeActionClassification = {
|
||||
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.'));
|
||||
const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.'));
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
|
||||
|
||||
interface IItem {
|
||||
readonly descriptor: IViewContentDescriptor;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
class ViewWelcomeController {
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private defaultItem: IItem | undefined;
|
||||
private items: IItem[] = [];
|
||||
get contents(): IViewContentDescriptor[] {
|
||||
const visibleItems = this.items.filter(v => v.visible);
|
||||
|
||||
if (visibleItems.length === 0 && this.defaultItem) {
|
||||
return [this.defaultItem.descriptor];
|
||||
}
|
||||
|
||||
return visibleItems.map(v => v.descriptor);
|
||||
}
|
||||
|
||||
private contextKeyService: IContextKeyService;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
this.contextKeyService = contextKeyService.createScoped();
|
||||
this.disposables.add(this.contextKeyService);
|
||||
|
||||
contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);
|
||||
Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables);
|
||||
this.onDidChangeViewWelcomeContent();
|
||||
}
|
||||
|
||||
private onDidChangeViewWelcomeContent(): void {
|
||||
const descriptors = viewsRegistry.getViewWelcomeContent(this.id);
|
||||
|
||||
this.items = [];
|
||||
|
||||
for (const descriptor of descriptors) {
|
||||
if (descriptor.when === 'default') {
|
||||
this.defaultItem = { descriptor, visible: true };
|
||||
} else {
|
||||
const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;
|
||||
this.items.push({ descriptor, visible });
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private onDidChangeContext(): void {
|
||||
let didChange = false;
|
||||
|
||||
for (const item of this.items) {
|
||||
if (!item.descriptor.when || item.descriptor.when === 'default') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);
|
||||
|
||||
if (item.visible === visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item.visible = visible;
|
||||
didChange = true;
|
||||
}
|
||||
|
||||
if (didChange) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ViewMenuActions extends CompositeMenuActions {
|
||||
constructor(
|
||||
viewId: string,
|
||||
menuId: MenuId,
|
||||
contextMenuId: MenuId,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
) {
|
||||
const scopedContextKeyService = contextKeyService.createScoped();
|
||||
scopedContextKeyService.createKey('view', viewId);
|
||||
const viewLocationKey = scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!));
|
||||
super(menuId, contextMenuId, { shouldForwardArgs: true }, scopedContextKeyService, menuService);
|
||||
this._register(scopedContextKeyService);
|
||||
this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
|
||||
|
||||
private _onDidFocus = this._register(new Emitter<void>());
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
private _onDidBlur = this._register(new Emitter<void>());
|
||||
readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;
|
||||
|
||||
protected _onDidChangeTitleArea = this._register(new Emitter<void>());
|
||||
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
|
||||
|
||||
protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());
|
||||
readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;
|
||||
|
||||
private focusedViewContextKey: IContextKey<string>;
|
||||
|
||||
private _isVisible: boolean = false;
|
||||
readonly id: string;
|
||||
|
||||
private _title: string;
|
||||
public get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
private _titleDescription: string | undefined;
|
||||
public get titleDescription(): string | undefined {
|
||||
return this._titleDescription;
|
||||
}
|
||||
|
||||
private readonly menuActions: ViewMenuActions;
|
||||
private progressBar!: ProgressBar;
|
||||
private progressIndicator!: IProgressIndicator;
|
||||
|
||||
private toolbar?: ToolBar;
|
||||
private readonly showActionsAlways: boolean = false;
|
||||
private headerContainer?: HTMLElement;
|
||||
private titleContainer?: HTMLElement;
|
||||
private titleDescriptionContainer?: HTMLElement;
|
||||
private iconContainer?: HTMLElement;
|
||||
protected twistiesContainer?: HTMLElement;
|
||||
|
||||
private bodyContainer!: HTMLElement;
|
||||
private viewWelcomeContainer!: HTMLElement;
|
||||
private viewWelcomeDisposable: IDisposable = Disposable.None;
|
||||
private viewWelcomeController: ViewWelcomeController;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IOpenerService protected openerService: IOpenerService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@ITelemetryService protected telemetryService: ITelemetryService,
|
||||
) {
|
||||
super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });
|
||||
|
||||
this.id = options.id;
|
||||
this._title = options.title;
|
||||
this._titleDescription = options.titleDescription;
|
||||
this.showActionsAlways = !!options.showActionsAlways;
|
||||
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
|
||||
|
||||
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
|
||||
this._register(this.menuActions.onDidChange(() => this.updateActions()));
|
||||
|
||||
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
|
||||
}
|
||||
|
||||
get headerVisible(): boolean {
|
||||
return super.headerVisible;
|
||||
}
|
||||
|
||||
set headerVisible(visible: boolean) {
|
||||
super.headerVisible = visible;
|
||||
this.element.classList.toggle('merged-header', !visible);
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
if (this._isVisible !== visible) {
|
||||
this._isVisible = visible;
|
||||
|
||||
if (this.isExpanded()) {
|
||||
this._onDidChangeBodyVisibility.fire(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
isBodyVisible(): boolean {
|
||||
return this._isVisible && this.isExpanded();
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): boolean {
|
||||
const changed = super.setExpanded(expanded);
|
||||
if (changed) {
|
||||
this._onDidChangeBodyVisibility.fire(expanded);
|
||||
}
|
||||
if (this.twistiesContainer) {
|
||||
this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));
|
||||
this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
render(): void {
|
||||
super.render();
|
||||
|
||||
const focusTracker = trackFocus(this.element);
|
||||
this._register(focusTracker);
|
||||
this._register(focusTracker.onDidFocus(() => {
|
||||
this.focusedViewContextKey.set(this.id);
|
||||
this._onDidFocus.fire();
|
||||
}));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
if (this.focusedViewContextKey.get() === this.id) {
|
||||
this.focusedViewContextKey.reset();
|
||||
}
|
||||
|
||||
this._onDidBlur.fire();
|
||||
}));
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
this.headerContainer = container;
|
||||
|
||||
this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));
|
||||
|
||||
this.renderHeaderTitle(container, this.title);
|
||||
|
||||
const actions = append(container, $('.actions'));
|
||||
actions.classList.toggle('show', this.showActionsAlways);
|
||||
this.toolbar = new ToolBar(actions, this.contextMenuService, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
actionViewItemProvider: action => this.getActionViewItem(action),
|
||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
renderDropdownAsChildElement: true
|
||||
});
|
||||
|
||||
this._register(this.toolbar);
|
||||
this.setActions();
|
||||
|
||||
this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));
|
||||
|
||||
this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => {
|
||||
this.updateTitle(this.title);
|
||||
}));
|
||||
|
||||
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
|
||||
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
|
||||
this.updateActionsVisibility();
|
||||
}
|
||||
|
||||
protected getTwistyIcon(expanded: boolean): ThemeIcon {
|
||||
return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;
|
||||
}
|
||||
|
||||
style(styles: IPaneStyles): void {
|
||||
super.style(styles);
|
||||
|
||||
const icon = this.getIcon();
|
||||
if (this.iconContainer) {
|
||||
const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground);
|
||||
if (URI.isUri(icon)) {
|
||||
// Apply background color to activity bar item provided with iconUrls
|
||||
this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : '';
|
||||
this.iconContainer.style.color = '';
|
||||
} else {
|
||||
// Apply foreground color to activity bar items provided with codicons
|
||||
this.iconContainer.style.color = fgColor ? fgColor.toString() : '';
|
||||
this.iconContainer.style.backgroundColor = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getIcon(): ThemeIcon | URI {
|
||||
return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement, title: string): void {
|
||||
this.iconContainer = append(container, $('.icon', undefined));
|
||||
const icon = this.getIcon();
|
||||
|
||||
let cssClass: string | undefined = undefined;
|
||||
if (URI.isUri(icon)) {
|
||||
cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;
|
||||
const iconClass = `.pane-header .icon.${cssClass}`;
|
||||
|
||||
createCSSRule(iconClass, `
|
||||
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
mask-size: 24px;
|
||||
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
-webkit-mask-size: 16px;
|
||||
`);
|
||||
} else if (ThemeIcon.isThemeIcon(icon)) {
|
||||
cssClass = ThemeIcon.asClassName(icon);
|
||||
}
|
||||
|
||||
if (cssClass) {
|
||||
this.iconContainer.classList.add(...cssClass.split(' '));
|
||||
}
|
||||
|
||||
const calculatedTitle = this.calculateTitle(title);
|
||||
this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));
|
||||
|
||||
if (this._titleDescription) {
|
||||
this.setTitleDescription(this._titleDescription);
|
||||
}
|
||||
|
||||
this.iconContainer.title = calculatedTitle;
|
||||
this.iconContainer.setAttribute('aria-label', calculatedTitle);
|
||||
}
|
||||
|
||||
protected updateTitle(title: string): void {
|
||||
const calculatedTitle = this.calculateTitle(title);
|
||||
if (this.titleContainer) {
|
||||
this.titleContainer.textContent = calculatedTitle;
|
||||
this.titleContainer.setAttribute('title', calculatedTitle);
|
||||
}
|
||||
|
||||
if (this.iconContainer) {
|
||||
this.iconContainer.title = calculatedTitle;
|
||||
this.iconContainer.setAttribute('aria-label', calculatedTitle);
|
||||
}
|
||||
|
||||
this._title = title;
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
private setTitleDescription(description: string | undefined) {
|
||||
if (this.titleDescriptionContainer) {
|
||||
this.titleDescriptionContainer.textContent = description ?? '';
|
||||
this.titleDescriptionContainer.setAttribute('title', description ?? '');
|
||||
}
|
||||
else if (description && this.titleContainer) {
|
||||
this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));
|
||||
}
|
||||
}
|
||||
|
||||
protected updateTitleDescription(description?: string | undefined): void {
|
||||
this.setTitleDescription(description);
|
||||
|
||||
this._titleDescription = description;
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
private calculateTitle(title: string): string {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;
|
||||
const model = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);
|
||||
const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;
|
||||
|
||||
if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {
|
||||
return `${viewDescriptor.containerTitle}: ${title}`;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private scrollableElement!: DomScrollableElement;
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
this.bodyContainer = container;
|
||||
|
||||
const viewWelcomeContainer = append(container, $('.welcome-view'));
|
||||
this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 });
|
||||
this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Visible,
|
||||
}));
|
||||
|
||||
append(viewWelcomeContainer, this.scrollableElement.getDomNode());
|
||||
|
||||
const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);
|
||||
this._register(onViewWelcomeChange(this.updateViewWelcome, this));
|
||||
this.updateViewWelcome();
|
||||
}
|
||||
|
||||
protected layoutBody(height: number, width: number): void {
|
||||
this.viewWelcomeContainer.style.height = `${height}px`;
|
||||
this.viewWelcomeContainer.style.width = `${width}px`;
|
||||
this.scrollableElement.scanDomNode();
|
||||
}
|
||||
|
||||
getProgressIndicator() {
|
||||
if (this.progressBar === undefined) {
|
||||
// Progress bar
|
||||
this.progressBar = this._register(new ProgressBar(this.element));
|
||||
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.hide();
|
||||
}
|
||||
|
||||
if (this.progressIndicator === undefined) {
|
||||
this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible());
|
||||
}
|
||||
return this.progressIndicator;
|
||||
}
|
||||
|
||||
protected getProgressLocation(): string {
|
||||
return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;
|
||||
}
|
||||
|
||||
protected getBackgroundColor(): string {
|
||||
return this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.shouldShowWelcome()) {
|
||||
this.viewWelcomeContainer.focus();
|
||||
} else if (this.element) {
|
||||
this.element.focus();
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
}
|
||||
|
||||
private setActions(): void {
|
||||
if (this.toolbar) {
|
||||
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()));
|
||||
this.toolbar.context = this.getActionsContext();
|
||||
}
|
||||
}
|
||||
|
||||
private updateActionsVisibility(): void {
|
||||
if (!this.headerContainer) {
|
||||
return;
|
||||
}
|
||||
const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');
|
||||
this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);
|
||||
}
|
||||
|
||||
protected updateActions(): void {
|
||||
this.setActions();
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return this.menuActions.getPrimaryActions();
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menuActions.getSecondaryActions();
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.menuActions.getContextMenuActions();
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
return createActionViewItem(this.instantiationService, action);
|
||||
}
|
||||
|
||||
getActionsContext(): unknown {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
saveState(): void {
|
||||
// Subclasses to implement for saving state
|
||||
}
|
||||
|
||||
private updateViewWelcome(): void {
|
||||
this.viewWelcomeDisposable.dispose();
|
||||
|
||||
if (!this.shouldShowWelcome()) {
|
||||
this.bodyContainer.classList.remove('welcome');
|
||||
this.viewWelcomeContainer.innerText = '';
|
||||
this.scrollableElement.scanDomNode();
|
||||
return;
|
||||
}
|
||||
|
||||
const contents = this.viewWelcomeController.contents;
|
||||
|
||||
if (contents.length === 0) {
|
||||
this.bodyContainer.classList.remove('welcome');
|
||||
this.viewWelcomeContainer.innerText = '';
|
||||
this.scrollableElement.scanDomNode();
|
||||
return;
|
||||
}
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
this.bodyContainer.classList.add('welcome');
|
||||
this.viewWelcomeContainer.innerText = '';
|
||||
|
||||
for (const { content, precondition } of contents) {
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const linkedText = parseLinkedText(line);
|
||||
|
||||
if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {
|
||||
const node = linkedText.nodes[0];
|
||||
const button = new Button(this.viewWelcomeContainer, { title: node.title, supportIcons: true });
|
||||
button.label = node.label;
|
||||
button.onDidClick(_ => {
|
||||
this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });
|
||||
this.openerService.open(node.href);
|
||||
}, null, disposables);
|
||||
disposables.add(button);
|
||||
disposables.add(attachButtonStyler(button, this.themeService));
|
||||
|
||||
if (precondition) {
|
||||
const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);
|
||||
updateEnablement();
|
||||
|
||||
const keys = new Set();
|
||||
precondition.keys().forEach(key => keys.add(key));
|
||||
const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));
|
||||
onDidChangeContext(updateEnablement, null, disposables);
|
||||
}
|
||||
} else {
|
||||
const p = append(this.viewWelcomeContainer, $('p'));
|
||||
|
||||
for (const node of linkedText.nodes) {
|
||||
if (typeof node === 'string') {
|
||||
append(p, document.createTextNode(node));
|
||||
} else {
|
||||
const link = this.instantiationService.createInstance(Link, node);
|
||||
append(p, link.el);
|
||||
disposables.add(link);
|
||||
disposables.add(attachLinkStyler(link, this.themeService));
|
||||
|
||||
if (precondition && node.href.startsWith('command:')) {
|
||||
const updateEnablement = () => link.style({ disabled: !this.contextKeyService.contextMatchesRules(precondition) });
|
||||
updateEnablement();
|
||||
|
||||
const keys = new Set();
|
||||
precondition.keys().forEach(key => keys.add(key));
|
||||
const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));
|
||||
onDidChangeContext(updateEnablement, null, disposables);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollableElement.scanDomNode();
|
||||
this.viewWelcomeDisposable = disposables;
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ViewAction<T extends IView> extends Action2 {
|
||||
constructor(readonly desc: Readonly<IAction2Options> & { viewId: string }) {
|
||||
super(desc);
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const view = accessor.get(IViewsService).getActiveViewWithId(this.desc.viewId);
|
||||
if (view) {
|
||||
return this.runInView(accessor, <T>view, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
abstract runInView(accessor: ServicesAccessor, view: T, ...args: any[]): any;
|
||||
}
|
||||
@@ -6,52 +6,45 @@
|
||||
import 'vs/css!./media/paneviewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ColorIdentifier, activeContrastBorder, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme';
|
||||
import { after, append, $, trackFocus, EventType, isAncestor, Dimension, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IAction, Separator, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme';
|
||||
import { EventType, Dimension, addDisposableListener, isAncestor } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { PaneView, IPaneViewOptions, IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview';
|
||||
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { PaneView, IPaneViewOptions } from 'vs/base/browser/ui/splitview/paneview';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, defaultViewIcon } from 'vs/workbench/common/views';
|
||||
import { IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { parseLinkedText } from 'vs/base/common/linkedText';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Link } from 'vs/platform/opener/browser/link';
|
||||
import { registerAction2, Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, ISubmenuItem, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { CompositeDragAndDropObserver, DragAndDropObserver, toggleDropEffect } from 'vs/workbench/browser/dnd';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
|
||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
|
||||
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
||||
export const ViewsSubMenu = new MenuId('Views');
|
||||
MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, <ISubmenuItem>{
|
||||
submenu: ViewsSubMenu,
|
||||
title: nls.localize('views', "Views"),
|
||||
order: 1,
|
||||
when: ContextKeyEqualsExpr.create('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)),
|
||||
});
|
||||
|
||||
export interface IPaneColors extends IColorMapping {
|
||||
dropBackground?: ColorIdentifier;
|
||||
@@ -61,566 +54,6 @@ export interface IPaneColors extends IColorMapping {
|
||||
leftBorder?: ColorIdentifier;
|
||||
}
|
||||
|
||||
export interface IViewPaneOptions extends IPaneOptions {
|
||||
id: string;
|
||||
showActionsAlways?: boolean;
|
||||
titleMenuId?: MenuId;
|
||||
}
|
||||
|
||||
type WelcomeActionClassification = {
|
||||
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.'));
|
||||
const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.'));
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
|
||||
|
||||
interface IItem {
|
||||
readonly descriptor: IViewContentDescriptor;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
class ViewWelcomeController {
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private defaultItem: IItem | undefined;
|
||||
private items: IItem[] = [];
|
||||
get contents(): IViewContentDescriptor[] {
|
||||
const visibleItems = this.items.filter(v => v.visible);
|
||||
|
||||
if (visibleItems.length === 0 && this.defaultItem) {
|
||||
return [this.defaultItem.descriptor];
|
||||
}
|
||||
|
||||
return visibleItems.map(v => v.descriptor);
|
||||
}
|
||||
|
||||
private contextKeyService: IContextKeyService;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
this.contextKeyService = contextKeyService.createScoped();
|
||||
this.disposables.add(this.contextKeyService);
|
||||
|
||||
contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);
|
||||
Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables);
|
||||
this.onDidChangeViewWelcomeContent();
|
||||
}
|
||||
|
||||
private onDidChangeViewWelcomeContent(): void {
|
||||
const descriptors = viewsRegistry.getViewWelcomeContent(this.id);
|
||||
|
||||
this.items = [];
|
||||
|
||||
for (const descriptor of descriptors) {
|
||||
if (descriptor.when === 'default') {
|
||||
this.defaultItem = { descriptor, visible: true };
|
||||
} else {
|
||||
const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;
|
||||
this.items.push({ descriptor, visible });
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private onDidChangeContext(): void {
|
||||
let didChange = false;
|
||||
|
||||
for (const item of this.items) {
|
||||
if (!item.descriptor.when || item.descriptor.when === 'default') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);
|
||||
|
||||
if (item.visible === visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item.visible = visible;
|
||||
didChange = true;
|
||||
}
|
||||
|
||||
if (didChange) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
|
||||
|
||||
private _onDidFocus = this._register(new Emitter<void>());
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
private _onDidBlur = this._register(new Emitter<void>());
|
||||
readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;
|
||||
|
||||
protected _onDidChangeTitleArea = this._register(new Emitter<void>());
|
||||
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
|
||||
|
||||
protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());
|
||||
readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;
|
||||
|
||||
private focusedViewContextKey: IContextKey<string>;
|
||||
|
||||
private _isVisible: boolean = false;
|
||||
readonly id: string;
|
||||
|
||||
private _title: string;
|
||||
public get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
private _titleDescription: string | undefined;
|
||||
public get titleDescription(): string | undefined {
|
||||
return this._titleDescription;
|
||||
}
|
||||
|
||||
private readonly menuActions: ViewMenuActions;
|
||||
private progressBar!: ProgressBar;
|
||||
private progressIndicator!: IProgressIndicator;
|
||||
|
||||
private toolbar?: ToolBar;
|
||||
private readonly showActionsAlways: boolean = false;
|
||||
private headerContainer?: HTMLElement;
|
||||
private titleContainer?: HTMLElement;
|
||||
private titleDescriptionContainer?: HTMLElement;
|
||||
private iconContainer?: HTMLElement;
|
||||
protected twistiesContainer?: HTMLElement;
|
||||
|
||||
private bodyContainer!: HTMLElement;
|
||||
private viewWelcomeContainer!: HTMLElement;
|
||||
private viewWelcomeDisposable: IDisposable = Disposable.None;
|
||||
private viewWelcomeController: ViewWelcomeController;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IOpenerService protected openerService: IOpenerService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@ITelemetryService protected telemetryService: ITelemetryService,
|
||||
) {
|
||||
super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });
|
||||
|
||||
this.id = options.id;
|
||||
this._title = options.title;
|
||||
this._titleDescription = options.titleDescription;
|
||||
this.showActionsAlways = !!options.showActionsAlways;
|
||||
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
|
||||
|
||||
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
|
||||
this._register(this.menuActions.onDidChangeTitle(() => this.updateActions()));
|
||||
|
||||
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
|
||||
}
|
||||
|
||||
get headerVisible(): boolean {
|
||||
return super.headerVisible;
|
||||
}
|
||||
|
||||
set headerVisible(visible: boolean) {
|
||||
super.headerVisible = visible;
|
||||
this.element.classList.toggle('merged-header', !visible);
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
if (this._isVisible !== visible) {
|
||||
this._isVisible = visible;
|
||||
|
||||
if (this.isExpanded()) {
|
||||
this._onDidChangeBodyVisibility.fire(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
isBodyVisible(): boolean {
|
||||
return this._isVisible && this.isExpanded();
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): boolean {
|
||||
const changed = super.setExpanded(expanded);
|
||||
if (changed) {
|
||||
this._onDidChangeBodyVisibility.fire(expanded);
|
||||
}
|
||||
if (this.twistiesContainer) {
|
||||
this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));
|
||||
this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
render(): void {
|
||||
super.render();
|
||||
|
||||
const focusTracker = trackFocus(this.element);
|
||||
this._register(focusTracker);
|
||||
this._register(focusTracker.onDidFocus(() => {
|
||||
this.focusedViewContextKey.set(this.id);
|
||||
this._onDidFocus.fire();
|
||||
}));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
if (this.focusedViewContextKey.get() === this.id) {
|
||||
this.focusedViewContextKey.reset();
|
||||
}
|
||||
|
||||
this._onDidBlur.fire();
|
||||
}));
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
this.headerContainer = container;
|
||||
|
||||
this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));
|
||||
|
||||
this.renderHeaderTitle(container, this.title);
|
||||
|
||||
const actions = append(container, $('.actions'));
|
||||
actions.classList.toggle('show', this.showActionsAlways);
|
||||
this.toolbar = new ToolBar(actions, this.contextMenuService, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
actionViewItemProvider: action => this.getActionViewItem(action),
|
||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
renderDropdownAsChildElement: true
|
||||
});
|
||||
|
||||
this._register(this.toolbar);
|
||||
this.setActions();
|
||||
|
||||
this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));
|
||||
|
||||
this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => {
|
||||
this.updateTitle(this.title);
|
||||
}));
|
||||
|
||||
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
|
||||
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
|
||||
this.updateActionsVisibility();
|
||||
}
|
||||
|
||||
protected getTwistyIcon(expanded: boolean): ThemeIcon {
|
||||
return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;
|
||||
}
|
||||
|
||||
style(styles: IPaneStyles): void {
|
||||
super.style(styles);
|
||||
|
||||
const icon = this.getIcon();
|
||||
if (this.iconContainer) {
|
||||
const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground);
|
||||
if (URI.isUri(icon)) {
|
||||
// Apply background color to activity bar item provided with iconUrls
|
||||
this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : '';
|
||||
this.iconContainer.style.color = '';
|
||||
} else {
|
||||
// Apply foreground color to activity bar items provided with codicons
|
||||
this.iconContainer.style.color = fgColor ? fgColor.toString() : '';
|
||||
this.iconContainer.style.backgroundColor = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getIcon(): ThemeIcon | URI {
|
||||
return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement, title: string): void {
|
||||
this.iconContainer = append(container, $('.icon', undefined));
|
||||
const icon = this.getIcon();
|
||||
|
||||
let cssClass: string | undefined = undefined;
|
||||
if (URI.isUri(icon)) {
|
||||
cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;
|
||||
const iconClass = `.pane-header .icon.${cssClass}`;
|
||||
|
||||
createCSSRule(iconClass, `
|
||||
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
mask-size: 24px;
|
||||
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
|
||||
-webkit-mask-size: 16px;
|
||||
`);
|
||||
} else if (ThemeIcon.isThemeIcon(icon)) {
|
||||
cssClass = ThemeIcon.asClassName(icon);
|
||||
}
|
||||
|
||||
if (cssClass) {
|
||||
this.iconContainer.classList.add(...cssClass.split(' '));
|
||||
}
|
||||
|
||||
const calculatedTitle = this.calculateTitle(title);
|
||||
this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));
|
||||
|
||||
if (this._titleDescription) {
|
||||
this.setTitleDescription(this._titleDescription);
|
||||
}
|
||||
|
||||
this.iconContainer.title = calculatedTitle;
|
||||
this.iconContainer.setAttribute('aria-label', calculatedTitle);
|
||||
}
|
||||
|
||||
protected updateTitle(title: string): void {
|
||||
const calculatedTitle = this.calculateTitle(title);
|
||||
if (this.titleContainer) {
|
||||
this.titleContainer.textContent = calculatedTitle;
|
||||
this.titleContainer.setAttribute('title', calculatedTitle);
|
||||
}
|
||||
|
||||
if (this.iconContainer) {
|
||||
this.iconContainer.title = calculatedTitle;
|
||||
this.iconContainer.setAttribute('aria-label', calculatedTitle);
|
||||
}
|
||||
|
||||
this._title = title;
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
private setTitleDescription(description: string | undefined) {
|
||||
if (this.titleDescriptionContainer) {
|
||||
this.titleDescriptionContainer.textContent = description ?? '';
|
||||
this.titleDescriptionContainer.setAttribute('title', description ?? '');
|
||||
}
|
||||
else if (description && this.titleContainer) {
|
||||
this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));
|
||||
}
|
||||
}
|
||||
|
||||
protected updateTitleDescription(description?: string | undefined): void {
|
||||
this.setTitleDescription(description);
|
||||
|
||||
this._titleDescription = description;
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
private calculateTitle(title: string): string {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;
|
||||
const model = this.viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);
|
||||
const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;
|
||||
|
||||
if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {
|
||||
return `${viewDescriptor.containerTitle}: ${title}`;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private scrollableElement!: DomScrollableElement;
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
this.bodyContainer = container;
|
||||
|
||||
const viewWelcomeContainer = append(container, $('.welcome-view'));
|
||||
this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 });
|
||||
this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Visible,
|
||||
}));
|
||||
|
||||
append(viewWelcomeContainer, this.scrollableElement.getDomNode());
|
||||
|
||||
const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);
|
||||
this._register(onViewWelcomeChange(this.updateViewWelcome, this));
|
||||
this.updateViewWelcome();
|
||||
}
|
||||
|
||||
protected layoutBody(height: number, width: number): void {
|
||||
this.viewWelcomeContainer.style.height = `${height}px`;
|
||||
this.viewWelcomeContainer.style.width = `${width}px`;
|
||||
this.scrollableElement.scanDomNode();
|
||||
}
|
||||
|
||||
getProgressIndicator() {
|
||||
if (this.progressBar === undefined) {
|
||||
// Progress bar
|
||||
this.progressBar = this._register(new ProgressBar(this.element));
|
||||
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.hide();
|
||||
}
|
||||
|
||||
if (this.progressIndicator === undefined) {
|
||||
this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible());
|
||||
}
|
||||
return this.progressIndicator;
|
||||
}
|
||||
|
||||
protected getProgressLocation(): string {
|
||||
return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;
|
||||
}
|
||||
|
||||
protected getBackgroundColor(): string {
|
||||
return this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.shouldShowWelcome()) {
|
||||
this.viewWelcomeContainer.focus();
|
||||
} else if (this.element) {
|
||||
this.element.focus();
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
}
|
||||
|
||||
private setActions(): void {
|
||||
if (this.toolbar) {
|
||||
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()));
|
||||
this.toolbar.context = this.getActionsContext();
|
||||
}
|
||||
}
|
||||
|
||||
private updateActionsVisibility(): void {
|
||||
if (!this.headerContainer) {
|
||||
return;
|
||||
}
|
||||
const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');
|
||||
this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);
|
||||
}
|
||||
|
||||
protected updateActions(): void {
|
||||
this.setActions();
|
||||
this._onDidChangeTitleArea.fire();
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return this.menuActions.getPrimaryActions();
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menuActions.getSecondaryActions();
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.menuActions.getContextMenuActions();
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action instanceof MenuItemAction) {
|
||||
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
|
||||
} else if (action instanceof SubmenuItemAction) {
|
||||
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getActionsContext(): unknown {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
saveState(): void {
|
||||
// Subclasses to implement for saving state
|
||||
}
|
||||
|
||||
private updateViewWelcome(): void {
|
||||
this.viewWelcomeDisposable.dispose();
|
||||
|
||||
if (!this.shouldShowWelcome()) {
|
||||
this.bodyContainer.classList.remove('welcome');
|
||||
this.viewWelcomeContainer.innerText = '';
|
||||
this.scrollableElement.scanDomNode();
|
||||
return;
|
||||
}
|
||||
|
||||
const contents = this.viewWelcomeController.contents;
|
||||
|
||||
if (contents.length === 0) {
|
||||
this.bodyContainer.classList.remove('welcome');
|
||||
this.viewWelcomeContainer.innerText = '';
|
||||
this.scrollableElement.scanDomNode();
|
||||
return;
|
||||
}
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
this.bodyContainer.classList.add('welcome');
|
||||
this.viewWelcomeContainer.innerText = '';
|
||||
|
||||
for (const { content, precondition } of contents) {
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const linkedText = parseLinkedText(line);
|
||||
|
||||
if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {
|
||||
const node = linkedText.nodes[0];
|
||||
const button = new Button(this.viewWelcomeContainer, { title: node.title, supportCodicons: true });
|
||||
button.label = node.label;
|
||||
button.onDidClick(_ => {
|
||||
this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });
|
||||
this.openerService.open(node.href);
|
||||
}, null, disposables);
|
||||
disposables.add(button);
|
||||
disposables.add(attachButtonStyler(button, this.themeService));
|
||||
|
||||
if (precondition) {
|
||||
const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);
|
||||
updateEnablement();
|
||||
|
||||
const keys = new Set();
|
||||
precondition.keys().forEach(key => keys.add(key));
|
||||
const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));
|
||||
onDidChangeContext(updateEnablement, null, disposables);
|
||||
}
|
||||
} else {
|
||||
const p = append(this.viewWelcomeContainer, $('p'));
|
||||
|
||||
for (const node of linkedText.nodes) {
|
||||
if (typeof node === 'string') {
|
||||
append(p, document.createTextNode(node));
|
||||
} else {
|
||||
const link = this.instantiationService.createInstance(Link, node);
|
||||
append(p, link.el);
|
||||
disposables.add(link);
|
||||
disposables.add(attachLinkStyler(link, this.themeService));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollableElement.scanDomNode();
|
||||
this.viewWelcomeDisposable = disposables;
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewPaneContainerOptions extends IPaneViewOptions {
|
||||
mergeViewWithContainerWhenSingleView: boolean;
|
||||
}
|
||||
@@ -860,6 +293,22 @@ class ViewPaneDropOverlay extends Themable {
|
||||
}
|
||||
}
|
||||
|
||||
class ViewContainerMenuActions extends CompositeMenuActions {
|
||||
constructor(
|
||||
viewContainer: ViewContainer,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
) {
|
||||
const scopedContextKeyService = contextKeyService.createScoped();
|
||||
scopedContextKeyService.createKey('viewContainer', viewContainer.id);
|
||||
const viewContainerLocationKey = scopedContextKeyService.createKey('viewContainerLocation', ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!));
|
||||
super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true }, scopedContextKeyService, menuService);
|
||||
this._register(scopedContextKeyService);
|
||||
this._register(Event.filter(viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === viewContainer)(() => viewContainerLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!))));
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
|
||||
readonly viewContainer: ViewContainer;
|
||||
@@ -910,6 +359,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return this.paneItems.length;
|
||||
}
|
||||
|
||||
private readonly menuActions: ViewContainerMenuActions;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private options: IViewPaneContainerOptions,
|
||||
@@ -922,7 +373,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService
|
||||
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
|
||||
) {
|
||||
|
||||
super(id, themeService, storageService);
|
||||
@@ -938,6 +389,9 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined);
|
||||
this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables)));
|
||||
this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container);
|
||||
|
||||
this.menuActions = this._register(instantiationService.createInstance(ViewContainerMenuActions, container));
|
||||
this._register(this.menuActions.onDidChange(() => this.updateTitleArea()));
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
@@ -1110,57 +564,62 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
let anchor: { x: number, y: number; } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => this.getContextMenuActions()
|
||||
getActions: () => [...this.getContextMenuActions2()]
|
||||
});
|
||||
}
|
||||
|
||||
getContextMenuActions2(): ReadonlyArray<IAction> {
|
||||
return this.menuActions.getContextMenuActions();
|
||||
}
|
||||
|
||||
getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
let showHide = true;
|
||||
if (!viewDescriptor && this.isViewMergedWithContainer()) {
|
||||
viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.panes[0].id) || undefined;
|
||||
showHide = false;
|
||||
getActions2(): IAction[] {
|
||||
const result = [];
|
||||
result.push(...this.menuActions.getPrimaryActions());
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
result.push(...this.paneItems[0].pane.getActions());
|
||||
}
|
||||
|
||||
if (viewDescriptor) {
|
||||
if (showHide) {
|
||||
result.push(<IAction>{
|
||||
id: `${viewDescriptor.id}.removeView`,
|
||||
label: nls.localize('hideView', "Hide"),
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor!.id)
|
||||
});
|
||||
}
|
||||
const view = this.getView(viewDescriptor.id);
|
||||
if (view) {
|
||||
result.push(...view.getContextMenuActions());
|
||||
}
|
||||
}
|
||||
|
||||
const viewToggleActions = this.getViewsVisibilityActions();
|
||||
if (result.length && viewToggleActions.length) {
|
||||
result.push(new Separator());
|
||||
}
|
||||
|
||||
result.push(...viewToggleActions);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getActions();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getSecondaryActions();
|
||||
getSecondaryActions2(): IAction[] {
|
||||
const viewPaneActions = this.isViewMergedWithContainer() ? this.paneItems[0].pane.getSecondaryActions() : [];
|
||||
let menuActions = this.menuActions.getSecondaryActions();
|
||||
|
||||
const viewsSubmenuActionIndex = menuActions.findIndex(action => action instanceof SubmenuItemAction && action.item.submenu === ViewsSubMenu);
|
||||
if (viewsSubmenuActionIndex !== -1) {
|
||||
const viewsSubmenuAction = <SubmenuItemAction>menuActions[viewsSubmenuActionIndex];
|
||||
if (viewsSubmenuAction.actions.some(({ enabled }) => enabled)) {
|
||||
if (menuActions.length === 1 && viewPaneActions.length === 0) {
|
||||
menuActions = viewsSubmenuAction.actions.slice();
|
||||
} else if (viewsSubmenuActionIndex !== 0) {
|
||||
menuActions = [viewsSubmenuAction, ...menuActions.slice(0, viewsSubmenuActionIndex), ...menuActions.slice(viewsSubmenuActionIndex + 1)];
|
||||
}
|
||||
} else {
|
||||
// Remove views submenu if none of the actions are enabled
|
||||
menuActions.splice(viewsSubmenuActionIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (menuActions.length && viewPaneActions.length) {
|
||||
return [
|
||||
...menuActions,
|
||||
new Separator(),
|
||||
...viewPaneActions
|
||||
];
|
||||
}
|
||||
|
||||
return menuActions.length ? menuActions : viewPaneActions;
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1168,22 +627,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getViewsVisibilityActions(): IAction[] {
|
||||
return this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => (<IAction>{
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
|
||||
enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1),
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getActionViewItem(action);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return createActionViewItem(this.instantiationService, action);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@@ -1321,11 +769,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void {
|
||||
private onContextMenu(event: StandardMouseEvent, viewPane: ViewPane): void {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const actions: IAction[] = this.getContextMenuActions(viewDescriptor);
|
||||
const actions: IAction[] = viewPane.getContextMenuActions();
|
||||
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
@@ -1364,7 +812,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
const contextMenuDisposable = addDisposableListener(pane.draggableElement, 'contextmenu', e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.onContextMenu(new StandardMouseEvent(e), viewDescriptor);
|
||||
this.onContextMenu(new StandardMouseEvent(e), pane);
|
||||
});
|
||||
|
||||
const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => {
|
||||
@@ -1401,7 +849,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
}
|
||||
|
||||
protected toggleViewVisibility(viewId: string): void {
|
||||
toggleViewVisibility(viewId: string): void {
|
||||
// Check if view is active
|
||||
if (this.viewContainerModel.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === viewId)) {
|
||||
const visible = !this.viewContainerModel.isVisible(viewId);
|
||||
@@ -1641,7 +1089,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
}
|
||||
|
||||
private isViewMergedWithContainer(): boolean {
|
||||
isViewMergedWithContainer(): boolean {
|
||||
if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1665,6 +1113,21 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ViewPaneContainerAction<T extends IViewPaneContainer> extends Action2 {
|
||||
constructor(readonly desc: Readonly<IAction2Options> & { viewPaneContainerId: string }) {
|
||||
super(desc);
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, ...args: any[]) {
|
||||
const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(this.desc.viewPaneContainerId);
|
||||
if (viewPaneContainer) {
|
||||
return this.runInViewPaneContainer(accessor, <T>viewPaneContainer, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
abstract runInViewPaneContainer(accessor: ServicesAccessor, viewPaneContainer: T, ...args: any[]): any;
|
||||
}
|
||||
|
||||
class MoveViewPosition extends Action2 {
|
||||
constructor(desc: Readonly<IAction2Options>, private readonly offset: number) {
|
||||
super(desc);
|
||||
|
||||
@@ -196,7 +196,9 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
ContextKeyExpr.equals('view', viewDescriptor.id),
|
||||
ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false)
|
||||
)
|
||||
)
|
||||
),
|
||||
group: '1_hide',
|
||||
order: 2
|
||||
}],
|
||||
});
|
||||
}
|
||||
@@ -392,12 +394,29 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
|
||||
getViewProgressIndicator(viewId: string): IProgressIndicator | undefined {
|
||||
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(viewId);
|
||||
if (viewContainer === null) {
|
||||
if (!viewContainer) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const view = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer?.getView(viewId);
|
||||
return view?.getProgressIndicator();
|
||||
const viewPaneContainer = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer;
|
||||
if (!viewPaneContainer) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const view = viewPaneContainer.getView(viewId);
|
||||
if (!view) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (viewPaneContainer.isViewMergedWithContainer()) {
|
||||
return this.getViewContainerProgressIndicator(viewContainer);
|
||||
}
|
||||
|
||||
return view.getProgressIndicator();
|
||||
}
|
||||
|
||||
private getViewContainerProgressIndicator(viewContainer: ViewContainer): IProgressIndicator | undefined {
|
||||
return this.viewDescriptorService.getViewContainerLocation(viewContainer) === ViewContainerLocation.Sidebar ? this.viewletService.getProgressIndicator(viewContainer.id) : this.panelService.getProgressIndicator(viewContainer.id);
|
||||
}
|
||||
|
||||
private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void {
|
||||
@@ -451,7 +470,6 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
undefined,
|
||||
viewContainer.order,
|
||||
viewContainer.requestedIndex,
|
||||
viewContainer.focusCommand?.id,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
|
||||
@@ -12,7 +11,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
@@ -81,18 +81,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
|
||||
this.getViewsForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, true));
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => (<IAction>{
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getViewsForTarget(target: string[]): IViewDescriptor[] {
|
||||
const views: IViewDescriptor[] = [];
|
||||
for (let i = 0; i < target.length; i++) {
|
||||
@@ -140,7 +128,4 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
abstract getTitle(): string;
|
||||
|
||||
getViewsVisibilityActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./media/style';
|
||||
|
||||
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
@@ -13,7 +13,7 @@ import { createMetaElement } from 'vs/base/browser/dom';
|
||||
import { isSafari, isStandalone } from 'vs/base/browser/browser';
|
||||
import { ColorScheme } from 'vs/platform/theme/common/theme';
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
// Foreground
|
||||
const windowForeground = theme.getColor(foreground);
|
||||
|
||||
@@ -3,23 +3,19 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ToggleSidebarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -28,6 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { Action2 } from 'vs/platform/actions/common/actions';
|
||||
|
||||
export abstract class Viewlet extends PaneComposite implements IViewlet {
|
||||
|
||||
@@ -52,40 +49,6 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
const parentActions = [...super.getContextMenuActions()];
|
||||
if (parentActions.length) {
|
||||
parentActions.push(new Separator());
|
||||
}
|
||||
|
||||
const toggleSidebarPositionAction = new ToggleSidebarPositionAction(ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService), this.layoutService, this.configurationService);
|
||||
return [...parentActions, toggleSidebarPositionAction,
|
||||
<IAction>{
|
||||
id: ToggleSidebarVisibilityAction.ID,
|
||||
label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
enabled: true,
|
||||
run: () => this.layoutService.setSideBarHidden(true)
|
||||
}];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
const viewVisibilityActions = this.viewPaneContainer.getViewsVisibilityActions();
|
||||
const secondaryActions = this.viewPaneContainer.getSecondaryActions();
|
||||
if (viewVisibilityActions.length <= 1 || viewVisibilityActions.every(({ enabled }) => !enabled)) {
|
||||
return secondaryActions;
|
||||
}
|
||||
|
||||
if (secondaryActions.length === 0) {
|
||||
return viewVisibilityActions;
|
||||
}
|
||||
|
||||
return [
|
||||
new SubmenuAction('workbench.views', nls.localize('views', "Views"), viewVisibilityActions),
|
||||
new Separator(),
|
||||
...secondaryActions
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +78,7 @@ export class ViewletDescriptor extends CompositeDescriptor<Viewlet> {
|
||||
requestedIndex?: number,
|
||||
readonly iconUrl?: URI
|
||||
) {
|
||||
super(ctor, id, name, cssClass, order, requestedIndex, id);
|
||||
super(ctor, id, name, cssClass, order, requestedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +115,6 @@ export class ViewletRegistry extends CompositeRegistry<Viewlet> {
|
||||
getViewlets(): ViewletDescriptor[] {
|
||||
return this.getComposites() as ViewletDescriptor[];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Viewlets, new ViewletRegistry());
|
||||
@@ -176,7 +138,7 @@ export class ShowViewletAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
|
||||
// Pass focus to viewlet if not open or focused
|
||||
if (this.otherViewletShowing() || !this.sidebarHasFocus()) {
|
||||
if (otherViewletShowing(this.viewletService, this.viewletId) || !sidebarHasFocus(this.viewletService, this.layoutService)) {
|
||||
await this.viewletService.openViewlet(this.viewletId, true);
|
||||
return;
|
||||
}
|
||||
@@ -184,28 +146,42 @@ export class ShowViewletAction extends Action {
|
||||
// Otherwise pass focus to editor group
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private otherViewletShowing(): boolean {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
/**
|
||||
* A reusable action to show a viewlet with a specific id.
|
||||
*/
|
||||
export abstract class ShowViewletAction2 extends Action2 {
|
||||
/**
|
||||
* Gets the viewlet ID to show.
|
||||
*/
|
||||
protected abstract viewletId(): string;
|
||||
|
||||
return !activeViewlet || activeViewlet.getId() !== this.viewletId;
|
||||
}
|
||||
public async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const layoutService = accessor.get(IWorkbenchLayoutService);
|
||||
|
||||
private sidebarHasFocus(): boolean {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const activeElement = document.activeElement;
|
||||
const sidebarPart = this.layoutService.getContainer(Parts.SIDEBAR_PART);
|
||||
// Pass focus to viewlet if not open or focused
|
||||
if (otherViewletShowing(viewletService, this.viewletId()) || !sidebarHasFocus(viewletService, layoutService)) {
|
||||
await viewletService.openViewlet(this.viewletId(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
|
||||
// Otherwise pass focus to editor group
|
||||
editorGroupService.activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class CollapseAction extends Action {
|
||||
// We need a tree getter because the action is sometimes instantiated too early
|
||||
constructor(treeGetter: () => AsyncDataTree<any, any, any> | AbstractTree<any, any, any>, enabled: boolean, clazz?: string) {
|
||||
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, async () => {
|
||||
const tree = treeGetter();
|
||||
tree.collapseAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
const otherViewletShowing = (viewletService: IViewletService, viewletId: string): boolean => {
|
||||
const activeViewlet = viewletService.getActiveViewlet();
|
||||
return !activeViewlet || activeViewlet.getId() !== viewletId;
|
||||
};
|
||||
|
||||
const sidebarHasFocus = (viewletService: IViewletService, layoutService: IWorkbenchLayoutService): boolean => {
|
||||
const activeViewlet = viewletService.getActiveViewlet();
|
||||
const activeElement = document.activeElement;
|
||||
const sidebarPart = layoutService.getContainer(Parts.SIDEBAR_PART);
|
||||
|
||||
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
|
||||
};
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { domContentLoaded, addDisposableListener, EventType, EventHelper, detectFullscreen, addDisposableThrottledListener } from 'vs/base/browser/dom';
|
||||
import { domContentLoaded, detectFullscreen, getCookieValue } from 'vs/base/browser/dom';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILogService, ConsoleLogService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { ConsoleLogInAutomationService } from 'vs/platform/log/browser/log';
|
||||
@@ -24,10 +23,9 @@ import { IFileService, IFileSystemProvider } from 'vs/platform/files/common/file
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { setFullscreen } from 'vs/base/browser/browser';
|
||||
import { isIOS, isMacintosh } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
|
||||
@@ -37,13 +35,16 @@ import { SignService } from 'vs/platform/sign/browser/signService';
|
||||
import type { IWorkbenchConstructionOptions, IWorkspace, IWorkbench } from 'vs/workbench/workbench.web.api';
|
||||
import { BrowserStorageService } from 'vs/platform/storage/browser/storageService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { FileLogService } from 'vs/platform/log/common/fileLogService';
|
||||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
|
||||
<<<<<<< HEAD
|
||||
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
|
||||
import { initialize } from 'vs/server/browser/client';
|
||||
=======
|
||||
import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
|
||||
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -62,6 +63,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
|
||||
import { BrowserWindow } from 'vs/workbench/browser/window';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
|
||||
class BrowserMain extends Disposable {
|
||||
|
||||
@@ -84,37 +87,44 @@ class BrowserMain extends Disposable {
|
||||
const services = await this.initServices();
|
||||
|
||||
await domContentLoaded();
|
||||
mark('willStartWorkbench');
|
||||
mark('code/willStartWorkbench');
|
||||
|
||||
// Create Workbench
|
||||
const workbench = new Workbench(
|
||||
this.domElement,
|
||||
services.serviceCollection,
|
||||
services.logService
|
||||
);
|
||||
const workbench = new Workbench(this.domElement, services.serviceCollection, services.logService);
|
||||
|
||||
// Listeners
|
||||
this.registerListeners(workbench, services.storageService, services.logService);
|
||||
|
||||
// Driver
|
||||
if (this.configuration.driver) {
|
||||
(async () => this._register(await registerWindowDriver()))();
|
||||
}
|
||||
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
<<<<<<< HEAD
|
||||
await initialize(services.serviceCollection);
|
||||
=======
|
||||
// Window
|
||||
this._register(instantiationService.createInstance(BrowserWindow));
|
||||
|
||||
// Logging
|
||||
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
|
||||
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
|
||||
|
||||
// Return API Facade
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
const timerService = accessor.get(ITimerService);
|
||||
|
||||
return {
|
||||
commands: {
|
||||
executeCommand: (command, ...args) => commandService.executeCommand(command, ...args)
|
||||
},
|
||||
env: {
|
||||
async retrievePerformanceMarks() {
|
||||
await timerService.whenReady();
|
||||
|
||||
return timerService.getPerformanceMarks();
|
||||
}
|
||||
},
|
||||
shutdown: () => lifecycleService.shutdown()
|
||||
};
|
||||
});
|
||||
@@ -122,46 +132,17 @@ class BrowserMain extends Disposable {
|
||||
|
||||
private registerListeners(workbench: Workbench, storageService: BrowserStorageService, logService: ILogService): void {
|
||||
|
||||
// Layout
|
||||
const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */;
|
||||
this._register(addDisposableListener(viewport, EventType.RESIZE, () => {
|
||||
logService.trace(`web.main#${isIOS && window.visualViewport ? 'visualViewport' : 'window'}Resize`);
|
||||
workbench.layout();
|
||||
}));
|
||||
|
||||
// Prevent the back/forward gestures in macOS
|
||||
this._register(addDisposableListener(this.domElement, EventType.WHEEL, e => e.preventDefault(), { passive: false }));
|
||||
|
||||
// Prevent native context menus in web
|
||||
this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, e => EventHelper.stop(e, true)));
|
||||
|
||||
// Prevent default navigation on drop
|
||||
this._register(addDisposableListener(this.domElement, EventType.DROP, e => EventHelper.stop(e, true)));
|
||||
|
||||
// Workbench Lifecycle
|
||||
this._register(workbench.onBeforeShutdown(event => {
|
||||
if (storageService.hasPendingUpdate) {
|
||||
logService.warn('Unload veto: pending storage update');
|
||||
event.veto(true); // prevent data loss from pending storage update
|
||||
event.veto(true, 'veto.pendingStorageUpdate'); // prevent data loss from pending storage update
|
||||
}
|
||||
}));
|
||||
this._register(workbench.onWillShutdown(() => {
|
||||
storageService.close();
|
||||
}));
|
||||
this._register(workbench.onWillShutdown(() => storageService.close()));
|
||||
this._register(workbench.onShutdown(() => this.dispose()));
|
||||
|
||||
// Fullscreen (Browser)
|
||||
[EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => {
|
||||
this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen())));
|
||||
});
|
||||
|
||||
// Fullscreen (Native)
|
||||
this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => {
|
||||
setFullscreen(!!detectFullscreen());
|
||||
}, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */));
|
||||
}
|
||||
|
||||
private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IConfigurationService, logService: ILogService, storageService: BrowserStorageService }> {
|
||||
private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService, storageService: BrowserStorageService }> {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
@@ -169,7 +150,7 @@ class BrowserMain extends Disposable {
|
||||
// CONTRIBUTE IT VIA WORKBENCH.WEB.MAIN.TS AND registerSingleton().
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
const payload = await this.resolveWorkspaceInitializationPayload();
|
||||
const payload = this.resolveWorkspaceInitializationPayload();
|
||||
|
||||
// Product
|
||||
const productService: IProductService = { _serviceBrand: undefined, ...product, ...this.configuration.productConfiguration };
|
||||
@@ -184,9 +165,8 @@ class BrowserMain extends Disposable {
|
||||
const logService = new BufferLogService(getLogLevel(environmentService));
|
||||
serviceCollection.set(ILogService, logService);
|
||||
|
||||
const connectionToken = environmentService.options.connectionToken || this.getCookieValue('vscode-tkn');
|
||||
|
||||
// Remote
|
||||
const connectionToken = environmentService.options.connectionToken || getCookieValue('vscode-tkn');
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider);
|
||||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
@@ -215,7 +195,7 @@ class BrowserMain extends Disposable {
|
||||
serviceCollection.set(IWorkspaceContextService, service);
|
||||
|
||||
// Configuration
|
||||
serviceCollection.set(IConfigurationService, service);
|
||||
serviceCollection.set(IWorkbenchConfigurationService, service);
|
||||
|
||||
return service;
|
||||
}),
|
||||
@@ -242,14 +222,16 @@ class BrowserMain extends Disposable {
|
||||
serviceCollection.set(IUserDataInitializationService, userDataInitializationService);
|
||||
|
||||
if (await userDataInitializationService.requiresInitialization()) {
|
||||
mark('willInitRequiredUserData');
|
||||
mark('code/willInitRequiredUserData');
|
||||
|
||||
// Initialize required resources - settings & global state
|
||||
await userDataInitializationService.initializeRequiredResources();
|
||||
|
||||
// Important: Reload only local user configuration after initializing
|
||||
// Reloading complete configuraiton blocks workbench until remote configuration is loaded.
|
||||
await configurationService.reloadLocalUserConfiguration();
|
||||
mark('didInitRequiredUserData');
|
||||
|
||||
mark('code/didInitRequiredUserData');
|
||||
}
|
||||
|
||||
return { serviceCollection, configurationService, logService, storageService };
|
||||
@@ -264,8 +246,9 @@ class BrowserMain extends Disposable {
|
||||
try {
|
||||
indexedDBLogProvider = await indexedDB.createFileSystemProvider(logsPath.scheme, INDEXEDDB_LOGS_OBJECT_STORE);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
|
||||
if (indexedDBLogProvider) {
|
||||
fileService.registerProvider(logsPath.scheme, indexedDBLogProvider);
|
||||
} else {
|
||||
@@ -282,6 +265,7 @@ class BrowserMain extends Disposable {
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
|
||||
// Remote file system
|
||||
const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
|
||||
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
|
||||
@@ -292,8 +276,9 @@ class BrowserMain extends Disposable {
|
||||
try {
|
||||
indexedDBUserDataProvider = await indexedDB.createFileSystemProvider(Schemas.userData, INDEXEDDB_USERDATA_OBJECT_STORE);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider());
|
||||
|
||||
if (indexedDBUserDataProvider) {
|
||||
registerAction2(class ResetUserDataAction extends Action2 {
|
||||
@@ -307,21 +292,22 @@ class BrowserMain extends Disposable {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
const hostService = accessor.get(IHostService);
|
||||
const result = await dialogService.confirm({
|
||||
message: localize('reset user data message', "Would you like to reset your data (settings, keybindings, extensions, snippets and UI State) and reload?")
|
||||
});
|
||||
|
||||
if (result.confirmed) {
|
||||
await indexedDBUserDataProvider!.reset();
|
||||
await indexedDBUserDataProvider?.reset();
|
||||
}
|
||||
|
||||
hostService.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider());
|
||||
}
|
||||
|
||||
private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise<BrowserStorageService> {
|
||||
@@ -354,7 +340,7 @@ class BrowserMain extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
|
||||
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
|
||||
let workspace: IWorkspace | undefined = undefined;
|
||||
if (this.configuration.workspaceProvider) {
|
||||
workspace = this.configuration.workspaceProvider.workspace;
|
||||
@@ -367,18 +353,11 @@ class BrowserMain extends Disposable {
|
||||
|
||||
// Single-folder workspace
|
||||
if (workspace && isFolderToOpen(workspace)) {
|
||||
const id = hash(workspace.folderUri.toString()).toString(16);
|
||||
return { id, folder: workspace.folderUri };
|
||||
return getSingleFolderWorkspaceIdentifier(workspace.folderUri);
|
||||
}
|
||||
|
||||
return { id: 'empty-window' };
|
||||
}
|
||||
|
||||
private getCookieValue(name: string): string | undefined {
|
||||
const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531
|
||||
|
||||
return match ? match.pop() : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise<IWorkbench> {
|
||||
|
||||
161
lib/vscode/src/vs/workbench/browser/window.ts
Normal file
161
lib/vscode/src/vs/workbench/browser/window.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { setFullscreen } from 'vs/base/browser/browser';
|
||||
import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpener } from 'vs/base/browser/dom';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isIOS, isMacintosh } from 'vs/base/common/platform';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
export class BrowserWindow extends Disposable {
|
||||
|
||||
constructor(
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@ILifecycleService private readonly lifecycleService: BrowserLifecycleService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
this.create();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Lifecycle
|
||||
this._register(this.lifecycleService.onWillShutdown(() => this.onWillShutdown()));
|
||||
|
||||
// Layout
|
||||
const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */;
|
||||
this._register(addDisposableListener(viewport, EventType.RESIZE, () => this.onWindowResize()));
|
||||
|
||||
// Prevent the back/forward gestures in macOS
|
||||
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.WHEEL, e => e.preventDefault(), { passive: false }));
|
||||
|
||||
// Prevent native context menus in web
|
||||
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.CONTEXT_MENU, e => EventHelper.stop(e, true)));
|
||||
|
||||
// Prevent default navigation on drop
|
||||
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.DROP, e => EventHelper.stop(e, true)));
|
||||
|
||||
// Fullscreen (Browser)
|
||||
[EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => {
|
||||
this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen())));
|
||||
});
|
||||
|
||||
// Fullscreen (Native)
|
||||
this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => {
|
||||
setFullscreen(!!detectFullscreen());
|
||||
}, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */));
|
||||
}
|
||||
|
||||
private onWindowResize(): void {
|
||||
this.logService.trace(`web.main#${isIOS && window.visualViewport ? 'visualViewport' : 'window'}Resize`);
|
||||
|
||||
this.layoutService.layout();
|
||||
}
|
||||
|
||||
private onWillShutdown(): void {
|
||||
|
||||
// Try to detect some user interaction with the workbench
|
||||
// when shutdown has happened to not show the dialog e.g.
|
||||
// when navigation takes a longer time.
|
||||
Event.toPromise(Event.any(
|
||||
Event.once(domEvent(document.body, EventType.KEY_DOWN, true)),
|
||||
Event.once(domEvent(document.body, EventType.MOUSE_DOWN, true))
|
||||
)).then(async () => {
|
||||
|
||||
// Delay the dialog in case the user interacted
|
||||
// with the page before it transitioned away
|
||||
await timeout(3000);
|
||||
|
||||
// This should normally not happen, but if for some reason
|
||||
// the workbench was shutdown while the page is still there,
|
||||
// inform the user that only a reload can bring back a working
|
||||
// state.
|
||||
const res = await this.dialogService.show(
|
||||
Severity.Error,
|
||||
localize('shutdownError', "An unexpected error occurred that requires a reload of this page."),
|
||||
[
|
||||
localize('reload', "Reload")
|
||||
],
|
||||
{
|
||||
detail: localize('shutdownErrorDetail', "The workbench was unexpectedly disposed while running.")
|
||||
}
|
||||
);
|
||||
|
||||
if (res.choice === 0) {
|
||||
this.hostService.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
|
||||
// Driver
|
||||
if (this.environmentService.options?.driver) {
|
||||
(async () => this._register(await registerWindowDriver()))();
|
||||
}
|
||||
|
||||
// Handle open calls
|
||||
this.setupOpenHandlers();
|
||||
|
||||
// Label formatting
|
||||
this.registerLabelFormatters();
|
||||
}
|
||||
|
||||
private setupOpenHandlers(): void {
|
||||
|
||||
// We need to ignore the `beforeunload` event while
|
||||
// we handle external links to open specifically for
|
||||
// the case of application protocols that e.g. invoke
|
||||
// vscode itself. We do not want to open these links
|
||||
// in a new window because that would leave a blank
|
||||
// window to the user, but using `window.location.href`
|
||||
// will trigger the `beforeunload`.
|
||||
this.openerService.setDefaultExternalOpener({
|
||||
openExternal: async (href: string) => {
|
||||
if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) {
|
||||
windowOpenNoOpener(href);
|
||||
} else {
|
||||
this.lifecycleService.withExpectedUnload(() => window.location.href = href);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerLabelFormatters() {
|
||||
this.labelService.registerFormatter({
|
||||
scheme: Schemas.userData,
|
||||
priority: true,
|
||||
formatting: {
|
||||
label: '${scheme}:${path}',
|
||||
separator: '/',
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,29 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."),
|
||||
'default': true
|
||||
},
|
||||
'workbench.editor.wrapTabs': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.scrollToSwitchTabs': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is `false`."),
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.highlightModifiedTabs': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is `false`."),
|
||||
'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.decorations.badges': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('decorations.badges', "Controls whether editor file decorations should use badges."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.decorations.colors': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('decorations.colors', "Controls whether editor file decorations should use colors."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.labelFormat': {
|
||||
@@ -75,7 +90,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
'type': 'string',
|
||||
'enum': ['left', 'right', 'off'],
|
||||
'default': 'right',
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is `false`.")
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
|
||||
},
|
||||
'workbench.editor.tabSizing': {
|
||||
'type': 'string',
|
||||
@@ -85,7 +100,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."),
|
||||
nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.")
|
||||
],
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is `false`.")
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
|
||||
},
|
||||
'workbench.editor.pinnedTabSizing': {
|
||||
'type': 'string',
|
||||
@@ -96,7 +111,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
nls.localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."),
|
||||
nls.localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name.")
|
||||
],
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is `false`.")
|
||||
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
|
||||
},
|
||||
'workbench.editor.splitSizing': {
|
||||
'type': 'string',
|
||||
@@ -130,7 +145,12 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
},
|
||||
'workbench.editor.enablePreviewFromQuickOpen': {
|
||||
'type': 'boolean',
|
||||
'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing)."),
|
||||
'markdownDescription': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.enablePreviewFromCodeNavigation': {
|
||||
'type': 'boolean',
|
||||
'markdownDescription': nls.localize('enablePreviewFromCodeNavigation', "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."),
|
||||
'default': false
|
||||
},
|
||||
'workbench.editor.closeOnFileDelete': {
|
||||
@@ -315,8 +335,8 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
nls.localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."),
|
||||
nls.localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."),
|
||||
nls.localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."),
|
||||
nls.localize('rootName', "`\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace)."),
|
||||
nls.localize('rootPath', "`\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace)."),
|
||||
nls.localize('rootName', "`\${rootName}`: name of the opened workspace or folder (e.g. myFolder or myWorkspace)."),
|
||||
nls.localize('rootPath', "`\${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."),
|
||||
nls.localize('appName', "`\${appName}`: e.g. VS Code."),
|
||||
nls.localize('remoteName', "`\${remoteName}`: e.g. SSH"),
|
||||
nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."),
|
||||
@@ -353,7 +373,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
'window.menuBarVisibility': {
|
||||
'type': 'string',
|
||||
'enum': ['default', 'visible', 'toggle', 'hidden', 'compact'],
|
||||
'enumDescriptions': [
|
||||
'markdownEnumDescriptions': [
|
||||
nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."),
|
||||
nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."),
|
||||
nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."),
|
||||
@@ -463,7 +483,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
||||
},
|
||||
'zenMode.restore': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'default': true,
|
||||
'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.")
|
||||
},
|
||||
'zenMode.silentNotifications': {
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'vs/workbench/browser/style';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event';
|
||||
import { runWhenIdle } from 'vs/base/common/async';
|
||||
import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser';
|
||||
import { getZoomLevel, isFirefox, isSafari, isChrome, getPixelRatio } from 'vs/base/browser/browser';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
@@ -177,10 +177,17 @@ export class Workbench extends Layout {
|
||||
// Layout Service
|
||||
serviceCollection.set(IWorkbenchLayoutService, this);
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE.
|
||||
// CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton().
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// native and web or `workbench.sandbox.main.ts` if the service
|
||||
// is native only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
// All Contributed Services
|
||||
const contributedServices = getSingletonServiceDescriptors();
|
||||
@@ -284,7 +291,7 @@ export class Workbench extends Layout {
|
||||
}
|
||||
}
|
||||
|
||||
readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel()));
|
||||
readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), getPixelRatio()));
|
||||
}
|
||||
|
||||
private storeFontInfo(storageService: IStorageService): void {
|
||||
@@ -412,10 +419,10 @@ export class Workbench extends Layout {
|
||||
}, 2500);
|
||||
|
||||
// Telemetry: startup metrics
|
||||
mark('didStartWorkbench');
|
||||
mark('code/didStartWorkbench');
|
||||
|
||||
// Perf reporting (devtools)
|
||||
performance.measure('perf: workbench create & restore', 'didLoadWorkbenchMain', 'didStartWorkbench');
|
||||
performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user