Path: blob/main/src/vs/editor/browser/controller/mouseHandler.ts
3294 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';2526export interface IPointerHandlerHelper {27viewDomNode: HTMLElement;28linesContentDomNode: HTMLElement;29viewLinesDomNode: HTMLElement;30viewLinesGpu: ViewLinesGpu | undefined;3132focusTextArea(): void;33dispatchTextAreaEvent(event: CustomEvent): void;3435/**36* Get the last rendered information for cursors & textarea.37*/38getLastRenderData(): PointerHandlerLastRenderData;3940/**41* Render right now42*/43renderNow(): void;4445shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean;46shouldSuppressMouseDownOnWidget(widgetId: string): boolean;4748/**49* Decode a position from a rendered dom node50*/51getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null;5253visibleRangeForPosition(lineNumber: number, column: number): HorizontalPosition | null;54getLineWidth(lineNumber: number): number;55}5657export class MouseHandler extends ViewEventHandler {5859protected _context: ViewContext;60protected viewController: ViewController;61protected viewHelper: IPointerHandlerHelper;62protected mouseTargetFactory: MouseTargetFactory;63protected readonly _mouseDownOperation: MouseDownOperation;64private lastMouseLeaveTime: number;65private _height: number;66private _mouseLeaveMonitor: IDisposable | null = null;6768constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {69super();7071this._context = context;72this.viewController = viewController;73this.viewHelper = viewHelper;74this.mouseTargetFactory = new MouseTargetFactory(this._context, viewHelper);7576this._mouseDownOperation = this._register(new MouseDownOperation(77this._context,78this.viewController,79this.viewHelper,80this.mouseTargetFactory,81(e, testEventTarget) => this._createMouseTarget(e, testEventTarget),82(e) => this._getMouseColumn(e)83));8485this.lastMouseLeaveTime = -1;86this._height = this._context.configuration.options.get(EditorOption.layoutInfo).height;8788const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode);8990this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true)));9192this._register(mouseEvents.onMouseMove(this.viewHelper.viewDomNode, (e) => {93this._onMouseMove(e);9495// See https://github.com/microsoft/vscode/issues/13878996// When moving the mouse really quickly, the browser sometimes forgets to97// send us a `mouseleave` or `mouseout` event. We therefore install here98// a global `mousemove` listener to manually recover if the mouse goes outside99// the editor. As soon as the mouse leaves outside of the editor, we100// remove this listener101102if (!this._mouseLeaveMonitor) {103this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => {104if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) {105// went outside the editor!106this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode));107}108});109}110}));111112this._register(mouseEvents.onMouseUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));113114this._register(mouseEvents.onMouseLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e)));115116// `pointerdown` events can't be used to determine if there's a double click, or triple click117// because their `e.detail` is always 0.118// We will therefore save the pointer id for the mouse and then reuse it in the `mousedown` event119// for `element.setPointerCapture`.120let capturePointerId: number = 0;121this._register(mouseEvents.onPointerDown(this.viewHelper.viewDomNode, (e, pointerId) => {122capturePointerId = pointerId;123}));124// The `pointerup` listener registered by `GlobalEditorPointerMoveMonitor` does not get invoked 100% of the times.125// I speculate that this is because the `pointerup` listener is only registered during the `mousedown` event, and perhaps126// the `pointerup` event is already queued for dispatching, which makes it that the new listener doesn't get fired.127// See https://github.com/microsoft/vscode/issues/146486 for repro steps.128// To compensate for that, we simply register here a `pointerup` listener and just communicate it.129this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.POINTER_UP, (e: PointerEvent) => {130this._mouseDownOperation.onPointerUp();131}));132this._register(mouseEvents.onMouseDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e, capturePointerId)));133this._setupMouseWheelZoomListener();134135this._context.addEventHandler(this);136}137138private _setupMouseWheelZoomListener(): void {139140const classifier = MouseWheelClassifier.INSTANCE;141142let prevMouseWheelTime = 0;143let gestureStartZoomLevel = EditorZoom.getZoomLevel();144let gestureHasZoomModifiers = false;145let gestureAccumulatedDelta = 0;146147const onMouseWheel = (browserEvent: IMouseWheelEvent) => {148this.viewController.emitMouseWheel(browserEvent);149150if (!this._context.configuration.options.get(EditorOption.mouseWheelZoom)) {151return;152}153154const e = new StandardWheelEvent(browserEvent);155classifier.acceptStandardWheelEvent(e);156157if (classifier.isPhysicalMouseWheel()) {158if (hasMouseWheelZoomModifiers(browserEvent)) {159const zoomLevel: number = EditorZoom.getZoomLevel();160const delta = e.deltaY > 0 ? 1 : -1;161EditorZoom.setZoomLevel(zoomLevel + delta);162e.preventDefault();163e.stopPropagation();164}165} else {166// we consider mousewheel events that occur within 50ms of each other to be part of the same gesture167// we don't want to consider mouse wheel events where ctrl/cmd is pressed during the inertia phase168// we also want to accumulate deltaY values from the same gesture and use that to set the zoom level169if (Date.now() - prevMouseWheelTime > 50) {170// reset if more than 50ms have passed171gestureStartZoomLevel = EditorZoom.getZoomLevel();172gestureHasZoomModifiers = hasMouseWheelZoomModifiers(browserEvent);173gestureAccumulatedDelta = 0;174}175176prevMouseWheelTime = Date.now();177gestureAccumulatedDelta += e.deltaY;178179if (gestureHasZoomModifiers) {180EditorZoom.setZoomLevel(gestureStartZoomLevel + gestureAccumulatedDelta / 5);181e.preventDefault();182e.stopPropagation();183}184}185};186this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false }));187188function hasMouseWheelZoomModifiers(browserEvent: IMouseWheelEvent): boolean {189return (190platform.isMacintosh191// on macOS we support cmd + two fingers scroll (`metaKey` set)192// and also the two fingers pinch gesture (`ctrKey` set)193? ((browserEvent.metaKey || browserEvent.ctrlKey) && !browserEvent.shiftKey && !browserEvent.altKey)194: (browserEvent.ctrlKey && !browserEvent.metaKey && !browserEvent.shiftKey && !browserEvent.altKey)195);196}197}198199public override dispose(): void {200this._context.removeEventHandler(this);201if (this._mouseLeaveMonitor) {202this._mouseLeaveMonitor.dispose();203this._mouseLeaveMonitor = null;204}205super.dispose();206}207208// --- begin event handlers209public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {210if (e.hasChanged(EditorOption.layoutInfo)) {211// layout change212const height = this._context.configuration.options.get(EditorOption.layoutInfo).height;213if (this._height !== height) {214this._height = height;215this._mouseDownOperation.onHeightChanged();216}217}218return false;219}220public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {221this._mouseDownOperation.onCursorStateChanged(e);222return false;223}224public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {225return false;226}227// --- end event handlers228229public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {230const clientPos = new ClientCoordinates(clientX, clientY);231const pos = clientPos.toPageCoordinates(dom.getWindow(this.viewHelper.viewDomNode));232const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode);233234if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) {235return null;236}237238const relativePos = createCoordinatesRelativeToEditor(this.viewHelper.viewDomNode, editorPos, pos);239return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, relativePos, null);240}241242protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget {243let target = e.target;244if (!this.viewHelper.viewDomNode.contains(target)) {245const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode);246if (shadowRoot) {247target = (<any>shadowRoot).elementsFromPoint(e.posx, e.posy).find(248(el: Element) => this.viewHelper.viewDomNode.contains(el)249);250}251}252return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null);253}254255private _getMouseColumn(e: EditorMouseEvent): number {256return this.mouseTargetFactory.getMouseColumn(e.relativePos);257}258259protected _onContextMenu(e: EditorMouseEvent, testEventTarget: boolean): void {260this.viewController.emitContextMenu({261event: e,262target: this._createMouseTarget(e, testEventTarget)263});264}265266protected _onMouseMove(e: EditorMouseEvent): void {267const targetIsWidget = this.mouseTargetFactory.mouseTargetIsWidget(e);268if (!targetIsWidget) {269e.preventDefault();270}271272if (this._mouseDownOperation.isActive()) {273// In selection/drag operation274return;275}276const actualMouseMoveTime = e.timestamp;277if (actualMouseMoveTime < this.lastMouseLeaveTime) {278// Due to throttling, this event occurred before the mouse left the editor, therefore ignore it.279return;280}281282this.viewController.emitMouseMove({283event: e,284target: this._createMouseTarget(e, true)285});286}287288protected _onMouseLeave(e: EditorMouseEvent): void {289if (this._mouseLeaveMonitor) {290this._mouseLeaveMonitor.dispose();291this._mouseLeaveMonitor = null;292}293this.lastMouseLeaveTime = (new Date()).getTime();294this.viewController.emitMouseLeave({295event: e,296target: null297});298}299300protected _onMouseUp(e: EditorMouseEvent): void {301this.viewController.emitMouseUp({302event: e,303target: this._createMouseTarget(e, true)304});305}306307protected _onMouseDown(e: EditorMouseEvent, pointerId: number): void {308const t = this._createMouseTarget(e, true);309310const targetIsContent = (t.type === MouseTargetType.CONTENT_TEXT || t.type === MouseTargetType.CONTENT_EMPTY);311const targetIsGutter = (t.type === MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === MouseTargetType.GUTTER_LINE_NUMBERS || t.type === MouseTargetType.GUTTER_LINE_DECORATIONS);312const targetIsLineNumbers = (t.type === MouseTargetType.GUTTER_LINE_NUMBERS);313const selectOnLineNumbers = this._context.configuration.options.get(EditorOption.selectOnLineNumbers);314const targetIsViewZone = (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE);315const targetIsWidget = (t.type === MouseTargetType.CONTENT_WIDGET);316317let shouldHandle = e.leftButton || e.middleButton;318if (platform.isMacintosh && e.leftButton && e.ctrlKey) {319shouldHandle = false;320}321322const focus = () => {323e.preventDefault();324this.viewHelper.focusTextArea();325};326327if (shouldHandle && (targetIsContent || (targetIsLineNumbers && selectOnLineNumbers))) {328focus();329this._mouseDownOperation.start(t.type, e, pointerId);330331} else if (targetIsGutter) {332// Do not steal focus333e.preventDefault();334} else if (targetIsViewZone) {335const viewZoneData = t.detail;336if (shouldHandle && this.viewHelper.shouldSuppressMouseDownOnViewZone(viewZoneData.viewZoneId)) {337focus();338this._mouseDownOperation.start(t.type, e, pointerId);339e.preventDefault();340}341} else if (targetIsWidget && this.viewHelper.shouldSuppressMouseDownOnWidget(<string>t.detail)) {342focus();343e.preventDefault();344}345346this.viewController.emitMouseDown({347event: e,348target: t349});350}351352protected _onMouseWheel(e: IMouseWheelEvent): void {353this.viewController.emitMouseWheel(e);354}355}356357class MouseDownOperation extends Disposable {358359private readonly _createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget;360private readonly _getMouseColumn: (e: EditorMouseEvent) => number;361362private readonly _mouseMoveMonitor: GlobalEditorPointerMoveMonitor;363private readonly _topBottomDragScrolling: TopBottomDragScrolling;364private readonly _leftRightDragScrolling: LeftRightDragScrolling;365private readonly _mouseState: MouseDownState;366367private _currentSelection: Selection;368private _isActive: boolean;369private _lastMouseEvent: EditorMouseEvent | null;370371constructor(372private readonly _context: ViewContext,373private readonly _viewController: ViewController,374private readonly _viewHelper: IPointerHandlerHelper,375private readonly _mouseTargetFactory: MouseTargetFactory,376createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget,377getMouseColumn: (e: EditorMouseEvent) => number378) {379super();380this._createMouseTarget = createMouseTarget;381this._getMouseColumn = getMouseColumn;382383this._mouseMoveMonitor = this._register(new GlobalEditorPointerMoveMonitor(this._viewHelper.viewDomNode));384this._topBottomDragScrolling = this._register(new TopBottomDragScrolling(385this._context,386this._viewHelper,387this._mouseTargetFactory,388(position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType)389));390this._leftRightDragScrolling = this._register(new LeftRightDragScrolling(391this._context,392this._viewHelper,393this._mouseTargetFactory,394(position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType)395));396this._mouseState = new MouseDownState();397398this._currentSelection = new Selection(1, 1, 1, 1);399this._isActive = false;400this._lastMouseEvent = null;401}402403public override dispose(): void {404super.dispose();405}406407public isActive(): boolean {408return this._isActive;409}410411private _onMouseDownThenMove(e: EditorMouseEvent): void {412this._lastMouseEvent = e;413this._mouseState.setModifiers(e);414415const position = this._findMousePosition(e, false);416if (!position) {417// Ignoring because position is unknown418return;419}420421if (this._mouseState.isDragAndDrop) {422this._viewController.emitMouseDrag({423event: e,424target: position425});426} else {427if (position.type === MouseTargetType.OUTSIDE_EDITOR) {428if (position.outsidePosition === 'above' || position.outsidePosition === 'below') {429this._topBottomDragScrolling.start(position, e);430this._leftRightDragScrolling.stop();431} else {432this._leftRightDragScrolling.start(position, e);433this._topBottomDragScrolling.stop();434}435} else {436this._topBottomDragScrolling.stop();437this._leftRightDragScrolling.stop();438this._dispatchMouse(position, true, NavigationCommandRevealType.Minimal);439}440}441}442443public start(targetType: MouseTargetType, e: EditorMouseEvent, pointerId: number): void {444this._lastMouseEvent = e;445446this._mouseState.setStartedOnLineNumbers(targetType === MouseTargetType.GUTTER_LINE_NUMBERS);447this._mouseState.setStartButtons(e);448this._mouseState.setModifiers(e);449const position = this._findMousePosition(e, true);450if (!position || !position.position) {451// Ignoring because position is unknown452return;453}454455this._mouseState.trySetCount(e.detail, position.position);456457// Overwrite the detail of the MouseEvent, as it will be sent out in an event and contributions might rely on it.458e.detail = this._mouseState.count;459460const options = this._context.configuration.options;461462if (!options.get(EditorOption.readOnly)463&& options.get(EditorOption.dragAndDrop)464&& !options.get(EditorOption.columnSelection)465&& !this._mouseState.altKey // we don't support multiple mouse466&& e.detail < 2 // only single click on a selection can work467&& !this._isActive // the mouse is not down yet468&& !this._currentSelection.isEmpty() // we don't drag single cursor469&& (position.type === MouseTargetType.CONTENT_TEXT) // single click on text470&& position.position && this._currentSelection.containsPosition(position.position) // single click on a selection471) {472this._mouseState.isDragAndDrop = true;473this._isActive = true;474475this._mouseMoveMonitor.startMonitoring(476this._viewHelper.viewLinesDomNode,477pointerId,478e.buttons,479(e) => this._onMouseDownThenMove(e),480(browserEvent?: MouseEvent | KeyboardEvent) => {481const position = this._findMousePosition(this._lastMouseEvent!, false);482483if (dom.isKeyboardEvent(browserEvent)) {484// cancel485this._viewController.emitMouseDropCanceled();486} else {487this._viewController.emitMouseDrop({488event: this._lastMouseEvent!,489target: (position ? this._createMouseTarget(this._lastMouseEvent!, true) : null) // Ignoring because position is unknown, e.g., Content View Zone490});491}492493this._stop();494}495);496497return;498}499500this._mouseState.isDragAndDrop = false;501this._dispatchMouse(position, e.shiftKey, NavigationCommandRevealType.Minimal);502503if (!this._isActive) {504this._isActive = true;505this._mouseMoveMonitor.startMonitoring(506this._viewHelper.viewLinesDomNode,507pointerId,508e.buttons,509(e) => this._onMouseDownThenMove(e),510() => this._stop()511);512}513}514515private _stop(): void {516this._isActive = false;517this._topBottomDragScrolling.stop();518this._leftRightDragScrolling.stop();519}520521public onHeightChanged(): void {522this._mouseMoveMonitor.stopMonitoring();523}524525public onPointerUp(): void {526this._mouseMoveMonitor.stopMonitoring();527}528529public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): void {530this._currentSelection = e.selections[0];531}532533private _getPositionOutsideEditor(e: EditorMouseEvent): IMouseTarget | null {534const editorContent = e.editorPos;535const model = this._context.viewModel;536const viewLayout = this._context.viewLayout;537538const mouseColumn = this._getMouseColumn(e);539540if (e.posy < editorContent.y) {541const outsideDistance = editorContent.y - e.posy;542const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - outsideDistance, 0);543const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);544if (viewZoneData) {545const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);546if (newPosition) {547return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'above', outsideDistance);548}549}550551const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);552return MouseTarget.createOutsideEditor(mouseColumn, new Position(aboveLineNumber, 1), 'above', outsideDistance);553}554555if (e.posy > editorContent.y + editorContent.height) {556const outsideDistance = e.posy - editorContent.y - editorContent.height;557const verticalOffset = viewLayout.getCurrentScrollTop() + e.relativePos.y;558const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset);559if (viewZoneData) {560const newPosition = this._helpPositionJumpOverViewZone(viewZoneData);561if (newPosition) {562return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'below', outsideDistance);563}564}565566const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset);567return MouseTarget.createOutsideEditor(mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)), 'below', outsideDistance);568}569570const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + e.relativePos.y);571572const layoutInfo = this._context.configuration.options.get(EditorOption.layoutInfo);573574const xLeftBoundary = layoutInfo.contentLeft;575if (e.relativePos.x <= xLeftBoundary) {576const outsideDistance = xLeftBoundary - e.relativePos.x;577return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, 1), 'left', outsideDistance);578}579580const contentRight = (581layoutInfo.minimap.minimapLeft === 0582? layoutInfo.width - layoutInfo.verticalScrollbarWidth // Happens when minimap is hidden583: layoutInfo.minimap.minimapLeft584);585const xRightBoundary = contentRight;586if (e.relativePos.x >= xRightBoundary) {587const outsideDistance = e.relativePos.x - xRightBoundary;588return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber)), 'right', outsideDistance);589}590591return null;592}593594private _findMousePosition(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget | null {595const positionOutsideEditor = this._getPositionOutsideEditor(e);596if (positionOutsideEditor) {597return positionOutsideEditor;598}599600const t = this._createMouseTarget(e, testEventTarget);601const hintedPosition = t.position;602if (!hintedPosition) {603return null;604}605606if (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE) {607const newPosition = this._helpPositionJumpOverViewZone(t.detail);608if (newPosition) {609return MouseTarget.createViewZone(t.type, t.element, t.mouseColumn, newPosition, t.detail);610}611}612613return t;614}615616private _helpPositionJumpOverViewZone(viewZoneData: IMouseTargetViewZoneData): Position | null {617// Force position on view zones to go above or below depending on where selection started from618const selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn);619const positionBefore = viewZoneData.positionBefore;620const positionAfter = viewZoneData.positionAfter;621622if (positionBefore && positionAfter) {623if (positionBefore.isBefore(selectionStart)) {624return positionBefore;625} else {626return positionAfter;627}628}629return null;630}631632private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType): void {633if (!position.position) {634return;635}636this._viewController.dispatchMouse({637position: position.position,638mouseColumn: position.mouseColumn,639startedOnLineNumbers: this._mouseState.startedOnLineNumbers,640revealType,641642inSelectionMode: inSelectionMode,643mouseDownCount: this._mouseState.count,644altKey: this._mouseState.altKey,645ctrlKey: this._mouseState.ctrlKey,646metaKey: this._mouseState.metaKey,647shiftKey: this._mouseState.shiftKey,648649leftButton: this._mouseState.leftButton,650middleButton: this._mouseState.middleButton,651652onInjectedText: position.type === MouseTargetType.CONTENT_TEXT && position.detail.injectedText !== null653});654}655}656657class MouseDownState {658659private static readonly CLEAR_MOUSE_DOWN_COUNT_TIME = 400; // ms660661private _altKey: boolean;662public get altKey(): boolean { return this._altKey; }663664private _ctrlKey: boolean;665public get ctrlKey(): boolean { return this._ctrlKey; }666667private _metaKey: boolean;668public get metaKey(): boolean { return this._metaKey; }669670private _shiftKey: boolean;671public get shiftKey(): boolean { return this._shiftKey; }672673private _leftButton: boolean;674public get leftButton(): boolean { return this._leftButton; }675676private _middleButton: boolean;677public get middleButton(): boolean { return this._middleButton; }678679private _startedOnLineNumbers: boolean;680public get startedOnLineNumbers(): boolean { return this._startedOnLineNumbers; }681682private _lastMouseDownPosition: Position | null;683private _lastMouseDownPositionEqualCount: number;684private _lastMouseDownCount: number;685private _lastSetMouseDownCountTime: number;686public isDragAndDrop: boolean;687688constructor() {689this._altKey = false;690this._ctrlKey = false;691this._metaKey = false;692this._shiftKey = false;693this._leftButton = false;694this._middleButton = false;695this._startedOnLineNumbers = false;696this._lastMouseDownPosition = null;697this._lastMouseDownPositionEqualCount = 0;698this._lastMouseDownCount = 0;699this._lastSetMouseDownCountTime = 0;700this.isDragAndDrop = false;701}702703public get count(): number {704return this._lastMouseDownCount;705}706707public setModifiers(source: EditorMouseEvent) {708this._altKey = source.altKey;709this._ctrlKey = source.ctrlKey;710this._metaKey = source.metaKey;711this._shiftKey = source.shiftKey;712}713714public setStartButtons(source: EditorMouseEvent) {715this._leftButton = source.leftButton;716this._middleButton = source.middleButton;717}718719public setStartedOnLineNumbers(startedOnLineNumbers: boolean): void {720this._startedOnLineNumbers = startedOnLineNumbers;721}722723public trySetCount(setMouseDownCount: number, newMouseDownPosition: Position): void {724// 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)725const currentTime = (new Date()).getTime();726if (currentTime - this._lastSetMouseDownCountTime > MouseDownState.CLEAR_MOUSE_DOWN_COUNT_TIME) {727setMouseDownCount = 1;728}729this._lastSetMouseDownCountTime = currentTime;730731// 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)732if (setMouseDownCount > this._lastMouseDownCount + 1) {733setMouseDownCount = this._lastMouseDownCount + 1;734}735736// c. Invalidate multiple clicking if the logical position is different737if (this._lastMouseDownPosition && this._lastMouseDownPosition.equals(newMouseDownPosition)) {738this._lastMouseDownPositionEqualCount++;739} else {740this._lastMouseDownPositionEqualCount = 1;741}742this._lastMouseDownPosition = newMouseDownPosition;743744// Finally set the lastMouseDownCount745this._lastMouseDownCount = Math.min(setMouseDownCount, this._lastMouseDownPositionEqualCount);746}747748}749750751