private registerTabListeners()

in patched-vscode/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts [870:1182]


	private registerTabListeners(tab: HTMLElement, tabIndex: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable {
		const disposables = new DisposableStore();

		const handleClickOrTouch = async (e: MouseEvent | GestureEvent, preserveFocus: boolean): Promise<void> => {
			tab.blur(); // prevent flicker of focus outline on tab until editor got focus

			if (isMouseEvent(e) && (e.button !== 0 /* middle/right mouse button */ || (isMacintosh && e.ctrlKey /* macOS context menu */))) {
				if (e.button === 1) {
					e.preventDefault(); // required to prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690)
				}

				return;
			}

			if (this.originatesFromTabActionBar(e)) {
				return; // not when clicking on actions
			}

			// Open tabs editor
			const editor = this.tabsModel.getEditorByIndex(tabIndex);
			if (editor) {
				if (e.shiftKey) {
					let anchor: EditorInput;
					if (this.lastSingleSelectSelectedEditor && this.tabsModel.isSelected(this.lastSingleSelectSelectedEditor)) {
						// The last selected editor is the anchor
						anchor = this.lastSingleSelectSelectedEditor;
					} else {
						// The active editor is the anchor
						const activeEditor = assertIsDefined(this.groupView.activeEditor);
						this.lastSingleSelectSelectedEditor = activeEditor;
						anchor = activeEditor;
					}
					await this.selectEditorsBetween(editor, anchor);
				} else if ((e.ctrlKey && !isMacintosh) || (e.metaKey && isMacintosh)) {
					if (this.tabsModel.isSelected(editor)) {
						await this.unselectEditor(editor);
					} else {
						await this.selectEditor(editor);
						this.lastSingleSelectSelectedEditor = editor;
					}
				} else {
					// Even if focus is preserved make sure to activate the group.
					// If a new active editor is selected, keep the current selection on key
					// down such that drag and drop can operate over the selection. The selection
					// is removed on key up in this case.
					const inactiveSelection = this.tabsModel.isSelected(editor) ? this.groupView.selectedEditors.filter(e => !e.matches(editor)) : [];
					await this.groupView.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE }, { inactiveSelection, focusTabControl: true });
				}
			}
		};

		const showContextMenu = (e: Event) => {
			EventHelper.stop(e);

			const editor = this.tabsModel.getEditorByIndex(tabIndex);
			if (editor) {
				this.onTabContextMenu(editor, e, tab);
			}
		};

		// Open on Click / Touch
		disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, e => handleClickOrTouch(e, false)));
		disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e, true))); // Preserve focus on touch #125470

		// Touch Scroll Support
		disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
			tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
		}));

		// Update selection & prevent flicker of focus outline on tab until editor got focus
		disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, async e => {
			EventHelper.stop(e);

			tab.blur();

			if (isMouseEvent(e) && (e.button !== 0 /* middle/right mouse button */ || (isMacintosh && e.ctrlKey /* macOS context menu */))) {
				return;
			}

			if (this.originatesFromTabActionBar(e)) {
				return; // not when clicking on actions
			}

			const isCtrlCmd = (e.ctrlKey && !isMacintosh) || (e.metaKey && isMacintosh);
			if (!isCtrlCmd && !e.shiftKey && this.groupView.selectedEditors.length > 1) {
				await this.unselectAllEditors();
			}
		}));

		// Close on mouse middle click
		disposables.add(addDisposableListener(tab, EventType.AUXCLICK, e => {
			if (e.button === 1 /* Middle Button*/) {
				EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */);

				const editor = this.tabsModel.getEditorByIndex(tabIndex);
				if (editor) {
					if (preventEditorClose(this.tabsModel, editor, EditorCloseMethod.MOUSE, this.groupsView.partOptions)) {
						return;
					}

					this.blockRevealActiveTabOnce();
					this.closeEditorAction.run({ groupId: this.groupView.id, editorIndex: this.groupView.getIndexOfEditor(editor) });
				}
			}
		}));

		// Context menu on Shift+F10
		disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, e => {
			const event = new StandardKeyboardEvent(e);
			if (event.shiftKey && event.keyCode === KeyCode.F10) {
				showContextMenu(e);
			}
		}));

		// Context menu on touch context menu gesture
		disposables.add(addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => {
			showContextMenu(e);
		}));

		// Keyboard accessibility
		disposables.add(addDisposableListener(tab, EventType.KEY_UP, e => {
			const event = new StandardKeyboardEvent(e);
			let handled = false;

			// Run action on Enter/Space
			if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
				handled = true;
				const editor = this.tabsModel.getEditorByIndex(tabIndex);
				if (editor) {
					this.groupView.openEditor(editor);
				}
			}

			// Navigate in editors
			else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
				let editorIndex = this.toEditorIndex(tabIndex);
				if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
					editorIndex = editorIndex - 1;
				} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
					editorIndex = editorIndex + 1;
				} else if (event.equals(KeyCode.Home)) {
					editorIndex = 0;
				} else {
					editorIndex = this.groupView.count - 1;
				}

				const target = this.groupView.getEditorByIndex(editorIndex);
				if (target) {
					handled = true;
					this.groupView.openEditor(target, { preserveFocus: true }, { focusTabControl: true });
				}
			}

			if (handled) {
				EventHelper.stop(e, true);
			}

			// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
			tabsScrollbar.setScrollPosition({
				scrollLeft: tabsContainer.scrollLeft
			});
		}));

		// Double click: either pin or toggle maximized
		for (const eventType of [TouchEventType.Tap, EventType.DBLCLICK]) {
			disposables.add(addDisposableListener(tab, eventType, (e: MouseEvent | GestureEvent) => {
				if (eventType === EventType.DBLCLICK) {
					EventHelper.stop(e);
				} else if ((<GestureEvent>e).tapCount !== 2) {
					return; // ignore single taps
				}

				const editor = this.tabsModel.getEditorByIndex(tabIndex);
				if (editor && this.tabsModel.isPinned(editor)) {
					switch (this.groupsView.partOptions.doubleClickTabToToggleEditorGroupSizes) {
						case 'maximize':
							this.groupsView.toggleMaximizeGroup(this.groupView);
							break;
						case 'expand':
							this.groupsView.toggleExpandGroup(this.groupView);
							break;
						case 'off':
							break;
					}

				} else {
					this.groupView.pinEditor(editor);
				}
			}));
		}

		// Context menu
		disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, e => {
			EventHelper.stop(e, true);

			const editor = this.tabsModel.getEditorByIndex(tabIndex);
			if (editor) {
				this.onTabContextMenu(editor, e, tab);
			}
		}, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */));

		// Drag & Drop support
		let lastDragEvent: DragEvent | undefined = undefined;
		let isNewWindowOperation = false;
		disposables.add(new DragAndDropObserver(tab, {
			onDragStart: e => {
				const editor = this.tabsModel.getEditorByIndex(tabIndex);
				if (!editor) {
					return;
				}

				isNewWindowOperation = this.isNewWindowOperation(e);
				const selectedEditors = this.groupView.selectedEditors;
				this.editorTransfer.setData(selectedEditors.map(e => new DraggedEditorIdentifier({ editor: e, groupId: this.groupView.id })), DraggedEditorIdentifier.prototype);

				if (e.dataTransfer) {
					e.dataTransfer.effectAllowed = 'copyMove';
					if (selectedEditors.length > 1) {
						const label = `${editor.getName()} + ${selectedEditors.length - 1}`;
						applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground));
					} else {
						e.dataTransfer.setDragImage(tab, 0, 0); // top left corner of dragged tab set to cursor position to make room for drop-border feedback
					}
				}

				// Apply some datatransfer types to allow for dragging the element outside of the application
				this.doFillResourceDataTransfers([editor], e, isNewWindowOperation);

				scheduleAtNextAnimationFrame(getWindow(this.parent), () => this.updateDropFeedback(tab, false, e, tabIndex));
			},

			onDrag: e => {
				lastDragEvent = e;
			},

			onDragEnter: e => {

				// Return if transfer is unsupported
				if (!this.isSupportedDropTransfer(e)) {
					if (e.dataTransfer) {
						e.dataTransfer.dropEffect = 'none';
					}

					return;
				}

				// Update the dropEffect to "copy" if there is no local data to be dragged because
				// in that case we can only copy the data into and not move it from its source
				if (!this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
					if (e.dataTransfer) {
						e.dataTransfer.dropEffect = 'copy';
					}
				}

				this.updateDropFeedback(tab, true, e, tabIndex);
			},

			onDragOver: (e, dragDuration) => {
				if (dragDuration >= MultiEditorTabsControl.DRAG_OVER_OPEN_TAB_THRESHOLD) {
					const draggedOverTab = this.tabsModel.getEditorByIndex(tabIndex);
					if (draggedOverTab && this.tabsModel.activeEditor !== draggedOverTab) {
						this.groupView.openEditor(draggedOverTab, { preserveFocus: true });
					}
				}

				this.updateDropFeedback(tab, true, e, tabIndex);
			},

			onDragEnd: async e => {
				this.updateDropFeedback(tab, false, e, tabIndex);
				const draggedEditors = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
				this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);

				if (
					!isNewWindowOperation ||
					isWindowDraggedOver() ||
					!draggedEditors ||
					draggedEditors.length === 0
				) {
					return; // drag to open in new window is disabled
				}

				const auxiliaryEditorPart = await this.maybeCreateAuxiliaryEditorPartAt(e, tab);
				if (!auxiliaryEditorPart) {
					return;
				}

				const targetGroup = auxiliaryEditorPart.activeGroup;
				const editors = draggedEditors.map(de => ({ editor: de.identifier.editor }));
				if (this.isMoveOperation(lastDragEvent ?? e, targetGroup.id, draggedEditors[0].identifier.editor)) {
					this.groupView.moveEditors(editors, targetGroup);
				} else {
					this.groupView.copyEditors(editors, targetGroup);
				}

				targetGroup.focus();
			},

			onDrop: e => {
				this.updateDropFeedback(tab, false, e, tabIndex);

				// compute the target index
				let targetIndex = tabIndex;
				if (this.getTabDragOverLocation(e, tab) === 'right') {
					targetIndex++;
				}

				this.onDrop(e, targetIndex, tabsContainer);
			}
		}));

		return disposables;
	}