Skip to content

Commit 531ee44

Browse files
committed
Add editor.getTargetAtClientPoint (microsoft/monaco-editor#249)
1 parent 6107902 commit 531ee44

8 files changed

Lines changed: 98 additions & 42 deletions

File tree

src/vs/editor/browser/controller/mouseHandler.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import * as editorBrowser from 'vs/editor/browser/editorBrowser';
1717
import { TimeoutTimer, RunOnceScheduler } from 'vs/base/common/async';
1818
import { ViewContext } from 'vs/editor/common/view/viewContext';
1919
import { VisibleRange } from 'vs/editor/common/view/renderingContext';
20-
import { EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, EditorMouseEvent } from 'vs/editor/browser/editorDom';
20+
import { EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, EditorMouseEvent, createEditorPagePosition, ClientCoordinates } from 'vs/editor/browser/editorDom';
2121
import { StandardMouseWheelEvent } from 'vs/base/browser/mouseEvent';
2222
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
2323
import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/viewCursor';
@@ -204,9 +204,7 @@ export class MouseHandler extends ViewEventHandler implements IDisposable {
204204
}
205205

206206
// --- begin event handlers
207-
_layoutInfo: editorCommon.EditorLayoutInfo;
208207
public onLayoutChanged(layoutInfo: editorCommon.EditorLayoutInfo): boolean {
209-
this._layoutInfo = layoutInfo;
210208
return false;
211209
}
212210
public onScrollChanged(e: editorCommon.IScrollEvent): boolean {
@@ -224,13 +222,26 @@ export class MouseHandler extends ViewEventHandler implements IDisposable {
224222
}
225223
// --- end event handlers
226224

225+
public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget {
226+
let clientPos = new ClientCoordinates(clientX, clientY);
227+
let pos = clientPos.toPageCoordinates();
228+
let editorPos = createEditorPagePosition(this.viewHelper.viewDomNode);
229+
230+
if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) {
231+
return null;
232+
}
233+
234+
let lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData();
235+
return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, editorPos, pos, null);
236+
}
237+
227238
protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): editorBrowser.IMouseTarget {
228239
let lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData();
229-
return this.mouseTargetFactory.createMouseTarget(this._layoutInfo, lastViewCursorsRenderData, e, testEventTarget);
240+
return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, e.editorPos, e.pos, testEventTarget ? e.target : null);
230241
}
231242

232243
private _getMouseColumn(e: EditorMouseEvent): number {
233-
return this.mouseTargetFactory.getMouseColumn(this._layoutInfo, e);
244+
return this.mouseTargetFactory.getMouseColumn(e.editorPos, e.pos);
234245
}
235246

236247
protected _onContextMenu(e: EditorMouseEvent, testEventTarget: boolean): void {

src/vs/editor/browser/controller/mouseTarget.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ class HitTestContext {
194194
private readonly _context: ViewContext;
195195
private readonly _viewHelper: IPointerHandlerHelper;
196196

197-
constructor(context: ViewContext, viewHelper: IPointerHandlerHelper, layoutInfo: EditorLayoutInfo, lastViewCursorsRenderData: IViewCursorRenderData[]) {
197+
constructor(context: ViewContext, viewHelper: IPointerHandlerHelper, lastViewCursorsRenderData: IViewCursorRenderData[]) {
198198
this.model = context.model;
199-
this.layoutInfo = layoutInfo;
199+
this.layoutInfo = context.configuration.editor.layoutInfo;
200200
this.viewDomNode = viewHelper.viewDomNode;
201201
this.lineHeight = context.configuration.editor.lineHeight;
202202
this.typicalHalfwidthCharacterWidth = context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth;
@@ -317,20 +317,18 @@ class HitTestContext {
317317

318318
abstract class BareHitTestRequest {
319319

320-
public readonly pos: PageCoordinates;
321320
public readonly editorPos: EditorPagePosition;
322-
321+
public readonly pos: PageCoordinates;
323322
public readonly mouseVerticalOffset: number;
324-
325323
public readonly isInMarginArea: boolean;
326324
public readonly isInContentArea: boolean;
327325
public readonly mouseContentHorizontalOffset: number;
328326

329327
protected readonly mouseColumn: number;
330328

331-
constructor(ctx: HitTestContext, pos: PageCoordinates, editorPos: EditorPagePosition) {
332-
this.pos = pos;
329+
constructor(ctx: HitTestContext, editorPos: EditorPagePosition, pos: PageCoordinates) {
333330
this.editorPos = editorPos;
331+
this.pos = pos;
334332

335333
this.mouseVerticalOffset = Math.max(0, ctx.getScrollTop() + pos.y - editorPos.y);
336334
this.mouseContentHorizontalOffset = ctx.getScrollLeft() + pos.x - editorPos.x - ctx.layoutInfo.contentLeft;
@@ -345,8 +343,8 @@ class HitTestRequest extends BareHitTestRequest {
345343
public readonly target: Element;
346344
public readonly targetPath: Uint8Array;
347345

348-
constructor(ctx: HitTestContext, pos: PageCoordinates, editorPos: EditorPagePosition, target: Element) {
349-
super(ctx, pos, editorPos);
346+
constructor(ctx: HitTestContext, editorPos: EditorPagePosition, pos: PageCoordinates, target: Element) {
347+
super(ctx, editorPos, pos);
350348
this._ctx = ctx;
351349

352350
if (target) {
@@ -367,16 +365,10 @@ class HitTestRequest extends BareHitTestRequest {
367365
}
368366

369367
public withTarget(target: Element): HitTestRequest {
370-
return new HitTestRequest(this._ctx, this.pos, this.editorPos, this.target);
368+
return new HitTestRequest(this._ctx, this.editorPos, this.pos, target);
371369
}
372370
}
373371

374-
export interface ISimplifiedEditorMouseEvent {
375-
readonly page: PageCoordinates;
376-
readonly editorPos: EditorPagePosition;
377-
readonly target: HTMLElement;
378-
}
379-
380372
export class MouseTargetFactory {
381373

382374
private _context: ViewContext;
@@ -404,9 +396,9 @@ export class MouseTargetFactory {
404396
return false;
405397
}
406398

407-
public createMouseTarget(layoutInfo: EditorLayoutInfo, lastViewCursorsRenderData: IViewCursorRenderData[], e: ISimplifiedEditorMouseEvent, testEventTarget: boolean): IMouseTarget {
408-
const ctx = new HitTestContext(this._context, this._viewHelper, layoutInfo, lastViewCursorsRenderData);
409-
const request = new HitTestRequest(ctx, e.page, e.editorPos, testEventTarget ? e.target : null);
399+
public createMouseTarget(lastViewCursorsRenderData: IViewCursorRenderData[], editorPos: EditorPagePosition, pos: PageCoordinates, target: HTMLElement): IMouseTarget {
400+
const ctx = new HitTestContext(this._context, this._viewHelper, lastViewCursorsRenderData);
401+
const request = new HitTestRequest(ctx, editorPos, pos, target);
410402
try {
411403
let r = MouseTargetFactory._createMouseTarget(ctx, request, false);
412404
// console.log(r.toString());
@@ -600,8 +592,9 @@ export class MouseTargetFactory {
600592
return null;
601593
}
602594

603-
public getMouseColumn(layoutInfo: EditorLayoutInfo, e: ISimplifiedEditorMouseEvent): number {
604-
let mouseContentHorizontalOffset = this._viewHelper.getScrollLeft() + e.page.x - e.editorPos.x - layoutInfo.contentLeft;
595+
public getMouseColumn(editorPos: EditorPagePosition, pos: PageCoordinates): number {
596+
let layoutInfo = this._context.configuration.editor.layoutInfo;
597+
let mouseContentHorizontalOffset = this._viewHelper.getScrollLeft() + pos.x - editorPos.x - layoutInfo.contentLeft;
605598
return MouseTargetFactory._getMouseColumn(mouseContentHorizontalOffset, this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth);
606599
}
607600

@@ -758,15 +751,30 @@ export class MouseTargetFactory {
758751
private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): IHitTestResult {
759752
let hitResult: { offsetNode: Node; offset: number; } = (<any>document).caretPositionFromPoint(coords.clientX, coords.clientY);
760753

761-
let range = document.createRange();
762-
range.setStart(hitResult.offsetNode, hitResult.offset);
763-
range.collapse(true);
764-
let resultPosition = ctx.getPositionFromDOMInfo(<HTMLElement>range.startContainer.parentNode, range.startOffset);
765-
range.detach();
754+
if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) {
755+
// offsetNode is expected to be the token text
756+
let parent1 = hitResult.offsetNode.parentNode; // expected to be the token span
757+
let parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span
758+
let parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div
759+
let parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (<HTMLElement>parent3).className : null;
760+
761+
if (parent3ClassName === ClassNames.VIEW_LINE) {
762+
let p = ctx.getPositionFromDOMInfo(<HTMLElement>hitResult.offsetNode.parentNode, hitResult.offset);
763+
return {
764+
position: p,
765+
hitTarget: null
766+
};
767+
} else {
768+
return {
769+
position: null,
770+
hitTarget: <HTMLElement>hitResult.offsetNode.parentNode
771+
};
772+
}
773+
}
766774

767775
return {
768-
position: resultPosition,
769-
hitTarget: null
776+
position: null,
777+
hitTarget: <HTMLElement>hitResult.offsetNode
770778
};
771779
}
772780

src/vs/editor/browser/controller/pointerHandler.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
import { IDisposable } from 'vs/base/common/lifecycle';
88
import * as dom from 'vs/base/browser/dom';
99
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
10-
import { IScrollEvent } from 'vs/editor/common/editorCommon';
1110
import { MouseHandler, IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler';
12-
import { IViewController } from 'vs/editor/browser/editorBrowser';
11+
import { IViewController, IMouseTarget } from 'vs/editor/browser/editorBrowser';
1312
import { ViewContext } from 'vs/editor/common/view/viewContext';
1413
import { EditorMouseEvent } from 'vs/editor/browser/editorDom';
1514

@@ -247,8 +246,8 @@ export class PointerHandler implements IDisposable {
247246
}
248247
}
249248

250-
public onScrollChanged(e: IScrollEvent): void {
251-
this.handler.onScrollChanged(e);
249+
public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget {
250+
return this.handler.getTargetAtClientPoint(clientX, clientY);
252251
}
253252

254253
public dispose(): void {

src/vs/editor/browser/editorBrowser.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface ICodeEditorHelper {
4444
getVerticalOffsetForPosition(lineNumber: number, column: number): number;
4545
delegateVerticalScrollbarMouseDown(browserEvent: MouseEvent): void;
4646
getOffsetForColumn(lineNumber: number, column: number): number;
47+
getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget;
4748
}
4849

4950
/**
@@ -522,6 +523,14 @@ export interface ICodeEditor extends editorCommon.ICommonCodeEditor {
522523
*/
523524
getTopForPosition(lineNumber: number, column: number): number;
524525

526+
/**
527+
* Get the hit test target at coordinates `clientX` and `clientY`.
528+
* The coordinates are relative to the top-left of the viewport.
529+
*
530+
* @returns Hit test target or null if the coordinates fall outside the editor or the editor has no model.
531+
*/
532+
getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget;
533+
525534
/**
526535
* Get the visible position for `position`.
527536
* The result position takes scrolling into account and is relative to the top left corner of the editor.

src/vs/editor/browser/editorDom.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export class ClientCoordinates {
4444
this.clientX = clientX;
4545
this.clientY = clientY;
4646
}
47+
48+
public toPageCoordinates(): PageCoordinates {
49+
return new PageCoordinates(this.clientX + dom.StandardWindow.scrollX, this.clientY + dom.StandardWindow.scrollY);
50+
}
4751
}
4852

4953
/**
@@ -65,13 +69,18 @@ export class EditorPagePosition {
6569
}
6670
}
6771

72+
export function createEditorPagePosition(editorViewDomNode: HTMLElement): EditorPagePosition {
73+
let editorPos = dom.getDomNodePagePosition(editorViewDomNode);
74+
return new EditorPagePosition(editorPos.left, editorPos.top, editorPos.width, editorPos.height);
75+
}
76+
6877
export class EditorMouseEvent extends StandardMouseEvent {
6978
_editorMouseEventBrand: void;
7079

7180
/**
7281
* Coordinates relative to the whole document.
7382
*/
74-
public readonly page: PageCoordinates;
83+
public readonly pos: PageCoordinates;
7584

7685
/**
7786
* Editor's coordinates relative to the whole document.
@@ -80,10 +89,8 @@ export class EditorMouseEvent extends StandardMouseEvent {
8089

8190
constructor(e: MouseEvent, editorViewDomNode: HTMLElement) {
8291
super(e);
83-
this.page = new PageCoordinates(this.posx, this.posy);
84-
85-
let editorPos = dom.getDomNodePagePosition(editorViewDomNode);
86-
this.editorPos = new EditorPagePosition(editorPos.left, editorPos.top, editorPos.width, editorPos.height);
92+
this.pos = new PageCoordinates(this.posx, this.posy);
93+
this.editorPos = createEditorPagePosition(editorViewDomNode);
8794
}
8895
}
8996

src/vs/editor/browser/view/viewImpl.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,15 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp
608608
return -1;
609609
}
610610
return visibleRanges[0].left;
611+
},
612+
613+
getTargetAtClientPoint: (clientX: number, clientY: number): editorBrowser.IMouseTarget => {
614+
if (this._isDisposed) {
615+
throw new Error('ViewImpl.codeEditorHelper.getTargetAtClientPoint: View is disposed');
616+
}
617+
return this.pointerHandler.getTargetAtClientPoint(clientX, clientY);
611618
}
619+
612620
};
613621
}
614622
return this.codeEditorHelper;

src/vs/editor/browser/widget/codeEditorWidget.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,13 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
404404
return this._view.getCodeEditorHelper().getVerticalOffsetForPosition(lineNumber, column);
405405
}
406406

407+
public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget {
408+
if (!this.hasView) {
409+
return null;
410+
}
411+
return this._view.getCodeEditorHelper().getTargetAtClientPoint(clientX, clientY);
412+
}
413+
407414
public getScrolledVisiblePosition(rawPosition: editorCommon.IPosition): { top: number; left: number; height: number; } {
408415
if (!this.hasView) {
409416
return null;

src/vs/monaco.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3803,6 +3803,13 @@ declare module monaco.editor {
38033803
* Get the vertical position (top offset) for the position w.r.t. to the first line.
38043804
*/
38053805
getTopForPosition(lineNumber: number, column: number): number;
3806+
/**
3807+
* Get the hit test target at coordinates `clientX` and `clientY`.
3808+
* The coordinates are relative to the top-left of the viewport.
3809+
*
3810+
* @returns Hit test target or null if the coordinates fall outside the editor or the editor has no model.
3811+
*/
3812+
getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget;
38063813
/**
38073814
* Get the visible position for `position`.
38083815
* The result position takes scrolling into account and is relative to the top left corner of the editor.

0 commit comments

Comments
 (0)