Merge pull request #128239 from microsoft/hediet/mouse-hit-detection-injected-text

Sets mightBeForeignElement to true when mouse is on injected text.
This commit is contained in:
Henning Dieterichs 2021-07-09 14:35:01 +02:00 committed by GitHub
commit 951d1c7030
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 16 deletions

View file

@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range as EditorRange } from 'vs/editor/common/core/range';
import { HorizontalPosition } from 'vs/editor/common/view/renderingContext';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { InjectedText, IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
import * as dom from 'vs/base/browser/dom';
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
@ -61,7 +61,8 @@ class ContentHitTestResult {
readonly type = HitTestResultType.Content;
constructor(
readonly position: Position,
readonly spanNode: HTMLElement
readonly spanNode: HTMLElement,
readonly injectedText: InjectedText | null,
) { }
}
@ -71,7 +72,7 @@ namespace HitTestResult {
export function createFromDOMInfo(ctx: HitTestContext, spanNode: HTMLElement, offset: number): HitTestResult {
const position = ctx.getPositionFromDOMInfo(spanNode, offset);
if (position) {
return new ContentHitTestResult(position, spanNode);
return new ContentHitTestResult(position, spanNode, null);
}
return new UnknownHitTestResult(spanNode);
}
@ -505,7 +506,7 @@ export class MouseTargetFactory {
const hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
if (hitTestResult.type === HitTestResultType.Content) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position);
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText);
}
return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
@ -702,7 +703,7 @@ export class MouseTargetFactory {
const hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
if (hitTestResult.type === HitTestResultType.Content) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position);
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position, hitTestResult.injectedText);
}
return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
@ -758,7 +759,7 @@ export class MouseTargetFactory {
return (chars + 1);
}
private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position): MouseTarget {
private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position, injectedText: InjectedText | null): MouseTarget {
const lineNumber = pos.lineNumber;
const column = pos.column;
@ -778,7 +779,7 @@ export class MouseTargetFactory {
const columnHorizontalOffset = visibleRange.left;
if (request.mouseContentHorizontalOffset === columnHorizontalOffset) {
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: false });
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !!injectedText });
}
// Let's define a, b, c and check if the offset is in between them...
@ -811,10 +812,10 @@ export class MouseTargetFactory {
const curr = points[i];
if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) {
const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column);
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode });
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText });
}
}
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode });
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText });
}
/**
@ -957,14 +958,16 @@ export class MouseTargetFactory {
result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
}
if (result.type === HitTestResultType.Content) {
const injectedText = ctx.model.getInjectedTextAt(result.position);
const normalizedPosition = ctx.model.normalizePosition(result.position, PositionAffinity.None);
if (!normalizedPosition.equals(result.position)) {
result = new ContentHitTestResult(normalizedPosition, result.spanNode);
if (injectedText || !normalizedPosition.equals(result.position)) {
result = new ContentHitTestResult(normalizedPosition, result.spanNode, injectedText);
}
}
// Snap to the nearest soft tab boundary if atomic soft tabs are enabled.
if (result.type === HitTestResultType.Content && ctx.stickyTabStops) {
result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode);
result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode, result.injectedText);
}
return result;
}

View file

@ -12,7 +12,7 @@ import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDe
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { EditorTheme } from 'vs/editor/common/view/viewContext';
@ -48,6 +48,8 @@ export interface ISplitLine {
getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number, affinity?: PositionAffinity): Position;
getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number;
normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position;
getInjectedTextAt(outputLineIndex: number, column: number): InjectedText | null;
}
export interface IViewModelLinesCollection extends IDisposable {
@ -78,6 +80,8 @@ export interface IViewModelLinesCollection extends IDisposable {
getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: EditorTheme): IOverviewRulerDecorations;
getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[];
getInjectedTextAt(viewPosition: Position): InjectedText | null;
normalizePosition(position: Position, affinity: PositionAffinity): Position;
/**
* Gets the column at which indentation stops at a given line.
@ -997,6 +1001,15 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return finalResult;
}
public getInjectedTextAt(position: Position): InjectedText | null {
const viewLineNumber = this._toValidViewLineNumber(position.lineNumber);
const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
const lineIndex = r.index;
const remainder = r.remainder;
return this.lines[lineIndex].getInjectedTextAt(remainder, position.column);
}
normalizePosition(position: Position, affinity: PositionAffinity): Position {
const viewLineNumber = this._toValidViewLineNumber(position.lineNumber);
const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
@ -1101,6 +1114,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
public normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position {
return outputPosition;
}
public getInjectedTextAt(_outputLineIndex: number, _outputColumn: number): InjectedText | null {
return null;
}
}
class InvisibleIdentitySplitLine implements ISplitLine {
@ -1167,6 +1184,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
public normalizePosition(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number, outputPosition: Position, affinity: PositionAffinity): Position {
throw new Error('Not supported');
}
public getInjectedTextAt(_outputLineIndex: number, _outputColumn: number): InjectedText | null {
throw new Error('Not supported');
}
}
export class SplitLine implements ISplitLine {
@ -1451,6 +1472,10 @@ export class SplitLine implements ISplitLine {
return outputPosition;
}
public getInjectedTextAt(outputLineIndex: number, outputColumn: number): InjectedText | null {
return this._lineBreakData.getInjectedText(outputLineIndex, outputColumn - 1);
}
}
let _spaces: string[] = [''];
@ -1694,6 +1719,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
public getLineIndentColumn(lineNumber: number): number {
return this.model.getLineIndentColumn(lineNumber);
}
public getInjectedTextAt(position: Position): InjectedText | null {
// Identity lines collection does not support injected text.
return null;
}
}
class OverviewRulerDecorations {

View file

@ -205,7 +205,7 @@ export class LineBreakData {
}
public normalizeOffsetAroundInjections(offsetInUnwrappedLine: number, affinity: PositionAffinity): number {
const injectedText = this.getInjectedTextAt(offsetInUnwrappedLine);
const injectedText = this.getInjectedTextAtOffset(offsetInUnwrappedLine);
if (!injectedText) {
return offsetInUnwrappedLine;
}
@ -242,7 +242,18 @@ export class LineBreakData {
return result;
}
private getInjectedTextAt(offsetInUnwrappedLine: number): { injectedTextIndex: number, offsetInUnwrappedLine: number, length: number } | undefined {
public getInjectedText(outputLineIndex: number, outputOffset: number): InjectedText | null {
const offset = this.outputPositionToOffsetInUnwrappedLine(outputLineIndex, outputOffset);
const injectedText = this.getInjectedTextAtOffset(offset);
if (!injectedText) {
return null;
}
return {
options: this.injectionOptions![injectedText.injectedTextIndex]
};
}
private getInjectedTextAtOffset(offsetInUnwrappedLine: number): { injectedTextIndex: number, offsetInUnwrappedLine: number, length: number } | undefined {
const injectionOffsets = this.injectionOffsets;
const injectionOptions = this.injectionOptions;
@ -328,6 +339,8 @@ export interface IViewModel extends ICursorSimpleModel {
invalidateMinimapColorCache(): void;
getValueInRange(range: Range, eol: EndOfLinePreference): string;
getInjectedTextAt(viewPosition: Position): InjectedText | null;
getModelLineMaxColumn(modelLineNumber: number): number;
validateModelPosition(modelPosition: IPosition): Position;
validateModelRange(range: IRange): Range;
@ -372,6 +385,10 @@ export interface IViewModel extends ICursorSimpleModel {
//#endregion
}
export class InjectedText {
constructor(public readonly options: InjectedTextOptions) { }
}
export class MinimapLinesRenderingData {
public readonly tabSize: number;
public readonly data: Array<ViewLineData | null>;

View file

@ -21,7 +21,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as platform from 'vs/base/common/platform';
@ -652,6 +652,10 @@ export class ViewModel extends Disposable implements IViewModel {
return this._decorations.getDecorationsViewportData(visibleRange).decorations;
}
public getInjectedTextAt(viewPosition: Position): InjectedText | null {
return this._lines.getInjectedTextAt(viewPosition);
}
public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData {
let mightContainRTL = this.model.mightContainRTL();
let mightContainNonBasicASCII = this.model.mightContainNonBasicASCII();