Path: blob/main/src/vs/editor/contrib/dnd/browser/dnd.ts
4779 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 { IMouseEvent } from '../../../../base/browser/mouseEvent.js';7import { KeyCode } from '../../../../base/common/keyCodes.js';8import { Disposable } from '../../../../base/common/lifecycle.js';9import { isMacintosh } from '../../../../base/common/platform.js';10import './dnd.css';11import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from '../../../browser/editorBrowser.js';12import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';13import { CodeEditorWidget } from '../../../browser/widget/codeEditor/codeEditorWidget.js';14import { EditorOption } from '../../../common/config/editorOptions.js';15import { CursorChangeReason } from '../../../common/cursorEvents.js';16import { Position } from '../../../common/core/position.js';17import { Range } from '../../../common/core/range.js';18import { Selection } from '../../../common/core/selection.js';19import { IEditorContribution, IEditorDecorationsCollection, ScrollType } from '../../../common/editorCommon.js';20import { ModelDecorationOptions } from '../../../common/model/textModel.js';21import { DragAndDropCommand } from './dragAndDropCommand.js';2223function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean {24if (isMacintosh) {25return e.altKey;26} else {27return e.ctrlKey;28}29}3031export class DragAndDropController extends Disposable implements IEditorContribution {3233public static readonly ID = 'editor.contrib.dragAndDrop';3435private readonly _editor: ICodeEditor;36private _dragSelection: Selection | null;37private readonly _dndDecorationIds: IEditorDecorationsCollection;38private _mouseDown: boolean;39private _modifierPressed: boolean;40static readonly TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl;4142static get(editor: ICodeEditor): DragAndDropController | null {43return editor.getContribution<DragAndDropController>(DragAndDropController.ID);44}4546constructor(editor: ICodeEditor) {47super();48this._editor = editor;49this._dndDecorationIds = this._editor.createDecorationsCollection();50this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));51this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));52this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));53this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e)));54this._register(this._editor.onMouseDropCanceled(() => this._onEditorMouseDropCanceled()));55this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));56this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));57this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));58this._register(this._editor.onDidBlurEditorText(() => this.onEditorBlur()));59this._mouseDown = false;60this._modifierPressed = false;61this._dragSelection = null;62}6364private onEditorBlur() {65this._removeDecoration();66this._dragSelection = null;67this._mouseDown = false;68this._modifierPressed = false;69}7071private onEditorKeyDown(e: IKeyboardEvent): void {72if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) {73return;74}7576if (hasTriggerModifier(e)) {77this._modifierPressed = true;78}7980if (this._mouseDown && hasTriggerModifier(e)) {81this._editor.updateOptions({82mouseStyle: 'copy'83});84}85}8687private onEditorKeyUp(e: IKeyboardEvent): void {88if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) {89return;90}9192if (hasTriggerModifier(e)) {93this._modifierPressed = false;94}9596if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) {97this._editor.updateOptions({98mouseStyle: 'default'99});100}101}102103private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {104this._mouseDown = true;105}106107private _onEditorMouseUp(mouseEvent: IEditorMouseEvent): void {108this._mouseDown = false;109// Whenever users release the mouse, the drag and drop operation should finish and the cursor should revert to text.110this._editor.updateOptions({111mouseStyle: 'text'112});113}114115private _onEditorMouseDrag(mouseEvent: IEditorMouseEvent): void {116const target = mouseEvent.target;117118if (this._dragSelection === null) {119const selections = this._editor.getSelections() || [];120const possibleSelections = selections.filter(selection => target.position && selection.containsPosition(target.position));121if (possibleSelections.length === 1) {122this._dragSelection = possibleSelections[0];123} else {124return;125}126}127128if (hasTriggerModifier(mouseEvent.event)) {129this._editor.updateOptions({130mouseStyle: 'copy'131});132} else {133this._editor.updateOptions({134mouseStyle: 'default'135});136}137138if (target.position) {139if (this._dragSelection.containsPosition(target.position)) {140this._removeDecoration();141} else {142this.showAt(target.position);143}144}145}146147private _onEditorMouseDropCanceled() {148this._editor.updateOptions({149mouseStyle: 'text'150});151152this._removeDecoration();153this._dragSelection = null;154this._mouseDown = false;155}156157private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void {158if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {159const newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);160161if (this._dragSelection === null) {162let newSelections: Selection[] | null = null;163if (mouseEvent.event.shiftKey) {164const primarySelection = this._editor.getSelection();165if (primarySelection) {166const { selectionStartLineNumber, selectionStartColumn } = primarySelection;167newSelections = [new Selection(selectionStartLineNumber, selectionStartColumn, newCursorPosition.lineNumber, newCursorPosition.column)];168}169} else {170newSelections = (this._editor.getSelections() || []).map(selection => {171if (selection.containsPosition(newCursorPosition)) {172return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);173} else {174return selection;175}176});177}178// Use `mouse` as the source instead of `api` and setting the reason to explicit (to behave like any other mouse operation).179(<CodeEditorWidget>this._editor).setSelections(newSelections || [], 'mouse', CursorChangeReason.Explicit);180} else if (!this._dragSelection.containsPosition(newCursorPosition) ||181(182(183hasTriggerModifier(mouseEvent.event) ||184this._modifierPressed185) && (186this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition)187) // we allow users to paste content beside the selection188)) {189this._editor.pushUndoStop();190this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modifierPressed));191this._editor.pushUndoStop();192}193}194195this._editor.updateOptions({196mouseStyle: 'text'197});198199this._removeDecoration();200this._dragSelection = null;201this._mouseDown = false;202}203204private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({205description: 'dnd-target',206className: 'dnd-target'207});208209public showAt(position: Position): void {210this._dndDecorationIds.set([{211range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),212options: DragAndDropController._DECORATION_OPTIONS213}]);214this._editor.revealPosition(position, ScrollType.Immediate);215}216217private _removeDecoration(): void {218this._dndDecorationIds.clear();219}220221private _hitContent(target: IMouseTarget): boolean {222return target.type === MouseTargetType.CONTENT_TEXT ||223target.type === MouseTargetType.CONTENT_EMPTY;224}225226private _hitMargin(target: IMouseTarget): boolean {227return target.type === MouseTargetType.GUTTER_GLYPH_MARGIN ||228target.type === MouseTargetType.GUTTER_LINE_NUMBERS ||229target.type === MouseTargetType.GUTTER_LINE_DECORATIONS;230}231232public override dispose(): void {233this._removeDecoration();234this._dragSelection = null;235this._mouseDown = false;236this._modifierPressed = false;237super.dispose();238}239}240241registerEditorContribution(DragAndDropController.ID, DragAndDropController, EditorContributionInstantiation.BeforeFirstInteraction);242243244