Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2

This commit is contained in:
ADS Merger
2020-05-31 19:47:51 +00:00
parent 84492049e8
commit 28be33cfea
913 changed files with 28242 additions and 15549 deletions

View File

@@ -141,15 +141,25 @@ class ToggleScreencastModeAction extends Action {
const keyboardMarker = append(container, $('.screencast-keyboard'));
disposables.add(toDisposable(() => keyboardMarker.remove()));
const updateKeyboardFontSize = () => {
keyboardMarker.style.fontSize = `${clamp(this.configurationService.getValue<number>('screencastMode.fontSize') || 56, 20, 100)}px`;
};
const updateKeyboardMarker = () => {
keyboardMarker.style.bottom = `${clamp(this.configurationService.getValue<number>('screencastMode.verticalOffset') || 0, 0, 90)}%`;
};
updateKeyboardFontSize();
updateKeyboardMarker();
disposables.add(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('screencastMode.verticalOffset')) {
updateKeyboardMarker();
}
if (e.affectsConfiguration('screencastMode.fontSize')) {
updateKeyboardFontSize();
}
}));
const onKeyDown = domEvent(window, 'keydown', true);
@@ -261,6 +271,13 @@ configurationRegistry.registerConfiguration({
maximum: 90,
description: nls.localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.")
},
'screencastMode.fontSize': {
type: 'number',
default: 56,
minimum: 20,
maximum: 100,
description: nls.localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.")
},
'screencastMode.onlyKeyboardShortcuts': {
type: 'boolean',
description: nls.localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in Screencast Mode."),

View File

@@ -11,7 +11,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/
import { IConfigurationService, ConfigurationTarget } 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 } from 'vs/platform/instantiation/common/instantiation';
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';
@@ -542,6 +542,117 @@ export abstract class ToggleViewAction extends Action {
}
// --- Move View with Command
export class MoveViewAction extends Action {
static readonly ID = 'workbench.action.moveView';
static readonly LABEL = nls.localize('moveView', "Move View");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
@IInstantiationService private instantiationService: IInstantiationService,
@IQuickInputService private quickInputService: IQuickInputService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IActivityBarService private activityBarService: IActivityBarService,
@IPanelService private panelService: IPanelService
) {
super(id, label);
}
private getViewItems(): Array<IQuickPickItem | IQuickPickSeparator> {
const results: Array<IQuickPickItem | IQuickPickSeparator> = [];
const viewlets = this.activityBarService.getVisibleViewContainerIds();
viewlets.forEach(viewletId => {
const container = this.viewDescriptorService.getViewContainerById(viewletId)!;
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
let hasAddedView = false;
containerModel.visibleViewDescriptors.forEach(viewDescriptor => {
if (viewDescriptor.canMoveView) {
if (!hasAddedView) {
results.push({
type: 'separator',
label: nls.localize('sidebarContainer', "Side Bar / {0}", containerModel.title)
});
hasAddedView = true;
}
results.push({
id: viewDescriptor.id,
label: viewDescriptor.name
});
}
});
});
const panels = this.panelService.getPinnedPanels();
panels.forEach(panel => {
const container = this.viewDescriptorService.getViewContainerById(panel.id)!;
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
let hasAddedView = false;
containerModel.visibleViewDescriptors.forEach(viewDescriptor => {
if (viewDescriptor.canMoveView) {
if (!hasAddedView) {
results.push({
type: 'separator',
label: nls.localize('panelContainer', "Panel / {0}", containerModel.title)
});
hasAddedView = true;
}
results.push({
id: viewDescriptor.id,
label: viewDescriptor.name
});
}
});
});
return results;
}
private async getView(viewId?: string): Promise<string> {
const quickPick = this.quickInputService.createQuickPick();
quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Select a View to Move");
quickPick.items = this.getViewItems();
quickPick.selectedItems = quickPick.items.filter(item => (item as IQuickPickItem).id === viewId) as IQuickPickItem[];
return new Promise((resolve, reject) => {
quickPick.onDidAccept(() => {
const viewId = quickPick.selectedItems[0];
resolve(viewId.id);
quickPick.hide();
});
quickPick.onDidHide(() => reject());
quickPick.show();
});
}
async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewId: string;
if (focusedViewId && this.viewDescriptorService.getViewDescriptorById(focusedViewId)?.canMoveView) {
viewId = focusedViewId;
}
viewId = await this.getView(viewId!);
if (!viewId) {
return;
}
this.instantiationService.createInstance(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL).run(viewId);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveViewAction), 'View: Move View', viewCategory);
// --- Move Focused View with Command
export class MoveFocusedViewAction extends Action {
static readonly ID = 'workbench.action.moveFocusedView';
static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View");
@@ -560,8 +671,8 @@ export class MoveFocusedViewAction extends Action {
super(id, label);
}
async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
async run(viewId: string): Promise<void> {
const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService);
if (focusedViewId === undefined || focusedViewId.trim() === '') {
this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."));
@@ -576,27 +687,33 @@ export class MoveFocusedViewAction extends Action {
const quickPick = this.quickInputService.createQuickPick();
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View");
quickPick.title = nls.localize('moveFocusedView.title', "View: Move {0}", viewDescriptor.name);
quickPick.title = nls.localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name);
const items: Array<IQuickPickItem | IQuickPickSeparator> = [];
const currentContainer = this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!;
const currentLocation = this.viewDescriptorService.getViewLocationById(focusedViewId)!;
const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1;
if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) {
items.push({
id: '_.panel.newcontainer',
label: nls.localize('moveFocusedView.newContainerInPanel', "New Panel Entry"),
});
}
if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) {
items.push({
id: '_.sidebar.newcontainer',
label: nls.localize('moveFocusedView.newContainerInSidebar', "New Side Bar Entry")
});
}
items.push({
type: 'separator',
label: nls.localize('sidebar', "Side Bar")
});
const currentContainer = this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!;
const currentLocation = this.viewDescriptorService.getViewLocationById(focusedViewId)!;
const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1;
if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) {
items.push({
id: '_.sidebar.newcontainer',
label: nls.localize('moveFocusedView.newContainerInSidebar', "New Container in Side Bar")
});
}
const pinnedViewlets = this.activityBarService.getPinnedViewContainerIds();
const pinnedViewlets = this.activityBarService.getVisibleViewContainerIds();
items.push(...pinnedViewlets
.filter(viewletId => {
if (viewletId === this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!.id) {
@@ -617,13 +734,6 @@ export class MoveFocusedViewAction extends Action {
label: nls.localize('panel', "Panel")
});
if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) {
items.push({
id: '_.panel.newcontainer',
label: nls.localize('moveFocusedView.newContainerInPanel', "New Container in Panel"),
});
}
const pinnedPanels = this.panelService.getPinnedPanels();
items.push(...pinnedPanels
.filter(panel => {

View File

@@ -25,15 +25,12 @@
position: absolute;
background-color: rgba(0, 0, 0 ,0.5);
width: 100%;
height: 100px;
bottom: 20%;
left: 0;
z-index: 100000;
pointer-events: none;
color: #eee;
line-height: 100px;
line-height: 1.75em;
text-align: center;
font-size: 56px;
transition: opacity 0.3s ease-out;
white-space: nowrap;
overflow: hidden;

View File

@@ -151,7 +151,7 @@ CommandsRegistry.registerCommand({
}
});
CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async function (accessor: ServicesAccessor, prefix: string | null = null) {
CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async accessor => {
const quickInputService = accessor.get(IQuickInputService);
quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND });

View File

@@ -47,11 +47,7 @@ export class DraggedEditorIdentifier {
export class DraggedEditorGroupIdentifier {
constructor(private _identifier: GroupIdentifier) { }
get identifier(): GroupIdentifier {
return this._identifier;
}
constructor(public readonly identifier: GroupIdentifier) { }
}
export interface IDraggedEditor extends IDraggedResource {
@@ -675,6 +671,11 @@ export class CompositeDragAndDropObserver extends Disposable {
disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => {
const { id, type } = draggedItemProvider();
this.writeDragData(id, type);
if (e.dataTransfer) {
e.dataTransfer.setDragImage(element, 0, 0);
}
this._onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! });
}));
disposableStore.add(new DragAndDropObserver(element, {

View File

@@ -505,6 +505,7 @@ class ResourceLabelWidget extends IconLabel {
italic: this.options?.italic,
strikethrough: this.options?.strikethrough,
matches: this.options?.matches,
descriptionMatches: this.options?.descriptionMatches,
extraClasses: [],
separator: this.options?.separator,
domId: this.options?.domId

View File

@@ -44,6 +44,7 @@ import { LineNumbersType } from 'vs/editor/common/config/editorOptions';
import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart';
import { URI } from 'vs/base/common/uri';
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
export enum Settings {
ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible',
@@ -178,6 +179,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private notificationService!: INotificationService;
private themeService!: IThemeService;
private activityBarService!: IActivityBarService;
private statusBarService!: IStatusbarService;
protected readonly state = {
fullscreen: false,
@@ -262,7 +264,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.titleService = accessor.get(ITitleService);
this.notificationService = accessor.get(INotificationService);
this.activityBarService = accessor.get(IActivityBarService);
accessor.get(IStatusbarService); // not used, but called to ensure instantiated
this.statusBarService = accessor.get(IStatusbarService);
// Listeners
this.registerLayoutListeners();
@@ -397,6 +399,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
this.setMenubarVisibility(newMenubarVisibility, !!skipLayout);
// Centered Layout
this.centerEditorLayout(this.state.editor.centered, skipLayout);
}
private setSideBarPosition(position: Position): void {
@@ -850,8 +854,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
case Parts.ACTIVITYBAR_PART:
this.activityBarService.focusActivityBar();
break;
case Parts.STATUSBAR_PART:
this.statusBarService.focus();
default:
// Status Bar, Activity Bar and Title Bar simply pass focus to container
// Title Bar simply pass focus to container
const container = this.getContainer(part);
if (container) {
container.focus();
@@ -1205,9 +1211,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
let smartActive = active;
const activeEditor = this.editorService.activeEditor;
if (this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')
&& (this.editorGroupService.groups.length > 1 || (activeEditor && activeEditor instanceof SideBySideEditorInput))) {
smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group.
const isSideBySideLayout = activeEditor
&& activeEditor instanceof SideBySideEditorInput
// DiffEditorInput inherits from SideBySideEditorInput but can still be functionally an inline editor.
&& (!(activeEditor instanceof DiffEditorInput) || this.configurationService.getValue('diffEditor.renderSideBySide'));
const isCenteredLayoutAutoResizing = this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize');
if (
isCenteredLayoutAutoResizing
&& (this.editorGroupService.groups.length > 1 || isSideBySideLayout)
) {
smartActive = false;
}
// Enter Centered Editor Layout

View File

@@ -5,23 +5,23 @@
/* Font Families (with CJK support) */
.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
.mac { font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; }
.mac:lang(zh-Hans) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
.mac:lang(zh-Hant) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
.mac:lang(ja) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
.mac:lang(ko) { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
.windows { font-family: system-ui, "Segoe WPC", "Segoe UI", sans-serif; }
.windows:lang(zh-Hans) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
.windows:lang(zh-Hant) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
.windows:lang(ja) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
.windows:lang(ko) { font-family: system-ui, "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
.linux { font-family: "Ubuntu", "Droid Sans", sans-serif; }
.linux:lang(zh-Hans) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
.linux:lang(zh-Hant) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Courier, monospace; }
.windows { --monaco-monospace-font: Consolas, "Courier New", monospace; }

View File

@@ -16,29 +16,28 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { ViewPaneContainer } from './parts/views/viewPaneContainer';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { IAction, IActionViewItem } from 'vs/base/common/actions';
import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { MenuId } from 'vs/platform/actions/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
export class PaneComposite extends Composite implements IPaneComposite {
private menuActions: ViewContainerMenuActions;
constructor(
id: string,
protected readonly viewPaneContainer: ViewPaneContainer,
@ITelemetryService
telemetryService: ITelemetryService,
@IStorageService
protected storageService: IStorageService,
@IInstantiationService
protected instantiationService: IInstantiationService,
@IThemeService
themeService: IThemeService,
@IContextMenuService
protected contextMenuService: IContextMenuService,
@IExtensionService
protected extensionService: IExtensionService,
@IWorkspaceContextService
protected contextService: IWorkspaceContextService
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService protected storageService: IStorageService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IExtensionService protected extensionService: IExtensionService,
@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()));
}
@@ -68,7 +67,15 @@ export class PaneComposite extends Composite implements IPaneComposite {
}
getContextMenuActions(): ReadonlyArray<IAction> {
return this.viewPaneContainer.getContextMenuActions();
const result = [];
result.push(...this.menuActions.getContextMenuActions());
if (result.length) {
result.push(new Separator());
}
result.push(...this.viewPaneContainer.getContextMenuActions());
return result;
}
getActions(): ReadonlyArray<IAction> {

View File

@@ -5,21 +5,19 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IPanel } from 'vs/workbench/common/panel';
import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { assertIsDefined } from 'vs/base/common/types';
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
export abstract class Panel extends Composite implements IPanel { }
export abstract class PaneCompositePanel extends PaneComposite implements IPanel { }
export abstract class Panel extends PaneComposite implements IPanel { }
/**
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
*/
export class PanelDescriptor extends CompositeDescriptor<Panel> {
public static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, _commandId?: string): PanelDescriptor {
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, _commandId?: string): PanelDescriptor {
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, _commandId);
}

View File

@@ -28,6 +28,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Codicon } from 'vs/base/common/codicons';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { isMacintosh } from 'vs/base/common/platform';
export class ViewContainerActivityAction extends ActivityAction {
@@ -287,6 +289,42 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction {
export class HomeAction extends Action {
constructor(
private readonly href: string,
name: string,
icon: Codicon
) {
super('workbench.action.home', name, icon.classNames);
}
async run(event: MouseEvent): Promise<void> {
let openInNewWindow = false;
if (isMacintosh) {
openInNewWindow = event.metaKey;
} else {
openInNewWindow = event.ctrlKey;
}
if (openInNewWindow) {
DOM.windowOpenNoOpener(this.href);
} else {
window.location.href = this.href;
}
}
}
export class HomeActionViewItem extends ActionViewItem {
constructor(action: IAction) {
super(undefined, action, { icon: true, label: false, useEventAsContext: true });
}
}
/**
* @deprecated TODO@ben remove me eventually
*/
export class DeprecatedHomeAction extends Action {
constructor(
private readonly command: string,
name: string,

View File

@@ -6,16 +6,16 @@
import 'vs/css!./media/activitybarpart';
import * as nls from 'vs/nls';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { GLOBAL_ACTIVITY_ID, IActivity } from 'vs/workbench/common/activity';
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity';
import { Part } from 'vs/workbench/browser/part';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction, HomeActionViewItem, DeprecatedHomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions';
import { IThemeService, IColorTheme } 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_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
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';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
import { Dimension, addClass, removeNode, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
@@ -74,6 +74,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private static readonly ACTION_HEIGHT = 48;
static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2';
private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets';
private static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator';
//#region IView
@@ -98,7 +99,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private globalActivityActionBar: ActionBar | undefined;
private readonly globalActivity: ICompositeActivity[] = [];
private readonly cachedViewContainers: ICachedViewContainer[] = [];
private accountsActivityAction: ActivityAction | undefined;
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction }>();
private readonly viewContainerDisposables = new Map<string, IDisposable>();
@@ -121,9 +123,9 @@ export class ActivitybarPart extends Part implements IActivityBarService {
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEW_CONTAINERS, version: 1 });
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, version: 1 });
this.migrateFromOldCachedViewContainersValue();
this.cachedViewContainers = this.getCachedViewContainers();
for (const cachedViewContainer of this.cachedViewContainers) {
if (environmentService.configuration.remoteAuthority // In remote window, hide activity bar entries until registered.
|| this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer)
@@ -144,6 +146,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
getContextMenuActions: () => {
const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
const actions = [];
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; }));
}
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")));
@@ -157,7 +166,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
hidePart: () => this.layoutService.setSideBarHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
(id: string, focus?: boolean) => this.viewsService.openViewContainer(id, focus),
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore)
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore),
() => this.compositeBar.getCompositeBarItems(),
),
compositeSize: 52,
colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme),
@@ -226,6 +236,12 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}
}
private onDidChangeHomeBarVisibility(): void {
if (this.homeBarContainer) {
this.homeBarContainer.style.display = this.homeBarVisibilityPreference ? '' : 'none';
}
}
private onDidRegisterExtensions(): void {
this.removeNotExistingComposites();
this.saveCachedViewContainers();
@@ -256,6 +272,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return this.showGlobalActivity(badge, clazz, priority);
}
if (viewContainerOrActionId === ACCOUNTS_ACTIIVTY_ID) {
if (this.accountsActivityAction) {
this.accountsActivityAction.setBadge(badge, clazz);
return toDisposable(() => this.accountsActivityAction?.setBadge(undefined));
}
}
return Disposable.None;
}
@@ -354,7 +378,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
console.warn(`Unknown home indicator icon ${homeIndicator.icon}`);
codicon = Codicon.code;
}
this.createHomeBar(homeIndicator.command, homeIndicator.title, codicon);
this.createHomeBar(homeIndicator.href, homeIndicator.command, homeIndicator.title, codicon);
this.onDidChangeHomeBarVisibility();
}
// Install menubar if compact
@@ -375,7 +400,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return this.content;
}
private createHomeBar(command: string, title: string, icon: Codicon): void {
private createHomeBar(href: string, command: string | undefined, title: string, icon: Codicon): void {
this.homeBarContainer = document.createElement('div');
this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home"));
this.homeBarContainer.setAttribute('role', 'toolbar');
@@ -383,14 +408,21 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.homeBar = this._register(new ActionBar(this.homeBarContainer, {
orientation: ActionsOrientation.VERTICAL,
animated: false
animated: false,
ariaLabel: nls.localize('home', "Home"),
actionViewItemProvider: command ? undefined : action => new HomeActionViewItem(action),
allowContextMenu: true
}));
const homeBarIconBadge = document.createElement('div');
addClass(homeBarIconBadge, 'home-bar-icon-badge');
this.homeBarContainer.appendChild(homeBarIconBadge);
this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, command, title, icon)), { icon: true, label: false });
if (command) {
this.homeBar.push(this._register(this.instantiationService.createInstance(DeprecatedHomeAction, command, title, icon)), { icon: true, label: false });
} else {
this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, href, title, icon)));
}
const content = assertIsDefined(this.content);
content.prepend(this.homeBarContainer);
@@ -422,7 +454,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND),
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
dragAndDropBackground: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND),
dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER),
activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined,
};
}
@@ -452,13 +484,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
});
if (getUserDataSyncStore(this.productService, this.configurationService)) {
const profileAction = new ActivityAction({
this.accountsActivityAction = new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
cssClass: Codicon.account.classNames
});
this.globalActivityActionBar.push(profileAction);
this.globalActivityActionBar.push(this.accountsActivityAction);
}
this.globalActivityActionBar.push(this.globalActivityAction);
@@ -528,7 +560,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}
this.viewContainerDisposables.delete(viewContainer.id);
this.hideComposite(viewContainer.id);
this.removeComposite(viewContainer.id);
}
private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
@@ -605,6 +637,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}
}
private removeComposite(compositeId: string): void {
this.compositeBar.removeComposite(compositeId);
const compositeActions = this.compositeActions.get(compositeId);
if (compositeActions) {
compositeActions.activityAction.dispose();
compositeActions.pinnedAction.dispose();
this.compositeActions.delete(compositeId);
}
}
getPinnedViewContainerIds(): string[] {
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);
return this.getViewContainers()
@@ -654,22 +697,19 @@ export class ActivitybarPart extends Part implements IActivityBarService {
if (e.key === ActivitybarPart.PINNED_VIEW_CONTAINERS && e.scope === StorageScope.GLOBAL
&& this.pinnedViewContainersValue !== this.getStoredPinnedViewContainersValue() /* This checks if current window changed the value or not */) {
this._pinnedViewContainersValue = undefined;
this._cachedViewContainers = undefined;
const newCompositeItems: ICompositeBarItem[] = [];
const compositeItems = this.compositeBar.getCompositeBarItems();
const cachedViewContainers = this.getCachedViewContainers();
for (const cachedViewContainer of cachedViewContainers) {
// Add and update existing items
const existingItem = compositeItems.filter(({ id }) => id === cachedViewContainer.id)[0];
if (existingItem) {
newCompositeItems.push({
id: existingItem.id,
name: existingItem.name,
order: existingItem.order,
pinned: cachedViewContainer.pinned,
visible: existingItem.visible
});
}
for (const cachedViewContainer of this.cachedViewContainers) {
newCompositeItems.push({
id: cachedViewContainer.id,
name: cachedViewContainer.name,
order: cachedViewContainer.order,
pinned: cachedViewContainer.pinned,
visible: !!compositeItems.find(({ id }) => id === cachedViewContainer.id)
});
}
for (let index = 0; index < compositeItems.length; index++) {
@@ -681,6 +721,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.compositeBar.setCompositeBarItems(newCompositeItems);
}
if (e.key === ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) {
this.onDidChangeHomeBarVisibility();
}
}
private saveCachedViewContainers(): void {
@@ -713,19 +757,21 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.storeCachedViewContainersState(state);
}
private getCachedViewContainers(): ICachedViewContainer[] {
const cachedViewContainers: ICachedViewContainer[] = this.getPinnedViewContainers();
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
const cachedViewContainer = cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
if (cachedViewContainer) {
cachedViewContainer.name = placeholderViewContainer.name;
cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS :
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
cachedViewContainer.views = placeholderViewContainer.views;
private _cachedViewContainers: ICachedViewContainer[] | undefined = undefined;
private get cachedViewContainers(): ICachedViewContainer[] {
if (this._cachedViewContainers === undefined) {
this._cachedViewContainers = this.getPinnedViewContainers();
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
if (cachedViewContainer) {
cachedViewContainer.name = placeholderViewContainer.name;
cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS :
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
cachedViewContainer.views = placeholderViewContainer.views;
}
}
}
return cachedViewContainers;
return this._cachedViewContainers;
}
private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void {
@@ -808,6 +854,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.GLOBAL);
}
private get homeBarVisibilityPreference(): boolean {
return this.storageService.getBoolean(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, StorageScope.GLOBAL, true);
}
private set homeBarVisibilityPreference(value: boolean) {
this.storageService.store(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL);
}
private migrateFromOldCachedViewContainersValue(): void {
const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
if (value !== undefined) {

View File

@@ -20,9 +20,8 @@
width: 48px;
height: 2px;
display: block;
background-color: var(--insert-border-color);
opacity: 0;
transition-property: opacity;
background-color: transparent;
transition-property: background-color;
transition-duration: 0ms;
transition-delay: 100ms;
}
@@ -53,7 +52,7 @@
.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.top::before,
.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.bottom::after,
.monaco-workbench .activitybar > .content.dragged-over > .composite-bar > .monaco-action-bar .action-item:last-of-type::after {
opacity: 1;
background-color: var(--insert-border-color);
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
@@ -101,7 +100,7 @@
}
/* Hides active elements in high contrast mode */
.hc-black .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
display: none;
}
@@ -120,8 +119,8 @@
}
/* Hides outline on HC as focus is handled by border */
.hc-black .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
.hc-black .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
.monaco-workbench.hc-black .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
.monaco-workbench.hc-black .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
outline: none;
}

View File

@@ -39,6 +39,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
private targetContainerLocation: ViewContainerLocation,
private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | null>,
private moveComposite: (from: string, to: string, before?: Before2D) => void,
private getItems: () => ICompositeBarItem[],
) { }
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: Before2D): void {
@@ -61,11 +62,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
return;
}
this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation);
if (targetCompositeId) {
this.moveComposite(currentContainer.id, targetCompositeId, before);
}
this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before));
}
}
@@ -98,6 +95,16 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
return this.canDrop(data, targetCompositeId);
}
private getTargetIndex(targetId: string | undefined, before2d: Before2D | undefined): number | undefined {
if (!targetId) {
return undefined;
}
const items = this.getItems();
const before = this.targetContainerLocation === ViewContainerLocation.Panel ? before2d?.horizontallyBefore : before2d?.verticallyBefore;
return items.findIndex(o => o.id === targetId) + (before ? 0 : 1);
}
private canDrop(data: CompositeDragAndDropData, targetCompositeId: string | undefined): boolean {
const dragData = data.getData();
@@ -226,8 +233,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
// Register a drop target on the whole bar to prevent forbidden feedback
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, {
onDragOver: (e: IDraggedCompositeData) => {
// don't add feedback if this is over the composite bar actions
if (e.eventData.target && isAncestor(e.eventData.target as HTMLElement, actionBarDiv)) {
// don't add feedback if this is over the composite bar actions or there are no actions
if (!(this.compositeSwitcherBar?.length()) || (e.eventData.target && isAncestor(e.eventData.target as HTMLElement, actionBarDiv))) {
toggleClass(parent, 'dragged-over', false);
return;
}
@@ -245,7 +252,9 @@ export class CompositeBar extends Widget implements ICompositeBar {
},
onDrop: (e: IDraggedCompositeData) => {
const pinnedItems = this.getPinnedComposites();
this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, { horizontallyBefore: false, verticallyBefore: false });
if (pinnedItems.length) {
this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, { horizontallyBefore: false, verticallyBefore: false });
}
toggleClass(parent, 'dragged-over', false);
}
}));
@@ -664,9 +673,18 @@ class CompositeBarModel {
}
this._items = result;
}
this.updateItemsOrder();
return hasChanges;
}
private updateItemsOrder(): void {
if (this._items) {
this.items.forEach((item, index) => { if (item.order !== undefined) { item.order = index; } });
}
}
get visibleItems(): ICompositeBarModelItem[] {
return this.items.filter(item => item.visible);
}
@@ -702,6 +720,8 @@ class CompositeBarModel {
item.visible = true;
changed = true;
}
this.updateItemsOrder();
return changed;
} else {
const item = this.createCompositeBarItem(id, name, order, true, true);
@@ -714,6 +734,8 @@ class CompositeBarModel {
}
this.items.splice(index, 0, item);
}
this.updateItemsOrder();
return true;
}
}
@@ -722,6 +744,7 @@ class CompositeBarModel {
for (let index = 0; index < this.items.length; index++) {
if (this.items[index].id === id) {
this.items.splice(index, 1);
this.updateItemsOrder();
return true;
}
}
@@ -757,6 +780,8 @@ class CompositeBarModel {
// Make sure a moved composite gets pinned
sourceItem.pinned = true;
this.updateItemsOrder();
return true;
}

View File

@@ -119,7 +119,7 @@ export interface ICompositeBarColors {
inactiveForegroundColor?: Color;
badgeBackground?: Color;
badgeForeground?: Color;
dragAndDropBackground?: Color;
dragAndDropBorder?: Color;
}
export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions {
@@ -169,16 +169,14 @@ export class ActivityActionViewItem extends BaseActionViewItem {
this.label.style.color = foreground ? foreground.toString() : '';
this.label.style.backgroundColor = '';
}
const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor;
this.container.style.setProperty('--insert-border-color', dragColor ? dragColor.toString() : '');
} else {
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
this.label.style.color = foreground ? foreground.toString() : '';
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
this.container.style.setProperty('--insert-border-color', colors.activeForegroundColor ? colors.activeForegroundColor.toString() : '');
}
this.container.style.setProperty('--insert-border-color', colors.dragAndDropBorder ? colors.dragAndDropBorder.toString() : '');
}
// Badge
@@ -203,7 +201,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
// Make the container tab-able for keyboard navigation
this.container.tabIndex = 0;
this.container.setAttribute('role', this.options.icon ? 'button' : 'tab');
this.container.setAttribute('role', 'tab');
// Try hard to prevent keyboard only focus feedback when using mouse
this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_DOWN, () => {
@@ -649,9 +647,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
if (this.getAction().checked) {
dom.addClass(this.container, 'checked');
this.container.setAttribute('aria-label', nls.localize('compositeActive', "{0} active", this.container.title));
this.container.setAttribute('aria-expanded', 'true');
} else {
dom.removeClass(this.container, 'checked');
this.container.setAttribute('aria-label', this.container.title);
this.container.setAttribute('aria-expanded', 'false');
}
this.updateStyles();
}

View File

@@ -316,11 +316,11 @@ export abstract class CompositePart<T extends Composite> extends Part {
toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
}
private collectCompositeActions(composite: Composite): () => void {
private collectCompositeActions(composite?: Composite): () => void {
// From Composite
const primaryActions: IAction[] = composite.getActions().slice(0);
const secondaryActions: IAction[] = composite.getSecondaryActions().slice(0);
const primaryActions: IAction[] = composite?.getActions().slice(0) || [];
const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || [];
// From Part
primaryActions.push(...this.getActions());
@@ -368,7 +368,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
// Empty Actions
if (this.toolBar) {
this.toolBar.setActions([])();
this.collectCompositeActions()();
}
this.onDidCompositeClose.fire(composite);
@@ -395,6 +395,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment()
}));
this.collectCompositeActions()();
return titleArea;
}

View File

@@ -10,7 +10,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { LRUCache } from 'vs/base/common/map';
import { LRUCache, Touch } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { isEmptyObject } from 'vs/base/common/types';
@@ -106,10 +106,6 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
this.createEditor(parent);
}
onHide() { }
onWillHide() { }
/**
* Called to create the editor in the parent HTMLElement.
*/
@@ -133,6 +129,16 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
this._group = group;
}
/**
* Called before the editor is being removed from the DOM.
*/
onWillHide() { }
/**
* Called after the editor has been removed from the DOM.
*/
onDidHide() { }
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
const mementoKey = `${this.getId()}${key}`;
@@ -256,7 +262,9 @@ export class EditorMemento<T> implements IEditorMemento<T> {
moveEditorState(source: URI, target: URI): void {
const cache = this.doLoad();
const cacheKeys = cache.keys();
// We need a copy of the keys to not iterate over
// newly inserted elements.
const cacheKeys = [...cache.keys()];
for (const cacheKey of cacheKeys) {
const resource = URI.parse(cacheKey);
@@ -273,7 +281,8 @@ export class EditorMemento<T> implements IEditorMemento<T> {
targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved
}
const value = cache.get(cacheKey);
// Don't modify LRU state.
const value = cache.get(cacheKey, Touch.None);
if (value) {
cache.delete(cacheKey);
cache.set(targetResource.toString(), value);
@@ -318,18 +327,19 @@ export class EditorMemento<T> implements IEditorMemento<T> {
private cleanUp(): void {
const cache = this.doLoad();
// Remove groups from states that no longer exist
cache.forEach((mapGroupToMemento, resource) => {
// Remove groups from states that no longer exist. Since we modify the
// cache and its is a LRU cache make a copy to ensure iteration succeeds
const entries = [...cache.entries()];
for (const [resource, mapGroupToMemento] of entries) {
Object.keys(mapGroupToMemento).forEach(group => {
const groupId: GroupIdentifier = Number(group);
if (!this.editorGroupService.getGroup(groupId)) {
delete mapGroupToMemento[groupId];
if (isEmptyObject(mapGroupToMemento)) {
cache.delete(resource);
}
}
});
});
}
}
}

View File

@@ -160,6 +160,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
super.dispose();
}
}
export interface IResourceDescriptor {
readonly resource: URI;
readonly name: string;

View File

@@ -11,7 +11,7 @@ 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 { isEqual } from 'vs/base/common/resources';
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';
@@ -71,7 +71,7 @@ class Item extends BreadcrumbsItem {
return false;
}
if (this.element instanceof FileElement && other.element instanceof FileElement) {
return (isEqual(this.element.uri, other.element.uri, false) &&
return (extUri.isEqual(this.element.uri, other.element.uri) &&
this.options.showFileIcons === other.options.showFileIcons &&
this.options.showSymbolIcons === other.options.showSymbolIcons);
}

View File

@@ -5,7 +5,7 @@
import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPane, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor';
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Dimension } from 'vs/base/browser/dom';
import { Event } from 'vs/base/common/event';
@@ -14,6 +14,7 @@ import { ISerializableView } from 'vs/base/browser/ui/grid/grid';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { localize } from 'vs/nls';
export const EDITOR_TITLE_HEIGHT = 35;
@@ -41,6 +42,26 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
splitSizing: 'distribute'
};
export function computeEditorAriaLabel(input: IEditorInput, index: number | undefined, group: IEditorGroup | undefined, groupCount: number): string {
let ariaLabel = input.getAriaLabel();
if (group && !group.isPinned(input)) {
ariaLabel = localize('preview', "{0}, preview", ariaLabel);
}
if (group && group.isSticky(index ?? input)) {
ariaLabel = localize('pinned', "{0}, pinned", ariaLabel);
}
// Apply group information to help identify in
// which group we are (only if more than one group
// is actually opened)
if (group && groupCount > 1) {
ariaLabel = `${ariaLabel}, ${group.ariaLabel}`;
}
return ariaLabel;
}
export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean {
return event.affectsConfiguration('workbench.editor') || event.affectsConfiguration('workbench.iconTheme');
}
@@ -68,6 +89,11 @@ export interface IEditorOpeningEvent extends IEditorIdentifier {
*/
options?: IEditorOptions;
/**
* Context indicates how the editor open event is initialized.
*/
context?: OpenEditorContext;
/**
* Allows to prevent the opening of an editor by providing a callback
* that will be executed instead. By returning another editor promise
@@ -144,11 +170,6 @@ export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEd
*/
export interface EditorServiceImpl extends IEditorService {
/**
* Emitted when an editor is closed.
*/
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
/**
* Emitted when an editor failed to open.
*/

View File

@@ -1339,7 +1339,8 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action {
id: string,
label: string,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IKeybindingService private readonly keybindingService: IKeybindingService
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
) {
super(id, label);
}
@@ -1347,7 +1348,14 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action {
async run(): Promise<void> {
const keybindings = this.keybindingService.lookupKeybindings(this.id);
this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings } });
// Enforce to activate the first item in quick access if
// the currently active editor group has n editor opened
let itemActivation: ItemActivation | undefined = undefined;
if (this.editorGroupService.activeGroup.count === 0) {
itemActivation = ItemActivation.FIRST;
}
this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings }, itemActivation });
}
}

View File

@@ -35,6 +35,8 @@ export class EditorControl extends Disposable {
readonly onDidSizeConstraintsChange = this._onDidSizeConstraintsChange.event;
private _activeEditorPane: BaseEditor | null = null;
get activeEditorPane(): IVisibleEditorPane | null { return this._activeEditorPane as IVisibleEditorPane | null; }
private readonly editorPanes: BaseEditor[] = [];
private readonly activeEditorPaneDisposables = this._register(new DisposableStore());
@@ -53,10 +55,6 @@ export class EditorControl extends Disposable {
this.editorOperation = this._register(new LongRunningOperation(editorProgressService));
}
get activeEditorPane(): IVisibleEditorPane | null {
return this._activeEditorPane as IVisibleEditorPane | null;
}
async openEditor(editor: EditorInput, options?: EditorOptions): Promise<IOpenEditorResult> {
// Editor pane
@@ -208,7 +206,7 @@ export class EditorControl extends Disposable {
this._activeEditorPane.onWillHide();
this.parent.removeChild(editorPaneContainer);
hide(editorPaneContainer);
this._activeEditorPane.onHide();
this._activeEditorPane.onDidHide();
}
// Indicate to editor pane

View File

@@ -22,6 +22,9 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
interface IDropOperation {
splitDirection?: GroupDirection;
@@ -31,8 +34,10 @@ class DropOverlay extends Themable {
private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay';
private container!: HTMLElement;
private overlay!: HTMLElement;
private static readonly MAX_FILE_UPLOAD_SIZE = 100 * 1024 * 1024; // 100mb
private container: HTMLElement | undefined;
private overlay: HTMLElement | undefined;
private currentDropOperation: IDropOperation | undefined;
private _disposed: boolean | undefined;
@@ -48,7 +53,8 @@ class DropOverlay extends Themable {
@IThemeService themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IEditorService private readonly editorService: IEditorService
@IEditorService private readonly editorService: IEditorService,
@INotificationService private readonly notificationService: INotificationService
) {
super(themeService);
@@ -65,45 +71,46 @@ class DropOverlay extends Themable {
const overlayOffsetHeight = this.getOverlayOffsetHeight();
// Container
this.container = document.createElement('div');
this.container.id = DropOverlay.OVERLAY_ID;
this.container.style.top = `${overlayOffsetHeight}px`;
const container = this.container = document.createElement('div');
container.id = DropOverlay.OVERLAY_ID;
container.style.top = `${overlayOffsetHeight}px`;
// Parent
this.groupView.element.appendChild(this.container);
this.groupView.element.appendChild(container);
addClass(this.groupView.element, 'dragged-over');
this._register(toDisposable(() => {
this.groupView.element.removeChild(this.container);
this.groupView.element.removeChild(container);
removeClass(this.groupView.element, 'dragged-over');
}));
// Overlay
this.overlay = document.createElement('div');
addClass(this.overlay, 'editor-group-overlay-indicator');
this.container.appendChild(this.overlay);
container.appendChild(this.overlay);
// Overlay Event Handling
this.registerListeners();
this.registerListeners(container);
// Styles
this.updateStyles();
}
protected updateStyles(): void {
const overlay = assertIsDefined(this.overlay);
// Overlay drop background
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
// Overlay contrast border (if any)
const activeContrastBorderColor = this.getColor(activeContrastBorder);
this.overlay.style.outlineColor = activeContrastBorderColor || '';
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
overlay.style.outlineColor = activeContrastBorderColor || '';
overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
}
private registerListeners(): void {
this._register(new DragAndDropObserver(this.container, {
private registerListeners(container: HTMLElement): void {
this._register(new DragAndDropObserver(container, {
onDragEnter: e => undefined,
onDragOver: e => {
const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype);
@@ -161,7 +168,7 @@ class DropOverlay extends Themable {
}
}));
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
this._register(addDisposableListener(container, EventType.MOUSE_OVER, () => {
// Under some circumstances we have seen reports where the drop overlay is not being
// cleaned up and as such the editor area remains under the overlay so that you cannot
// type into the editor anymore. This seems related to using VMs and DND via host and
@@ -295,6 +302,14 @@ class DropOverlay extends Themable {
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
if (file) {
// Skip for very large files because this operation is unbuffered
if (file.size > DropOverlay.MAX_FILE_UPLOAD_SIZE) {
this.notificationService.warn(localize('fileTooLarge', "File is too large to open as untitled editor. Please upload it first into the file explorer and then try again."));
continue;
}
// Read file fully and open as untitled editor
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = async event => {
@@ -456,30 +471,32 @@ class DropOverlay extends Themable {
}
// Make sure the overlay is visible now
this.overlay.style.opacity = '1';
const overlay = assertIsDefined(this.overlay);
overlay.style.opacity = '1';
// Enable transition after a timeout to prevent initial animation
setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0);
setTimeout(() => addClass(overlay, 'overlay-move-transition'), 0);
// Remember as current split direction
this.currentDropOperation = { splitDirection };
}
private doPositionOverlay(options: { top: string, left: string, width: string, height: string }): void {
const [container, overlay] = assertAllDefined(this.container, this.overlay);
// Container
const offsetHeight = this.getOverlayOffsetHeight();
if (offsetHeight) {
this.container.style.height = `calc(100% - ${offsetHeight}px)`;
container.style.height = `calc(100% - ${offsetHeight}px)`;
} else {
this.container.style.height = '100%';
container.style.height = '100%';
}
// Overlay
this.overlay.style.top = options.top;
this.overlay.style.left = options.left;
this.overlay.style.width = options.width;
this.overlay.style.height = options.height;
overlay.style.top = options.top;
overlay.style.left = options.left;
overlay.style.width = options.width;
overlay.style.height = options.height;
}
private getOverlayOffsetHeight(): number {
@@ -491,11 +508,12 @@ class DropOverlay extends Themable {
}
private hideOverlay(): void {
const overlay = assertIsDefined(this.overlay);
// Reset overlay
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
this.overlay.style.opacity = '0';
removeClass(this.overlay, 'overlay-move-transition');
overlay.style.opacity = '0';
removeClass(overlay, 'overlay-move-transition');
// Reset current operation
this.currentDropOperation = undefined;

View File

@@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/editorgroupview';
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorStickyContext, EditorPinnedContext } from 'vs/workbench/common/editor';
import { Event, Emitter, Relay } from 'vs/base/common/event';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom';
@@ -17,7 +16,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
@@ -220,8 +219,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private handleGroupContextKeys(contextKeyService: IContextKeyService): void {
const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyService);
const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyService);
const groupActiveEditorPinnedContext = EditorPinnedContext.bindTo(contextKeyService);
const groupActiveEditorStickyContext = EditorStickyContext.bindTo(contextKeyService);
let activeEditorListener = new MutableDisposable();
const activeEditorListener = new MutableDisposable();
const observeActiveEditor = () => {
activeEditorListener.clear();
@@ -237,11 +238,22 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Update group contexts based on group changes
this._register(this.onDidGroupChange(e => {
// Track the active editor and update context key that reflects
// the dirty state of this editor
if (e.kind === GroupChangeKind.EDITOR_ACTIVE) {
observeActiveEditor();
switch (e.kind) {
case GroupChangeKind.EDITOR_ACTIVE:
// Track the active editor and update context key that reflects
// the dirty state of this editor
observeActiveEditor();
break;
case GroupChangeKind.EDITOR_PIN:
if (e.editor && e.editor === this._group.activeEditor) {
groupActiveEditorPinnedContext.set(this._group.isPinned(this._group.activeEditor));
}
break;
case GroupChangeKind.EDITOR_STICKY:
if (e.editor && e.editor === this._group.activeEditor) {
groupActiveEditorStickyContext.set(this._group.isSticky(this._group.activeEditor));
}
break;
}
// Group editors count context
@@ -464,6 +476,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Model Events
this._register(this._group.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor)));
this._register(this._group.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor)));
this._register(this._group.onDidOpenEditor(editor => this.onDidOpenEditor(editor)));
this._register(this._group.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor)));
this._register(this._group.onDidDisposeEditor(editor => this.onDidDisposeEditor(editor)));
@@ -478,11 +491,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
private onDidChangeEditorPinned(editor: EditorInput): void {
// Event
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_PIN, editor });
}
private onDidChangeEditorSticky(editor: EditorInput): void {
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_STICKY, editor });
}
private onDidOpenEditor(editor: EditorInput): void {
/* __GDPR__
@@ -596,11 +611,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Title control Switch between showing tabs <=> not showing tabs
if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) {
// Recreate and layout control
// Recreate title control
this.createTitleAreaControl();
if (this.dimension) {
this.layoutTitleAreaControl(this.dimension.width);
}
// Re-layout
this.relayout();
// Ensure to show active editor if any
if (this._group.activeEditor) {
@@ -848,7 +863,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region openEditor()
async openEditor(editor: EditorInput, options?: EditorOptions): Promise<IEditorPane | null> {
async openEditor(editor: EditorInput, options?: EditorOptions, context?: OpenEditorContext): Promise<IEditorPane | null> {
// Guard against invalid inputs
if (!editor) {
@@ -856,7 +871,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Editor opening event allows for prevention
const event = new EditorOpeningEvent(this._group.id, editor, options);
const event = new EditorOpeningEvent(this._group.id, editor, options, context);
this._onWillOpenEditor.fire(event);
const prevented = event.isPrevented();
if (prevented) {
@@ -1110,7 +1125,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Move across groups
else {
this.doMoveOrCopyEditorAcrossGroups(editor, target, options);
this.doMoveOrCopyEditorAcrossGroups(editor, target, options, false);
}
}
@@ -1156,7 +1171,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}));
// A move to another group is an open first...
target.openEditor(editor, options);
target.openEditor(editor, options, keepCopy ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
// ...and a close afterwards (unless we copy)
if (!keepCopy) {
@@ -1702,7 +1717,8 @@ class EditorOpeningEvent implements IEditorOpeningEvent {
constructor(
private _group: GroupIdentifier,
private _editor: EditorInput,
private _options: EditorOptions | undefined
private _options: EditorOptions | undefined,
private _context: OpenEditorContext | undefined
) {
}
@@ -1718,6 +1734,10 @@ class EditorOpeningEvent implements IEditorOpeningEvent {
return this._options;
}
get context(): OpenEditorContext | undefined {
return this._context;
}
prevent(callback: () => Promise<IEditorPane | undefined>): void {
this.override = callback;
}

View File

@@ -25,7 +25,7 @@ import { EditorDropTarget, EditorDropTargetDelegate } from 'vs/workbench/browser
import { Color } from 'vs/base/common/color';
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { Parts, IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { MementoObject } from 'vs/workbench/common/memento';
import { assertIsDefined } from 'vs/base/common/types';
@@ -839,12 +839,62 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
}));
let panelOpenerTimeout: any;
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(overlay, {
onDragOver: e => {
EventHelper.stop(e.eventData, true);
if (e.eventData.dataTransfer) {
e.eventData.dataTransfer.dropEffect = 'none';
}
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
const boundingRect = overlay.getBoundingClientRect();
let openPanel = false;
const proximity = 100;
switch (this.layoutService.getPanelPosition()) {
case Position.BOTTOM:
if (e.eventData.clientY > boundingRect.bottom - proximity) {
openPanel = true;
}
break;
case Position.LEFT:
if (e.eventData.clientX < boundingRect.left + proximity) {
openPanel = true;
}
break;
case Position.RIGHT:
if (e.eventData.clientX > boundingRect.right - proximity) {
openPanel = true;
}
break;
}
if (!panelOpenerTimeout && openPanel) {
panelOpenerTimeout = setTimeout(() => this.layoutService.setPanelHidden(false), 200);
} else if (panelOpenerTimeout && !openPanel) {
clearTimeout(panelOpenerTimeout);
panelOpenerTimeout = undefined;
}
}
},
onDragLeave: () => {
if (panelOpenerTimeout) {
clearTimeout(panelOpenerTimeout);
panelOpenerTimeout = undefined;
}
},
onDragEnd: () => {
if (panelOpenerTimeout) {
clearTimeout(panelOpenerTimeout);
panelOpenerTimeout = undefined;
}
},
onDrop: () => {
if (panelOpenerTimeout) {
clearTimeout(panelOpenerTimeout);
panelOpenerTimeout = undefined;
}
}
}));

View File

@@ -48,7 +48,7 @@ export class EditorsObserver extends Disposable {
}
get editors(): IEditorIdentifier[] {
return this.mostRecentEditorsMap.values();
return [...this.mostRecentEditorsMap.values()];
}
hasEditor(resource: URI): boolean {
@@ -283,7 +283,7 @@ export class EditorsObserver extends Disposable {
// Across all editor groups
else {
await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude);
await this.doEnsureOpenedEditorsLimit(limit, [...this.mostRecentEditorsMap.values()], exclude);
}
}
@@ -346,7 +346,7 @@ export class EditorsObserver extends Disposable {
private serialize(): ISerializedEditorsList {
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
const entries = this.mostRecentEditorsMap.values();
const entries = [...this.mostRecentEditorsMap.values()];
const mapGroupToSerializableEditorsOfGroup = new Map<IEditorGroup, IEditorInput[]>();
return {

View File

@@ -211,7 +211,7 @@
text-overflow: clip;
}
.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container {
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container {
text-overflow: ellipsis;
}
@@ -290,13 +290,12 @@
padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky) */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close {
display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top) {
padding-right: 0; /* remove extra padding when we are running without close button */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top):not(.sticky) {
padding-right: 0; /* remove extra padding when we are running without close button (unless tab is sticky) */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {

View File

@@ -29,7 +29,7 @@
/* Title Actions */
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label,
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label {
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(span) {
display: flex;
height: 35px;
min-width: 28px;
@@ -40,8 +40,8 @@
background-repeat: no-repeat;
}
.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label,
.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) {
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .title-actions .action-label,
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) {
line-height: initial;
}

View File

@@ -9,7 +9,7 @@ import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor
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';
import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass } from 'vs/base/browser/dom';
import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass, Dimension } from 'vs/base/browser/dom';
import { EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
import { IAction } from 'vs/base/common/actions';
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
@@ -232,7 +232,7 @@ export class NoTabsTitleControl extends TitleControl {
private redraw(): void {
const editor = withNullAsUndefined(this.group.activeEditor);
const isEditorPinned = this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false;
const isEditorPinned = editor ? this.group.isPinned(editor) : false;
const isGroupActive = this.accessor.activeGroup === this.group;
this.activeLabel = { editor, pinned: isEditorPinned };
@@ -320,4 +320,10 @@ export class NoTabsTitleControl extends TitleControl {
// Group inactive: only show close action
return { primaryEditorActions: editorActions.primary.filter(action => action.id === CLOSE_EDITOR_COMMAND_ID), secondaryEditorActions: [] };
}
layout(dimension: Dimension): void {
if (this.breadcrumbsControl) {
this.breadcrumbsControl.layout(undefined);
}
}
}

View File

@@ -5,7 +5,7 @@
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
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';
@@ -26,9 +26,11 @@ export class RangeHighlightDecorations extends Disposable {
private readonly editorDisposables = this._register(new DisposableStore());
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
readonly onHighlightRemoved: Event<void> = this._onHighlightRemoved.event;
readonly onHighlightRemoved = this._onHighlightRemoved.event;
constructor(@IEditorService private readonly editorService: IEditorService) {
constructor(
@IEditorService private readonly editorService: IEditorService
) {
super();
}

View File

@@ -31,10 +31,10 @@ import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdenti
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 } from 'vs/workbench/services/editor/common/editorGroupsService';
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, EDITOR_TITLE_HEIGHT, computeEditorAriaLabel } from 'vs/workbench/browser/parts/editor/editor';
import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
@@ -107,7 +107,8 @@ export class TabsTitleControl extends TitleControl {
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService,
@IEditorService private readonly editorService: EditorServiceImpl,
@IPathService private readonly pathService: IPathService
@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);
@@ -199,15 +200,12 @@ export class TabsTitleControl extends TitleControl {
private updateBreadcrumbsControl(): void {
if (this.breadcrumbsControl && this.breadcrumbsControl.update()) {
// relayout when we have a breadcrumbs and when update changed
// its hidden-status
this.group.relayout();
this.group.relayout(); // relayout when we have a breadcrumbs and when update changed its hidden-status
}
}
protected handleBreadcrumbsEnablementChange(): void {
// relayout when breadcrumbs are enable/disabled
this.group.relayout();
this.group.relayout(); // relayout when breadcrumbs are enable/disabled
}
private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void {
@@ -898,12 +896,12 @@ export class TabsTitleControl extends TitleControl {
const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat);
// Build labels and descriptions for each editor
const labels = this.group.editors.map(editor => ({
const labels = this.group.editors.map((editor, index) => ({
editor,
name: editor.getName(),
description: editor.getDescription(verbosity),
title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)),
ariaLabel: editor.isReadonly() ? localize('readonlyEditor', "{0} readonly", editor.getTitle(Verbosity.SHORT)) : editor.getTitle(Verbosity.SHORT)
ariaLabel: computeEditorAriaLabel(editor, index, this.group, this.editorGroupService.count)
}));
// Shorten labels as needed
@@ -1211,6 +1209,10 @@ export class TabsTitleControl extends TitleControl {
return hasModifiedBorderColor;
}
getPreferredHeight(): number {
return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0);
}
layout(dimension: Dimension | undefined): void {
this.dimension = dimension;

View File

@@ -223,19 +223,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
return options;
}
protected getAriaLabel(): string {
let ariaLabel: string;
const inputName = this.input?.getName();
if (this.input?.isReadonly()) {
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0} readonly compare", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly compare");
} else {
ariaLabel = inputName ? nls.localize('editableEditorWithInputAriaLabel', "{0} compare", inputName) : nls.localize('editableEditorAriaLabel', "Compare");
}
return ariaLabel;
}
private isFileBinaryError(error: Error[]): boolean;
private isFileBinaryError(error: Error): boolean;
private isFileBinaryError(error: Error | Error[]): boolean {

View File

@@ -23,6 +23,7 @@ import { isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { computeEditorAriaLabel } from 'vs/workbench/browser/parts/editor/editor';
export interface IEditorConfiguration {
editor: object;
@@ -102,16 +103,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditorPa
}
private computeAriaLabel(): string {
let ariaLabel = this.getAriaLabel();
// Apply group information to help identify in
// which group we are (only if more than one group
// is actually opened)
if (ariaLabel && this.group && this.editorGroupService.count > 1) {
ariaLabel = localize('editorLabelWithGroup', "{0}, {1}", ariaLabel, this.group.ariaLabel);
}
return ariaLabel;
return this._input ? computeEditorAriaLabel(this._input, undefined, this.group, this.editorGroupService.count) : localize('editor', "Editor");
}
protected getConfigurationOverrides(): IEditorOptions {
@@ -303,8 +295,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditorPa
return undefined;
}
protected abstract getAriaLabel(): string;
dispose(): void {
this.lastAppliedEditorOptions = undefined;

View File

@@ -25,7 +25,6 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { basenameOrAuthority } from 'vs/base/common/resources';
import { ModelConstants } from 'vs/editor/common/model';
/**
@@ -108,11 +107,6 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
}
}
protected getAriaLabel(): string {
const inputName = this.input instanceof UntitledTextEditorInput ? basenameOrAuthority(this.input.resource) : this.input?.getName() || nls.localize('writeableEditorAriaLabel', "Editor");
return this.input?.isReadonly() ? nls.localize('readonlyEditor', "{0} readonly", inputName) : inputName;
}
/**
* Reveals the last line of this editor if it has a model set.
*/

View File

@@ -31,7 +31,7 @@ import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceData
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext, EditorStickyContext } from 'vs/workbench/common/editor';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -51,7 +51,7 @@ export abstract class TitleControl extends Themable {
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
protected readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
protected breadcrumbsControl?: BreadcrumbsControl;
protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined;
private currentPrimaryEditorActionIds: string[] = [];
private currentSecondaryEditorActionIds: string[] = [];
@@ -118,6 +118,7 @@ export abstract class TitleControl extends Themable {
this.handleBreadcrumbsEnablementChange();
}
}));
if (config.getValue()) {
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
}
@@ -401,15 +402,9 @@ export abstract class TitleControl extends Themable {
abstract updateStyles(): void;
layout(dimension: Dimension): void {
if (this.breadcrumbsControl) {
this.breadcrumbsControl.layout(undefined);
}
}
abstract layout(dimension: Dimension): void;
getPreferredHeight(): number {
return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0);
}
abstract getPreferredHeight(): number;
dispose(): void {
dispose(this.breadcrumbsControl);

View File

@@ -305,7 +305,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
this.hide();
// Close all
for (const notification of this.model.notifications) {
for (const notification of [...this.model.notifications] /* copy array since we modify it from closing */) {
if (!notification.hasProgress) {
notification.close();
}

View File

@@ -63,12 +63,9 @@
}
.monaco-workbench .part.panel .empty-panel-message-area {
position: absolute;
display: none;
top: 0px;
height: 100%;
width: 100%;
z-index: 10;
}
.monaco-workbench .part.panel .empty-panel-message-area.visible {

View File

@@ -282,7 +282,6 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TogglePanelActi
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPanelAction), 'View: Focus into Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Maximized Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClosePanelAction), 'View: Close Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Panel Position', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(PreviousPanelViewAction), 'View: Previous Panel View', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NextPanelViewAction), 'View: Next Panel View', nls.localize('view', "View"));

View File

@@ -20,7 +20,7 @@ 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 { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
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';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
@@ -35,7 +35,7 @@ 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 } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { ViewMenuActions, ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd';
@@ -50,11 +50,17 @@ interface ICachedPanel {
views?: { when?: string }[];
}
interface IPlaceholderViewContainer {
id: string;
name?: string;
}
export class PanelPart extends CompositePart<Panel> implements IPanelService {
static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid';
static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels';
static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.panel.placeholderPanels';
private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
_serviceBrand: undefined;
@@ -94,6 +100,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
private blockOpeningPanel = false;
private contentDimension: Dimension | undefined;
private extensionsRegistered = false;
private panelRegistry: PanelRegistry;
private dndHandler: ICompositeDragAndDrop;
@@ -135,8 +143,9 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
storageKeysSyncRegistryService.registerStorageKey({ key: PanelPart.PINNED_PANELS, version: 1 });
this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
(id: string, focus?: boolean) => (<unknown>this.openPanel(id, focus) as Promise<IPaneComposite | undefined>).then(panel => panel || null),
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore)
(id: string, focus?: boolean) => (this.openPanel(id, focus) as Promise<IPaneComposite | undefined>).then(panel => panel || null),
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore),
() => this.compositeBar.getCompositeBarItems()
);
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), {
@@ -167,7 +176,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND),
badgeBackground: theme.getColor(badgeBackground),
badgeForeground: theme.getColor(badgeForeground),
dragAndDropBackground: theme.getColor(PANEL_DRAG_AND_DROP_BACKGROUND)
dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER)
})
}));
@@ -188,6 +197,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
result.push(...viewMenuActions.getContextMenuActions());
viewMenuActions.dispose();
}
const viewContainerMenuActions = this.instantiationService.createInstance(ViewContainerMenuActions, container.id, MenuId.ViewContainerTitleContext);
result.push(...viewContainerMenuActions.getContextMenuActions());
viewContainerMenuActions.dispose();
}
return result;
}
@@ -196,10 +209,21 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
for (const panel of panels) {
const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0];
const activePanel = this.getActivePanel();
const isActive = activePanel?.getId() === panel.id || (!activePanel && this.getLastActivePanelId() === panel.id);
const isActive =
activePanel?.getId() === panel.id ||
(!activePanel && this.getLastActivePanelId() === panel.id) ||
(this.extensionsRegistered && this.compositeBar.getVisibleComposites().length === 0);
if (isActive || !this.shouldBeHidden(panel.id, cachedPanel)) {
this.compositeBar.addComposite(panel);
// Override order
const newPanel = {
id: panel.id,
name: panel.name,
order: cachedPanel?.order === undefined ? panel.order : cachedPanel.order
};
this.compositeBar.addComposite(newPanel);
// Pin it by default if it is new
if (!cachedPanel) {
@@ -255,9 +279,11 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
const cachedTitle = this.getPlaceholderViewContainers().filter(panel => panel.id === viewContainer.id)[0]?.name;
const activity: IActivity = {
id: viewContainer.id,
name: viewContainerModel.title,
name: this.extensionsRegistered || cachedTitle === undefined ? viewContainerModel.title : cachedTitle,
keybindingId: viewContainer.focusCommand?.id
};
@@ -268,7 +294,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
pinnedAction.setActivity(activity);
}
this.saveCachedPanels();
// only update our cached panel info after extensions are done registering
if (this.extensionsRegistered) {
this.saveCachedPanels();
}
}
private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
@@ -291,6 +320,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
private registerListeners(): void {
// Panel registration
this._register(this.registry.onDidRegister(panel => this.onDidRegisterPanels([panel])));
this._register(this.registry.onDidDeregister(panel => this.onDidDeregisterPanel(panel.id)));
@@ -313,6 +343,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
private onDidRegisterExtensions(): void {
this.extensionsRegistered = true;
this.removeNotExistingComposites();
this.saveCachedPanels();
@@ -388,15 +419,16 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
private createEmptyPanelMessage(): void {
const contentArea = this.getContentArea()!;
this.emptyPanelMessageElement = document.createElement('div');
addClass(this.emptyPanelMessageElement, 'empty-panel-message-area');
const messageElement = document.createElement('div');
addClass(messageElement, 'empty-panel-message');
messageElement.innerText = localize('panel.emptyMessage', "No panels to display. Drag a view into the panel.");
messageElement.innerText = localize('panel.emptyMessage', "Drag a view into the panel to display.");
this.emptyPanelMessageElement.appendChild(messageElement);
this.element.appendChild(this.emptyPanelMessageElement);
contentArea.appendChild(this.emptyPanelMessageElement);
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.emptyPanelMessageElement, {
onDragOver: (e) => {
@@ -455,7 +487,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
}
return this.openComposite(id, focus);
return this.openComposite(id, focus) as Panel;
}
async openPanel(id?: string, focus?: boolean): Promise<Panel | undefined> {
@@ -670,6 +702,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
private saveCachedPanels(): void {
const state: ICachedPanel[] = [];
const placeholders: IPlaceholderViewContainer[] = [];
const compositeItems = this.compositeBar.getCompositeBarItems();
for (const compositeItem of compositeItems) {
@@ -677,10 +710,12 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
if (viewContainer) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
state.push({ id: compositeItem.id, name: viewContainerModel.title, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible });
placeholders.push({ id: compositeItem.id, name: this.getCompositeActions(compositeItem.id).activityAction.label });
}
}
this.cachedPanelsValue = JSON.stringify(state);
this.setPlaceholderViewContainers(placeholders);
}
private getCachedPanels(): ICachedPanel[] {
@@ -694,6 +729,13 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return serialized;
});
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
const cachedViewContainer = cachedPanels.filter(cached => cached.id === placeholderViewContainer.id)[0];
if (cachedViewContainer) {
cachedViewContainer.name = placeholderViewContainer.name;
}
}
return cachedPanels;
}
@@ -721,6 +763,38 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL);
}
private getPlaceholderViewContainers(): IPlaceholderViewContainer[] {
return JSON.parse(this.placeholderViewContainersValue);
}
private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void {
this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers);
}
private _placeholderViewContainersValue: string | undefined;
private get placeholderViewContainersValue(): string {
if (!this._placeholderViewContainersValue) {
this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue();
}
return this._placeholderViewContainersValue;
}
private set placeholderViewContainersValue(placeholderViewContainesValue: string) {
if (this.placeholderViewContainersValue !== placeholderViewContainesValue) {
this._placeholderViewContainersValue = placeholderViewContainesValue;
this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue);
}
}
private getStoredPlaceholderViewContainersValue(): string {
return this.storageService.get(PanelPart.PLACEHOLDER_VIEW_CONTAINERS, StorageScope.WORKSPACE, '[]');
}
private setStoredPlaceholderViewContainersValue(value: string): void {
this.storageService.store(PanelPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.WORKSPACE);
}
private getViewContainer(panelId: string): ViewContainer | undefined {
return this.viewDescriptorService.getViewContainerById(panelId) || undefined;
}

View File

@@ -98,6 +98,7 @@
align-items: center;
text-overflow: ellipsis;
overflow: hidden;
outline-width: 0px; /* do not render focus outline, we already have background */
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover {

View File

@@ -9,7 +9,6 @@ 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 { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Part } from 'vs/workbench/browser/part';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -69,6 +68,10 @@ class StatusbarViewModel extends Disposable {
get entries(): IStatusbarViewModelEntry[] { return this._entries; }
private hidden!: Set<string>;
get lastFocusedEntry(): IStatusbarViewModelEntry | undefined {
return this._lastFocusedEntry && !this.isHidden(this._lastFocusedEntry.id) ? this._lastFocusedEntry : undefined;
}
private _lastFocusedEntry: IStatusbarViewModelEntry | undefined;
private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>());
readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event;
@@ -219,6 +222,7 @@ class StatusbarViewModel extends Disposable {
if (focused) {
const entry = getVisibleEntry(this._entries.indexOf(focused) + delta);
if (entry) {
this._lastFocusedEntry = entry;
entry.labelContainer.focus();
return;
}
@@ -226,6 +230,7 @@ class StatusbarViewModel extends Disposable {
const entry = getVisibleEntry(restartPosition);
if (entry) {
this._lastFocusedEntry = entry;
entry.labelContainer.focus();
}
}
@@ -493,6 +498,15 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.viewModel.focusPreviousEntry();
}
focus(preserveEntryFocus = true): void {
this.getContainer()?.focus();
const lastFocusedEntry = this.viewModel.lastFocusedEntry;
if (preserveEntryFocus && lastFocusedEntry) {
// Need a timeout, for some reason without it the inner label container will not get focused
setTimeout(() => lastFocusedEntry.labelContainer.focus(), 0);
}
}
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
@@ -680,10 +694,6 @@ export class StatusbarPart extends Part implements IStatusbarService {
return itemContainer;
}
focus(): void {
this.getContainer();
}
layout(width: number, height: number): void {
super.layout(width, height);
super.layoutContents(width, height);
@@ -715,7 +725,6 @@ class StatusbarEntryItem extends Disposable {
@ICommandService private readonly commandService: ICommandService,
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IEditorService private readonly editorService: IEditorService,
@IThemeService private readonly themeService: IThemeService
) {
super();
@@ -773,7 +782,7 @@ class StatusbarEntryItem extends Disposable {
const command = entry.command;
if (command) {
this.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_UP, e => {
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
this.executeCommand(command);
@@ -818,12 +827,6 @@ class StatusbarEntryItem extends Disposable {
const id = typeof command === 'string' ? command : command.id;
const args = typeof command === 'string' ? [] : command.arguments ?? [];
// Maintain old behaviour of always focusing the editor here
const activeTextEditorControl = this.editorService.activeTextEditorControl;
if (activeTextEditorControl) {
activeTextEditorControl.focus();
}
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });
try {
await this.commandService.executeCommand(id, ...args);
@@ -935,3 +938,38 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
statusBarService.focusNextEntry();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.statusBar.focusFirst',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Home,
when: CONTEXT_STATUS_BAR_FOCUSED,
handler: (accessor: ServicesAccessor) => {
const statusBarService = accessor.get(IStatusbarService);
statusBarService.focus(false);
statusBarService.focusNextEntry();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.statusBar.focusLast',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.End,
when: CONTEXT_STATUS_BAR_FOCUSED,
handler: (accessor: ServicesAccessor) => {
const statusBarService = accessor.get(IStatusbarService);
statusBarService.focus(false);
statusBarService.focusPreviousEntry();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.statusBar.clearFocus',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Escape,
when: CONTEXT_STATUS_BAR_FOCUSED,
handler: (accessor: ServicesAccessor) => {
const statusBarService = accessor.get(IStatusbarService);
statusBarService.focus(false);
}
});

View File

@@ -333,7 +333,6 @@ export class TitlebarPart extends Part implements ITitleService {
this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
this.menubar = this.element.insertBefore($('div.menubar'), this.title);
this.menubar.setAttribute('role', 'menubar');
this.customMenubar.create(this.menubar);

View File

@@ -18,6 +18,16 @@
.monaco-pane-view .pane > .pane-header > .actions.show {
display: initial;
}
.monaco-pane-view .pane > .pane-header .icon {
display: none;
width: 16px;
height: 16px;
}
.monaco-pane-view .pane.pane.horizontal:not(.expanded) > .pane-header .icon {
display: inline;
margin-top: 4px;
}
.monaco-pane-view .pane > .pane-header h3.title {
white-space: nowrap;
@@ -28,6 +38,11 @@
-webkit-margin-after: 0;
}
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header h3.title,
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header .description {
display: none;
}
.monaco-pane-view .pane .monaco-progress-container {
position: absolute;
left: 0;

View File

@@ -68,6 +68,9 @@ export class TreeViewPane extends ViewPane {
this._register(toDisposable(() => this.treeView.setVisibility(false)));
this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire()));
if (options.title !== this.treeView.title) {
this.updateTitle(this.treeView.title);
}
this.updateTreeVisibility();
}
@@ -161,7 +164,7 @@ export class TreeView extends Disposable implements ITreeView {
private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());
constructor(
protected readonly id: string,
readonly id: string,
private _title: string,
@IThemeService private readonly themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -425,8 +428,15 @@ export class TreeView extends Disposable implements ITreeView {
identityProvider: new TreeViewIdentityProvider(),
accessibilityProvider: {
getAriaLabel(element: ITreeItem): string {
if (element.accessibilityInformation) {
return element.accessibilityInformation.label;
}
return element.tooltip ? element.tooltip : element.label ? element.label.label : '';
},
getRole(element: ITreeItem): string | undefined {
return element.accessibilityInformation?.role;
},
getWidgetAriaLabel(): string {
return widgetAriaLabel;
}
@@ -783,15 +793,15 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined;
const label = treeItemLabel ? treeItemLabel.label : undefined;
const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => {
if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) {
return ({ start: 0, end: 0 });
}
if (start < 0) {
start = label.length + start;
}
if (end < 0) {
end = label.length + end;
}
if ((start >= label.length) || (end > label.length)) {
return ({ start: 0, end: 0 });
}
if (start > end) {
const swap = start;
start = end;

View File

@@ -69,3 +69,37 @@ export class ViewMenuActions extends Disposable {
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;
}
}

View File

@@ -6,10 +6,10 @@
import 'vs/css!./media/paneviewlet';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
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, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_BORDER } from 'vs/workbench/common/theme';
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
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 { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass, createCSSRule, asCSSUrl, addClasses } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { firstIndex } from 'vs/base/common/arrays';
import { IAction } from 'vs/base/common/actions';
@@ -20,20 +20,20 @@ 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, Themable } from 'vs/platform/theme/common/themeService';
import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview';
import { PaneView, IPaneViewOptions, IPaneOptions, Pane, IPaneStyles } 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 } from 'vs/workbench/common/views';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { assertIsDefined, isString } 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 } from 'vs/platform/actions/common/actions';
import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions';
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { parseLinkedText } from 'vs/base/common/linkedText';
@@ -48,6 +48,9 @@ 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';
export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier;
@@ -185,6 +188,7 @@ export abstract class ViewPane extends Pane implements IView {
private readonly showActionsAlways: boolean = false;
private headerContainer?: HTMLElement;
private titleContainer?: HTMLElement;
private iconContainer?: HTMLElement;
protected twistiesContainer?: HTMLElement;
private bodyContainer!: HTMLElement;
@@ -290,6 +294,10 @@ export abstract class ViewPane extends Pane implements IView {
this._register(this.toolbar);
this.setActions();
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();
@@ -299,18 +307,86 @@ export abstract class ViewPane extends Pane implements IView {
this.twistiesContainer = append(container, $('.twisties.codicon.codicon-chevron-right'));
}
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(): string | URI {
return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || 'codicon-window';
}
protected renderHeaderTitle(container: HTMLElement, title: string): void {
this.titleContainer = append(container, $('h3.title', undefined, title));
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 (isString(icon)) {
addClass(this.iconContainer, 'codicon');
cssClass = icon;
}
if (cssClass) {
addClasses(this.iconContainer, cssClass);
}
const calculatedTitle = this.calculateTitle(title);
this.titleContainer = append(container, $('h3.title', undefined, calculatedTitle));
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 = title;
this.titleContainer.textContent = calculatedTitle;
}
if (this.iconContainer) {
this.iconContainer.title = calculatedTitle;
this.iconContainer.setAttribute('aria-label', calculatedTitle);
}
this.title = title;
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 {
@@ -510,7 +586,6 @@ export abstract class ViewPane extends Pane implements IView {
export interface IViewPaneContainerOptions extends IPaneViewOptions {
mergeViewWithContainerWhenSingleView: boolean;
donotShowContainerTitleWhenMergedWithContainer?: boolean;
}
interface IViewPaneItem {
@@ -546,7 +621,8 @@ class ViewPaneDropOverlay extends Themable {
constructor(
private paneElement: HTMLElement,
private orientation: Orientation | undefined,
protected themeService: IThemeService
protected location: ViewContainerLocation,
protected themeService: IThemeService,
) {
super(themeService);
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
@@ -587,7 +663,7 @@ class ViewPaneDropOverlay extends Themable {
protected updateStyles(): void {
// Overlay drop background
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
this.overlay.style.backgroundColor = this.getColor(this.location === ViewContainerLocation.Panel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND) || '';
// Overlay contrast border (if any)
const activeContrastBorderColor = this.getColor(activeContrastBorder);
@@ -830,7 +906,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return;
}
overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService);
overlay = new ViewPaneDropOverlay(parent, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
}
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
@@ -838,7 +914,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
if (!viewsToMove.some(v => !v.canMoveView)) {
overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService);
overlay = new ViewPaneDropOverlay(parent, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
}
}
@@ -904,7 +980,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
if (this.isViewMergedWithContainer()) {
const paneItemTitle = this.paneItems[0].pane.title;
if (this.options.donotShowContainerTitleWhenMergedWithContainer || containerTitle === paneItemTitle) {
if (containerTitle === paneItemTitle) {
return this.paneItems[0].pane.title;
}
return paneItemTitle ? `${containerTitle}: ${paneItemTitle}` : containerTitle;
@@ -1227,6 +1303,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.updateTitleArea();
}
});
const onDidChangeVisibility = pane.onDidChangeBodyVisibility(() => this._onDidChangeViewVisibility.fire(pane));
const onDidChange = pane.onDidChange(() => {
if (pane === this.lastFocusedPane && !pane.isExpanded()) {
@@ -1234,13 +1311,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
});
// TODO@sbatten Styling is viewlet specific, must fix
const isPanel = this.viewDescriptorService.getViewLocationById(this.viewContainer.id) === ViewContainerLocation.Panel;
const paneStyler = attachStyler<IPaneColors>(this.themeService, {
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
headerBorder: SIDE_BAR_SECTION_HEADER_BORDER,
leftBorder: PANEL_BORDER,
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
headerForeground: isPanel ? PANEL_SECTION_HEADER_FOREGROUND : SIDE_BAR_SECTION_HEADER_FOREGROUND,
headerBackground: isPanel ? PANEL_SECTION_HEADER_BACKGROUND : SIDE_BAR_SECTION_HEADER_BACKGROUND,
headerBorder: isPanel ? PANEL_SECTION_HEADER_BORDER : SIDE_BAR_SECTION_HEADER_BORDER,
dropBackground: isPanel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND,
leftBorder: isPanel ? PANEL_SECTION_BORDER : undefined
}, pane);
const disposable = combinedDisposable(pane, onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange, onDidChangeVisibility);
const paneItem: IViewPaneItem = { pane, disposable };
@@ -1265,7 +1342,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return;
}
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.themeService);
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
}
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) {
@@ -1273,7 +1350,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
if (!viewsToMove.some(v => !v.canMoveView)) {
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.themeService);
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
}
}
}
@@ -1466,3 +1543,96 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
}
}
class MoveViewPosition extends Action2 {
constructor(desc: Readonly<IAction2Options>, private readonly offset: number) {
super(desc);
}
async run(accessor: ServicesAccessor): Promise<void> {
const viewDescriptorService = accessor.get(IViewDescriptorService);
const contextKeyService = accessor.get(IContextKeyService);
const viewId = FocusedViewContext.getValue(contextKeyService);
if (viewId === undefined) {
return;
}
const viewContainer = viewDescriptorService.getViewContainerByViewId(viewId)!;
const model = viewDescriptorService.getViewContainerModel(viewContainer);
const viewDescriptor = model.visibleViewDescriptors.find(vd => vd.id === viewId)!;
const currentIndex = model.visibleViewDescriptors.indexOf(viewDescriptor);
if (currentIndex + this.offset < 0 || currentIndex + this.offset >= model.visibleViewDescriptors.length) {
return;
}
const newPosition = model.visibleViewDescriptors[currentIndex + this.offset];
model.move(viewDescriptor.id, newPosition.id);
}
}
registerAction2(
class MoveViewUp extends MoveViewPosition {
constructor() {
super({
id: 'views.moveViewUp',
title: nls.localize('viewMoveUp', "Move View Up"),
keybinding: {
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.UpArrow),
weight: KeybindingWeight.WorkbenchContrib + 1,
when: FocusedViewContext.notEqualsTo('')
}
}, -1);
}
}
);
registerAction2(
class MoveViewLeft extends MoveViewPosition {
constructor() {
super({
id: 'views.moveViewLeft',
title: nls.localize('viewMoveLeft', "Move View Left"),
keybinding: {
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.LeftArrow),
weight: KeybindingWeight.WorkbenchContrib + 1,
when: FocusedViewContext.notEqualsTo('')
}
}, -1);
}
}
);
registerAction2(
class MoveViewDown extends MoveViewPosition {
constructor() {
super({
id: 'views.moveViewDown',
title: nls.localize('viewMoveDown', "Move View Down"),
keybinding: {
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.DownArrow),
weight: KeybindingWeight.WorkbenchContrib + 1,
when: FocusedViewContext.notEqualsTo('')
}
}, 1);
}
}
);
registerAction2(
class MoveViewRight extends MoveViewPosition {
constructor() {
super({
id: 'views.moveViewRight',
title: nls.localize('viewMoveRight', "Move View Right"),
keybinding: {
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KEY_K, KeyCode.RightArrow),
weight: KeybindingWeight.WorkbenchContrib + 1,
when: FocusedViewContext.notEqualsTo('')
}
}, 1);
}
}
);

View File

@@ -20,7 +20,7 @@ import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { PaneCompositePanel, PanelRegistry, PanelDescriptor, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
import { PanelRegistry, PanelDescriptor, Extensions as PanelExtensions, Panel } from 'vs/workbench/browser/panel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -186,8 +186,8 @@ export class ViewsService extends Disposable implements IViewsService {
super({
id: `${viewDescriptor.id}.resetViewLocation`,
title: {
original: 'Reset View Location',
value: localize('resetViewLocation', "Reset View Location")
original: 'Reset Location',
value: localize('resetViewLocation', "Reset Location")
},
menu: [{
id: MenuId.ViewTitleContext,
@@ -202,6 +202,15 @@ export class ViewsService extends Disposable implements IViewsService {
}
run(accessor: ServicesAccessor): void {
const viewDescriptorService = accessor.get(IViewDescriptorService);
const defaultContainer = viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!;
const containerModel = viewDescriptorService.getViewContainerModel(defaultContainer)!;
// The default container is hidden so we should try to reset its location first
if (defaultContainer.hideIfEmpty && containerModel.visibleViewDescriptors.length === 0) {
const defaultLocation = viewDescriptorService.getDefaultViewContainerLocation(defaultContainer)!;
viewDescriptorService.moveViewContainerToLocation(defaultContainer, defaultLocation);
}
viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!);
accessor.get(IViewsService).openView(viewDescriptor.id, true);
}
@@ -414,7 +423,7 @@ export class ViewsService extends Disposable implements IViewsService {
private registerPanel(viewContainer: ViewContainer): void {
const that = this;
class PaneContainerPanel extends PaneCompositePanel {
class PaneContainerPanel extends Panel {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { mark } from 'vs/base/common/performance';
import { domContentLoaded, addDisposableListener, EventType, addClass, EventHelper } from 'vs/base/browser/dom';
import { domContentLoaded, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILogService, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log';
import { ConsoleLogInAutomationService } from 'vs/platform/log/browser/log';
@@ -39,7 +39,6 @@ import { BACKUPS } from 'vs/platform/environment/common/environment';
import { joinPath } from 'vs/base/common/resources';
import { BrowserStorageService } from 'vs/platform/storage/browser/storageService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService';
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { FileLogService } from 'vs/platform/log/common/fileLogService';
@@ -74,9 +73,6 @@ class BrowserMain extends Disposable {
await domContentLoaded();
mark('willStartWorkbench');
// Base Theme
this.restoreBaseTheme();
// Create Workbench
const workbench = new Workbench(
this.domElement,
@@ -131,7 +127,6 @@ class BrowserMain extends Disposable {
}));
this._register(workbench.onWillShutdown(() => {
storageService.close();
this.saveBaseTheme();
}));
this._register(workbench.onShutdown(() => this.dispose()));
@@ -147,21 +142,6 @@ class BrowserMain extends Disposable {
});
}
private restoreBaseTheme(): void {
addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(LIGHT) /* Fallback to a light theme by default on web */);
}
private saveBaseTheme(): void {
const classes = this.domElement.className;
const baseThemes = [DARK, LIGHT, HIGH_CONTRAST].map(baseTheme => getThemeTypeSelector(baseTheme));
for (const baseTheme of baseThemes) {
if (classes.indexOf(baseTheme) >= 0) {
window.localStorage.setItem('vscode.baseTheme', baseTheme);
break;
}
}
}
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: BrowserStorageService }> {
const serviceCollection = new ServiceCollection();

View File

@@ -34,7 +34,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
},
'workbench.editor.scrollToSwitchTabs': {
'type': 'boolean',
'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls wether 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."),
'description': 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."),
'default': false
},
'workbench.editor.highlightModifiedTabs': {