dnd.ts 7.4 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';
20
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
21 22 23 24 25 26 27

export class DragAndDropController implements editorCommon.IEditorContribution {

	private static ID = 'editor.contrib.dragAndDrop';

	private _editor: ICodeEditor;
	private _toUnhook: IDisposable[];
28
	private _dragSelection: Selection;
R
rebornix 已提交
29
	private _dndDecorationIds: string[];
30
	private _mouseDown: boolean;
31
	private _modiferPressed: boolean;
32 33
	static TRIGGER_MODIFIER = isMacintosh ? 'altKey' : 'ctrlKey';
	static TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl;
34

35
	static get(editor: ICodeEditor): DragAndDropController {
36 37 38 39 40 41
		return editor.getContribution<DragAndDropController>(DragAndDropController.ID);
	}

	constructor(editor: ICodeEditor) {
		this._editor = editor;
		this._toUnhook = [];
42 43
		this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
		this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
44
		this._toUnhook.push(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
45
		this._toUnhook.push(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e)));
46 47
		this._toUnhook.push(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
		this._toUnhook.push(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
R
rebornix 已提交
48
		this._dndDecorationIds = [];
49
		this._mouseDown = false;
50
		this._modiferPressed = false;
51
		this._dragSelection = null;
52 53
	}

54
	private onEditorKeyDown(e: IKeyboardEvent): void {
55 56 57 58
		if (!this._editor.getConfiguration().dragAndDrop) {
			return;
		}

59 60 61 62
		if (e[DragAndDropController.TRIGGER_MODIFIER]) {
			this._modiferPressed = true;
		}

63 64 65 66
		if (this._mouseDown && e[DragAndDropController.TRIGGER_MODIFIER]) {
			this._editor.updateOptions({
				mouseStyle: 'copy'
			});
R
rebornix 已提交
67
		}
68
	}
R
rebornix 已提交
69

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

75 76 77 78
		if (e[DragAndDropController.TRIGGER_MODIFIER]) {
			this._modiferPressed = false;
		}

79 80 81 82
		if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) {
			this._editor.updateOptions({
				mouseStyle: 'default'
			});
R
rebornix 已提交
83
		}
84
	}
R
rebornix 已提交
85

86 87 88 89 90 91
	private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
		this._mouseDown = true;
	}

	private _onEditorMouseUp(mouseEvent: IEditorMouseEvent): void {
		this._mouseDown = false;
R
rebornix 已提交
92 93 94 95
		// 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 已提交
96 97
	}

98 99
	private _onEditorMouseDrag(mouseEvent: IEditorMouseEvent): void {
		let target = mouseEvent.target;
100

101
		if (this._dragSelection === null) {
102 103 104
			let possibleSelections = this._editor.getSelections().filter(selection => selection.containsPosition(target.position));
			if (possibleSelections.length === 1) {
				this._dragSelection = possibleSelections[0];
105 106
			} else {
				return;
107 108
			}
		}
109

110
		if (mouseEvent.event[DragAndDropController.TRIGGER_MODIFIER]) {
R
rebornix 已提交
111 112 113 114 115 116 117 118 119
			this._editor.updateOptions({
				mouseStyle: 'copy'
			});
		} else {
			this._editor.updateOptions({
				mouseStyle: 'default'
			});
		}

120 121 122 123 124
		if (this._dragSelection.containsPosition(target.position)) {
			this._removeDecoration();
		} else {
			this.showAt(target.position);
		}
125 126 127
	}

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

131
			if (this._dragSelection === null) {
132
				let newSelections = this._editor.getSelections().map(selection => {
133
					if (selection.containsPosition(newCursorPosition)) {
134 135 136 137 138 139
						return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
					} else {
						return selection;
					}
				});
				this._editor.setSelections(newSelections);
140 141 142 143 144 145 146 147 148
			} else if (!this._dragSelection.containsPosition(newCursorPosition) ||
				(
					(
						mouseEvent.event[DragAndDropController.TRIGGER_MODIFIER] ||
						this._modiferPressed
					) && (
						this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition)
					) // we allow users to paste content beside the selection
				)) {
A
Alex Dima 已提交
149
				this._editor.pushUndoStop();
150
				this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, mouseEvent.event[DragAndDropController.TRIGGER_MODIFIER] || this._modiferPressed));
A
Alex Dima 已提交
151
				this._editor.pushUndoStop();
152
			}
153 154
		}

R
rebornix 已提交
155 156 157 158
		this._editor.updateOptions({
			mouseStyle: 'text'
		});

R
rebornix 已提交
159
		this._removeDecoration();
160
		this._dragSelection = null;
161
		this._mouseDown = false;
162 163
	}

164 165 166 167
	private static _DECORATION_OPTIONS = ModelDecorationOptions.register({
		className: 'dnd-target'
	});

R
rebornix 已提交
168 169 170 171 172
	public showAt(position: Position): void {
		this._editor.changeDecorations(changeAccessor => {
			let newDecorations: editorCommon.IModelDeltaDecoration[] = [];
			newDecorations.push({
				range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),
173
				options: DragAndDropController._DECORATION_OPTIONS
R
rebornix 已提交
174 175 176 177
			});

			this._dndDecorationIds = changeAccessor.deltaDecorations(this._dndDecorationIds, newDecorations);
		});
178
		this._editor.revealPosition(position, editorCommon.ScrollType.Immediate);
R
rebornix 已提交
179 180 181 182 183 184
	}

	private _removeDecoration(): void {
		this._editor.changeDecorations(changeAccessor => {
			changeAccessor.deltaDecorations(this._dndDecorationIds, []);
		});
185 186
	}

187
	private _hitContent(target: IMouseTarget): boolean {
A
Alex Dima 已提交
188 189
		return target.type === MouseTargetType.CONTENT_TEXT ||
			target.type === MouseTargetType.CONTENT_EMPTY;
190 191 192
	}

	private _hitMargin(target: IMouseTarget): boolean {
A
Alex Dima 已提交
193 194 195
		return target.type === MouseTargetType.GUTTER_GLYPH_MARGIN ||
			target.type === MouseTargetType.GUTTER_LINE_NUMBERS ||
			target.type === MouseTargetType.GUTTER_LINE_DECORATIONS;
196 197
	}

198 199 200 201 202
	public getId(): string {
		return DragAndDropController.ID;
	}

	public dispose(): void {
R
rebornix 已提交
203
		this._removeDecoration();
204 205
		this._dragSelection = null;
		this._mouseDown = false;
206
		this._modiferPressed = false;
207 208
		this._toUnhook = dispose(this._toUnhook);
	}
209 210 211
}

registerEditorContribution(DragAndDropController);