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

'use strict';

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

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

export class DragAndDropController implements editorCommon.IEditorContribution {

35
	private static readonly ID = 'editor.contrib.dragAndDrop';
36 37 38

	private _editor: ICodeEditor;
	private _toUnhook: IDisposable[];
39
	private _dragSelection: Selection;
R
rebornix 已提交
40
	private _dndDecorationIds: string[];
41
	private _mouseDown: boolean;
42
	private _mouseDownInfo: IEditorMouseEvent;
43
	private _modiferPressed: boolean;
44
	static TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl;
45

46
	static get(editor: ICodeEditor): DragAndDropController {
47 48 49 50 51 52
		return editor.getContribution<DragAndDropController>(DragAndDropController.ID);
	}

	constructor(editor: ICodeEditor) {
		this._editor = editor;
		this._toUnhook = [];
53 54
		this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
		this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
55
		this._toUnhook.push(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
56
		this._toUnhook.push(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e)));
57 58
		this._toUnhook.push(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
		this._toUnhook.push(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
R
rebornix 已提交
59
		this._toUnhook.push(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
R
rebornix 已提交
60
		this._dndDecorationIds = [];
61
		this._mouseDown = false;
62
		this._mouseDownInfo = null;
63
		this._modiferPressed = false;
64
		this._dragSelection = null;
65 66
	}

R
rebornix 已提交
67 68 69 70 71 72 73
	private onEditorBlur() {
		this._removeDecoration();
		this._dragSelection = null;
		this._mouseDown = false;
		this._modiferPressed = false;
	}

74
	private onEditorKeyDown(e: IKeyboardEvent): void {
75 76 77 78
		if (!this._editor.getConfiguration().dragAndDrop) {
			return;
		}

A
Alex Dima 已提交
79
		if (hasTriggerModifier(e)) {
80 81 82
			this._modiferPressed = true;
		}

A
Alex Dima 已提交
83
		if (this._mouseDown && hasTriggerModifier(e)) {
84 85 86
			this._editor.updateOptions({
				mouseStyle: 'copy'
			});
R
rebornix 已提交
87
		}
88
	}
R
rebornix 已提交
89

90
	private onEditorKeyUp(e: IKeyboardEvent): void {
91 92 93 94
		if (!this._editor.getConfiguration().dragAndDrop) {
			return;
		}

A
Alex Dima 已提交
95
		if (hasTriggerModifier(e)) {
96 97 98
			this._modiferPressed = false;
		}

99 100 101 102
		if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) {
			this._editor.updateOptions({
				mouseStyle: 'default'
			});
R
rebornix 已提交
103
		}
104
	}
R
rebornix 已提交
105

106 107
	private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
		this._mouseDown = true;
108
		this._mouseDownInfo = mouseEvent;
109 110 111 112
	}

	private _onEditorMouseUp(mouseEvent: IEditorMouseEvent): void {
		this._mouseDown = false;
113
		this._mouseDownInfo = null;
R
rebornix 已提交
114 115 116 117
		// 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 已提交
118 119
	}

120
	private _onEditorMouseDrag(mouseEvent: IEditorMouseEvent): void {
121 122 123 124 125 126 127 128 129 130 131 132 133
		if (!this._mouseDownInfo) {
			return;
		}

		if (this._mouseDownInfo.target.type === MouseTargetType.CONTENT_EMPTY) {
			const epsilon = this._editor.getConfiguration().fontInfo.typicalHalfwidthCharacterWidth / 2;
			const data = <IEmptyContentData>mouseEvent.target.detail;
			if (!data || data.isAfterLines || typeof data.horizontalDistanceToText !== 'number' || data.horizontalDistanceToText >= epsilon) {
				this._mouseDownInfo = null; // clear the mousedown info then we can early exit for this drag listener.
				return;
			}
		}

134
		let target = mouseEvent.target;
135

136
		if (this._dragSelection === null) {
137 138 139
			let possibleSelections = this._editor.getSelections().filter(selection => selection.containsPosition(target.position));
			if (possibleSelections.length === 1) {
				this._dragSelection = possibleSelections[0];
140 141
			} else {
				return;
142 143
			}
		}
144

A
Alex Dima 已提交
145
		if (hasTriggerModifier(mouseEvent.event)) {
R
rebornix 已提交
146 147 148 149 150 151 152 153 154
			this._editor.updateOptions({
				mouseStyle: 'copy'
			});
		} else {
			this._editor.updateOptions({
				mouseStyle: 'default'
			});
		}

155 156 157 158 159
		if (this._dragSelection.containsPosition(target.position)) {
			this._removeDecoration();
		} else {
			this.showAt(target.position);
		}
160 161 162
	}

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

166
			if (this._dragSelection === null) {
167 168
				if (mouseEvent.event.shiftKey) {
					let primarySelection = this._editor.getSelection();
169 170
					let { selectionStartLineNumber, selectionStartColumn } = primarySelection;
					this._editor.setSelections([new Selection(selectionStartLineNumber, selectionStartColumn, newCursorPosition.lineNumber, newCursorPosition.column)]);
171 172 173 174 175 176 177 178 179 180
				} else {
					let newSelections = this._editor.getSelections().map(selection => {
						if (selection.containsPosition(newCursorPosition)) {
							return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
						} else {
							return selection;
						}
					});
					this._editor.setSelections(newSelections);
				}
181 182 183
			} else if (!this._dragSelection.containsPosition(newCursorPosition) ||
				(
					(
A
Alex Dima 已提交
184
						hasTriggerModifier(mouseEvent.event) ||
185 186 187 188 189
						this._modiferPressed
					) && (
						this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition)
					) // we allow users to paste content beside the selection
				)) {
A
Alex Dima 已提交
190
				this._editor.pushUndoStop();
A
Alex Dima 已提交
191
				this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modiferPressed));
A
Alex Dima 已提交
192
				this._editor.pushUndoStop();
193
			}
194 195
		}

R
rebornix 已提交
196 197 198 199
		this._editor.updateOptions({
			mouseStyle: 'text'
		});

R
rebornix 已提交
200
		this._removeDecoration();
201
		this._dragSelection = null;
202
		this._mouseDown = false;
203 204
	}

205
	private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
206 207 208
		className: 'dnd-target'
	});

R
rebornix 已提交
209
	public showAt(position: Position): void {
A
Alex Dima 已提交
210 211 212 213
		let newDecorations: IModelDeltaDecoration[] = [{
			range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),
			options: DragAndDropController._DECORATION_OPTIONS
		}];
R
rebornix 已提交
214

A
Alex Dima 已提交
215
		this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, newDecorations);
216
		this._editor.revealPosition(position, editorCommon.ScrollType.Immediate);
R
rebornix 已提交
217 218 219
	}

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

223
	private _hitContent(target: IMouseTarget): boolean {
A
Alex Dima 已提交
224 225
		return target.type === MouseTargetType.CONTENT_TEXT ||
			target.type === MouseTargetType.CONTENT_EMPTY;
226 227 228
	}

	private _hitMargin(target: IMouseTarget): boolean {
A
Alex Dima 已提交
229 230 231
		return target.type === MouseTargetType.GUTTER_GLYPH_MARGIN ||
			target.type === MouseTargetType.GUTTER_LINE_NUMBERS ||
			target.type === MouseTargetType.GUTTER_LINE_DECORATIONS;
232 233
	}

234 235 236 237 238
	public getId(): string {
		return DragAndDropController.ID;
	}

	public dispose(): void {
R
rebornix 已提交
239
		this._removeDecoration();
240 241
		this._dragSelection = null;
		this._mouseDown = false;
242
		this._modiferPressed = false;
243 244
		this._toUnhook = dispose(this._toUnhook);
	}
245 246 247
}

registerEditorContribution(DragAndDropController);