Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/dnd/browser/dnd.ts
4779 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
7
import { IMouseEvent } from '../../../../base/browser/mouseEvent.js';
8
import { KeyCode } from '../../../../base/common/keyCodes.js';
9
import { Disposable } from '../../../../base/common/lifecycle.js';
10
import { isMacintosh } from '../../../../base/common/platform.js';
11
import './dnd.css';
12
import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from '../../../browser/editorBrowser.js';
13
import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';
14
import { CodeEditorWidget } from '../../../browser/widget/codeEditor/codeEditorWidget.js';
15
import { EditorOption } from '../../../common/config/editorOptions.js';
16
import { CursorChangeReason } from '../../../common/cursorEvents.js';
17
import { Position } from '../../../common/core/position.js';
18
import { Range } from '../../../common/core/range.js';
19
import { Selection } from '../../../common/core/selection.js';
20
import { IEditorContribution, IEditorDecorationsCollection, ScrollType } from '../../../common/editorCommon.js';
21
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
22
import { DragAndDropCommand } from './dragAndDropCommand.js';
23
24
function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean {
25
if (isMacintosh) {
26
return e.altKey;
27
} else {
28
return e.ctrlKey;
29
}
30
}
31
32
export class DragAndDropController extends Disposable implements IEditorContribution {
33
34
public static readonly ID = 'editor.contrib.dragAndDrop';
35
36
private readonly _editor: ICodeEditor;
37
private _dragSelection: Selection | null;
38
private readonly _dndDecorationIds: IEditorDecorationsCollection;
39
private _mouseDown: boolean;
40
private _modifierPressed: boolean;
41
static readonly TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl;
42
43
static get(editor: ICodeEditor): DragAndDropController | null {
44
return editor.getContribution<DragAndDropController>(DragAndDropController.ID);
45
}
46
47
constructor(editor: ICodeEditor) {
48
super();
49
this._editor = editor;
50
this._dndDecorationIds = this._editor.createDecorationsCollection();
51
this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
52
this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
53
this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
54
this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e)));
55
this._register(this._editor.onMouseDropCanceled(() => this._onEditorMouseDropCanceled()));
56
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
57
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
58
this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
59
this._register(this._editor.onDidBlurEditorText(() => this.onEditorBlur()));
60
this._mouseDown = false;
61
this._modifierPressed = false;
62
this._dragSelection = null;
63
}
64
65
private onEditorBlur() {
66
this._removeDecoration();
67
this._dragSelection = null;
68
this._mouseDown = false;
69
this._modifierPressed = false;
70
}
71
72
private onEditorKeyDown(e: IKeyboardEvent): void {
73
if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) {
74
return;
75
}
76
77
if (hasTriggerModifier(e)) {
78
this._modifierPressed = true;
79
}
80
81
if (this._mouseDown && hasTriggerModifier(e)) {
82
this._editor.updateOptions({
83
mouseStyle: 'copy'
84
});
85
}
86
}
87
88
private onEditorKeyUp(e: IKeyboardEvent): void {
89
if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) {
90
return;
91
}
92
93
if (hasTriggerModifier(e)) {
94
this._modifierPressed = false;
95
}
96
97
if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) {
98
this._editor.updateOptions({
99
mouseStyle: 'default'
100
});
101
}
102
}
103
104
private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
105
this._mouseDown = true;
106
}
107
108
private _onEditorMouseUp(mouseEvent: IEditorMouseEvent): void {
109
this._mouseDown = false;
110
// Whenever users release the mouse, the drag and drop operation should finish and the cursor should revert to text.
111
this._editor.updateOptions({
112
mouseStyle: 'text'
113
});
114
}
115
116
private _onEditorMouseDrag(mouseEvent: IEditorMouseEvent): void {
117
const target = mouseEvent.target;
118
119
if (this._dragSelection === null) {
120
const selections = this._editor.getSelections() || [];
121
const possibleSelections = selections.filter(selection => target.position && selection.containsPosition(target.position));
122
if (possibleSelections.length === 1) {
123
this._dragSelection = possibleSelections[0];
124
} else {
125
return;
126
}
127
}
128
129
if (hasTriggerModifier(mouseEvent.event)) {
130
this._editor.updateOptions({
131
mouseStyle: 'copy'
132
});
133
} else {
134
this._editor.updateOptions({
135
mouseStyle: 'default'
136
});
137
}
138
139
if (target.position) {
140
if (this._dragSelection.containsPosition(target.position)) {
141
this._removeDecoration();
142
} else {
143
this.showAt(target.position);
144
}
145
}
146
}
147
148
private _onEditorMouseDropCanceled() {
149
this._editor.updateOptions({
150
mouseStyle: 'text'
151
});
152
153
this._removeDecoration();
154
this._dragSelection = null;
155
this._mouseDown = false;
156
}
157
158
private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void {
159
if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {
160
const newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);
161
162
if (this._dragSelection === null) {
163
let newSelections: Selection[] | null = null;
164
if (mouseEvent.event.shiftKey) {
165
const primarySelection = this._editor.getSelection();
166
if (primarySelection) {
167
const { selectionStartLineNumber, selectionStartColumn } = primarySelection;
168
newSelections = [new Selection(selectionStartLineNumber, selectionStartColumn, newCursorPosition.lineNumber, newCursorPosition.column)];
169
}
170
} else {
171
newSelections = (this._editor.getSelections() || []).map(selection => {
172
if (selection.containsPosition(newCursorPosition)) {
173
return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
174
} else {
175
return selection;
176
}
177
});
178
}
179
// Use `mouse` as the source instead of `api` and setting the reason to explicit (to behave like any other mouse operation).
180
(<CodeEditorWidget>this._editor).setSelections(newSelections || [], 'mouse', CursorChangeReason.Explicit);
181
} else if (!this._dragSelection.containsPosition(newCursorPosition) ||
182
(
183
(
184
hasTriggerModifier(mouseEvent.event) ||
185
this._modifierPressed
186
) && (
187
this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition)
188
) // we allow users to paste content beside the selection
189
)) {
190
this._editor.pushUndoStop();
191
this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modifierPressed));
192
this._editor.pushUndoStop();
193
}
194
}
195
196
this._editor.updateOptions({
197
mouseStyle: 'text'
198
});
199
200
this._removeDecoration();
201
this._dragSelection = null;
202
this._mouseDown = false;
203
}
204
205
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
206
description: 'dnd-target',
207
className: 'dnd-target'
208
});
209
210
public showAt(position: Position): void {
211
this._dndDecorationIds.set([{
212
range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),
213
options: DragAndDropController._DECORATION_OPTIONS
214
}]);
215
this._editor.revealPosition(position, ScrollType.Immediate);
216
}
217
218
private _removeDecoration(): void {
219
this._dndDecorationIds.clear();
220
}
221
222
private _hitContent(target: IMouseTarget): boolean {
223
return target.type === MouseTargetType.CONTENT_TEXT ||
224
target.type === MouseTargetType.CONTENT_EMPTY;
225
}
226
227
private _hitMargin(target: IMouseTarget): boolean {
228
return target.type === MouseTargetType.GUTTER_GLYPH_MARGIN ||
229
target.type === MouseTargetType.GUTTER_LINE_NUMBERS ||
230
target.type === MouseTargetType.GUTTER_LINE_DECORATIONS;
231
}
232
233
public override dispose(): void {
234
this._removeDecoration();
235
this._dragSelection = null;
236
this._mouseDown = false;
237
this._modifierPressed = false;
238
super.dispose();
239
}
240
}
241
242
registerEditorContribution(DragAndDropController.ID, DragAndDropController, EditorContributionInstantiation.BeforeFirstInteraction);
243
244