in src/vs/workbench/browser/parts/editor/tabsTitleControl.ts [600:835]
private registerTabListeners(tab: HTMLElement, index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable {
const disposables = new DisposableStore();
const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
tab.blur(); // prevent flicker of focus outline on tab until editor got focus
if (e instanceof MouseEvent && e.button !== 0) {
if (e.button === 1) {
e.preventDefault(); // required to prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690)
}
return undefined; // only for left mouse click
}
if (this.originatesFromTabActionBar(e)) {
return; // not when clicking on actions
}
// Open tabs editor
const input = this.group.getEditorByIndex(index);
if (input) {
this.group.openEditor(input);
}
return undefined;
};
const showContextMenu = (e: Event) => {
EventHelper.stop(e);
const input = this.group.getEditorByIndex(index);
if (input) {
this.onContextMenu(input, e, tab);
}
};
// Open on Click / Touch
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));
disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
// Touch Scroll Support
disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
}));
// 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*/) {
EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */);
this.blockRevealActiveTabOnce();
this.closeOneEditorAction.run({ groupId: this.group.id, editorIndex: index });
}
}));
// Context menu on Shift+F10
disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => {
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: KeyboardEvent) => {
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 input = this.group.getEditorByIndex(index);
if (input) {
this.group.openEditor(input);
}
}
// Navigate in editors
else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
let targetIndex: number;
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
targetIndex = index - 1;
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
targetIndex = index + 1;
} else if (event.equals(KeyCode.Home)) {
targetIndex = 0;
} else {
targetIndex = this.group.count - 1;
}
const target = this.group.getEditorByIndex(targetIndex);
if (target) {
handled = true;
this.group.openEditor(target, { preserveFocus: true });
(<HTMLElement>tabsContainer.childNodes[targetIndex]).focus();
}
}
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
[TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => {
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.group.getEditorByIndex(index);
if (editor && this.group.isPinned(editor)) {
this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.group);
} else {
this.group.pinEditor(editor);
}
}));
});
// Context menu
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => {
EventHelper.stop(e, true);
const input = this.group.getEditorByIndex(index);
if (input) {
this.onContextMenu(input, e, tab);
}
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
// Drag support
disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => {
const editor = this.group.getEditorByIndex(index);
if (!editor) {
return;
}
this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.group.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
addClass(tab, 'dragged');
scheduleAtNextAnimationFrame(() => removeClass(tab, 'dragged'));
}));
// Drop support
disposables.add(new DragAndDropObserver(tab, {
onDragEnter: e => {
// Update class to signal drag operation
addClass(tab, 'dragged-over');
// Return if transfer is unsupported
if (!this.isSupportedDropTransfer(e)) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
return;
}
// Return if dragged editor is the current tab dragged over
let isLocalDragAndDrop = false;
if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
isLocalDragAndDrop = true;
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const localDraggedEditor = data[0].identifier;
if (localDraggedEditor.editor === this.group.getEditorByIndex(index) && localDraggedEditor.groupId === this.group.id) {
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 (!isLocalDragAndDrop) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'copy';
}
}
this.updateDropFeedback(tab, true, index);
},
onDragLeave: e => {
removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
},
onDragEnd: e => {
removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
},
onDrop: e => {
removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
this.onDrop(e, index, tabsContainer);
}
}));
return disposables;
}