dnd.ts 8.1 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

R
rebornix 已提交
6
import 'vs/css!./dnd';
7
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
8
import { Disposable } from 'vs/base/common/lifecycle';
9
import { isMacintosh } from 'vs/base/common/platform';
10
import { KeyCode } from 'vs/base/common/keyCodes';
A
Alex Dima 已提交
11
import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
12
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
13 14
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Position } from 'vs/editor/common/core/position';
R
rebornix 已提交
15
import { Range } from 'vs/editor/common/core/range';
16
import { Selection } from 'vs/editor/common/core/selection';
17
import { DragAndDropCommand } from 'vs/editor/contrib/dnd/dragAndDropCommand';
A
Alex Dima 已提交
18
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
A
Alex Dima 已提交
19
import { IModelDeltaDecoration } from 'vs/editor/common/model';
A
Alex Dima 已提交
20
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
A
Alex Dima 已提交
21
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
A
Alex Dima 已提交
22 23 24 25 26 27 28 29

function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean {
	if (isMacintosh) {
		return e.altKey;
	} else {
		return e.ctrlKey;
	}
}
30

31
export class DragAndDropController extends Disposable implements editorCommon.IEditorContribution {
32

33
	private static readonly ID = 'editor.contrib.dragAndDrop';
34

35
	private readonly _editor: ICodeEditor;
M
Matt Bierner 已提交
36
	private _dragSelection: Selection | null;
R
rebornix 已提交
37
	private _dndDecorationIds: string[];
38
	private _mouseDown: boolean;
K
khaled4vokalz 已提交
39
	private _modifierPressed: boolean;
40
	static TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl;
41

42
	static get(editor: ICodeEditor): DragAndDropController {
43 44 45 46
		return editor.getContribution<DragAndDropController>(DragAndDropController.ID);
	}

	constructor(editor: ICodeEditor) {
47
		super();
48
		this._editor = editor;
49 50 51 52 53 54 55
		this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
		this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
		this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
		this._register(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e)));
		this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
		this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
		this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
R
rebornix 已提交
56
		this._dndDecorationIds = [];
57
		this._mouseDown = false;
K
khaled4vokalz 已提交
58
		this._modifierPressed = false;
59
		this._dragSelection = null;
60 61
	}

R
rebornix 已提交
62 63 64 65
	private onEditorBlur() {
		this._removeDecoration();
		this._dragSelection = null;
		this._mouseDown = false;
P
Peng Lyu 已提交
66
		this._modifierPressed = false;
R
rebornix 已提交
67 68
	}

69
	private onEditorKeyDown(e: IKeyboardEvent): void {
70 71 72 73
		if (!this._editor.getConfiguration().dragAndDrop) {
			return;
		}

A
Alex Dima 已提交
74
		if (hasTriggerModifier(e)) {
K
khaled4vokalz 已提交
75
			this._modifierPressed = true;
76 77
		}

A
Alex Dima 已提交
78
		if (this._mouseDown && hasTriggerModifier(e)) {
79 80 81
			this._editor.updateOptions({
				mouseStyle: 'copy'
			});
R
rebornix 已提交
82
		}
83
	}
R
rebornix 已提交
84

85
	private onEditorKeyUp(e: IKeyboardEvent): void {
86 87 88 89
		if (!this._editor.getConfiguration().dragAndDrop) {
			return;
		}

A
Alex Dima 已提交
90
		if (hasTriggerModifier(e)) {
K
khaled4vokalz 已提交
91
			this._modifierPressed = false;
92 93
		}

94 95 96 97
		if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) {
			this._editor.updateOptions({
				mouseStyle: 'default'
			});
R
rebornix 已提交
98
		}
99
	}
R
rebornix 已提交
100

101 102 103 104 105 106
	private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
		this._mouseDown = true;
	}

	private _onEditorMouseUp(mouseEvent: IEditorMouseEvent): void {
		this._mouseDown = false;
R
rebornix 已提交
107 108 109 110
		// Whenever users release the mouse, the drag and drop operation should finish and the cursor should revert to text.
		this._editor.updateOptions({
			mouseStyle: 'text'
		});
R
rebornix 已提交
111 112
	}

113 114
	private _onEditorMouseDrag(mouseEvent: IEditorMouseEvent): void {
		let target = mouseEvent.target;
115

116
		if (this._dragSelection === null) {
M
Matt Bierner 已提交
117 118
			const selections = this._editor.getSelections() || [];
			let possibleSelections = selections.filter(selection => target.position && selection.containsPosition(target.position));
119 120
			if (possibleSelections.length === 1) {
				this._dragSelection = possibleSelections[0];
121 122
			} else {
				return;
123 124
			}
		}
125

A
Alex Dima 已提交
126
		if (hasTriggerModifier(mouseEvent.event)) {
R
rebornix 已提交
127 128 129 130 131 132 133 134 135
			this._editor.updateOptions({
				mouseStyle: 'copy'
			});
		} else {
			this._editor.updateOptions({
				mouseStyle: 'default'
			});
		}

M
Matt Bierner 已提交
136 137 138 139 140 141
		if (target.position) {
			if (this._dragSelection.containsPosition(target.position)) {
				this._removeDecoration();
			} else {
				this.showAt(target.position);
			}
142
		}
143 144 145
	}

	private _onEditorMouseDrop(mouseEvent: IEditorMouseEvent): void {
146
		if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {
R
rebornix 已提交
147
			let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);
148

149
			if (this._dragSelection === null) {
150
				let newSelections: Selection[] | null = null;
151 152
				if (mouseEvent.event.shiftKey) {
					let primarySelection = this._editor.getSelection();
M
Matt Bierner 已提交
153 154 155 156
					if (primarySelection) {
						const { selectionStartLineNumber, selectionStartColumn } = primarySelection;
						newSelections = [new Selection(selectionStartLineNumber, selectionStartColumn, newCursorPosition.lineNumber, newCursorPosition.column)];
					}
157
				} else {
M
Matt Bierner 已提交
158
					newSelections = (this._editor.getSelections() || []).map(selection => {
159 160 161 162 163 164 165
						if (selection.containsPosition(newCursorPosition)) {
							return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
						} else {
							return selection;
						}
					});
				}
A
Alex Dima 已提交
166
				// Use `mouse` as the source instead of `api`.
M
Matt Bierner 已提交
167
				(<CodeEditorWidget>this._editor).setSelections(newSelections || [], 'mouse');
168 169 170
			} else if (!this._dragSelection.containsPosition(newCursorPosition) ||
				(
					(
A
Alex Dima 已提交
171
						hasTriggerModifier(mouseEvent.event) ||
K
khaled4vokalz 已提交
172
						this._modifierPressed
173 174 175 176
					) && (
						this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition)
					) // we allow users to paste content beside the selection
				)) {
A
Alex Dima 已提交
177
				this._editor.pushUndoStop();
K
khaled4vokalz 已提交
178
				this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modifierPressed));
A
Alex Dima 已提交
179
				this._editor.pushUndoStop();
180
			}
181 182
		}

R
rebornix 已提交
183 184 185 186
		this._editor.updateOptions({
			mouseStyle: 'text'
		});

R
rebornix 已提交
187
		this._removeDecoration();
188
		this._dragSelection = null;
189
		this._mouseDown = false;
190 191
	}

192
	private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
193 194 195
		className: 'dnd-target'
	});

R
rebornix 已提交
196
	public showAt(position: Position): void {
A
Alex Dima 已提交
197 198 199 200
		let newDecorations: IModelDeltaDecoration[] = [{
			range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),
			options: DragAndDropController._DECORATION_OPTIONS
		}];
R
rebornix 已提交
201

A
Alex Dima 已提交
202
		this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, newDecorations);
203
		this._editor.revealPosition(position, editorCommon.ScrollType.Immediate);
R
rebornix 已提交
204 205 206
	}

	private _removeDecoration(): void {
A
Alex Dima 已提交
207
		this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, []);
208 209
	}

210
	private _hitContent(target: IMouseTarget): boolean {
A
Alex Dima 已提交
211 212
		return target.type === MouseTargetType.CONTENT_TEXT ||
			target.type === MouseTargetType.CONTENT_EMPTY;
213 214 215
	}

	private _hitMargin(target: IMouseTarget): boolean {
A
Alex Dima 已提交
216 217 218
		return target.type === MouseTargetType.GUTTER_GLYPH_MARGIN ||
			target.type === MouseTargetType.GUTTER_LINE_NUMBERS ||
			target.type === MouseTargetType.GUTTER_LINE_DECORATIONS;
219 220
	}

221 222 223 224 225
	public getId(): string {
		return DragAndDropController.ID;
	}

	public dispose(): void {
R
rebornix 已提交
226
		this._removeDecoration();
227 228
		this._dragSelection = null;
		this._mouseDown = false;
K
khaled4vokalz 已提交
229
		this._modifierPressed = false;
230
		super.dispose();
231
	}
232 233 234
}

registerEditorContribution(DragAndDropController);