Path: blob/main/src/vs/editor/browser/controller/mouseHandler.ts
5222 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../base/browser/dom.js';6import { StandardWheelEvent, IMouseWheelEvent } from '../../../base/browser/mouseEvent.js';7import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';8import * as platform from '../../../base/common/platform.js';9import { HitTestContext, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from './mouseTarget.js';10import { IMouseTarget, IMouseTargetViewZoneData, MouseTargetType } from '../editorBrowser.js';11import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorPointerMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor } from '../editorDom.js';12import { ViewController } from '../view/viewController.js';13import { EditorZoom } from '../../common/config/editorZoom.js';14import { Position } from '../../common/core/position.js';15import { Selection } from '../../common/core/selection.js';16import { HorizontalPosition } from '../view/renderingContext.js';17import { ViewContext } from '../../common/viewModel/viewContext.js';18import * as viewEvents from '../../common/viewEvents.js';19import { ViewEventHandler } from '../../common/viewEventHandler.js';20import { EditorOption } from '../../common/config/editorOptions.js';21import { NavigationCommandRevealType } from '../coreCommands.js';22import { MouseWheelClassifier } from '../../../base/browser/ui/scrollbar/scrollableElement.js';23import type { ViewLinesGpu } from '../viewParts/viewLinesGpu/viewLinesGpu.js';24import { TopBottomDragScrolling, LeftRightDragScrolling } from './dragScrolling.js';25import { TextDirection } from '../../common/model.js';2627export interface IPointerHandlerHelper {28viewDomNode: HTMLElement;29linesContentDomNode: HTMLElement;30viewLinesDomNode: HTMLElement;31viewLinesGpu: ViewLinesGpu | undefined;3233focusTextArea(): void;34dispatchTextAreaEvent(event: CustomEvent): void;3536/**37* Get the last rendered information for cursors & textarea.38*/39getLastRenderData(): PointerHandlerLastRenderData;4041/**42* Render right now43*/44renderNow(): void;4546shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean;47shouldSuppressMouseDownOnWidget(widgetId: string): boolean;4849/**50* Decode a position from a rendered dom node51*/52getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null;5354visibleRangeForPosition(lineNumber: number, column: number): HorizontalPosition | null;55getLineWidth(lineNumber: number): number;56}5758export class MouseHandler extends ViewEventHandler {5960protected _context: ViewContext;61protected viewController: ViewController;62protected viewHelper: IPointerHandlerHelper;63protected mouseTargetFactory: MouseTargetFactory;64protected readonly _mouseDownOperation: MouseDownOperation;65private lastMouseLeaveTime: number;66private _height: number;67private _mouseLeaveMonitor: IDisposable | null = null;6869constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {70super();7172this._context = context;73this.viewController = viewController;74this.viewHelper = viewHelper;75this.mouseTargetFactory = new MouseTargetFactory(this._context, viewHelper);7677this._mouseDownOperation = this._register(new MouseDownOperation(78this._context,79this.viewController,80this.viewHelper,81this.mouseTargetFactory,82(e, testEventTarget) => this._createMouseTarget(e, testEventTarget),83(e) => this._getMouseColumn(e)84));8586this.lastMouseLeaveTime = -1;87this._height = this._context.configuration.options.get(EditorOption.layoutInfo).height;8889const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode);9091this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true)));9293this._register(mouseEvents.onMouseMove(this.viewHelper.viewDomNode, (e) => {94this._onMouseMove(e);9596// See https://github.com/microsoft/vscode/issues/13878997// When moving the mouse really quickly, the browser sometimes forgets to98// send us a `mouseleave` or `mouseout` event. We therefore install here99// a global `mousemove` listener to manually recover if the mouse goes outside100// the editor. As soon as the mouse leaves outside of the editor, we101// remove this listener102103if (!this._mouseLeaveMonitor) {104this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => {105if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) {106// went outside the editor!107this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode));108}109});110}111}));112113this._register(mouseEvents.onMouseUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));114115this._register(mouseEvents.onMouseLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e)));116117// `pointerdown` events can't be used to determine if there's a double click, or triple click118// because their `e.detail` is always 0.119// We will therefore save the pointer id for the mouse and then reuse it in the `mousedown` event120// for `element.setPointerCapture`.121let capturePointerId: number = 0;122this._register(mouseEvents.onPointerDown(this.viewHelper.viewDomNode, (e, pointerId) => {123capturePointerId = pointerId;124}));125// The `pointerup` listener registered by `GlobalEditorPointerMoveMonitor` does not get invoked 100% of the times.126// I speculate that this is because the `pointerup` listener is only registered during the `mousedown` event, and perhaps127// the `pointerup` event is already queued for dispatching, which makes it that the new listener doesn't get fired.128// See https://github.com/microsoft/vscode/issues/146486 for repro steps.129// To compensate for that, we simply register here a `pointerup` listener and just communicate it.130this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.POINTER_UP, (e: PointerEvent) => {131this._mouseDownOperation.onPointerUp();132}));133this._register(mouseEvents.onMouseDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e, capturePointerId)));134this._setupMouseWheelZoomListener();135136this._context.addEventHandler(this);137}138139private _setupMouseWheelZoomListener(): void {140141const classifier = MouseWheelClassifier.INSTANCE;142143let prevMouseWheelTime = 0;144let gestureStartZoomLevel = EditorZoom.getZoomLevel();145let gestureHasZoomModifiers = false;146let gestureAccumulatedDelta = 0;147148const onMouseWheel = (browserEvent: IMouseWheelEvent) => {149this.viewController.emitMouseWheel(browserEvent);150151if (!this._context.configuration.options.get(EditorOption.mouseWheelZoom)) {152return;153}154155const e = new StandardWheelEvent(browserEvent);156classifier.acceptStandardWheelEvent(e);157158if (classifier.isPhysicalMouseWheel()) {159if (hasMouseWheelZoomModifiers(browserEvent)) {160const zoomLevel: number = EditorZoom.getZoomLevel();161const delta = e.deltaY > 0 ? 1 : -1;162EditorZoom.setZoomLevel(zoomLevel + delta);163e.preventDefault();164e.stopPropagation();165}166} else {167// we consider mousewheel events that occur within 50ms of each other to be part of the same gesture168// we don't want to consider mouse wheel events where ctrl/cmd is pressed during the inertia phase169// we also want to accumulate deltaY values from the same gesture and use that to set the zoom level170if (Date.now() - prevMouseWheelTime > 50) {171// reset if more than 50ms have passed172gestureStartZoomLevel = EditorZoom.getZoomLevel();173gestureHasZoomModifiers = hasMouseWheelZoomModifiers(browserEvent);174gestureAccumulatedDelta = 0;175}176177prevMouseWheelTime = Date.now();178gestureAccumulatedDelta += e.deltaY;179180if (gestureHasZoomModifiers) {181EditorZoom.setZoomLevel(gestureStartZoomLevel + gestureAccumulatedDelta / 5);182e.preventDefault();183e.stopPropagation();184}185}186};187this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false }));188189function hasMouseWheelZoomModifiers(browserEvent: IMouseWheelEvent): boolean {190return (191platform.isMacintosh192// on macOS we support cmd + two fingers scroll (`metaKey` set)193// and also the two fingers pinch gesture (`ctrKey` set)194? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey)195: (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey)196);197}198}199200public override dispose(): void {201this._context.removeEventHandler(this);202if (this._mouseLeaveMonitor) {203this._mouseLeaveMonitor.dispose();204this._mouseLeaveMonitor = null;205}206super.dispose();207}208209// --- begin event handlers210public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {211if (e.hasChanged(EditorOption.layoutInfo)) {212// layout change213const height = this._context.configuration.options.get(EditorOption.layoutInfo).height;214if (this._height !== height) {215this._height = height;216this._mouseDownOperation.onHeightChanged();217}218}219return false;220}221public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {222this._mouseDownOperation.onCursorStateChanged(e);223return false;224}225public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {226return false;227}228// --- end event handlers229230public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {231const clientPos = new ClientCoordinates(clientX, clientY);232const pos = clientPos.toPageCoordinates(dom.getWindow(this.viewHelper.viewDomNode));233const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode);234235if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) {236return null;237}238239const relativePos = createCoordinatesRelativeToEditor(this.viewHelper.viewDomNode, editorPos, pos);240return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);241}242243protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget {244let target: HTMLElement | null = e.target;245if (!this.viewHelper.viewDomNode.contains(target)) {246const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode);247if (shadowRoot) {248const potentialTarget = shadowRoot.elementsFromPoint(e.posx, e.posy).find(249(el: Element) => this.viewHelper.viewDomNode.contains(el)250) ?? null;251target = potentialTarget as HTMLElement;252}253}254return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null);255}256257private _getMouseColumn(e: EditorMouseEvent): number {258return this.mouseTargetFactory.getMouseColumn(e.relativePos);259}260261protected _onContextMenu(e: EditorMouseEvent, testEventTarget: boolean): void {262this.viewController.emitContextMenu({263event: e,264target: this._createMouseTarget(e, testEventTarget)265});266}267268protected _onMouseMove(e: EditorMouseEvent): void {269const targetIsWidget = this.mouseTargetFactory.mouseTargetIsWidget(e);270if (!targetIsWidget) {271e.preventDefault();272}273274if (this._mouseDownOperation.isActive()) {275// In selection/drag operation276return;277}278const actualMouseMoveTime = e.timestamp;279if (actualMouseMoveTime < this.lastMouseLeaveTime) {280// Due to throttling, this event occurred before the mouse left the editor, therefore ignore it.281return;282}283284this.viewController.emitMouseMove({285event: e,286target: this._createMouseTarget(e, true)287});288}289290protected _onMouseLeave(e: EditorMouseEvent): void {291if (this._mouseLeaveMonitor) {292this._mouseLeaveMonitor.dispose();293this._mouseLeaveMonitor = null;294}295this.lastMouseLeaveTime = (new Date()).getTime();296this.viewController.emitMouseLeave({297event: e,298target: null299});300}301302protected _onMouseUp(e: EditorMouseEvent): void {303this.viewController.emitMouseUp({304event: e,305target: this._createMouseTarget(e, true)306});307}308309protected _onMouseDown(e: EditorMouseEvent, pointerId: number): void {310const t = this._createMouseTarget(e, true);311312const targetIsContent = (t.type === MouseTargetType.CONTENT_TEXT || t.type === MouseTargetType.CONTENT_EMPTY);313const targetIsGutter = (t.type === MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === MouseTargetType.GUTTER_LINE_NUMBERS || t.type === MouseTargetType.GUTTER_LINE_DECORATIONS);314const targetIsLineNumbers = (t.type === MouseTargetType.GUTTER_LINE_NUMBERS);315const selectOnLineNumbers = this._context.configuration.options.get(EditorOption.selectOnLineNumbers);316const targetIsViewZone = (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE);317const targetIsWidget = (t.type === MouseTargetType.CONTENT_WIDGET);318319let shouldHandle = e.leftButton || e.middleButton;320if (platform.isMacintosh && e.leftButton && e.ctrlKey) {321shouldHandle = false;322}323324const focus = () => {325e.preventDefault();326this.viewHelper.focusTextArea();327};328329if (shouldHandle && (targetIsContent || (targetIsLineNumbers && selectOnLineNumbers))) {330focus();331this._mouseDownOperation.start(t.type, e, pointerId);332333} else if (targetIsGutter) {334// Do not steal focus335e.preventDefault();336} else if (targetIsViewZone) {337const viewZoneData = t.detail;338if (shouldHandle && this.viewHelper.shouldSuppressMouseDownOnViewZone(viewZoneData.viewZoneId)) {339focus();340this._mouseDownOperation.start(t.type, e, pointerId);341e.preventDefault();342}343} else if (targetIsWidget && this.viewHelper.shouldSuppressMouseDownOnWidget(t.detail)) {344focus();345e.preventDefault();346}347348this.viewController.emitMouseDown({349event: e,350target: t351});352}353354protected _onMouseWheel(e: IMouseWheelEvent): void {355this.viewController.emitMouseWheel(e);356}357}358359class MouseDownOperation extends Disposable {360361private readonly _createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget;362private readonly _getMouseColumn: (e: EditorMouseEvent) => number;363364private readonly _mouseMoveMonitor: GlobalEditorPointerMoveMonitor;365private readonly _topBottomDragScrolling: TopBottomDragScrolling;366private readonly _leftRightDragScrolling: LeftRightDragScrolling;367private readonly _mouseState: MouseDownState;368369private _currentSelection: Selection;370private _isActive: boolean;371private _lastMouseEvent: EditorMouseEvent | null;372373constructor(374private readonly _context: ViewContext,375private readonly _viewController: ViewController,376private readonly _viewHelper: IPointerHandlerHelper,377private readonly _mouseTargetFactory: MouseTargetFactory,378createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget,379getMouseColumn: (e: EditorMouseEvent) => number380) {381super();382this._createMouseTarget = createMouseTarget;383this._getMouseColumn = getMouseColumn;384385this._mouseMoveMonitor = this._register(new GlobalEditorPointerMoveMonitor(this._viewHelper.viewDomNode));386this._topBottomDragScrolling = this._register(new TopBottomDragScrolling(387this._context,388this._viewHelper,389this._mouseTargetFactory,390(position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType)391));392this._leftRightDragScrolling = this._register(new LeftRightDragScrolling(393this._context,394this._viewHelper,395this._mouseTargetFactory,396(position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType)397));398this._mouseState = new MouseDownState();399400this._currentSelection = new Selection(1, 1, 1, 1);401this._isActive = false;402this._lastMouseEvent = null;403}404405public override dispose(): void {406super.dispose();407}408409public isActive(): boolean {410return this._isActive;411}412413private _onMouseDownThenMove(e: EditorMouseEvent): void {414this._lastMouseEvent = e;415this._mouseState.setModifiers(e);416417const position = this._findMousePosition(e, false);418if (!position) {419// Ignoring because position is unknown420return;421}422423if (this._mouseState.isDragAndDrop) {424this._viewController.emitMouseDrag({425event: e,426target: position427});428} else {429if (position.type === MouseTargetType.OUTSIDE_EDITOR) {430if (position.outsidePosition === 'above' || position.outsidePosition === 'below') {431this._topBottomDragScrolling.start(position, e);432this._leftRightDragScrolling.stop();433} else {434this._leftRightDragScrolling.start(position, e);435this._topBottomDragScrolling.stop();436}437} else {438this._topBottomDragScrolling.stop();439this._leftRightDragScrolling.stop();440this._dispatchMouse(position, true, NavigationCommandRevealType.Minimal);441}442}443}444445public start(targetType: MouseTargetType, e: EditorMouseEvent, pointerId: number): void {446this._lastMouseEvent = e;447448this._mouseState.setStartedOnLineNumbers(targetType === MouseTargetType.GUTTER_LINE_NUMBERS);449this._mouseState.setStartButtons(e);450this._mouseState.setModifiers(e);451const position = this._findMousePosition(e, true);452if (!position || !position.position) {453// Ignoring because position is unknown454return;455}456457this._mouseState.trySetCount(e.detail, position.position);458459// Overwrite the detail of the MouseEvent, as it will be sent out in an event and contributions might rely on it.460e.detail = this._mouseState.count;461462const options = this._context.configuration.options;463464if (!options.get(EditorOption.readOnly)465&& options.get(EditorOption.dragAndDrop)466&& !options.get(EditorOption.columnSelection)467&& !this._mouseState.altKey // we don't support multiple mouse468&& e.detail < 2 // only single click on a selection can work469&& !this._isActive // the mouse is not down yet470&& !this._currentSelection.isEmpty() // we don't drag single cursor471&& (position.type === MouseTargetType.CONTENT_TEXT) // single click on text472&& position.position && this._currentSelection.containsPosition(position.position) // single click on a selection473) {474this._mouseState.isDragAndDrop = true;475this._isActive = true;476477this._mouseMoveMonitor.startMonitoring(478this._viewHelper.viewLinesDomNode,479pointerId,480e.buttons,481(e) => this._onMouseDownThenMove(e),482(browserEvent?: MouseEvent | KeyboardEvent) => {483const position = this._findMousePosition(this._lastMouseEvent!, false);484485if (dom.isKeyboardEvent(browserEvent)) {486// cancel487this._viewController.emitMouseDropCanceled();488} else {489this._viewController.emitMouseDrop({490event: this._lastMouseEvent!,491target: (position ? this._createMouseTarget(this._lastMouseEvent!, true) : null) // Ignoring because position is unknown, e.g., Content View Zone492});493}494495this._stop();496}497);498499return;500}501502this._mouseState.isDragAndDrop = false;503this._dispatchMouse(position, e.shiftKey, NavigationCommandRevealType.Minimal);504505if (!this._isActive) {506this._isActive = true;507this._mouseMoveMonitor.startMonitoring(508this._viewHelper.viewLinesDomNode,509pointerId,510e.buttons,511(e) => this._onMouseDownThenMove(e),512() => this._stop()513);514}515}516517private _stop(): void {518this._isActive = false;519this._topBottomDragScrolling.stop();520this._leftRightDragScrolling.stop();521}522523public onHeightChanged(): void {524this._mouseMoveMonitor.stopMonitoring();525}526527public onPointerUp(): void {528this._mouseMoveMonitor.stopMonitoring();529}530531public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): void {532this._currentSelection = e.selections[0];533}534535private _getPositionOutsideEditor(e: EditorMouseEvent): IMouseTarget | null {536const editorContent = e.editorPos;537const model = this._context.viewModel;538const viewLayout = this._context.viewLayout;539540const mouseColumn = this._getMouseColumn(e);541542if (e.posy < editorContent.y) {543const outsideDistance = editorContent.y - e.posy;544const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - outsideDistance, 0);545const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);546if (viewZoneData) {547const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);548if (newPosition) {549return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'above', outsideDistance);550}551}552553const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);554return MouseTarget.createOutsideEditor(mouseColumn, new Position(aboveLineNumber, 1), 'above', outsideDistance);555}556557if (e.posy > editorContent.y + editorContent.height) {558const outsideDistance = e.posy - editorContent.y - editorContent.height;559const verticalOffset = viewLayout.getCurrentScrollTop() + e.relativePos.y;560const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);561if (viewZoneData) {562const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);563if (newPosition) {564return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'below', outsideDistance);565}566}567568const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);569return MouseTarget.createOutsideEditor(mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)), 'below', outsideDistance);570}571572const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + e.relativePos.y);573574const layoutInfo = this._context.configuration.options.get(EditorOption.layoutInfo);575576const xLeftBoundary = layoutInfo.contentLeft;577if (e.relativePos.x <= xLeftBoundary) {578const outsideDistance = xLeftBoundary - e.relativePos.x;579const isRtl = model.getTextDirection(possibleLineNumber) === TextDirection.RTL;580return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, isRtl ? model.getLineMaxColumn(possibleLineNumber) : 1), 'left', outsideDistance);581}582583const contentRight = (584layoutInfo.minimap.minimapLeft === 0585? layoutInfo.width - layoutInfo.verticalScrollbarWidth // Happens when minimap is hidden586: layoutInfo.minimap.minimapLeft587);588const xRightBoundary = contentRight;589if (e.relativePos.x >= xRightBoundary) {590const outsideDistance = e.relativePos.x - xRightBoundary;591const isRtl = model.getTextDirection(possibleLineNumber) === TextDirection.RTL;592return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, isRtl ? 1 : model.getLineMaxColumn(possibleLineNumber)), 'right', outsideDistance);593}594595return null;596}597598private _findMousePosition(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget | null {599const positionOutsideEditor = this._getPositionOutsideEditor(e);600if (positionOutsideEditor) {601return positionOutsideEditor;602}603604const t = this._createMouseTarget(e, testEventTarget);605const hintedPosition = t.position;606if (!hintedPosition) {607return null;608}609610if (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE) {611const newPosition = this._helpPositionJumpOverViewZone(t.detail);612if (newPosition) {613return MouseTarget.createViewZone(t.type, t.element, t.mouseColumn, newPosition, t.detail);614}615}616617return t;618}619620private _helpPositionJumpOverViewZone(viewZoneData: IMouseTargetViewZoneData): Position | null {621// Force position on view zones to go above or below depending on where selection started from622const selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn);623const positionBefore = viewZoneData.positionBefore;624const positionAfter = viewZoneData.positionAfter;625626if (positionBefore && positionAfter) {627if (positionBefore.isBefore(selectionStart)) {628return positionBefore;629} else {630return positionAfter;631}632}633return null;634}635636private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType): void {637if (!position.position) {638return;639}640this._viewController.dispatchMouse({641position: position.position,642mouseColumn: position.mouseColumn,643startedOnLineNumbers: this._mouseState.startedOnLineNumbers,644revealType,645646inSelectionMode: inSelectionMode,647mouseDownCount: this._mouseState.count,648altKey: this._mouseState.altKey,649ctrlKey: this._mouseState.ctrlKey,650metaKey: this._mouseState.metaKey,651shiftKey: this._mouseState.shiftKey,652653leftButton: this._mouseState.leftButton,654middleButton: this._mouseState.middleButton,655656onInjectedText: position.type === MouseTargetType.CONTENT_TEXT && position.detail.injectedText !== null657});658}659}660661class MouseDownState {662663private static readonly CLEAR_MOUSE_DOWN_COUNT_TIME = 400; // ms664665private _altKey: boolean;666public get altKey(): boolean { return this._altKey; }667668private _ctrlKey: boolean;669public get ctrlKey(): boolean { return this._ctrlKey; }670671private _metaKey: boolean;672public get metaKey(): boolean { return this._metaKey; }673674private _shiftKey: boolean;675public get shiftKey(): boolean { return this._shiftKey; }676677private _leftButton: boolean;678public get leftButton(): boolean { return this._leftButton; }679680private _middleButton: boolean;681public get middleButton(): boolean { return this._middleButton; }682683private _startedOnLineNumbers: boolean;684public get startedOnLineNumbers(): boolean { return this._startedOnLineNumbers; }685686private _lastMouseDownPosition: Position | null;687private _lastMouseDownPositionEqualCount: number;688private _lastMouseDownCount: number;689private _lastSetMouseDownCountTime: number;690public isDragAndDrop: boolean;691692constructor() {693this._altKey = false;694this._ctrlKey = false;695this._metaKey = false;696this._shiftKey = false;697this._leftButton = false;698this._middleButton = false;699this._startedOnLineNumbers = false;700this._lastMouseDownPosition = null;701this._lastMouseDownPositionEqualCount = 0;702this._lastMouseDownCount = 0;703this._lastSetMouseDownCountTime = 0;704this.isDragAndDrop = false;705}706707public get count(): number {708return this._lastMouseDownCount;709}710711public setModifiers(source: EditorMouseEvent) {712this._altKey = source.altKey;713this._ctrlKey = source.ctrlKey;714this._metaKey = source.metaKey;715this._shiftKey = source.shiftKey;716}717718public setStartButtons(source: EditorMouseEvent) {719this._leftButton = source.leftButton;720this._middleButton = source.middleButton;721}722723public setStartedOnLineNumbers(startedOnLineNumbers: boolean): void {724this._startedOnLineNumbers = startedOnLineNumbers;725}726727public trySetCount(setMouseDownCount: number, newMouseDownPosition: Position): void {728// a. Invalidate multiple clicking if too much time has passed (will be hit by IE because the detail field of mouse events contains garbage in IE10)729const currentTime = (new Date()).getTime();730if (currentTime - this._lastSetMouseDownCountTime > MouseDownState.CLEAR_MOUSE_DOWN_COUNT_TIME) {731setMouseDownCount = 1;732}733this._lastSetMouseDownCountTime = currentTime;734735// b. Ensure that we don't jump from single click to triple click in one go (will be hit by IE because the detail field of mouse events contains garbage in IE10)736if (setMouseDownCount > this._lastMouseDownCount + 1) {737setMouseDownCount = this._lastMouseDownCount + 1;738}739740// c. Invalidate multiple clicking if the logical position is different741if (this._lastMouseDownPosition && this._lastMouseDownPosition.equals(newMouseDownPosition)) {742this._lastMouseDownPositionEqualCount++;743} else {744this._lastMouseDownPositionEqualCount = 1;745}746this._lastMouseDownPosition = newMouseDownPosition;747748// Finally set the lastMouseDownCount749this._lastMouseDownCount = Math.min(setMouseDownCount, this._lastMouseDownPositionEqualCount);750}751752}753754755