mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-04 01:25:38 -05:00
Merge from vscode 2cd495805cf99b31b6926f08ff4348124b2cf73d
This commit is contained in:
committed by
AzureDataStudio
parent
a8a7559229
commit
1388493cc1
@@ -249,7 +249,7 @@ class LogWorkingCopiesAction extends Action {
|
||||
|
||||
// --- Actions Registration
|
||||
|
||||
const developerCategory = nls.localize('developer', "Developer");
|
||||
const developerCategory = nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer");
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(InspectContextKeysAction), 'Developer: Inspect Context Keys', developerCategory);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleScreencastModeAction), 'Developer: Toggle Screencast Mode', developerCategory);
|
||||
|
||||
@@ -697,7 +697,7 @@ export class MoveFocusedViewAction extends Action {
|
||||
if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) {
|
||||
items.push({
|
||||
id: '_.panel.newcontainer',
|
||||
label: nls.localize('moveFocusedView.newContainerInPanel', "New Panel Entry"),
|
||||
label: nls.localize({ key: 'moveFocusedView.newContainerInPanel', comment: ['Creates a new top-level tab in the panel.'] }, "New Panel Entry"),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ CommandsRegistry.registerCommand({
|
||||
handler: async function (accessor: ServicesAccessor, prefix: unknown) {
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
|
||||
quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined);
|
||||
quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined, { preserveValue: typeof prefix === 'string' /* preserve as is if provided */ });
|
||||
},
|
||||
description: {
|
||||
description: `Quick access`,
|
||||
|
||||
@@ -344,7 +344,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenRecentAction, { p
|
||||
const viewCategory = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleFullScreenAction, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory);
|
||||
|
||||
const developerCategory = nls.localize('developer', "Developer");
|
||||
const developerCategory = nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer");
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ReloadWindowAction), 'Developer: Reload Window', developerCategory);
|
||||
|
||||
const helpCategory = nls.localize('help', "Help");
|
||||
|
||||
@@ -18,7 +18,6 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
|
||||
export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder';
|
||||
export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace...");
|
||||
@@ -55,8 +54,6 @@ CommandsRegistry.registerCommand({
|
||||
CommandsRegistry.registerCommand({
|
||||
id: ADD_ROOT_FOLDER_COMMAND_ID,
|
||||
handler: async (accessor) => {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
|
||||
const dialogsService = accessor.get(IFileDialogService);
|
||||
const folders = await dialogsService.showOpenDialog({
|
||||
@@ -72,7 +69,6 @@ CommandsRegistry.registerCommand({
|
||||
}
|
||||
|
||||
await workspaceEditingService.addFolders(folders.map(folder => ({ uri: resources.removeTrailingPathSeparator(folder) })));
|
||||
await viewsService.openViewContainer(viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)!.id, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -33,9 +33,6 @@ export abstract class Composite extends Component implements IComposite {
|
||||
private readonly _onTitleAreaUpdate = this._register(new Emitter<void>());
|
||||
readonly onTitleAreaUpdate = this._onTitleAreaUpdate.event;
|
||||
|
||||
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
private _onDidFocus: Emitter<void> | undefined;
|
||||
get onDidFocus(): Event<void> {
|
||||
if (!this._onDidFocus) {
|
||||
@@ -135,8 +132,6 @@ export abstract class Composite extends Component implements IComposite {
|
||||
setVisible(visible: boolean): void {
|
||||
if (this.visible !== !!visible) {
|
||||
this.visible = visible;
|
||||
|
||||
this._onDidChangeVisibility.fire(visible);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { hasWorkspaceFileExtension, IWorkspaceFolderCreationData, IRecentFile, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { normalize } from 'vs/base/common/path';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { basename, extUri } from 'vs/base/common/resources';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -357,7 +357,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
for (const textEditorControl of textEditorControls) {
|
||||
if (isCodeEditor(textEditorControl)) {
|
||||
const model = textEditorControl.getModel();
|
||||
if (model?.uri?.toString() === file.resource.toString()) {
|
||||
if (extUri.isEqual(model?.uri, file.resource)) {
|
||||
return withNullAsUndefined(textEditorControl.saveViewState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { dirname, isEqual, basenameOrAuthority, extUri } from 'vs/base/common/resources';
|
||||
import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -24,7 +24,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export interface IResourceLabelProps {
|
||||
resource?: URI | { master?: URI, detail?: URI };
|
||||
resource?: URI | { primary?: URI, secondary?: URI };
|
||||
name?: string | string[];
|
||||
description?: string;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ function toResource(props: IResourceLabelProps | undefined): URI | undefined {
|
||||
return props.resource;
|
||||
}
|
||||
|
||||
return props.resource.master;
|
||||
return props.resource.primary;
|
||||
}
|
||||
|
||||
export interface IResourceLabelOptions extends IIconLabelValueOptions {
|
||||
@@ -307,7 +307,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
return; // only update if resource exists
|
||||
}
|
||||
|
||||
if (model.uri.toString() === resource.toString()) {
|
||||
if (extUri.isEqual(model.uri, resource)) {
|
||||
if (this.lastKnownDetectedModeId !== model.getModeId()) {
|
||||
this.render(true); // update if the language id of the model has changed from our last known state
|
||||
}
|
||||
@@ -379,9 +379,9 @@ class ResourceLabelWidget extends IconLabel {
|
||||
|
||||
setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void {
|
||||
/*const resource = toResource(label); {{SQL CARBON EDIT}} we don't want to special case untitled files
|
||||
const isMasterDetail = label?.resource && !URI.isUri(label.resource);
|
||||
const isSideBySideEditor = label?.resource && !URI.isUri(label.resource);
|
||||
|
||||
if (!options.forceLabel && !isMasterDetail && resource?.scheme === Schemas.untitled) {
|
||||
if (!options.forceLabel && !isSideBySideEditor && resource?.scheme === Schemas.untitled) {
|
||||
// Untitled labels are very dynamic because they may change
|
||||
// whenever the content changes (unless a path is associated).
|
||||
// As such we always ask the actual editor for it's name and
|
||||
@@ -390,7 +390,7 @@ class ResourceLabelWidget extends IconLabel {
|
||||
// we assume that the client does not want to display them
|
||||
// and as such do not override.
|
||||
//
|
||||
// We do not touch the label if it represents a master-detail
|
||||
// We do not touch the label if it represents a primary-secondary
|
||||
// because in that case we expect it to carry a proper label
|
||||
// and description.
|
||||
const untitledModel = this.textFileService.untitled.get(resource);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
|
||||
import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
|
||||
import { Position, Parts, IWorkbenchLayoutService, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IStorageService, StorageScope, WillSaveStateReason, WorkspaceStorageSettings } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
@@ -568,8 +568,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
return;
|
||||
}
|
||||
|
||||
const firstOpen = storageService.getBoolean(WorkspaceStorageSettings.WORKSPACE_FIRST_OPEN, StorageScope.WORKSPACE);
|
||||
if (!firstOpen) {
|
||||
if (!storageService.isNew(StorageScope.WORKSPACE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -794,7 +793,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
|
||||
|
||||
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined {
|
||||
const defaultLayout = this.environmentService.options?.defaultLayout;
|
||||
if (defaultLayout?.editors?.length && this.storageService.getBoolean(WorkspaceStorageSettings.WORKSPACE_FIRST_OPEN, StorageScope.WORKSPACE)) {
|
||||
if (defaultLayout?.editors?.length && this.storageService.isNew(StorageScope.WORKSPACE)) {
|
||||
this._openedDefaultEditors = true;
|
||||
|
||||
return {
|
||||
|
||||
@@ -263,3 +263,7 @@ body.web {
|
||||
.monaco-workbench .monaco-list:focus {
|
||||
outline: 0 !important; /* tree indicates focus not via outline but through the focused item */
|
||||
}
|
||||
|
||||
.monaco-workbench .codicon[class*='codicon-'] {
|
||||
font-size: 16px; /* sets font-size for codicons in workbench https://github.com/microsoft/vscode/issues/98495 */
|
||||
}
|
||||
|
||||
@@ -170,10 +170,12 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
|
||||
menus.push(new Separator());
|
||||
}
|
||||
|
||||
otherCommands.forEach(group => {
|
||||
otherCommands.forEach((group, i) => {
|
||||
const actions = group[1];
|
||||
menus = menus.concat(actions);
|
||||
menus.push(new Separator());
|
||||
if (i !== otherCommands.length - 1) {
|
||||
menus.push(new Separator());
|
||||
}
|
||||
});
|
||||
|
||||
return menus;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbenc
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
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 { IWorkbenchLayoutService, Parts } 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';
|
||||
@@ -18,7 +18,7 @@ import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeServic
|
||||
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';
|
||||
import { Dimension, addClass, removeNode, createCSSRule, asCSSUrl, toggleClass } from 'vs/base/browser/dom';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
@@ -458,14 +458,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
container.style.backgroundColor = background;
|
||||
|
||||
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || '';
|
||||
const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : '';
|
||||
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : '';
|
||||
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : '';
|
||||
container.style.borderRightColor = isPositionLeft ? borderColor : '';
|
||||
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
|
||||
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
|
||||
container.style.borderLeftColor = !isPositionLeft ? borderColor : '';
|
||||
toggleClass(container, 'bordered', !!borderColor);
|
||||
container.style.borderColor = borderColor ? borderColor : '';
|
||||
}
|
||||
|
||||
private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors {
|
||||
|
||||
@@ -8,6 +8,28 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar.bordered::before {
|
||||
content: '';
|
||||
float: left;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 0px;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar.left.bordered::before {
|
||||
right: 0;
|
||||
border-right-style: solid;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar.right.bordered::before {
|
||||
left: 0;
|
||||
border-left-style: solid;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -122,8 +122,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
const draggedViews = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors;
|
||||
|
||||
// ... all views must be movable
|
||||
// Prevent moving scm explicitly TODO@joaomoreno remove when scm is moveable
|
||||
return !draggedViews.some(v => !v.canMoveView) && currentContainer.id !== 'workbench.view.scm';
|
||||
return !draggedViews.some(v => !v.canMoveView);
|
||||
} else {
|
||||
// Dragging an individual view
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dragData.id);
|
||||
|
||||
@@ -331,7 +331,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
toolBar.context = this.actionsContextProvider();
|
||||
|
||||
// Return fn to set into toolbar
|
||||
return toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
|
||||
return () => toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
|
||||
}
|
||||
|
||||
protected getActiveComposite(): IComposite | undefined {
|
||||
@@ -392,7 +392,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
actionViewItemProvider: action => this.actionViewItemProvider(action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment()
|
||||
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(),
|
||||
toggleMenuTitle: nls.localize('viewsAndMoreActions', "Views and More Actions...")
|
||||
}));
|
||||
|
||||
this.collectCompositeActions()();
|
||||
|
||||
@@ -58,7 +58,7 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
|
||||
protected _options: EditorOptions | undefined;
|
||||
get options(): EditorOptions | undefined { return this._options; }
|
||||
|
||||
private _group?: IEditorGroup;
|
||||
private _group: IEditorGroup | undefined;
|
||||
get group(): IEditorGroup | undefined { return this._group; }
|
||||
|
||||
constructor(
|
||||
@@ -78,7 +78,8 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent HTMLElement.
|
||||
* Called to create the editor in the parent HTMLElement. Subclasses implement
|
||||
* this method to construct the editor widget.
|
||||
*/
|
||||
protected abstract createEditor(parent: HTMLElement): void;
|
||||
|
||||
@@ -101,6 +102,12 @@ export abstract class BaseEditor extends Composite implements IEditorPane {
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and
|
||||
* resources associated with the input should be freed.
|
||||
*
|
||||
* This method can be called based on different contexts, e.g. when opening
|
||||
* a different editor control or when closing all editors in a group.
|
||||
*
|
||||
* To monitor the lifecycle of editor inputs, you should not rely on this
|
||||
* method, rather refer to the listeners on `IEditorGroup` via `IEditorGroupService`.
|
||||
*/
|
||||
clearInput(): void {
|
||||
this._input = undefined;
|
||||
@@ -136,16 +143,6 @@ 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}`;
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@ export class BinaryResourceDiffEditor extends SideBySideEditor {
|
||||
}
|
||||
|
||||
getMetadata(): string | undefined {
|
||||
const master = this.masterEditorPane;
|
||||
const details = this.detailsEditorPane;
|
||||
const primary = this.primaryEditorPane;
|
||||
const secondary = this.secondaryEditorPane;
|
||||
|
||||
if (master instanceof BaseBinaryResourceEditor && details instanceof BaseBinaryResourceEditor) {
|
||||
return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata());
|
||||
if (primary instanceof BaseBinaryResourceEditor && secondary instanceof BaseBinaryResourceEditor) {
|
||||
return nls.localize('metadataDiff', "{0} ↔ {1}", secondary.getMetadata(), primary.getMetadata());
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -234,7 +234,7 @@ export class BreadcrumbsControl {
|
||||
this._breadcrumbsDisposables.clear();
|
||||
|
||||
// honor diff editors and such
|
||||
const uri = toResource(this._editorGroup.activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const uri = toResource(this._editorGroup.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
|
||||
if (!uri || !this._fileService.canHandleResource(uri)) {
|
||||
// cleanup and return when there is no input or when
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext, EditorStickyContext } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext, EditorStickyContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
|
||||
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction,
|
||||
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction
|
||||
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -173,28 +173,28 @@ interface ISerializedSideBySideEditorInput {
|
||||
name: string;
|
||||
description: string | undefined;
|
||||
|
||||
detailsSerialized: string;
|
||||
masterSerialized: string;
|
||||
primarySerialized: string;
|
||||
secondarySerialized: string;
|
||||
|
||||
detailsTypeId: string;
|
||||
masterTypeId: string;
|
||||
primaryTypeId: string;
|
||||
secondaryTypeId: string;
|
||||
}
|
||||
|
||||
export abstract class AbstractSideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
private getInputFactories(detailsId: string, masterId: string): [IEditorInputFactory | undefined, IEditorInputFactory | undefined] {
|
||||
private getInputFactories(secondaryId: string, primaryId: string): [IEditorInputFactory | undefined, IEditorInputFactory | undefined] {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
|
||||
return [registry.getEditorInputFactory(detailsId), registry.getEditorInputFactory(masterId)];
|
||||
return [registry.getEditorInputFactory(secondaryId), registry.getEditorInputFactory(primaryId)];
|
||||
}
|
||||
|
||||
canSerialize(editorInput: EditorInput): boolean {
|
||||
const input = editorInput as SideBySideEditorInput | DiffEditorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const [detailsInputFactory, masterInputFactory] = this.getInputFactories(input.details.getTypeId(), input.master.getTypeId());
|
||||
if (input.primary && input.secondary) {
|
||||
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId());
|
||||
|
||||
return !!(detailsInputFactory?.canSerialize(input.details) && masterInputFactory?.canSerialize(input.master));
|
||||
return !!(secondaryInputFactory?.canSerialize(input.secondary) && primaryInputFactory?.canSerialize(input.primary));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -203,20 +203,20 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
|
||||
serialize(editorInput: EditorInput): string | undefined {
|
||||
const input = editorInput as SideBySideEditorInput | DiffEditorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const [detailsInputFactory, masterInputFactory] = this.getInputFactories(input.details.getTypeId(), input.master.getTypeId());
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsSerialized = detailsInputFactory.serialize(input.details);
|
||||
const masterSerialized = masterInputFactory.serialize(input.master);
|
||||
if (input.primary && input.secondary) {
|
||||
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId());
|
||||
if (primaryInputFactory && secondaryInputFactory) {
|
||||
const primarySerialized = primaryInputFactory.serialize(input.primary);
|
||||
const secondarySerialized = secondaryInputFactory.serialize(input.secondary);
|
||||
|
||||
if (detailsSerialized && masterSerialized) {
|
||||
if (primarySerialized && secondarySerialized) {
|
||||
const serializedEditorInput: ISerializedSideBySideEditorInput = {
|
||||
name: input.getName(),
|
||||
description: input.getDescription(),
|
||||
detailsSerialized,
|
||||
masterSerialized,
|
||||
detailsTypeId: input.details.getTypeId(),
|
||||
masterTypeId: input.master.getTypeId()
|
||||
primarySerialized: primarySerialized,
|
||||
secondarySerialized: secondarySerialized,
|
||||
primaryTypeId: input.primary.getTypeId(),
|
||||
secondaryTypeId: input.secondary.getTypeId()
|
||||
};
|
||||
|
||||
return JSON.stringify(serializedEditorInput);
|
||||
@@ -230,33 +230,33 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined {
|
||||
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const [detailsInputFactory, masterInputFactory] = this.getInputFactories(deserialized.detailsTypeId, deserialized.masterTypeId);
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsInput = detailsInputFactory.deserialize(instantiationService, deserialized.detailsSerialized);
|
||||
const masterInput = masterInputFactory.deserialize(instantiationService, deserialized.masterSerialized);
|
||||
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(deserialized.secondaryTypeId, deserialized.primaryTypeId);
|
||||
if (primaryInputFactory && secondaryInputFactory) {
|
||||
const primaryInput = primaryInputFactory.deserialize(instantiationService, deserialized.primarySerialized);
|
||||
const secondaryInput = secondaryInputFactory.deserialize(instantiationService, deserialized.secondarySerialized);
|
||||
|
||||
if (detailsInput && masterInput) {
|
||||
return this.createEditorInput(deserialized.name, deserialized.description, detailsInput, masterInput);
|
||||
if (primaryInput && secondaryInput) {
|
||||
return this.createEditorInput(deserialized.name, deserialized.description, secondaryInput, primaryInput);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected abstract createEditorInput(name: string, description: string | undefined, detailsInput: EditorInput, masterInput: EditorInput): EditorInput;
|
||||
protected abstract createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput;
|
||||
}
|
||||
|
||||
class SideBySideEditorInputFactory extends AbstractSideBySideEditorInputFactory {
|
||||
|
||||
protected createEditorInput(name: string, description: string | undefined, detailsInput: EditorInput, masterInput: EditorInput): EditorInput {
|
||||
return new SideBySideEditorInput(name, description, detailsInput, masterInput);
|
||||
protected createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
|
||||
return new SideBySideEditorInput(name, description, secondaryInput, primaryInput);
|
||||
}
|
||||
}
|
||||
|
||||
class DiffEditorInputFactory extends AbstractSideBySideEditorInputFactory {
|
||||
|
||||
protected createEditorInput(name: string, description: string | undefined, detailsInput: EditorInput, masterInput: EditorInput): EditorInput {
|
||||
return new DiffEditorInput(name, description, detailsInput, masterInput);
|
||||
protected createEditorInput(name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
|
||||
return new DiffEditorInput(name, description, secondaryInput, primaryInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +386,8 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(EditorLayoutThreeRows
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(EditorLayoutTwoByTwoGridAction), 'View: Grid Editor Layout (2x2)', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(EditorLayoutTwoRowsRightAction), 'View: Two Rows Right Editor Layout', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(EditorLayoutTwoColumnsBottomAction), 'View: Two Columns Bottom Editor Layout', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ReopenResourcesAction), 'View: Reopen Editor With...', category, ActiveEditorAvailableEditorIdsContext);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorTypeAction), 'View: Toggle Editor Type', category, ActiveEditorAvailableEditorIdsContext);
|
||||
|
||||
// Register Quick Editor Actions including built in quick navigate support for some
|
||||
|
||||
@@ -458,9 +460,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
|
||||
|
||||
// Editor Title Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_SIDE_BY_SIDE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: ReopenResourcesAction.ID, title: ReopenResourcesAction.LABEL }, group: '6_reopen', order: 20, when: ActiveEditorAvailableEditorIdsContext });
|
||||
|
||||
interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; }
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
@@ -22,6 +22,7 @@ import { ItemActivation, IQuickInputService } from 'vs/platform/quickinput/commo
|
||||
import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/services/editor/common/editorOpenWith';
|
||||
|
||||
export class ExecuteCommandAction extends Action {
|
||||
|
||||
@@ -580,7 +581,7 @@ abstract class BaseCloseAllAction extends Action {
|
||||
else {
|
||||
let name: string;
|
||||
if (editor instanceof SideBySideEditorInput) {
|
||||
name = editor.master.getName(); // prefer shorter names by using master's name in this case
|
||||
name = editor.primary.getName(); // prefer shorter names by using primary's name in this case
|
||||
} else {
|
||||
name = editor.getName();
|
||||
}
|
||||
@@ -1775,3 +1776,72 @@ export class NewEditorGroupBelowAction extends BaseCreateEditorGroupAction {
|
||||
super(id, label, GroupDirection.DOWN, editorGroupService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReopenResourcesAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.reopenWithEditor';
|
||||
static readonly LABEL = nls.localize('workbench.action.reopenWithEditor', "Reopen Editor With...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const activeInput = this.editorService.activeEditor;
|
||||
if (!activeInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEditorPane = this.editorService.activeEditorPane;
|
||||
if (!activeEditorPane) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = activeEditorPane.options;
|
||||
const group = activeEditorPane.group;
|
||||
await openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleEditorTypeAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.toggleEditorType';
|
||||
static readonly LABEL = nls.localize('workbench.action.toggleEditorType', "Toggle Editor Type");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const activeEditorPane = this.editorService.activeEditorPane;
|
||||
if (!activeEditorPane) {
|
||||
return;
|
||||
}
|
||||
|
||||
const input = activeEditorPane.input;
|
||||
if (!input.resource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = activeEditorPane.options;
|
||||
const group = activeEditorPane.group;
|
||||
|
||||
const overrides = getAllAvailableEditors(input.resource, options, group, this.editorService);
|
||||
const firstNonActiveOverride = overrides.find(([_, entry]) => !entry.active);
|
||||
if (!firstNonActiveOverride) {
|
||||
return;
|
||||
}
|
||||
|
||||
await firstNonActiveOverride[0].open(input, { ...options, override: firstNonActiveOverride[1].id }, group, OpenEditorContext.NEW_EDITOR)?.override;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,19 +198,19 @@ export class EditorControl extends Disposable {
|
||||
// Stop any running operation
|
||||
this.editorOperation.stop();
|
||||
|
||||
// Remove editor pane from parent and hide
|
||||
const editorPaneContainer = this._activeEditorPane.getContainer();
|
||||
if (editorPaneContainer) {
|
||||
this._activeEditorPane.onWillHide();
|
||||
this.parent.removeChild(editorPaneContainer);
|
||||
hide(editorPaneContainer);
|
||||
this._activeEditorPane.onDidHide();
|
||||
}
|
||||
|
||||
// Indicate to editor pane
|
||||
// Indicate to editor pane before removing the editor from
|
||||
// the DOM to give a chance to persist certain state that
|
||||
// might depend on still being the active DOM element.
|
||||
this._activeEditorPane.clearInput();
|
||||
this._activeEditorPane.setVisible(false, this.groupView);
|
||||
|
||||
// Remove editor pane from parent
|
||||
const editorPaneContainer = this._activeEditorPane.getContainer();
|
||||
if (editorPaneContainer) {
|
||||
this.parent.removeChild(editorPaneContainer);
|
||||
hide(editorPaneContainer);
|
||||
}
|
||||
|
||||
// Clear active editor pane
|
||||
this.doSetActiveEditorPane(null);
|
||||
}
|
||||
|
||||
@@ -548,8 +548,12 @@ class DropOverlay extends Themable {
|
||||
}
|
||||
}
|
||||
|
||||
export interface EditorDropTargetDelegate {
|
||||
groupContainsPredicate?(groupView: IEditorGroupView): boolean;
|
||||
export interface IEditorDropTargetDelegate {
|
||||
|
||||
/**
|
||||
* A helper to figure out if the drop target contains the provided group.
|
||||
*/
|
||||
containsGroup?(groupView: IEditorGroupView): boolean;
|
||||
}
|
||||
|
||||
export class EditorDropTarget extends Themable {
|
||||
@@ -564,7 +568,7 @@ export class EditorDropTarget extends Themable {
|
||||
constructor(
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private container: HTMLElement,
|
||||
private readonly delegate: EditorDropTargetDelegate,
|
||||
private readonly delegate: IEditorDropTargetDelegate,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
@@ -638,7 +642,8 @@ export class EditorDropTarget extends Themable {
|
||||
|
||||
private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined {
|
||||
const groups = this.accessor.groups;
|
||||
return groups.find(groupView => isAncestor(child, groupView.element) || this.delegate.groupContainsPredicate?.(groupView));
|
||||
|
||||
return groups.find(groupView => isAncestor(child, groupView.element) || this.delegate.containsGroup?.(groupView));
|
||||
}
|
||||
|
||||
private updateContainer(isDraggedOver: boolean): void {
|
||||
|
||||
@@ -275,9 +275,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}));
|
||||
|
||||
// Close empty editor group via middle mouse click
|
||||
this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => {
|
||||
this._register(addDisposableListener(this.element, EventType.AUXCLICK, e => {
|
||||
if (this.isEmpty && e.button === 1 /* Middle Button */) {
|
||||
EventHelper.stop(e);
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.accessor.removeGroup(this);
|
||||
}
|
||||
@@ -527,7 +527,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// Include both sides of side by side editors when being closed
|
||||
if (editor instanceof SideBySideEditorInput) {
|
||||
editorsToClose.push(editor.master, editor.details);
|
||||
editorsToClose.push(editor.primary, editor.secondary);
|
||||
}
|
||||
|
||||
// For each editor to close, we call dispose() to free up any resources.
|
||||
@@ -537,7 +537,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
for (const editor of editorsToClose) {
|
||||
if (!this.accessor.groups.some(groupView => groupView.group.contains(editor, {
|
||||
strictEquals: true, // only if this input is not shared across editor groups
|
||||
supportSideBySide: true // include side by side editor master & details
|
||||
supportSideBySide: true // include side by side editor primary & secondary
|
||||
}))) {
|
||||
editor.dispose();
|
||||
}
|
||||
@@ -1359,8 +1359,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return false; // editor must be dirty and not saving
|
||||
}
|
||||
|
||||
if (editor instanceof SideBySideEditorInput && this._group.contains(editor.master)) {
|
||||
return false; // master-side of editor is still opened somewhere else
|
||||
if (editor instanceof SideBySideEditorInput && this._group.contains(editor.primary)) {
|
||||
return false; // primary-side of editor is still opened somewhere else
|
||||
}
|
||||
|
||||
// Note: we explicitly decide to ask for confirm if closing a normal editor even
|
||||
@@ -1378,8 +1378,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return true; // exact editor still opened
|
||||
}
|
||||
|
||||
if (editor instanceof SideBySideEditorInput && otherGroup.contains(editor.master)) {
|
||||
return true; // master side of side by side editor still opened
|
||||
if (editor instanceof SideBySideEditorInput && otherGroup.contains(editor.primary)) {
|
||||
return true; // primary side of side by side editor still opened
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1404,7 +1404,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
let name: string;
|
||||
if (editor instanceof SideBySideEditorInput) {
|
||||
name = editor.master.getName(); // prefer shorter names by using master's name in this case
|
||||
name = editor.primary.getName(); // prefer shorter names by using primary's name in this case
|
||||
} else {
|
||||
name = editor.getName();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co
|
||||
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { EditorDropTarget, EditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget';
|
||||
import { EditorDropTarget, IEditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget';
|
||||
import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
@@ -80,7 +81,7 @@ class GridWidgetView<T extends IView> implements IView {
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsAccessor {
|
||||
export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsAccessor, IEditorDropService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -780,6 +781,14 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region IEditorDropService
|
||||
|
||||
createEditorDropTarget(container: HTMLElement, delegate: IEditorDropTargetDelegate): IDisposable {
|
||||
return this.instantiationService.createInstance(EditorDropTarget, this, container, delegate);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Part
|
||||
|
||||
// TODO @sbatten @joao find something better to prevent editor taking over #79897
|
||||
@@ -820,7 +829,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY]));
|
||||
|
||||
// Drop support
|
||||
this._register(this.createEditorDropTarget(this.container, {}));
|
||||
this._register(this.createEditorDropTarget(this.container, Object.create(null)));
|
||||
|
||||
// No drop in the editor
|
||||
const overlay = document.createElement('div');
|
||||
@@ -1097,16 +1106,24 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
}
|
||||
|
||||
// Persist centered view state
|
||||
const centeredLayoutState = this.centeredLayoutWidget.state;
|
||||
if (this.centeredLayoutWidget.isDefault(centeredLayoutState)) {
|
||||
delete this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY];
|
||||
} else {
|
||||
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = centeredLayoutState;
|
||||
if (this.centeredLayoutWidget) {
|
||||
const centeredLayoutState = this.centeredLayoutWidget.state;
|
||||
if (this.centeredLayoutWidget.isDefault(centeredLayoutState)) {
|
||||
delete this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY];
|
||||
} else {
|
||||
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = centeredLayoutState;
|
||||
}
|
||||
}
|
||||
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
return {
|
||||
type: Parts.EDITOR_PART
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
// Forward to all groups
|
||||
@@ -1122,20 +1139,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
return {
|
||||
type: Parts.EDITOR_PART
|
||||
};
|
||||
class EditorDropService implements IEditorDropService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(@IEditorGroupsService private readonly editorPart: EditorPart) { }
|
||||
|
||||
createEditorDropTarget(container: HTMLElement, delegate: IEditorDropTargetDelegate): IDisposable {
|
||||
return this.editorPart.createEditorDropTarget(container, delegate);
|
||||
}
|
||||
|
||||
//#region TODO@matt this should move into some kind of service
|
||||
|
||||
createEditorDropTarget(container: HTMLElement, delegate: EditorDropTargetDelegate): IDisposable {
|
||||
return this.instantiationService.createInstance(EditorDropTarget, this, container, delegate);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
registerSingleton(IEditorGroupsService, EditorPart);
|
||||
registerSingleton(IEditorDropService, EditorDropService);
|
||||
|
||||
@@ -137,7 +137,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
|
||||
}
|
||||
|
||||
return this.doGetEditors().map(({ editor, groupId }): IEditorQuickPickItem => {
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
const isDirty = editor.isDirty() && !editor.isSaving();
|
||||
const description = editor.getDescription();
|
||||
const nameAndDescription = description ? `${editor.getName()} ${description}` : editor.getName();
|
||||
|
||||
@@ -57,22 +57,22 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { setMode } from 'sql/workbench/browser/parts/editor/editorStatusModeSelect'; // {{SQL CARBON EDIT}}
|
||||
|
||||
class SideBySideEditorEncodingSupport implements IEncodingSupport {
|
||||
constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }
|
||||
constructor(private primary: IEncodingSupport, private secondary: IEncodingSupport) { }
|
||||
|
||||
getEncoding(): string | undefined {
|
||||
return this.master.getEncoding(); // always report from modified (right hand) side
|
||||
return this.primary.getEncoding(); // always report from modified (right hand) side
|
||||
}
|
||||
|
||||
setEncoding(encoding: string, mode: EncodingMode): void {
|
||||
[this.master, this.details].forEach(editor => editor.setEncoding(encoding, mode));
|
||||
[this.primary, this.secondary].forEach(editor => editor.setEncoding(encoding, mode));
|
||||
}
|
||||
}
|
||||
|
||||
class SideBySideEditorModeSupport implements IModeSupport {
|
||||
constructor(private master: IModeSupport, private details: IModeSupport) { }
|
||||
constructor(private primary: IModeSupport, private secondary: IModeSupport) { }
|
||||
|
||||
setMode(mode: string): void {
|
||||
[this.master, this.details].forEach(editor => editor.setMode(mode));
|
||||
[this.primary, this.secondary].forEach(editor => editor.setMode(mode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +85,14 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu
|
||||
|
||||
// Side by Side (diff) Editor
|
||||
if (input instanceof SideBySideEditorInput) {
|
||||
const masterEncodingSupport = toEditorWithEncodingSupport(input.master);
|
||||
const detailsEncodingSupport = toEditorWithEncodingSupport(input.details);
|
||||
const primaryEncodingSupport = toEditorWithEncodingSupport(input.primary);
|
||||
const secondaryEncodingSupport = toEditorWithEncodingSupport(input.secondary);
|
||||
|
||||
if (masterEncodingSupport && detailsEncodingSupport) {
|
||||
return new SideBySideEditorEncodingSupport(masterEncodingSupport, detailsEncodingSupport);
|
||||
if (primaryEncodingSupport && secondaryEncodingSupport) {
|
||||
return new SideBySideEditorEncodingSupport(primaryEncodingSupport, secondaryEncodingSupport);
|
||||
}
|
||||
|
||||
return masterEncodingSupport;
|
||||
return primaryEncodingSupport;
|
||||
}
|
||||
|
||||
// File or Resource Editor
|
||||
@@ -114,14 +114,14 @@ function toEditorWithModeSupport(input: IEditorInput): IModeSupport | null {
|
||||
|
||||
// Side by Side (diff) Editor
|
||||
if (input instanceof SideBySideEditorInput) {
|
||||
const masterModeSupport = toEditorWithModeSupport(input.master);
|
||||
const detailsModeSupport = toEditorWithModeSupport(input.details);
|
||||
const primaryModeSupport = toEditorWithModeSupport(input.primary);
|
||||
const secondaryModeSupport = toEditorWithModeSupport(input.secondary);
|
||||
|
||||
if (masterModeSupport && detailsModeSupport) {
|
||||
return new SideBySideEditorModeSupport(masterModeSupport, detailsModeSupport);
|
||||
if (primaryModeSupport && secondaryModeSupport) {
|
||||
return new SideBySideEditorModeSupport(primaryModeSupport, secondaryModeSupport);
|
||||
}
|
||||
|
||||
return masterModeSupport;
|
||||
return primaryModeSupport;
|
||||
}
|
||||
|
||||
// File or Resource Editor
|
||||
@@ -688,14 +688,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
else if (activeEditorPane instanceof BaseBinaryResourceEditor || activeEditorPane instanceof BinaryResourceDiffEditor) {
|
||||
const binaryEditors: BaseBinaryResourceEditor[] = [];
|
||||
if (activeEditorPane instanceof BinaryResourceDiffEditor) {
|
||||
const details = activeEditorPane.getDetailsEditorPane();
|
||||
if (details instanceof BaseBinaryResourceEditor) {
|
||||
binaryEditors.push(details);
|
||||
const primary = activeEditorPane.getPrimaryEditorPane();
|
||||
if (primary instanceof BaseBinaryResourceEditor) {
|
||||
binaryEditors.push(primary);
|
||||
}
|
||||
|
||||
const master = activeEditorPane.getMasterEditorPane();
|
||||
if (master instanceof BaseBinaryResourceEditor) {
|
||||
binaryEditors.push(master);
|
||||
const secondary = activeEditorPane.getSecondaryEditorPane();
|
||||
if (secondary instanceof BaseBinaryResourceEditor) {
|
||||
binaryEditors.push(secondary);
|
||||
}
|
||||
} else {
|
||||
binaryEditors.push(activeEditorPane);
|
||||
@@ -874,7 +874,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private onResourceEncodingChange(resource: URI): void {
|
||||
const activeEditorPane = this.editorService.activeEditorPane;
|
||||
if (activeEditorPane) {
|
||||
const activeResource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const activeResource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (activeResource && isEqual(activeResource, resource)) {
|
||||
const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeEditorPane.getControl()));
|
||||
|
||||
@@ -1067,7 +1067,7 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
|
||||
const textModel = activeTextEditorControl.getModel();
|
||||
const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null;
|
||||
const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }) : null;
|
||||
|
||||
let hasLanguageSupport = !!resource;
|
||||
if (resource?.scheme === Schemas.untitled && !this.textFileService.untitled.get(resource)?.hasAssociatedFilePath) {
|
||||
@@ -1164,7 +1164,7 @@ export class ChangeModeAction extends Action {
|
||||
let languageSelection: ILanguageSelection | undefined;
|
||||
if (pick === autoDetectMode) {
|
||||
if (textModel) {
|
||||
const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (resource) {
|
||||
languageSelection = this.modeService.createByFilepathOrFirstLine(resource, textModel.getLineContent(1));
|
||||
}
|
||||
@@ -1359,7 +1359,7 @@ export class ChangeEncodingAction extends Action {
|
||||
|
||||
await timeout(50); // quick input is sensitive to being opened so soon after another
|
||||
|
||||
const resource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (!resource || (!this.fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) {
|
||||
return; // encoding detection only possible for resources the file service can handle or that are untitled
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ export class EditorsObserver extends Disposable {
|
||||
}
|
||||
|
||||
private updateEditorResourcesMap(editor: IEditorInput, add: boolean): void {
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (!resource) {
|
||||
return; // require a resource
|
||||
}
|
||||
|
||||
@@ -28,7 +28,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 .title-actions .action-label:not(span),
|
||||
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(span) {
|
||||
display: flex;
|
||||
height: 35px;
|
||||
|
||||
@@ -67,10 +67,10 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this._register(addDisposableListener(titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, (e: MouseEvent) => this.onTitleAuxClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
|
||||
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleTap(e)));
|
||||
|
||||
// Context Menu
|
||||
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => {
|
||||
@@ -98,25 +98,21 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.group.pinEditor();
|
||||
}
|
||||
|
||||
private onTitleClick(e: MouseEvent | GestureEvent): void {
|
||||
if (e instanceof MouseEvent) {
|
||||
// Close editor on middle mouse click
|
||||
if (e.button === 1 /* Middle Button */) {
|
||||
EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */);
|
||||
private onTitleAuxClick(e: MouseEvent): void {
|
||||
if (e.button === 1 /* Middle Button */ && this.group.activeEditor) {
|
||||
EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */);
|
||||
|
||||
if (this.group.activeEditor) {
|
||||
this.group.closeEditor(this.group.activeEditor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO@rebornix
|
||||
// gesture tap should open the quick access
|
||||
// editorGroupView will focus on the editor again when there are mouse/pointer/touch down events
|
||||
// we need to wait a bit as `GesureEvent.Tap` is generated from `touchstart` and then `touchend` evnets, which are not an atom event.
|
||||
setTimeout(() => this.quickInputService.quickAccess.show(), 50);
|
||||
this.group.closeEditor(this.group.activeEditor);
|
||||
}
|
||||
}
|
||||
|
||||
private onTitleTap(e: GestureEvent): void {
|
||||
// TODO@rebornix gesture tap should open the quick access
|
||||
// editorGroupView will focus on the editor again when there are mouse/pointer/touch down events
|
||||
// we need to wait a bit as `GesureEvent.Tap` is generated from `touchstart` and then `touchend` evnets, which are not an atom event.
|
||||
setTimeout(() => this.quickInputService.quickAccess.show(), 50);
|
||||
}
|
||||
|
||||
getPreferredHeight(): number {
|
||||
return EDITOR_TITLE_HEIGHT;
|
||||
}
|
||||
|
||||
@@ -22,17 +22,16 @@ import { assertIsDefined } from 'vs/base/common/types';
|
||||
export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
static readonly ID: string = 'workbench.editor.sidebysideEditor';
|
||||
static MASTER: SideBySideEditor | undefined;
|
||||
|
||||
get minimumMasterWidth() { return this.masterEditorPane ? this.masterEditorPane.minimumWidth : 0; }
|
||||
get maximumMasterWidth() { return this.masterEditorPane ? this.masterEditorPane.maximumWidth : Number.POSITIVE_INFINITY; }
|
||||
get minimumMasterHeight() { return this.masterEditorPane ? this.masterEditorPane.minimumHeight : 0; }
|
||||
get maximumMasterHeight() { return this.masterEditorPane ? this.masterEditorPane.maximumHeight : Number.POSITIVE_INFINITY; }
|
||||
private get minimumPrimaryWidth() { return this.primaryEditorPane ? this.primaryEditorPane.minimumWidth : 0; }
|
||||
private get maximumPrimaryWidth() { return this.primaryEditorPane ? this.primaryEditorPane.maximumWidth : Number.POSITIVE_INFINITY; }
|
||||
private get minimumPrimaryHeight() { return this.primaryEditorPane ? this.primaryEditorPane.minimumHeight : 0; }
|
||||
private get maximumPrimaryHeight() { return this.primaryEditorPane ? this.primaryEditorPane.maximumHeight : Number.POSITIVE_INFINITY; }
|
||||
|
||||
get minimumDetailsWidth() { return this.detailsEditorPane ? this.detailsEditorPane.minimumWidth : 0; }
|
||||
get maximumDetailsWidth() { return this.detailsEditorPane ? this.detailsEditorPane.maximumWidth : Number.POSITIVE_INFINITY; }
|
||||
get minimumDetailsHeight() { return this.detailsEditorPane ? this.detailsEditorPane.minimumHeight : 0; }
|
||||
get maximumDetailsHeight() { return this.detailsEditorPane ? this.detailsEditorPane.maximumHeight : Number.POSITIVE_INFINITY; }
|
||||
private get minimumSecondaryWidth() { return this.secondaryEditorPane ? this.secondaryEditorPane.minimumWidth : 0; }
|
||||
private get maximumSecondaryWidth() { return this.secondaryEditorPane ? this.secondaryEditorPane.maximumWidth : Number.POSITIVE_INFINITY; }
|
||||
private get minimumSecondaryHeight() { return this.secondaryEditorPane ? this.secondaryEditorPane.minimumHeight : 0; }
|
||||
private get maximumSecondaryHeight() { return this.secondaryEditorPane ? this.secondaryEditorPane.maximumHeight : Number.POSITIVE_INFINITY; }
|
||||
|
||||
// these setters need to exist because this extends from BaseEditor
|
||||
set minimumWidth(value: number) { /* noop */ }
|
||||
@@ -40,16 +39,16 @@ export class SideBySideEditor extends BaseEditor {
|
||||
set minimumHeight(value: number) { /* noop */ }
|
||||
set maximumHeight(value: number) { /* noop */ }
|
||||
|
||||
get minimumWidth() { return this.minimumMasterWidth + this.minimumDetailsWidth; }
|
||||
get maximumWidth() { return this.maximumMasterWidth + this.maximumDetailsWidth; }
|
||||
get minimumHeight() { return this.minimumMasterHeight + this.minimumDetailsHeight; }
|
||||
get maximumHeight() { return this.maximumMasterHeight + this.maximumDetailsHeight; }
|
||||
get minimumWidth() { return this.minimumPrimaryWidth + this.minimumSecondaryWidth; }
|
||||
get maximumWidth() { return this.maximumPrimaryWidth + this.maximumSecondaryWidth; }
|
||||
get minimumHeight() { return this.minimumPrimaryHeight + this.minimumSecondaryHeight; }
|
||||
get maximumHeight() { return this.maximumPrimaryHeight + this.maximumSecondaryHeight; }
|
||||
|
||||
protected masterEditorPane?: BaseEditor;
|
||||
protected detailsEditorPane?: BaseEditor;
|
||||
protected primaryEditorPane?: BaseEditor;
|
||||
protected secondaryEditorPane?: BaseEditor;
|
||||
|
||||
private masterEditorContainer: HTMLElement | undefined;
|
||||
private detailsEditorContainer: HTMLElement | undefined;
|
||||
private primaryEditorContainer: HTMLElement | undefined;
|
||||
private secondaryEditorContainer: HTMLElement | undefined;
|
||||
|
||||
private splitview: SplitView | undefined;
|
||||
private dimension: DOM.Dimension = new DOM.Dimension(0, 0);
|
||||
@@ -74,19 +73,19 @@ export class SideBySideEditor extends BaseEditor {
|
||||
const splitview = this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL }));
|
||||
this._register(this.splitview.onDidSashReset(() => splitview.distributeViewSizes()));
|
||||
|
||||
this.detailsEditorContainer = DOM.$('.details-editor-container');
|
||||
this.secondaryEditorContainer = DOM.$('.secondary-editor-container');
|
||||
this.splitview.addView({
|
||||
element: this.detailsEditorContainer,
|
||||
layout: size => this.detailsEditorPane && this.detailsEditorPane.layout(new DOM.Dimension(size, this.dimension.height)),
|
||||
element: this.secondaryEditorContainer,
|
||||
layout: size => this.secondaryEditorPane && this.secondaryEditorPane.layout(new DOM.Dimension(size, this.dimension.height)),
|
||||
minimumSize: 220,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this.masterEditorContainer = DOM.$('.master-editor-container');
|
||||
this.primaryEditorContainer = DOM.$('.primary-editor-container');
|
||||
this.splitview.addView({
|
||||
element: this.masterEditorContainer,
|
||||
layout: size => this.masterEditorPane && this.masterEditorPane.layout(new DOM.Dimension(size, this.dimension.height)),
|
||||
element: this.primaryEditorContainer,
|
||||
layout: size => this.primaryEditorPane && this.primaryEditorPane.layout(new DOM.Dimension(size, this.dimension.height)),
|
||||
minimumSize: 220,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
@@ -103,30 +102,30 @@ export class SideBySideEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
setOptions(options: EditorOptions | undefined): void {
|
||||
if (this.masterEditorPane) {
|
||||
this.masterEditorPane.setOptions(options);
|
||||
if (this.primaryEditorPane) {
|
||||
this.primaryEditorPane.setOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
|
||||
if (this.masterEditorPane) {
|
||||
this.masterEditorPane.setVisible(visible, group);
|
||||
if (this.primaryEditorPane) {
|
||||
this.primaryEditorPane.setVisible(visible, group);
|
||||
}
|
||||
|
||||
if (this.detailsEditorPane) {
|
||||
this.detailsEditorPane.setVisible(visible, group);
|
||||
if (this.secondaryEditorPane) {
|
||||
this.secondaryEditorPane.setVisible(visible, group);
|
||||
}
|
||||
|
||||
super.setEditorVisible(visible, group);
|
||||
}
|
||||
|
||||
clearInput(): void {
|
||||
if (this.masterEditorPane) {
|
||||
this.masterEditorPane.clearInput();
|
||||
if (this.primaryEditorPane) {
|
||||
this.primaryEditorPane.clearInput();
|
||||
}
|
||||
|
||||
if (this.detailsEditorPane) {
|
||||
this.detailsEditorPane.clearInput();
|
||||
if (this.secondaryEditorPane) {
|
||||
this.secondaryEditorPane.clearInput();
|
||||
}
|
||||
|
||||
this.disposeEditors();
|
||||
@@ -135,8 +134,8 @@ export class SideBySideEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.masterEditorPane) {
|
||||
this.masterEditorPane.focus();
|
||||
if (this.primaryEditorPane) {
|
||||
this.primaryEditorPane.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,19 +147,19 @@ export class SideBySideEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
getControl(): IEditorControl | undefined {
|
||||
if (this.masterEditorPane) {
|
||||
return this.masterEditorPane.getControl();
|
||||
if (this.primaryEditorPane) {
|
||||
return this.primaryEditorPane.getControl();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getMasterEditorPane(): IEditorPane | undefined {
|
||||
return this.masterEditorPane;
|
||||
getPrimaryEditorPane(): IEditorPane | undefined {
|
||||
return this.primaryEditorPane;
|
||||
}
|
||||
|
||||
getDetailsEditorPane(): IEditorPane | undefined {
|
||||
return this.detailsEditorPane;
|
||||
getSecondaryEditorPane(): IEditorPane | undefined {
|
||||
return this.secondaryEditorPane;
|
||||
}
|
||||
|
||||
private async updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
@@ -172,21 +171,21 @@ export class SideBySideEditor extends BaseEditor {
|
||||
return this.setNewInput(newInput, options, token);
|
||||
}
|
||||
|
||||
if (!this.detailsEditorPane || !this.masterEditorPane) {
|
||||
if (!this.secondaryEditorPane || !this.primaryEditorPane) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.detailsEditorPane.setInput(newInput.details, undefined, token),
|
||||
this.masterEditorPane.setInput(newInput.master, options, token)
|
||||
this.secondaryEditorPane.setInput(newInput.secondary, undefined, token),
|
||||
this.primaryEditorPane.setInput(newInput.primary, options, token)
|
||||
]);
|
||||
}
|
||||
|
||||
private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
const detailsEditor = this.doCreateEditor(newInput.details, assertIsDefined(this.detailsEditorContainer));
|
||||
const masterEditor = this.doCreateEditor(newInput.master, assertIsDefined(this.masterEditorContainer));
|
||||
const secondaryEditor = this.doCreateEditor(newInput.secondary, assertIsDefined(this.secondaryEditorContainer));
|
||||
const primaryEditor = this.doCreateEditor(newInput.primary, assertIsDefined(this.primaryEditorContainer));
|
||||
|
||||
return this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options, token);
|
||||
return this.onEditorsCreated(secondaryEditor, primaryEditor, newInput.secondary, newInput.primary, options, token);
|
||||
}
|
||||
|
||||
private doCreateEditor(editorInput: EditorInput, container: HTMLElement): BaseEditor {
|
||||
@@ -202,48 +201,48 @@ export class SideBySideEditor extends BaseEditor {
|
||||
return editor;
|
||||
}
|
||||
|
||||
private async onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
this.detailsEditorPane = details;
|
||||
this.masterEditorPane = master;
|
||||
private async onEditorsCreated(secondary: BaseEditor, primary: BaseEditor, secondaryInput: EditorInput, primaryInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||
this.secondaryEditorPane = secondary;
|
||||
this.primaryEditorPane = primary;
|
||||
|
||||
this._onDidSizeConstraintsChange.input = Event.any(
|
||||
Event.map(details.onDidSizeConstraintsChange, () => undefined),
|
||||
Event.map(master.onDidSizeConstraintsChange, () => undefined)
|
||||
Event.map(secondary.onDidSizeConstraintsChange, () => undefined),
|
||||
Event.map(primary.onDidSizeConstraintsChange, () => undefined)
|
||||
);
|
||||
|
||||
this.onDidCreateEditors.fire(undefined);
|
||||
|
||||
await Promise.all([
|
||||
this.detailsEditorPane.setInput(detailsInput, undefined, token),
|
||||
this.masterEditorPane.setInput(masterInput, options, token)]
|
||||
this.secondaryEditorPane.setInput(secondaryInput, undefined, token),
|
||||
this.primaryEditorPane.setInput(primaryInput, options, token)]
|
||||
);
|
||||
}
|
||||
|
||||
updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
if (this.masterEditorContainer) {
|
||||
this.masterEditorContainer.style.boxShadow = `-6px 0 5px -5px ${this.getColor(scrollbarShadow)}`;
|
||||
if (this.primaryEditorContainer) {
|
||||
this.primaryEditorContainer.style.boxShadow = `-6px 0 5px -5px ${this.getColor(scrollbarShadow)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private disposeEditors(): void {
|
||||
if (this.detailsEditorPane) {
|
||||
this.detailsEditorPane.dispose();
|
||||
this.detailsEditorPane = undefined;
|
||||
if (this.secondaryEditorPane) {
|
||||
this.secondaryEditorPane.dispose();
|
||||
this.secondaryEditorPane = undefined;
|
||||
}
|
||||
|
||||
if (this.masterEditorPane) {
|
||||
this.masterEditorPane.dispose();
|
||||
this.masterEditorPane = undefined;
|
||||
if (this.primaryEditorPane) {
|
||||
this.primaryEditorPane.dispose();
|
||||
this.primaryEditorPane = undefined;
|
||||
}
|
||||
|
||||
if (this.detailsEditorContainer) {
|
||||
DOM.clearNode(this.detailsEditorContainer);
|
||||
if (this.secondaryEditorContainer) {
|
||||
DOM.clearNode(this.secondaryEditorContainer);
|
||||
}
|
||||
|
||||
if (this.masterEditorContainer) {
|
||||
DOM.clearNode(this.masterEditorContainer);
|
||||
if (this.primaryEditorContainer) {
|
||||
DOM.clearNode(this.primaryEditorContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -605,7 +605,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
|
||||
tab.blur();
|
||||
tab.blur(); // prevent flicker of focus outline on tab until editor got focus
|
||||
|
||||
if (e instanceof MouseEvent && e.button !== 0) {
|
||||
if (e.button === 1) {
|
||||
@@ -646,14 +646,17 @@ export class TabsTitleControl extends TitleControl {
|
||||
tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
// Prevent flicker of focus outline on tab until editor got focus
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => {
|
||||
EventHelper.stop(e);
|
||||
|
||||
tab.blur();
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
disposables.add(addDisposableListener(tab, EventType.AUXCLICK, (e: MouseEvent) => {
|
||||
if (e.button === 1 /* Middle Button*/) {
|
||||
e.stopPropagation(); // for https://github.com/Microsoft/vscode/issues/56715
|
||||
EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */);
|
||||
|
||||
this.blockRevealActiveTabOnce();
|
||||
this.closeOneEditorAction.run({ groupId: this.group.id, editorIndex: index });
|
||||
@@ -1100,7 +1103,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor)); // {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
|
||||
// Tests helper
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (resource) {
|
||||
tabContainer.setAttribute('data-resource-name', basenameOrAuthority(resource));
|
||||
} else {
|
||||
|
||||
@@ -192,7 +192,7 @@ export abstract class TitleControl extends Themable {
|
||||
secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/Microsoft/vscode/issues/16298
|
||||
) {
|
||||
const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar);
|
||||
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
|
||||
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions);
|
||||
|
||||
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
|
||||
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
|
||||
@@ -224,7 +224,7 @@ export abstract class TitleControl extends Themable {
|
||||
this.editorToolBarMenuDisposables.clear();
|
||||
|
||||
// Update contexts
|
||||
this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null);
|
||||
this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY })) : null);
|
||||
this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false);
|
||||
this.editorStickyContext.set(this.group.activeEditor ? this.group.isSticky(this.group.activeEditor) : false);
|
||||
|
||||
@@ -239,7 +239,7 @@ export abstract class TitleControl extends Themable {
|
||||
this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change
|
||||
}));
|
||||
|
||||
this.editorToolBarMenuDisposables.add(createAndFillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }));
|
||||
this.editorToolBarMenuDisposables.add(createAndFillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, (group: string) => group === 'navigation' || group === '1_run'));
|
||||
}
|
||||
|
||||
return { primary, secondary };
|
||||
@@ -247,7 +247,7 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
protected clearEditorActionsToolbar(): void {
|
||||
if (this.editorActionsToolbar) {
|
||||
this.editorActionsToolbar.setActions([], [])();
|
||||
this.editorActionsToolbar.setActions([], []);
|
||||
}
|
||||
|
||||
this.currentPrimaryEditorActionIds = [];
|
||||
@@ -299,7 +299,7 @@ export abstract class TitleControl extends Themable {
|
||||
}
|
||||
|
||||
protected doFillResourceDataTransfers(editor: IEditorInput, e: DragEvent): boolean {
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (!resource) {
|
||||
return false;
|
||||
}
|
||||
@@ -327,7 +327,7 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
// Update contexts based on editor picked and remember previous to restore
|
||||
const currentResourceContext = this.resourceContext.get();
|
||||
this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })));
|
||||
this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.PRIMARY })));
|
||||
const currentPinnedContext = !!this.editorPinnedContext.get();
|
||||
this.editorPinnedContext.set(this.group.isPinned(editor));
|
||||
const currentStickyContext = !!this.editorStickyContext.get();
|
||||
|
||||
@@ -109,18 +109,18 @@ export class NotificationsStatus extends Disposable {
|
||||
return localize('oneNotification', "1 New Notification");
|
||||
}
|
||||
|
||||
return localize('notifications', "{0} New Notifications", this.newNotificationsCount);
|
||||
return localize({ key: 'notifications', comment: ['{0} will be replaced by a number'] }, "{0} New Notifications", this.newNotificationsCount);
|
||||
}
|
||||
|
||||
if (this.newNotificationsCount === 0) {
|
||||
return localize('noNotificationsWithProgress', "No New Notifications ({0} in progress)", notificationsInProgress);
|
||||
return localize({ key: 'noNotificationsWithProgress', comment: ['{0} will be replaced by a number'] }, "No New Notifications ({0} in progress)", notificationsInProgress);
|
||||
}
|
||||
|
||||
if (this.newNotificationsCount === 1) {
|
||||
return localize('oneNotificationWithProgress', "1 New Notification ({0} in progress)", notificationsInProgress);
|
||||
return localize({ key: 'oneNotificationWithProgress', comment: ['{0} will be replaced by a number'] }, "1 New Notification ({0} in progress)", notificationsInProgress);
|
||||
}
|
||||
|
||||
return localize('notificationsWithProgress', "{0} New Notifications ({0} in progress)", this.newNotificationsCount, notificationsInProgress);
|
||||
return localize({ key: 'notificationsWithProgress', comment: ['{0} and {1} will be replaced by a number'] }, "{0} New Notifications ({1} in progress)", this.newNotificationsCount, notificationsInProgress);
|
||||
}
|
||||
|
||||
update(isCenterVisible: boolean, isToastsVisible: boolean): void {
|
||||
|
||||
@@ -307,9 +307,9 @@ export class NotificationTemplateRenderer extends Disposable {
|
||||
|
||||
// Container
|
||||
toggleClass(this.template.container, 'expanded', notification.expanded);
|
||||
this.inputDisposables.add(addDisposableListener(this.template.container, EventType.MOUSE_UP, e => {
|
||||
this.inputDisposables.add(addDisposableListener(this.template.container, EventType.AUXCLICK, e => {
|
||||
if (!notification.hasProgress && e.button === 1 /* Middle Button */) {
|
||||
EventHelper.stop(e);
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
notification.close();
|
||||
}
|
||||
|
||||
@@ -173,19 +173,6 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/** Actions */
|
||||
|
||||
.monaco-workbench .panel .monaco-action-bar .action-item.select-container {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench .panel .monaco-action-bar .action-item .monaco-select-box {
|
||||
cursor: pointer;
|
||||
min-width: 110px;
|
||||
min-height: 18px;
|
||||
padding: 2px 23px 2px 8px;
|
||||
}
|
||||
|
||||
/* Rotate icons when panel is on right */
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-split-horizontal,
|
||||
.monaco-workbench .part.panel.right .title-actions .codicon-panel-maximize,
|
||||
|
||||
@@ -271,7 +271,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
folder = workspace.folders[0];
|
||||
} else {
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
|
||||
if (resource) {
|
||||
folder = this.contextService.getWorkspaceFolder(resource);
|
||||
}
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* File icons in trees */
|
||||
|
||||
.file-icon-themable-tree.align-icons-and-twisties .monaco-tl-twistie:not(.force-twistie):not(.collapsible),
|
||||
.file-icon-themable-tree .align-icon-with-twisty .monaco-tl-twistie:not(.force-twistie):not(.collapsible),
|
||||
.file-icon-themable-tree.hide-arrows .monaco-tl-twistie:not(.force-twistie) {
|
||||
background-image: none !important;
|
||||
width: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Misc */
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message {
|
||||
display: flex;
|
||||
padding: 4px 12px 4px 18px;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message p {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message ul {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .customview-tree {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .customview-tree.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane > .pane-body > .welcome-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane > .pane-body:not(.welcome) > .welcome-view,
|
||||
.monaco-workbench .pane > .pane-body.welcome > :not(.welcome-view) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane > .pane-body > .welcome-view .monaco-button {
|
||||
max-width: 260px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane > .pane-body .welcome-view-content {
|
||||
padding: 0 20px 0 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane > .pane-body .welcome-view-content > * {
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list-row .monaco-tl-contents.align-icon-with-twisty::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list-row .monaco-tl-contents:not(.align-icon-with-twisty)::before {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row {
|
||||
padding-right: 12px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex-wrap: nowrap;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-inputbox {
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
||||
background-size: 16px;
|
||||
background-position: left center;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 6px;
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.codicon {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row.selected .custom-view-tree-node-item > .custom-view-tree-node-item-icon.codicon {
|
||||
color: currentColor !important;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-icon-label-container > .monaco-icon-name-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel::after {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .monaco-list-row:hover .custom-view-tree-node-item .actions,
|
||||
.customview-tree .monaco-list .monaco-list-row.selected .custom-view-tree-node-item .actions,
|
||||
.customview-tree .monaco-list .monaco-list-row.focused .custom-view-tree-node-item .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-size: 16px;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -3,44 +3,18 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { dirname, basename } from 'vs/base/common/resources';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
@@ -81,10 +55,7 @@ export class TreeViewPane extends ViewPane {
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
if (this.treeView instanceof TreeView) {
|
||||
this.treeView.show(container);
|
||||
}
|
||||
this.treeView.show(container);
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
@@ -104,935 +75,3 @@ export class TreeViewPane extends ViewPane {
|
||||
this.treeView.setVisibility(this.isBodyVisible());
|
||||
}
|
||||
}
|
||||
|
||||
class Root implements ITreeItem {
|
||||
label = { label: 'root' };
|
||||
handle = '0';
|
||||
parentHandle: string | undefined = undefined;
|
||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
children: ITreeItem[] | undefined = undefined;
|
||||
}
|
||||
|
||||
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");
|
||||
|
||||
class Tree extends WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> { }
|
||||
|
||||
export class TreeView extends Disposable implements ITreeView {
|
||||
|
||||
private isVisible: boolean = false;
|
||||
private _hasIconForParentNode = false;
|
||||
private _hasIconForLeafNode = false;
|
||||
|
||||
private readonly collapseAllContextKey: RawContextKey<boolean>;
|
||||
private readonly collapseAllContext: IContextKey<boolean>;
|
||||
private readonly refreshContextKey: RawContextKey<boolean>;
|
||||
private readonly refreshContext: IContextKey<boolean>;
|
||||
|
||||
private focused: boolean = false;
|
||||
private domNode!: HTMLElement;
|
||||
private treeContainer!: HTMLElement;
|
||||
private _messageValue: string | undefined;
|
||||
private _canSelectMany: boolean = false;
|
||||
private messageElement!: HTMLDivElement;
|
||||
private tree: Tree | undefined;
|
||||
private treeLabels: ResourceLabels | undefined;
|
||||
|
||||
private root: ITreeItem;
|
||||
private elementsToRefresh: ITreeItem[] = [];
|
||||
|
||||
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
|
||||
|
||||
private readonly _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||
readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;
|
||||
|
||||
private _onDidChangeSelection: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
|
||||
readonly onDidChangeSelection: Event<ITreeItem[]> = this._onDidChangeSelection.event;
|
||||
|
||||
private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
|
||||
|
||||
private readonly _onDidChangeActions: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeActions: Event<void> = this._onDidChangeActions.event;
|
||||
|
||||
private readonly _onDidChangeWelcomeState: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeWelcomeState: Event<void> = this._onDidChangeWelcomeState.event;
|
||||
|
||||
private readonly _onDidChangeTitle: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidChangeTitle: Event<string> = this._onDidChangeTitle.event;
|
||||
|
||||
private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
private _title: string,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProgressService protected readonly progressService: IProgressService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
this.root = new Root();
|
||||
this.collapseAllContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableCollapseAll`, false);
|
||||
this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService);
|
||||
this.refreshContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableRefresh`, false);
|
||||
this.refreshContext = this.refreshContextKey.bindTo(contextKeyService);
|
||||
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('explorer.decorations')) {
|
||||
this.doRefresh([this.root]); /** soft refresh **/
|
||||
}
|
||||
}));
|
||||
this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => {
|
||||
if (views.some(v => v.id === this.id)) {
|
||||
this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } });
|
||||
}
|
||||
}));
|
||||
this.registerActions();
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
get viewContainer(): ViewContainer {
|
||||
return this.viewDescriptorService.getViewContainerByViewId(this.id)!;
|
||||
}
|
||||
|
||||
get viewLocation(): ViewContainerLocation {
|
||||
return this.viewDescriptorService.getViewLocationById(this.id)!;
|
||||
}
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider | undefined;
|
||||
get dataProvider(): ITreeViewDataProvider | undefined {
|
||||
return this._dataProvider;
|
||||
}
|
||||
|
||||
set dataProvider(dataProvider: ITreeViewDataProvider | undefined) {
|
||||
if (this.tree === undefined) {
|
||||
this.createTree();
|
||||
}
|
||||
|
||||
if (dataProvider) {
|
||||
this._dataProvider = new class implements ITreeViewDataProvider {
|
||||
private _isEmpty: boolean = true;
|
||||
private _onDidChangeEmpty: Emitter<void> = new Emitter();
|
||||
public onDidChangeEmpty: Event<void> = this._onDidChangeEmpty.event;
|
||||
|
||||
get isTreeEmpty(): boolean {
|
||||
return this._isEmpty;
|
||||
}
|
||||
|
||||
async getChildren(node: ITreeItem): Promise<ITreeItem[]> {
|
||||
let children: ITreeItem[];
|
||||
if (node && node.children) {
|
||||
children = node.children;
|
||||
} else {
|
||||
children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node));
|
||||
node.children = children;
|
||||
}
|
||||
if (node instanceof Root) {
|
||||
const oldEmpty = this._isEmpty;
|
||||
this._isEmpty = children.length === 0;
|
||||
if (oldEmpty !== this._isEmpty) {
|
||||
this._onDidChangeEmpty.fire();
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
};
|
||||
if (this._dataProvider.onDidChangeEmpty) {
|
||||
this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire()));
|
||||
}
|
||||
this.updateMessage();
|
||||
this.refresh();
|
||||
} else {
|
||||
this._dataProvider = undefined;
|
||||
this.updateMessage();
|
||||
}
|
||||
|
||||
this._onDidChangeWelcomeState.fire();
|
||||
}
|
||||
|
||||
private _message: string | undefined;
|
||||
get message(): string | undefined {
|
||||
return this._message;
|
||||
}
|
||||
|
||||
set message(message: string | undefined) {
|
||||
this._message = message;
|
||||
this.updateMessage();
|
||||
this._onDidChangeWelcomeState.fire();
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(name: string) {
|
||||
this._title = name;
|
||||
this._onDidChangeTitle.fire(this._title);
|
||||
}
|
||||
|
||||
get canSelectMany(): boolean {
|
||||
return this._canSelectMany;
|
||||
}
|
||||
|
||||
set canSelectMany(canSelectMany: boolean) {
|
||||
this._canSelectMany = canSelectMany;
|
||||
}
|
||||
|
||||
get hasIconForParentNode(): boolean {
|
||||
return this._hasIconForParentNode;
|
||||
}
|
||||
|
||||
get hasIconForLeafNode(): boolean {
|
||||
return this._hasIconForLeafNode;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
get showCollapseAllAction(): boolean {
|
||||
return !!this.collapseAllContext.get();
|
||||
}
|
||||
|
||||
set showCollapseAllAction(showCollapseAllAction: boolean) {
|
||||
this.collapseAllContext.set(showCollapseAllAction);
|
||||
}
|
||||
|
||||
get showRefreshAction(): boolean {
|
||||
return !!this.refreshContext.get();
|
||||
}
|
||||
|
||||
set showRefreshAction(showRefreshAction: boolean) {
|
||||
this.refreshContext.set(showRefreshAction);
|
||||
}
|
||||
|
||||
private registerActions() {
|
||||
const that = this;
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.treeView.${that.id}.refresh`,
|
||||
title: localize('refresh', "Refresh"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey),
|
||||
group: 'navigation',
|
||||
order: Number.MAX_SAFE_INTEGER - 1,
|
||||
},
|
||||
icon: { id: 'codicon/refresh' }
|
||||
});
|
||||
}
|
||||
async run(): Promise<void> {
|
||||
return that.refresh();
|
||||
}
|
||||
}));
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.treeView.${that.id}.collapseAll`,
|
||||
title: localize('collapseAll', "Collapse All"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey),
|
||||
group: 'navigation',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
icon: { id: 'codicon/collapse-all' }
|
||||
});
|
||||
}
|
||||
async run(): Promise<void> {
|
||||
if (that.tree) {
|
||||
return new CollapseAllAction<ITreeItem, ITreeItem, FuzzyScore>(that.tree, true).run();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
isVisible = !!isVisible;
|
||||
if (this.isVisible === isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isVisible = isVisible;
|
||||
|
||||
if (this.tree) {
|
||||
if (this.isVisible) {
|
||||
DOM.show(this.tree.getHTMLElement());
|
||||
} else {
|
||||
DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it
|
||||
}
|
||||
|
||||
if (this.isVisible && this.elementsToRefresh.length) {
|
||||
this.doRefresh(this.elementsToRefresh);
|
||||
this.elementsToRefresh = [];
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChangeVisibility.fire(this.isVisible);
|
||||
}
|
||||
|
||||
focus(reveal: boolean = true): void {
|
||||
if (this.tree && this.root.children && this.root.children.length > 0) {
|
||||
// Make sure the current selected element is revealed
|
||||
const selectedElement = this.tree.getSelection()[0];
|
||||
if (selectedElement && reveal) {
|
||||
this.tree.reveal(selectedElement, 0.5);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.domFocus();
|
||||
} else if (this.tree) {
|
||||
this.tree.domFocus();
|
||||
} else {
|
||||
this.domNode.focus();
|
||||
}
|
||||
}
|
||||
|
||||
show(container: HTMLElement): void {
|
||||
DOM.append(container, this.domNode);
|
||||
}
|
||||
|
||||
private create() {
|
||||
this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
|
||||
this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
|
||||
this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
|
||||
DOM.addClass(this.treeContainer, 'file-icon-themable-tree');
|
||||
DOM.addClass(this.treeContainer, 'show-file-icons');
|
||||
const focusTracker = this._register(DOM.trackFocus(this.domNode));
|
||||
this._register(focusTracker.onDidFocus(() => this.focused = true));
|
||||
this._register(focusTracker.onDidBlur(() => this.focused = false));
|
||||
}
|
||||
|
||||
private createTree() {
|
||||
const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
|
||||
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
|
||||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
|
||||
const aligner = new Aligner(this.themeService);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);
|
||||
const widgetAriaLabel = this._title;
|
||||
|
||||
this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer],
|
||||
dataSource, {
|
||||
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 ?? 'treeitem';
|
||||
},
|
||||
getWidgetAriaLabel(): string {
|
||||
return widgetAriaLabel;
|
||||
}
|
||||
},
|
||||
keyboardNavigationLabelProvider: {
|
||||
getKeyboardNavigationLabel: (item: ITreeItem) => {
|
||||
return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined);
|
||||
}
|
||||
},
|
||||
expandOnlyOnTwistieClick: (e: ITreeItem) => !!e.command,
|
||||
collapseByDefault: (e: ITreeItem): boolean => {
|
||||
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
|
||||
},
|
||||
multipleSelectionSupport: this.canSelectMany,
|
||||
overrideStyles: {
|
||||
listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND
|
||||
}
|
||||
}) as WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>);
|
||||
aligner.tree = this.tree;
|
||||
const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection());
|
||||
renderer.actionRunner = actionRunner;
|
||||
|
||||
this.tree.contextKeyService.createKey<boolean>(this.id, true);
|
||||
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
|
||||
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
|
||||
this._register(this.tree.onDidChangeCollapseState(e => {
|
||||
if (!e.node.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element;
|
||||
if (e.node.collapsed) {
|
||||
this._onDidCollapseItem.fire(element);
|
||||
} else {
|
||||
this._onDidExpandItem.fire(element);
|
||||
}
|
||||
}));
|
||||
this.tree.setInput(this.root).then(() => this.updateContentAreas());
|
||||
|
||||
this._register(this.tree.onDidOpen(e => {
|
||||
if (!e.browserEvent) {
|
||||
return;
|
||||
}
|
||||
const selection = this.tree!.getSelection();
|
||||
if ((selection.length === 1) && selection[0].command) {
|
||||
this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || []));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>, actionRunner: MultipleSelectionActionRunner): void {
|
||||
const node: ITreeItem | null = treeEvent.element;
|
||||
if (node === null) {
|
||||
return;
|
||||
}
|
||||
const event: UIEvent = treeEvent.browserEvent;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.tree!.setFocus([node]);
|
||||
const actions = treeMenus.getResourceContextActions(node);
|
||||
if (!actions.length) {
|
||||
return;
|
||||
}
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => treeEvent.anchor,
|
||||
|
||||
getActions: () => actions,
|
||||
|
||||
getActionViewItem: (action) => {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(action.id);
|
||||
if (keybinding) {
|
||||
return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
this.tree!.domFocus();
|
||||
}
|
||||
},
|
||||
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.id, $treeItemHandle: node.handle }),
|
||||
|
||||
actionRunner
|
||||
});
|
||||
}
|
||||
|
||||
protected updateMessage(): void {
|
||||
if (this._message) {
|
||||
this.showMessage(this._message);
|
||||
} else if (!this.dataProvider) {
|
||||
this.showMessage(noDataProviderMessage);
|
||||
} else {
|
||||
this.hideMessage();
|
||||
}
|
||||
this.updateContentAreas();
|
||||
}
|
||||
|
||||
private showMessage(message: string): void {
|
||||
DOM.removeClass(this.messageElement, 'hide');
|
||||
this.resetMessageElement();
|
||||
this._messageValue = message;
|
||||
if (!isFalsyOrWhitespace(this._message)) {
|
||||
this.messageElement.textContent = this._messageValue;
|
||||
}
|
||||
this.layout(this._height, this._width);
|
||||
}
|
||||
|
||||
private hideMessage(): void {
|
||||
this.resetMessageElement();
|
||||
DOM.addClass(this.messageElement, 'hide');
|
||||
this.layout(this._height, this._width);
|
||||
}
|
||||
|
||||
private resetMessageElement(): void {
|
||||
DOM.clearNode(this.messageElement);
|
||||
}
|
||||
|
||||
private _height: number = 0;
|
||||
private _width: number = 0;
|
||||
layout(height: number, width: number) {
|
||||
if (height && width) {
|
||||
this._height = height;
|
||||
this._width = width;
|
||||
const treeHeight = height - DOM.getTotalHeight(this.messageElement);
|
||||
this.treeContainer.style.height = treeHeight + 'px';
|
||||
if (this.tree) {
|
||||
this.tree.layout(treeHeight, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
if (this.tree) {
|
||||
const parentNode = this.tree.getHTMLElement();
|
||||
const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
return DOM.getLargestChildWidth(parentNode, childNodes);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async refresh(elements?: ITreeItem[]): Promise<void> {
|
||||
if (this.dataProvider && this.tree) {
|
||||
if (this.refreshing) {
|
||||
await Event.toPromise(this._onDidCompleteRefresh.event);
|
||||
}
|
||||
if (!elements) {
|
||||
elements = [this.root];
|
||||
// remove all waiting elements to refresh if root is asked to refresh
|
||||
this.elementsToRefresh = [];
|
||||
}
|
||||
for (const element of elements) {
|
||||
element.children = undefined; // reset children
|
||||
}
|
||||
if (this.isVisible) {
|
||||
return this.doRefresh(elements);
|
||||
} else {
|
||||
if (this.elementsToRefresh.length) {
|
||||
const seen: Set<string> = new Set<string>();
|
||||
this.elementsToRefresh.forEach(element => seen.add(element.handle));
|
||||
for (const element of elements) {
|
||||
if (!seen.has(element.handle)) {
|
||||
this.elementsToRefresh.push(element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.elementsToRefresh.push(...elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
|
||||
const tree = this.tree;
|
||||
if (tree) {
|
||||
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
||||
await Promise.all(itemOrItems.map(element => {
|
||||
return tree.expand(element, false);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
setSelection(items: ITreeItem[]): void {
|
||||
if (this.tree) {
|
||||
this.tree.setSelection(items);
|
||||
}
|
||||
}
|
||||
|
||||
setFocus(item: ITreeItem): void {
|
||||
if (this.tree) {
|
||||
this.focus();
|
||||
this.tree.setFocus([item]);
|
||||
}
|
||||
}
|
||||
|
||||
async reveal(item: ITreeItem): Promise<void> {
|
||||
if (this.tree) {
|
||||
return this.tree.reveal(item);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshing: boolean = false;
|
||||
private async doRefresh(elements: ITreeItem[]): Promise<void> {
|
||||
const tree = this.tree;
|
||||
if (tree && this.visible) {
|
||||
this.refreshing = true;
|
||||
await Promise.all(elements.map(element => tree.updateChildren(element, true, true)));
|
||||
this.refreshing = false;
|
||||
this._onDidCompleteRefresh.fire();
|
||||
this.updateContentAreas();
|
||||
if (this.focused) {
|
||||
this.focus(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateContentAreas(): void {
|
||||
const isTreeEmpty = !this.root.children || this.root.children.length === 0;
|
||||
// Hide tree container only when there is a message and tree is empty and not refreshing
|
||||
if (this._messageValue && isTreeEmpty && !this.refreshing) {
|
||||
DOM.addClass(this.treeContainer, 'hide');
|
||||
this.domNode.setAttribute('tabindex', '0');
|
||||
} else {
|
||||
DOM.removeClass(this.treeContainer, 'hide');
|
||||
this.domNode.removeAttribute('tabindex');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TreeViewIdentityProvider implements IIdentityProvider<ITreeItem> {
|
||||
getId(element: ITreeItem): { toString(): string; } {
|
||||
return element.handle;
|
||||
}
|
||||
}
|
||||
|
||||
class TreeViewDelegate implements IListVirtualDelegate<ITreeItem> {
|
||||
|
||||
getHeight(element: ITreeItem): number {
|
||||
return TreeRenderer.ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
getTemplateId(element: ITreeItem): string {
|
||||
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||
}
|
||||
}
|
||||
|
||||
class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
|
||||
|
||||
constructor(
|
||||
private treeView: ITreeView,
|
||||
private withProgress: <T>(task: Promise<T>) => Promise<T>
|
||||
) {
|
||||
}
|
||||
|
||||
hasChildren(element: ITreeItem): boolean {
|
||||
return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None);
|
||||
}
|
||||
|
||||
async getChildren(element: ITreeItem): Promise<ITreeItem[]> {
|
||||
if (this.treeView.dataProvider) {
|
||||
return this.withProgress(this.treeView.dataProvider.getChildren(element));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// todo@joh,sandy make this proper and contributable from extensions
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
const matchBackgroundColor = theme.getColor(listFilterMatchHighlight);
|
||||
if (matchBackgroundColor) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
|
||||
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
|
||||
}
|
||||
const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder);
|
||||
if (matchBorderColor) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
|
||||
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
|
||||
}
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focusBorderColor}; outline-offset: -1px; }`);
|
||||
}
|
||||
const codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
if (codeBackground) {
|
||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
interface ITreeExplorerTemplateData {
|
||||
elementDisposable: IDisposable;
|
||||
container: HTMLElement;
|
||||
resourceLabel: IResourceLabel;
|
||||
icon: HTMLElement;
|
||||
actionBar: ActionBar;
|
||||
}
|
||||
|
||||
class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyScore, ITreeExplorerTemplateData> {
|
||||
static readonly ITEM_HEIGHT = 22;
|
||||
static readonly TREE_TEMPLATE_ID = 'treeExplorer';
|
||||
|
||||
private _actionRunner: MultipleSelectionActionRunner | undefined;
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: TreeMenus,
|
||||
private labels: ResourceLabels,
|
||||
private actionViewItemProvider: IActionViewItemProvider,
|
||||
private aligner: Aligner,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILabelService private readonly labelService: ILabelService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get templateId(): string {
|
||||
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
set actionRunner(actionRunner: MultipleSelectionActionRunner) {
|
||||
this._actionRunner = actionRunner;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
|
||||
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||
|
||||
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
|
||||
const resourceLabel = this.labels.create(container, { supportHighlights: true });
|
||||
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
|
||||
const actionBar = new ActionBar(actionsContainer, {
|
||||
actionViewItemProvider: this.actionViewItemProvider
|
||||
});
|
||||
|
||||
return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.elementDisposable.dispose();
|
||||
const node = element.element;
|
||||
const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
|
||||
const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined;
|
||||
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 (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;
|
||||
end = swap;
|
||||
}
|
||||
return ({ start, end });
|
||||
}) : undefined;
|
||||
const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
const iconUrl = icon ? URI.revive(icon) : null;
|
||||
const title = node.tooltip ? node.tooltip : resource ? undefined : label;
|
||||
|
||||
// reset
|
||||
templateData.actionBar.clear();
|
||||
|
||||
if (resource || this.isFileKindThemeIcon(node.themeIcon)) {
|
||||
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
||||
templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
|
||||
} else {
|
||||
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
|
||||
}
|
||||
|
||||
templateData.icon.title = title ? title : '';
|
||||
|
||||
if (iconUrl) {
|
||||
templateData.icon.className = 'custom-view-tree-node-item-icon';
|
||||
templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl);
|
||||
|
||||
} else {
|
||||
let iconClass: string | undefined;
|
||||
if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) {
|
||||
iconClass = ThemeIcon.asClassName(node.themeIcon);
|
||||
}
|
||||
templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : '';
|
||||
templateData.icon.style.backgroundImage = '';
|
||||
}
|
||||
|
||||
templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
|
||||
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
||||
if (this._actionRunner) {
|
||||
templateData.actionBar.actionRunner = this._actionRunner;
|
||||
}
|
||||
this.setAlignment(templateData.container, node);
|
||||
templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node)));
|
||||
}
|
||||
|
||||
private setAlignment(container: HTMLElement, treeItem: ITreeItem) {
|
||||
DOM.toggleClass(container.parentElement!, 'align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
|
||||
}
|
||||
|
||||
private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean {
|
||||
if (icon) {
|
||||
return icon.id === FileThemeIcon.id || icon.id === FolderThemeIcon.id;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private getFileKind(node: ITreeItem): FileKind {
|
||||
if (node.themeIcon) {
|
||||
switch (node.themeIcon.id) {
|
||||
case FileThemeIcon.id:
|
||||
return FileKind.FILE;
|
||||
case FolderThemeIcon.id:
|
||||
return FileKind.FOLDER;
|
||||
}
|
||||
}
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
|
||||
}
|
||||
|
||||
disposeElement(resource: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.elementDisposable.dispose();
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.resourceLabel.dispose();
|
||||
templateData.actionBar.dispose();
|
||||
templateData.elementDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class Aligner extends Disposable {
|
||||
private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> | undefined;
|
||||
|
||||
constructor(private themeService: IThemeService) {
|
||||
super();
|
||||
}
|
||||
|
||||
set tree(tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>) {
|
||||
this._tree = tree;
|
||||
}
|
||||
|
||||
public alignIconWithTwisty(treeItem: ITreeItem): boolean {
|
||||
if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
|
||||
return false;
|
||||
}
|
||||
if (!this.hasIcon(treeItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._tree) {
|
||||
const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
|
||||
if (this.hasIcon(parent)) {
|
||||
return false;
|
||||
}
|
||||
return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private hasIcon(node: ITreeItem): boolean {
|
||||
const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
if (icon) {
|
||||
return true;
|
||||
}
|
||||
if (node.resourceUri || node.themeIcon) {
|
||||
const fileIconTheme = this.themeService.getFileIconTheme();
|
||||
const isFolder = node.themeIcon ? node.themeIcon.id === FolderThemeIcon.id : node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||
if (isFolder) {
|
||||
return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons;
|
||||
}
|
||||
return fileIconTheme.hasFileIcons;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
|
||||
constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) {
|
||||
super();
|
||||
this._register(this.onDidRun(e => {
|
||||
if (e.error) {
|
||||
notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> {
|
||||
const selection = this.getSelectedResources();
|
||||
let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined;
|
||||
let actionInSelected: boolean = false;
|
||||
if (selection.length > 1) {
|
||||
selectionHandleArgs = selection.map(selected => {
|
||||
if (selected.handle === context.$treeItemHandle) {
|
||||
actionInSelected = true;
|
||||
}
|
||||
return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle };
|
||||
});
|
||||
}
|
||||
|
||||
if (!actionInSelected) {
|
||||
selectionHandleArgs = undefined;
|
||||
}
|
||||
|
||||
return action.run(...[context, selectionHandleArgs]);
|
||||
}
|
||||
}
|
||||
|
||||
class TreeMenus extends Disposable implements IDisposable {
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getResourceActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary;
|
||||
}
|
||||
|
||||
getResourceContextActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary;
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
|
||||
const menu = this.menuService.createMenu(menuId, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
|
||||
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomTreeView extends TreeView {
|
||||
|
||||
private activated: boolean = false;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
title: string,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
) {
|
||||
super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, contextKeyService);
|
||||
}
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
super.setVisibility(isVisible);
|
||||
if (this.visible) {
|
||||
this.activate();
|
||||
}
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||
.then(() => timeout(2000))
|
||||
.then(() => {
|
||||
this.updateMessage();
|
||||
});
|
||||
this.activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ export abstract class ViewPane extends Pane implements IView {
|
||||
|
||||
private setActions(): void {
|
||||
if (this.toolbar) {
|
||||
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))();
|
||||
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()));
|
||||
this.toolbar.context = this.getActionsContext();
|
||||
}
|
||||
}
|
||||
@@ -1040,14 +1040,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
}
|
||||
}
|
||||
|
||||
const viewToggleActions = this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => (<IAction>{
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
|
||||
enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1),
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
|
||||
const viewToggleActions = this.getViewsVisibilityActions();
|
||||
if (result.length && viewToggleActions.length) {
|
||||
result.push(new Separator());
|
||||
}
|
||||
@@ -1073,6 +1066,16 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||
return [];
|
||||
}
|
||||
|
||||
getViewsVisibilityActions(): IAction[] {
|
||||
return this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => (<IAction>{
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
|
||||
enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1),
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (this.isViewMergedWithContainer()) {
|
||||
return this.paneItems[0].pane.getActionViewItem(action);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IViewDescriptorService, ViewContainer, IViewDescriptor, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
@@ -159,7 +158,7 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
constructor() {
|
||||
super({
|
||||
id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`,
|
||||
title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) },
|
||||
title: { original: `Focus on ${viewDescriptor.name} View`, value: localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name) },
|
||||
category: composite ? composite.name : localize('view category', "View"),
|
||||
menu: [{
|
||||
id: MenuId.CommandPalette,
|
||||
|
||||
@@ -139,4 +139,8 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
|
||||
}
|
||||
|
||||
abstract getTitle(): string;
|
||||
|
||||
getViewsVisibilityActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export abstract class Viewlet extends PaneComposite implements IViewlet {
|
||||
|
||||
@@ -43,6 +45,10 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
|
||||
@IConfigurationService protected configurationService: IConfigurationService
|
||||
) {
|
||||
super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews)(() => {
|
||||
// Update title area since there is no better way to update secondary actions
|
||||
this.updateTitleArea();
|
||||
}));
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
@@ -60,6 +66,24 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
|
||||
run: () => this.layoutService.setSideBarHidden(true)
|
||||
}];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
const viewSecondaryActions = this.viewPaneContainer.getViewsVisibilityActions();
|
||||
const secondaryActions = this.viewPaneContainer.getSecondaryActions();
|
||||
if (viewSecondaryActions.length <= 1) {
|
||||
return secondaryActions;
|
||||
}
|
||||
|
||||
if (secondaryActions.length === 0) {
|
||||
return viewSecondaryActions;
|
||||
}
|
||||
|
||||
return [
|
||||
new ContextSubMenu(nls.localize('views', "Views"), viewSecondaryActions),
|
||||
new Separator(),
|
||||
...secondaryActions
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IFileService, IFileSystemProvider } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -43,14 +43,13 @@ import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { FileLogService } from 'vs/platform/log/common/fileLogService';
|
||||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider';
|
||||
import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider';
|
||||
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
|
||||
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IndexedDB, INDEXEDDB_LOGS_OBJECT_STORE, INDEXEDDB_USERDATA_OBJECT_STORE } from 'vs/platform/files/browser/indexedDBFileSystemProvider';
|
||||
|
||||
class BrowserMain extends Disposable {
|
||||
|
||||
@@ -178,7 +177,7 @@ class BrowserMain extends Disposable {
|
||||
// Files
|
||||
const fileService = this._register(new FileService(logService));
|
||||
serviceCollection.set(IFileService, fileService);
|
||||
this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath);
|
||||
await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath);
|
||||
|
||||
// Long running services (workspace, config, storage)
|
||||
const services = await Promise.all([
|
||||
@@ -205,24 +204,22 @@ class BrowserMain extends Disposable {
|
||||
return { serviceCollection, logService, storageService: services[1] };
|
||||
}
|
||||
|
||||
private registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): void {
|
||||
private async registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): Promise<void> {
|
||||
|
||||
const indexedDB = new IndexedDB();
|
||||
|
||||
// Logger
|
||||
(async () => {
|
||||
if (browser.isEdge) {
|
||||
fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme));
|
||||
let indexedDBLogProvider: IFileSystemProvider | null = null;
|
||||
try {
|
||||
indexedDBLogProvider = await indexedDB.createFileSystemProvider(logsPath.scheme, INDEXEDDB_LOGS_OBJECT_STORE);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (indexedDBLogProvider) {
|
||||
fileService.registerProvider(logsPath.scheme, indexedDBLogProvider);
|
||||
} else {
|
||||
try {
|
||||
const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme);
|
||||
await indexedDBLogProvider.database;
|
||||
|
||||
fileService.registerProvider(logsPath.scheme, indexedDBLogProvider);
|
||||
} catch (error) {
|
||||
logService.info('Error while creating indexedDB log provider. Falling back to in-memory log provider.');
|
||||
logService.error(error);
|
||||
|
||||
fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme));
|
||||
}
|
||||
fileService.registerProvider(logsPath.scheme, new InMemoryFileSystemProvider());
|
||||
}
|
||||
|
||||
logService.logger = new MultiplexLogService(coalesce([
|
||||
@@ -250,8 +247,19 @@ class BrowserMain extends Disposable {
|
||||
|
||||
// User data
|
||||
if (!this.configuration.userDataProvider) {
|
||||
this.configuration.userDataProvider = this._register(new InMemoryFileSystemProvider());
|
||||
let indexedDBUserDataProvider: IFileSystemProvider | null = null;
|
||||
try {
|
||||
indexedDBUserDataProvider = await indexedDB.createFileSystemProvider(Schemas.userData, INDEXEDDB_USERDATA_OBJECT_STORE);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (indexedDBUserDataProvider) {
|
||||
this.configuration.userDataProvider = indexedDBUserDataProvider;
|
||||
} else {
|
||||
this.configuration.userDataProvider = new InMemoryFileSystemProvider();
|
||||
}
|
||||
}
|
||||
|
||||
fileService.registerProvider(Schemas.userData, this.configuration.userDataProvider);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user