Path: blob/main/src/vs/editor/browser/view/viewController.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 { IKeyboardEvent } from '../../../base/browser/keyboardEvent.js';6import { CoreNavigationCommands, NavigationCommandRevealType } from '../coreCommands.js';7import { IEditorMouseEvent, IPartialEditorMouseEvent } from '../editorBrowser.js';8import { ViewUserInputEvents } from './viewUserInputEvents.js';9import { Position } from '../../common/core/position.js';10import { Selection } from '../../common/core/selection.js';11import { IEditorConfiguration } from '../../common/config/editorConfiguration.js';12import { IViewModel } from '../../common/viewModel.js';13import { IMouseWheelEvent } from '../../../base/browser/mouseEvent.js';14import { EditorOption } from '../../common/config/editorOptions.js';15import * as platform from '../../../base/common/platform.js';16import { StandardTokenType } from '../../common/encodedTokenAttributes.js';17import { ITextModel } from '../../common/model.js';1819export interface IMouseDispatchData {20position: Position;21/**22* Desired mouse column (e.g. when position.column gets clamped to text length -- clicking after text on a line).23*/24mouseColumn: number;25revealType: NavigationCommandRevealType;26startedOnLineNumbers: boolean;2728inSelectionMode: boolean;29mouseDownCount: number;30altKey: boolean;31ctrlKey: boolean;32metaKey: boolean;33shiftKey: boolean;3435leftButton: boolean;36middleButton: boolean;37onInjectedText: boolean;38}3940export interface ICommandDelegate {41paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void;42type(text: string): void;43compositionType(text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): void;44startComposition(): void;45endComposition(): void;46cut(): void;47}4849export class ViewController {5051private readonly configuration: IEditorConfiguration;52private readonly viewModel: IViewModel;53private readonly userInputEvents: ViewUserInputEvents;54private readonly commandDelegate: ICommandDelegate;5556constructor(57configuration: IEditorConfiguration,58viewModel: IViewModel,59userInputEvents: ViewUserInputEvents,60commandDelegate: ICommandDelegate61) {62this.configuration = configuration;63this.viewModel = viewModel;64this.userInputEvents = userInputEvents;65this.commandDelegate = commandDelegate;66}6768public paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void {69this.commandDelegate.paste(text, pasteOnNewLine, multicursorText, mode);70}7172public type(text: string): void {73this.commandDelegate.type(text);74}7576public compositionType(text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): void {77this.commandDelegate.compositionType(text, replacePrevCharCnt, replaceNextCharCnt, positionDelta);78}7980public compositionStart(): void {81this.commandDelegate.startComposition();82}8384public compositionEnd(): void {85this.commandDelegate.endComposition();86}8788public cut(): void {89this.commandDelegate.cut();90}9192public setSelection(modelSelection: Selection): void {93CoreNavigationCommands.SetSelection.runCoreEditorCommand(this.viewModel, {94source: 'keyboard',95selection: modelSelection96});97}9899private _validateViewColumn(viewPosition: Position): Position {100const minColumn = this.viewModel.getLineMinColumn(viewPosition.lineNumber);101if (viewPosition.column < minColumn) {102return new Position(viewPosition.lineNumber, minColumn);103}104return viewPosition;105}106107private _hasMulticursorModifier(data: IMouseDispatchData): boolean {108switch (this.configuration.options.get(EditorOption.multiCursorModifier)) {109case 'altKey':110return data.altKey;111case 'ctrlKey':112return data.ctrlKey;113case 'metaKey':114return data.metaKey;115default:116return false;117}118}119120private _hasNonMulticursorModifier(data: IMouseDispatchData): boolean {121switch (this.configuration.options.get(EditorOption.multiCursorModifier)) {122case 'altKey':123return data.ctrlKey || data.metaKey;124case 'ctrlKey':125return data.altKey || data.metaKey;126case 'metaKey':127return data.ctrlKey || data.altKey;128default:129return false;130}131}132133/**134* Selects content inside brackets if the position is right after an opening bracket or right before a closing bracket.135* @param pos The position in the model.136* @param model The text model.137*/138private static _trySelectBracketContent(model: ITextModel, pos: Position): Selection | undefined {139// Try to find bracket match if we're right after an opening bracket.140if (pos.column > 1) {141const pair = model.bracketPairs.matchBracket(pos.with(undefined, pos.column - 1));142if (pair && pair[0].getEndPosition().equals(pos)) {143return Selection.fromPositions(pair[0].getEndPosition(), pair[1].getStartPosition());144}145}146147// Try to find bracket match if we're right before a closing bracket.148if (pos.column <= model.getLineMaxColumn(pos.lineNumber)) {149const pair = model.bracketPairs.matchBracket(pos);150if (pair && pair[1].getStartPosition().equals(pos)) {151return Selection.fromPositions(pair[0].getEndPosition(), pair[1].getStartPosition());152}153}154155return undefined;156}157158/**159* Selects content inside a string if the position is right after an opening quote or right before a closing quote.160* @param pos The position in the model.161* @param model The text model.162*/163private static _trySelectStringContent(model: ITextModel, pos: Position): Selection | undefined {164const { lineNumber, column } = pos;165const { tokenization: tokens } = model;166167// Ensure we have accurate tokens for the line.168if (!tokens.hasAccurateTokensForLine(lineNumber)) {169if (tokens.isCheapToTokenize(lineNumber)) {170tokens.forceTokenization(lineNumber);171} else {172return undefined;173}174}175176// Check if current token is a string.177const lineTokens = tokens.getLineTokens(lineNumber);178const index = lineTokens.findTokenIndexAtOffset(column - 1);179if (lineTokens.getStandardTokenType(index) !== StandardTokenType.String) {180return undefined;181}182183// Get 1-based boundaries of the string content (excluding quotes).184const start = lineTokens.getStartOffset(index) + 2;185const end = lineTokens.getEndOffset(index);186187if (column !== start && column !== end) {188return undefined;189}190191return new Selection(lineNumber, start, lineNumber, end);192}193194public dispatchMouse(data: IMouseDispatchData): void {195const options = this.configuration.options;196const selectionClipboardIsOn = (platform.isLinux && options.get(EditorOption.selectionClipboard));197const columnSelection = options.get(EditorOption.columnSelection);198const scrollOnMiddleClick = options.get(EditorOption.scrollOnMiddleClick);199if (data.middleButton && !selectionClipboardIsOn) {200if (scrollOnMiddleClick) {201// nothing to do here, handled in the contribution202} else {203this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode);204}205} else if (data.startedOnLineNumbers) {206// If the dragging started on the gutter, then have operations work on the entire line207if (this._hasMulticursorModifier(data)) {208if (data.inSelectionMode) {209this._lastCursorLineSelect(data.position, data.revealType);210} else {211this._createCursor(data.position, true);212}213} else {214if (data.inSelectionMode) {215this._lineSelectDrag(data.position, data.revealType);216} else {217this._lineSelect(data.position, data.revealType);218}219}220} else if (data.mouseDownCount >= 4) {221this._selectAll();222} else if (data.mouseDownCount === 3) {223if (this._hasMulticursorModifier(data)) {224if (data.inSelectionMode) {225this._lastCursorLineSelectDrag(data.position, data.revealType);226} else {227this._lastCursorLineSelect(data.position, data.revealType);228}229} else {230if (data.inSelectionMode) {231this._lineSelectDrag(data.position, data.revealType);232} else {233this._lineSelect(data.position, data.revealType);234}235}236} else if (data.mouseDownCount === 2) {237if (!data.onInjectedText) {238if (this._hasMulticursorModifier(data)) {239this._lastCursorWordSelect(data.position, data.revealType);240} else {241if (data.inSelectionMode) {242this._wordSelectDrag(data.position, data.revealType);243} else {244const model = this.viewModel.model;245const modelPos = this._convertViewToModelPosition(data.position);246const selection = ViewController._trySelectBracketContent(model, modelPos) || ViewController._trySelectStringContent(model, modelPos);247if (selection) {248this._select(selection);249} else {250this._wordSelect(data.position, data.revealType);251}252}253}254}255} else {256if (this._hasMulticursorModifier(data)) {257if (!this._hasNonMulticursorModifier(data)) {258if (data.shiftKey) {259this._columnSelect(data.position, data.mouseColumn, true);260} else {261// Do multi-cursor operations only when purely alt is pressed262if (data.inSelectionMode) {263this._lastCursorMoveToSelect(data.position, data.revealType);264} else {265this._createCursor(data.position, false);266}267}268}269} else {270if (data.inSelectionMode) {271if (data.altKey) {272this._columnSelect(data.position, data.mouseColumn, true);273} else {274if (columnSelection) {275this._columnSelect(data.position, data.mouseColumn, true);276} else {277this._moveToSelect(data.position, data.revealType);278}279}280} else {281this.moveTo(data.position, data.revealType);282}283}284}285}286287private _usualArgs(viewPosition: Position, revealType: NavigationCommandRevealType): CoreNavigationCommands.MoveCommandOptions {288viewPosition = this._validateViewColumn(viewPosition);289return {290source: 'mouse',291position: this._convertViewToModelPosition(viewPosition),292viewPosition,293revealType294};295}296297public moveTo(viewPosition: Position, revealType: NavigationCommandRevealType): void {298CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));299}300301private _moveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {302CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));303}304305private _columnSelect(viewPosition: Position, mouseColumn: number, doColumnSelect: boolean): void {306viewPosition = this._validateViewColumn(viewPosition);307CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(this.viewModel, {308source: 'mouse',309position: this._convertViewToModelPosition(viewPosition),310viewPosition: viewPosition,311mouseColumn: mouseColumn,312doColumnSelect: doColumnSelect313});314}315316private _createCursor(viewPosition: Position, wholeLine: boolean): void {317viewPosition = this._validateViewColumn(viewPosition);318CoreNavigationCommands.CreateCursor.runCoreEditorCommand(this.viewModel, {319source: 'mouse',320position: this._convertViewToModelPosition(viewPosition),321viewPosition: viewPosition,322wholeLine: wholeLine323});324}325326private _lastCursorMoveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {327CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));328}329330private _wordSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {331CoreNavigationCommands.WordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));332}333334private _wordSelectDrag(viewPosition: Position, revealType: NavigationCommandRevealType): void {335CoreNavigationCommands.WordSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));336}337338private _lastCursorWordSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {339CoreNavigationCommands.LastCursorWordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));340}341342private _lineSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {343CoreNavigationCommands.LineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));344}345346private _lineSelectDrag(viewPosition: Position, revealType: NavigationCommandRevealType): void {347CoreNavigationCommands.LineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));348}349350private _lastCursorLineSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {351CoreNavigationCommands.LastCursorLineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));352}353354private _lastCursorLineSelectDrag(viewPosition: Position, revealType: NavigationCommandRevealType): void {355CoreNavigationCommands.LastCursorLineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));356}357358private _select(selection: Selection): void {359CoreNavigationCommands.SetSelection.runCoreEditorCommand(this.viewModel, { source: 'mouse', selection });360}361362private _selectAll(): void {363CoreNavigationCommands.SelectAll.runCoreEditorCommand(this.viewModel, { source: 'mouse' });364}365366// ----------------------367368private _convertViewToModelPosition(viewPosition: Position): Position {369return this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(viewPosition);370}371372public emitKeyDown(e: IKeyboardEvent): void {373this.userInputEvents.emitKeyDown(e);374}375376public emitKeyUp(e: IKeyboardEvent): void {377this.userInputEvents.emitKeyUp(e);378}379380public emitContextMenu(e: IEditorMouseEvent): void {381this.userInputEvents.emitContextMenu(e);382}383384public emitMouseMove(e: IEditorMouseEvent): void {385this.userInputEvents.emitMouseMove(e);386}387388public emitMouseLeave(e: IPartialEditorMouseEvent): void {389this.userInputEvents.emitMouseLeave(e);390}391392public emitMouseUp(e: IEditorMouseEvent): void {393this.userInputEvents.emitMouseUp(e);394}395396public emitMouseDown(e: IEditorMouseEvent): void {397this.userInputEvents.emitMouseDown(e);398}399400public emitMouseDrag(e: IEditorMouseEvent): void {401this.userInputEvents.emitMouseDrag(e);402}403404public emitMouseDrop(e: IPartialEditorMouseEvent): void {405this.userInputEvents.emitMouseDrop(e);406}407408public emitMouseDropCanceled(): void {409this.userInputEvents.emitMouseDropCanceled();410}411412public emitMouseWheel(e: IMouseWheelEvent): void {413this.userInputEvents.emitMouseWheel(e);414}415}416417418