diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index b82c6e95660..cca41a8fbf9 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -2031,10 +2031,12 @@ export function getCookieValue(name: string): string | undefined { } export interface IDragAndDropObserverCallbacks { - readonly onDragEnter: (e: DragEvent) => void; - readonly onDragLeave: (e: DragEvent) => void; - readonly onDrop: (e: DragEvent) => void; - readonly onDragEnd: (e: DragEvent) => void; + readonly onDragEnter?: (e: DragEvent) => void; + readonly onDragLeave?: (e: DragEvent) => void; + readonly onDrop?: (e: DragEvent) => void; + readonly onDragEnd?: (e: DragEvent) => void; + readonly onDragStart?: (e: DragEvent) => void; + readonly onDrag?: (e: DragEvent) => void; readonly onDragOver?: (e: DragEvent, dragDuration: number) => void; } @@ -2056,11 +2058,23 @@ export class DragAndDropObserver extends Disposable { } private registerListeners(): void { + if (this.callbacks.onDragStart) { + this._register(addDisposableListener(this.element, EventType.DRAG_START, (e: DragEvent) => { + this.callbacks.onDragStart?.(e); + })); + } + + if (this.callbacks.onDrag) { + this._register(addDisposableListener(this.element, EventType.DRAG, (e: DragEvent) => { + this.callbacks.onDrag?.(e); + })); + } + this._register(addDisposableListener(this.element, EventType.DRAG_ENTER, (e: DragEvent) => { this.counter++; this.dragStartTime = e.timeStamp; - this.callbacks.onDragEnter(e); + this.callbacks.onDragEnter?.(e); })); this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => { @@ -2075,7 +2089,7 @@ export class DragAndDropObserver extends Disposable { if (this.counter === 0) { this.dragStartTime = 0; - this.callbacks.onDragLeave(e); + this.callbacks.onDragLeave?.(e); } })); @@ -2083,14 +2097,14 @@ export class DragAndDropObserver extends Disposable { this.counter = 0; this.dragStartTime = 0; - this.callbacks.onDragEnd(e); + this.callbacks.onDragEnd?.(e); })); this._register(addDisposableListener(this.element, EventType.DROP, (e: DragEvent) => { this.counter = 0; this.dragStartTime = 0; - this.callbacks.onDrop(e); + this.callbacks.onDrop?.(e); })); } } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 144f7dab389..675691568ac 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -363,7 +363,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; this._register(new dom.DragAndDropObserver(this._domElement, { - onDragEnter: () => undefined, onDragOver: e => { if (!isDropIntoEnabled()) { return; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 628845544ad..b7f969da39c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4911,7 +4911,7 @@ class EditorDropIntoEditor extends BaseEditorOption> { const editors: IDraggedResourceEditorInput[] = []; const resourcesKey = Mimes.uriList.toLowerCase(); @@ -187,10 +188,10 @@ export class ResourcesDropHandler { } } -export function fillEditorsDragData(accessor: ServicesAccessor, resources: URI[], event: DragMouseEvent | DragEvent): void; -export function fillEditorsDragData(accessor: ServicesAccessor, resources: IResourceStat[], event: DragMouseEvent | DragEvent): void; -export function fillEditorsDragData(accessor: ServicesAccessor, editors: IEditorIdentifier[], event: DragMouseEvent | DragEvent): void; -export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEditors: Array, event: DragMouseEvent | DragEvent): void { +export function fillEditorsDragData(accessor: ServicesAccessor, resources: URI[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, resources: IResourceStat[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, editors: IEditorIdentifier[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEditors: Array, event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void { if (resourcesOrEditors.length === 0 || !event.dataTransfer) { return; } @@ -217,22 +218,25 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito return resourceOrEditor; })); + const fileSystemResources = resources.filter(({ resource }) => fileService.hasProvider(resource)); + if (!options?.disableStandardTransfer) { - // Text: allows to paste into text-capable areas - const lineDelimiter = isWindows ? '\r\n' : '\n'; - event.dataTransfer.setData(DataTransfers.TEXT, fileSystemResources.map(({ resource }) => labelService.getUriLabel(resource, { noPrefix: true })).join(lineDelimiter)); + // Text: allows to paste into text-capable areas + const lineDelimiter = isWindows ? '\r\n' : '\n'; + event.dataTransfer.setData(DataTransfers.TEXT, fileSystemResources.map(({ resource }) => labelService.getUriLabel(resource, { noPrefix: true })).join(lineDelimiter)); - // Download URL: enables support to drag a tab as file to desktop - // Requirements: - // - Chrome/Edge only - // - only a single file is supported - // - only file:/ resources are supported - const firstFile = fileSystemResources.find(({ isDirectory }) => !isDirectory); - if (firstFile) { - const firstFileUri = FileAccess.uriToFileUri(firstFile.resource); // enforce `file:` URIs - if (firstFileUri.scheme === Schemas.file) { - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [Mimes.binary, basename(firstFile.resource), firstFileUri.toString()].join(':')); + // Download URL: enables support to drag a tab as file to desktop + // Requirements: + // - Chrome/Edge only + // - only a single file is supported + // - only file:/ resources are supported + const firstFile = fileSystemResources.find(({ isDirectory }) => !isDirectory); + if (firstFile) { + const firstFileUri = FileAccess.uriToFileUri(firstFile.resource); // enforce `file:` URIs + if (firstFileUri.scheme === Schemas.file) { + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [Mimes.binary, basename(firstFile.resource), firstFileUri.toString()].join(':')); + } } } @@ -467,9 +471,6 @@ export class CompositeDragAndDropObserver extends Disposable { registerTarget(element: HTMLElement, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable { const disposableStore = new DisposableStore(); disposableStore.add(new DragAndDropObserver(element, { - onDragEnd: e => { - // no-op - }, onDragEnter: e => { e.preventDefault(); @@ -533,16 +534,15 @@ export class CompositeDragAndDropObserver extends Disposable { const disposableStore = new DisposableStore(); - disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => { - const { id, type } = draggedItemProvider(); - this.writeDragData(id, type); - - e.dataTransfer?.setDragImage(element, 0, 0); - - this.onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! }); - })); - disposableStore.add(new DragAndDropObserver(element, { + onDragStart: e => { + const { id, type } = draggedItemProvider(); + this.writeDragData(id, type); + + e.dataTransfer?.setDragImage(element, 0, 0); + + this.onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! }); + }, onDragEnd: e => { const { type } = draggedItemProvider(); const data = this.readDragData(type); @@ -661,3 +661,68 @@ export class ResourceListDnDHandler implements IListDragAndDrop { } //#endregion + +class GlobalWindowDraggedOverTracker extends Disposable { + + private static readonly CHANNEL_NAME = 'monaco-workbench-global-dragged-over'; + + private readonly broadcaster = this._register(new BroadcastDataChannel(GlobalWindowDraggedOverTracker.CHANNEL_NAME)); + + constructor() { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { + disposables.add(addDisposableListener(window, EventType.DRAG_OVER, () => this.markDraggedOver(false), true)); + disposables.add(addDisposableListener(window, EventType.DRAG_LEAVE, () => this.clearDraggedOver(false), true)); + }, { window: mainWindow, disposables: this._store })); + + this._register(this.broadcaster.onDidReceiveData(data => { + if (data === true) { + this.markDraggedOver(true); + } else { + this.clearDraggedOver(true); + } + })); + } + + private draggedOver = false; + get isDraggedOver(): boolean { return this.draggedOver; } + + private markDraggedOver(fromBroadcast: boolean): void { + if (this.draggedOver === true) { + return; // alrady marked + } + + this.draggedOver = true; + + if (!fromBroadcast) { + this.broadcaster.postData(true); + } + } + + private clearDraggedOver(fromBroadcast: boolean): void { + if (this.draggedOver === false) { + return; // alrady cleared + } + + this.draggedOver = false; + + if (!fromBroadcast) { + this.broadcaster.postData(false); + } + } +} + +const globalDraggedOverTracker = new GlobalWindowDraggedOverTracker(); + +/** + * Returns whether the workbench is currently dragged over in any of + * the opened windows (main windows and auxiliary windows). + */ +export function isWindowDraggedOver(): boolean { + return globalDraggedOverTracker.isDraggedOver; +} diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a74c41bba1b..d8c44258547 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -230,7 +230,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // main window return this.mainContainerOffset; } else { - // TODO@bpasero auxiliary window: no support for custom title bar or banner yet + // auxiliary window: no support for custom title bar or banner yet return { top: 0, quickPickTop: 0 }; } } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 52bc1cf3f38..7f309572d05 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -66,7 +66,6 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; import { EditorActionsDefaultAction, EditorActionsTitleBarAction, HideEditorActionsAction, HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction } from 'vs/workbench/browser/actions/layoutActions'; -import product from 'vs/platform/product/common/product'; import { ICommandAction } from 'vs/platform/action/common/action'; //#region Editor Registrations @@ -293,11 +292,7 @@ registerAction2(QuickAccessLeastRecentlyUsedEditorAction); registerAction2(QuickAccessPreviousRecentlyUsedEditorInGroupAction); registerAction2(QuickAccessLeastRecentlyUsedEditorInGroupAction); registerAction2(QuickAccessPreviousEditorFromHistoryAction); - -if (product.quality !== 'stable') { - // TODO@bpasero revisit - registerAction2(ExperimentalMoveEditorIntoNewWindowAction); -} +registerAction2(ExperimentalMoveEditorIntoNewWindowAction); const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 70b57dbdd41..9622165c2ee 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -17,6 +17,7 @@ import { isObject } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; import { BooleanVerifier, EnumVerifier, NumberVerifier, ObjectVerifier, SetVerifier, verifyObject } from 'vs/base/common/verifier'; +import product from 'vs/platform/product/common/product'; export interface IEditorPartCreationOptions { readonly restorePreviousState: boolean; @@ -49,6 +50,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { labelFormat: 'default', splitSizing: 'auto', splitOnDragAndDrop: true, + dragToOpenWindow: product.quality !== 'stable', centeredLayoutFixedWidth: false, doubleClickTabToToggleEditorGroupSizes: 'expand', editorActionsLocation: 'default', @@ -131,6 +133,7 @@ function validateEditorPartOptions(options: IEditorPartOptions): IEditorPartOpti 'mouseBackForwardToNavigate': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['mouseBackForwardToNavigate']), 'restoreViewState': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['restoreViewState']), 'splitOnDragAndDrop': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['splitOnDragAndDrop']), + 'dragToOpenWindow': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['dragToOpenWindow']), 'centeredLayoutFixedWidth': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['centeredLayoutFixedWidth']), 'hasIcons': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['hasIcons']), diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 646f5869387..14f3ad5b728 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -144,7 +144,6 @@ class DropOverlay extends Themable { private registerListeners(container: HTMLElement): void { this._register(new DragAndDropObserver(container, { - onDragEnter: e => undefined, onDragOver: e => { if (this.enableDropIntoEditor && isDragIntoEditorEvent(e)) { this.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index 7cd98a8ed3f..f57b9e54e1c 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -131,7 +131,6 @@ export class EditorPanes extends Disposable { try { // Assert the `EditorInputCapabilities.AuxWindowUnsupported` condition - // TODO@bpasero revisit this once all editors can support aux windows if (getWindow(this.editorPanesParent) !== mainWindow && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) { return await this.doShowError(createEditorOpenError(localize('editorUnsupportedInAuxWindow', "This type of editor cannot be opened in floating windows yet."), [ toAction({ diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 5df704b041e..d548a7a3295 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -1406,11 +1406,11 @@ export class AuxiliaryEditorPart extends EditorPart implements IAuxiliaryEditorP } protected override saveState(): void { - return; // TODO@bpasero support auxiliary editor state + return; // TODO support auxiliary editor state } async close(): Promise { - // TODO@bpasero this needs full support for closing all editors, handling vetos and showing dialogs + // TODO this needs full support for closing all editors, handling vetos and showing dialogs this._onDidClose.fire(); } } diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 7b52a57a303..b1895de1bc2 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -40,7 +40,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd //#region Auxiliary Editor Parts - async createAuxiliaryEditorPart(options?: { position?: IRectangle }): Promise { + async createAuxiliaryEditorPart(options?: { bounds?: Partial }): Promise { const disposables = new DisposableStore(); const auxiliaryWindow = disposables.add(await this.auxiliaryWindowService.open(options)); diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 6f9a241d4b3..15fb4a1579c 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editortabscontrol'; import { localize } from 'vs/nls'; import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; -import { addDisposableListener, Dimension, EventType, isMouseEvent } from 'vs/base/browser/dom'; +import { Dimension, isMouseEvent } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; @@ -22,10 +22,10 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; -import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData } from 'vs/workbench/browser/dnd'; +import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorGroupsView, IEditorGroupView, IEditorPartsView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -40,6 +40,8 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { EDITOR_CORE_NAVIGATION_COMMANDS } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IEditorGroupsService, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { isMacintosh } from 'vs/base/common/platform'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -129,7 +131,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @INotificationService private readonly notificationService: INotificationService, @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IEditorResolverService private readonly editorResolverService: IEditorResolverService + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService ) { super(themeService); @@ -262,58 +265,90 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC editorActionsToolbar.setActions([], []); } - protected enableGroupDragging(element: HTMLElement): void { + protected onGroupDragStart(e: DragEvent, element: HTMLElement): void { + if (e.target !== element) { + return; // only if originating from tabs container + } - // Drag start - this._register(addDisposableListener(element, EventType.DRAG_START, e => { - if (e.target !== element) { - return; // only if originating from tabs container - } + // Set editor group as transfer + this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupView.id)], DraggedEditorGroupIdentifier.prototype); + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copyMove'; + } - // Set editor group as transfer - this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupView.id)], DraggedEditorGroupIdentifier.prototype); - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'copyMove'; - } + // Drag all tabs of the group if tabs are enabled + let hasDataTransfer = false; + if (this.groupsView.partOptions.showTabs === 'multiple') { + hasDataTransfer = this.doFillResourceDataTransfers(this.groupView.getEditors(EditorsOrder.SEQUENTIAL), e); + } - // Drag all tabs of the group if tabs are enabled - let hasDataTransfer = false; - if (this.groupsView.partOptions.showTabs === 'multiple') { - hasDataTransfer = this.doFillResourceDataTransfers(this.groupView.getEditors(EditorsOrder.SEQUENTIAL), e); - } - - // Otherwise only drag the active editor - else { - if (this.groupView.activeEditor) { - hasDataTransfer = this.doFillResourceDataTransfers([this.groupView.activeEditor], e); - } - } - - // Firefox: requires to set a text data transfer to get going - if (!hasDataTransfer && isFirefox) { - e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupView.label)); - } - - // Drag Image + // Otherwise only drag the active editor + else { if (this.groupView.activeEditor) { - let label = this.groupView.activeEditor.getName(); - if (this.groupsView.partOptions.showTabs === 'multiple' && this.groupView.count > 1) { - label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupView.count - 1); - } - - applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground)); + hasDataTransfer = this.doFillResourceDataTransfers([this.groupView.activeEditor], e); } - })); + } - // Drag end - this._register(addDisposableListener(element, EventType.DRAG_END, () => { - this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); - })); + // Firefox: requires to set a text data transfer to get going + if (!hasDataTransfer && isFirefox) { + e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupView.label)); + } + + // Drag Image + if (this.groupView.activeEditor) { + let label = this.groupView.activeEditor.getName(); + if (this.groupsView.partOptions.showTabs === 'multiple' && this.groupView.count > 1) { + label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupView.count - 1); + } + + applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground)); + } + } + + protected async onGroupDragEnd(e: DragEvent, previousDragEvent: DragEvent | undefined, element: HTMLElement): Promise { + this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); + + if ( + e.target !== element || + !this.isNewWindowOperation(previousDragEvent ?? e) || + isWindowDraggedOver() + ) { + return; // drag to open in new window is disabled + } + + const auxiliaryEditorPart = await this.editorGroupService.createAuxiliaryEditorPart({ + bounds: { x: e.screenX, y: e.screenY } + }); + + const targetGroup = auxiliaryEditorPart.activeGroup; + this.groupsView.mergeGroup(this.groupView, targetGroup.id, { + mode: this.isMoveOperation(previousDragEvent ?? e, targetGroup.id) ? MergeGroupMode.MOVE_EDITORS : MergeGroupMode.COPY_EDITORS + }); + + targetGroup.focus(); + } + + protected isNewWindowOperation(e: DragEvent): boolean { + if (this.groupsView.partOptions.dragToOpenWindow) { + return !e.altKey; + } + + return e.altKey; + } + + protected isMoveOperation(e: DragEvent, sourceGroup: GroupIdentifier, sourceEditor?: EditorInput): boolean { + if (sourceEditor?.hasCapability(EditorInputCapabilities.Singleton)) { + return true; // Singleton editors cannot be split + } + + const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh); + + return (!isCopy || sourceGroup === this.groupView.id); } protected doFillResourceDataTransfers(editors: readonly EditorInput[], e: DragEvent): boolean { if (editors.length) { - this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupView.id })), e); + this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupView.id })), e, { disableStandardTransfer: this.isNewWindowOperation(e) }); return true; } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index e3da5fea781..8f719803267 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/multieditortabscontrol'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; -import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder, IToolbarActions } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { computeEditorAriaLabel } from 'vs/workbench/browser/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -28,7 +28,7 @@ import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData } from 'vs/workbench/browser/dnd'; +import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { MergeGroupMode, IMergeGroupOptions, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -147,12 +147,12 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IThemeService themeService: IThemeService, @IEditorService private readonly editorService: EditorServiceImpl, @IPathService private readonly pathService: IPathService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, @ILifecycleService private readonly lifecycleService: ILifecycleService ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, editorGroupService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -271,9 +271,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void { - // Group dragging - this.enableGroupDragging(tabsContainer); - // Forward scrolling inside the container to our custom scrollbar this._register(addDisposableListener(tabsContainer, EventType.SCROLL, () => { if (tabsContainer.classList.contains('scroll')) { @@ -320,8 +317,17 @@ export class MultiEditorTabsControl extends EditorTabsControl { } })); - // Drop support + // Drag & Drop support + let lastDragEvent: DragEvent | undefined = undefined; this._register(new DragAndDropObserver(tabsContainer, { + onDragStart: e => { + this.onGroupDragStart(e, tabsContainer); + }, + + onDrag: e => { + lastDragEvent = e; + }, + onDragEnter: e => { // Always enable support to scroll while dragging @@ -379,6 +385,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { onDragEnd: e => { this.updateDropFeedback(tabsContainer, false); tabsContainer.classList.remove('scroll'); + + this.onGroupDragEnd(e, lastDragEvent, tabsContainer); }, onDrop: e => { @@ -1014,29 +1022,33 @@ export class MultiEditorTabsControl extends EditorTabsControl { } }, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */)); - // Drag support - disposables.add(addDisposableListener(tab, EventType.DRAG_START, e => { - const editor = this.tabsModel.getEditorByIndex(tabIndex); - if (!editor) { - return; - } - - this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupView.id })], DraggedEditorIdentifier.prototype); - - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'copyMove'; - } - - // Apply some datatransfer types to allow for dragging the element outside of the application - this.doFillResourceDataTransfers([editor], e); - - // Fixes https://github.com/microsoft/vscode/issues/18733 - tab.classList.add('dragged'); - scheduleAtNextAnimationFrame(getWindow(tab), () => tab.classList.remove('dragged')); - })); - - // Drop support + // Drag & Drop support + let lastDragEvent: DragEvent | undefined = undefined; disposables.add(new DragAndDropObserver(tab, { + onDragStart: e => { + const editor = this.tabsModel.getEditorByIndex(tabIndex); + if (!editor) { + return; + } + + this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupView.id })], DraggedEditorIdentifier.prototype); + + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copyMove'; + } + + // Apply some datatransfer types to allow for dragging the element outside of the application + this.doFillResourceDataTransfers([editor], e); + + // Fixes https://github.com/microsoft/vscode/issues/18733 + tab.classList.add('dragged'); + scheduleAtNextAnimationFrame(getWindow(tab), () => tab.classList.remove('dragged')); + }, + + onDrag: e => { + lastDragEvent = e; + }, + onDragEnter: e => { // Update class to signal drag operation @@ -1094,11 +1106,33 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.updateDropFeedback(tab, false, tabIndex); }, - onDragEnd: () => { + onDragEnd: async e => { tab.classList.remove('dragged-over'); this.updateDropFeedback(tab, false, tabIndex); this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); + + const editor = this.tabsModel.getEditorByIndex(tabIndex); + if ( + !this.isNewWindowOperation(lastDragEvent ?? e) || + isWindowDraggedOver() || + !editor + ) { + return; // drag to open in new window is disabled + } + + const auxiliaryEditorPart = await this.editorGroupService.createAuxiliaryEditorPart({ + bounds: { x: e.screenX, y: e.screenY } + }); + + const targetGroup = auxiliaryEditorPart.activeGroup; + if (this.isMoveOperation(lastDragEvent ?? e, targetGroup.id, editor)) { + this.groupView.moveEditor(editor, targetGroup); + } else { + this.groupView.copyEditor(editor, targetGroup); + } + + targetGroup.focus(); }, onDrop: e => { @@ -2089,16 +2123,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - private isMoveOperation(e: DragEvent, sourceGroup: GroupIdentifier, sourceEditor?: EditorInput) { - if (sourceEditor?.hasCapability(EditorInputCapabilities.Singleton)) { - return true; // Singleton editors cannot be split - } - - const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh); - - return (!isCopy || sourceGroup === this.groupView.id); - } - override dispose(): void { super.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 766845687d1..8241b06ed91 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -10,7 +10,7 @@ import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsC import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; -import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor, DragAndDropObserver } from 'vs/base/browser/dom'; import { CLOSE_EDITOR_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; @@ -72,8 +72,13 @@ export class SingleEditorTabsControl extends EditorTabsControl { private registerContainerListeners(titleContainer: HTMLElement): void { - // Group dragging - this.enableGroupDragging(titleContainer); + // Drag & Drop support + let lastDragEvent: DragEvent | undefined = undefined; + this._register(new DragAndDropObserver(titleContainer, { + onDragStart: e => this.onGroupDragStart(e, titleContainer), + onDrag: e => { lastDragEvent = e; }, + onDragEnd: e => { this.onGroupDragEnd(e, lastDragEvent, titleContainer); }, + })); // Pin on double click this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, e => this.onTitleDoubleClick(e))); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 8f6e6b7e277..f565a70c138 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -146,7 +146,6 @@ class ViewPaneDropOverlay extends Themable { private registerListeners(): void { this._register(new DragAndDropObserver(this.container, { - onDragEnter: e => undefined, onDragOver: e => { // Position overlay diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index e39f9c35dd1..69109a59d2d 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -12,6 +12,7 @@ import { isStandalone } from 'vs/base/browser/browser'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ActivityBarPosition, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import product from 'vs/platform/product/common/product'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -243,6 +244,11 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': true, 'description': localize('splitOnDragAndDrop', "Controls if editor groups can be split from drag and drop operations by dropping an editor or file on the edges of the editor area.") }, + 'workbench.editor.dragToOpenWindow': { + 'type': 'boolean', + 'default': product.quality !== 'stable', + 'markdownDescription': localize('dragToOpenWindow', "Controls if editors can be dragged out of the window to open them in a new floating window. Press and hold `Alt`-key while dragging to toggle this dynamically.") + }, 'workbench.editor.focusRecentEditorAfterClose': { 'type': 'boolean', 'description': localize('focusRecentEditorAfterClose', "Controls whether editors are closed in most recently used order or from left to right."), diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 8b24c469394..2c0acaa442a 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1166,6 +1166,7 @@ interface IEditorPartConfiguration { splitInGroupLayout?: 'vertical' | 'horizontal'; splitSizing?: 'auto' | 'split' | 'distribute'; splitOnDragAndDrop?: boolean; + dragToOpenWindow?: boolean; centeredLayoutFixedWidth?: boolean; doubleClickTabToToggleEditorGroupSizes?: 'maximize' | 'expand' | 'off'; editorActionsLocation?: 'default' | 'titleBar' | 'hidden'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 0ff891c4e28..05469dbf48a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -591,7 +591,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE // Register DragAndDrop support this._register(new DragAndDropObserver(this.root, { - onDragEnd: (e: DragEvent) => undefined, onDragEnter: (e: DragEvent) => { if (this.isSupportedDragElement(e)) { show(overlay); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index d4f17ff6449..0866c6c009b 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -26,8 +26,6 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getActiveWindow } from 'vs/base/browser/dom'; import { IOpenedAuxiliaryWindow, IOpenedMainWindow, isOpenedAuxiliaryWindow } from 'vs/platform/window/common/window'; @@ -378,52 +376,3 @@ export const ToggleWindowTabsBarHandler: ICommandHandler = function (accessor: S return accessor.get(INativeHostService).toggleWindowTabsBar(); }; - -export class ExperimentalSplitWindowAction extends Action2 { - - constructor() { - super({ - id: 'workbench.action.experimentalSplitWindowAction', - title: { - value: localize('splitWindow', "Split Window (Experimental)"), - mnemonicTitle: localize({ key: 'miSplitWindow', comment: ['&& denotes a mnemonic'] }, "&&Split Window (Experimental)"), - original: 'Split Window (Experimental)' - }, - category: Categories.View, - f1: true - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); - const nativeHostService = accessor.get(INativeHostService); - - const activeWindow = getActiveWindow(); - - // First position the active window which may involve - // leaving fullscreen mode and then split it. - await nativeHostService.positionWindow({ - x: 0, - y: 0, - width: activeWindow.screen.availWidth / 2, - height: activeWindow.screen.availHeight - }, { targetWindowId: activeWindow.vscodeWindowId }); - - // Then create a new window next to the active window - const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart({ - position: { - x: activeWindow.screen.availWidth / 2, - y: 0, - width: activeWindow.screen.availWidth / 2, - height: activeWindow.screen.availHeight - } - }); - - // Finally copy over the active editor if any - const activeEditorPane = editorService.activeEditorPane; - if (activeEditorPane) { - activeEditorPane.group.copyEditor(activeEditorPane.input, auxiliaryEditorPart.activeGroup); - } - } -} diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 829ef06961c..f904efaafae 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ConfigureRuntimeArgumentsAction, ToggleDevToolsAction, ReloadWindowWithExtensionsDisabledAction, OpenUserDataFolderAction } from 'vs/workbench/electron-sandbox/actions/developerActions'; -import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler, ExperimentalSplitWindowAction } from 'vs/workbench/electron-sandbox/actions/windowActions'; +import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-sandbox/actions/windowActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -27,7 +27,6 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { ModifierKeyEmitter } from 'vs/base/browser/dom'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import product from 'vs/platform/product/common/product'; // Actions (function registerActions(): void { @@ -41,10 +40,6 @@ import product from 'vs/platform/product/common/product'; registerAction2(SwitchWindowAction); registerAction2(QuickSwitchWindowAction); registerAction2(CloseWindowAction); - if (product.quality !== 'stable') { - // TODO@bpasero revisit - registerAction2(ExperimentalSplitWindowAction); - } if (isMacintosh) { // macOS: behave like other native apps that have documents diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index fa2def3fa03..5d64cb7c419 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -33,7 +33,7 @@ export interface IAuxiliaryWindowService { hasWindow(windowId: number): boolean; - open(options?: { position?: IRectangle }): Promise; + open(options?: { bounds?: Partial }): Promise; } export interface IAuxiliaryWindow extends IDisposable { @@ -77,7 +77,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili super(); } - async open(options?: { position?: IRectangle }): Promise { + async open(options?: { bounds?: Partial }): Promise { mark('code/auxiliaryWindow/willOpen'); const disposables = new DisposableStore(); @@ -114,19 +114,17 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili return result; } - private async doOpen(options?: { position?: IRectangle }): Promise { - let position: IRectangle | undefined = options?.position; - if (!position) { - const activeWindow = getActiveWindow(); - position = { - x: activeWindow.screen.availWidth / 2 - BrowserAuxiliaryWindowService.DEFAULT_SIZE.width / 2, - y: activeWindow.screen.availHeight / 2 - BrowserAuxiliaryWindowService.DEFAULT_SIZE.height / 2, - width: BrowserAuxiliaryWindowService.DEFAULT_SIZE.width, - height: BrowserAuxiliaryWindowService.DEFAULT_SIZE.height - }; - } + private async doOpen(options?: { bounds?: Partial }): Promise { + const activeWindow = getActiveWindow(); - const auxiliaryWindow = mainWindow.open('about:blank', undefined, `popup=yes,left=${position.x},top=${position.y},width=${position.width},height=${position.height}`); + const bounds: IRectangle = { + x: options?.bounds?.x ?? activeWindow.screen.availWidth / 2 - BrowserAuxiliaryWindowService.DEFAULT_SIZE.width / 2, + y: options?.bounds?.y ?? activeWindow.screen.availHeight / 2 - BrowserAuxiliaryWindowService.DEFAULT_SIZE.height / 2, + width: options?.bounds?.width ?? BrowserAuxiliaryWindowService.DEFAULT_SIZE.width, + height: options?.bounds?.height ?? BrowserAuxiliaryWindowService.DEFAULT_SIZE.height + }; + + const auxiliaryWindow = mainWindow.open('about:blank', undefined, `popup=yes,left=${bounds.x},top=${bounds.y},width=${bounds.width},height=${bounds.height}`); if (!auxiliaryWindow && isWeb) { return (await this.dialogService.prompt({ type: Severity.Warning, @@ -135,7 +133,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili buttons: [ { label: localize({ key: 'retry', comment: ['&& denotes a mnemonic'] }, "&&Retry"), - run: () => this.doOpen(options) + run: () => this.doOpen({ bounds }) } ], cancelButton: true diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index d56f4fc3fb6..2fed550260d 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -501,9 +501,9 @@ export interface IEditorGroupsService extends IEditorGroupsContainer { /** * Opens a new window with a full editor part instantiated - * in there at the optional position on screen. + * in there at the optional position and size on screen. */ - createAuxiliaryEditorPart(options?: { position?: IRectangle }): Promise; + createAuxiliaryEditorPart(options?: { bounds?: Partial }): Promise; } export const enum OpenEditorContext {