dnd.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. import { Disposable } from '../../../base/common/lifecycle.js';
  6. import { isMacintosh } from '../../../base/common/platform.js';
  7. import './dnd.css';
  8. import { registerEditorContribution } from '../../browser/editorExtensions.js';
  9. import { Position } from '../../common/core/position.js';
  10. import { Range } from '../../common/core/range.js';
  11. import { Selection } from '../../common/core/selection.js';
  12. import { ModelDecorationOptions } from '../../common/model/textModel.js';
  13. import { DragAndDropCommand } from './dragAndDropCommand.js';
  14. function hasTriggerModifier(e) {
  15. if (isMacintosh) {
  16. return e.altKey;
  17. }
  18. else {
  19. return e.ctrlKey;
  20. }
  21. }
  22. export class DragAndDropController extends Disposable {
  23. constructor(editor) {
  24. super();
  25. this._editor = editor;
  26. this._register(this._editor.onMouseDown((e) => this._onEditorMouseDown(e)));
  27. this._register(this._editor.onMouseUp((e) => this._onEditorMouseUp(e)));
  28. this._register(this._editor.onMouseDrag((e) => this._onEditorMouseDrag(e)));
  29. this._register(this._editor.onMouseDrop((e) => this._onEditorMouseDrop(e)));
  30. this._register(this._editor.onMouseDropCanceled(() => this._onEditorMouseDropCanceled()));
  31. this._register(this._editor.onKeyDown((e) => this.onEditorKeyDown(e)));
  32. this._register(this._editor.onKeyUp((e) => this.onEditorKeyUp(e)));
  33. this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
  34. this._register(this._editor.onDidBlurEditorText(() => this.onEditorBlur()));
  35. this._dndDecorationIds = [];
  36. this._mouseDown = false;
  37. this._modifierPressed = false;
  38. this._dragSelection = null;
  39. }
  40. onEditorBlur() {
  41. this._removeDecoration();
  42. this._dragSelection = null;
  43. this._mouseDown = false;
  44. this._modifierPressed = false;
  45. }
  46. onEditorKeyDown(e) {
  47. if (!this._editor.getOption(31 /* dragAndDrop */) || this._editor.getOption(18 /* columnSelection */)) {
  48. return;
  49. }
  50. if (hasTriggerModifier(e)) {
  51. this._modifierPressed = true;
  52. }
  53. if (this._mouseDown && hasTriggerModifier(e)) {
  54. this._editor.updateOptions({
  55. mouseStyle: 'copy'
  56. });
  57. }
  58. }
  59. onEditorKeyUp(e) {
  60. if (!this._editor.getOption(31 /* dragAndDrop */) || this._editor.getOption(18 /* columnSelection */)) {
  61. return;
  62. }
  63. if (hasTriggerModifier(e)) {
  64. this._modifierPressed = false;
  65. }
  66. if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) {
  67. this._editor.updateOptions({
  68. mouseStyle: 'default'
  69. });
  70. }
  71. }
  72. _onEditorMouseDown(mouseEvent) {
  73. this._mouseDown = true;
  74. }
  75. _onEditorMouseUp(mouseEvent) {
  76. this._mouseDown = false;
  77. // Whenever users release the mouse, the drag and drop operation should finish and the cursor should revert to text.
  78. this._editor.updateOptions({
  79. mouseStyle: 'text'
  80. });
  81. }
  82. _onEditorMouseDrag(mouseEvent) {
  83. let target = mouseEvent.target;
  84. if (this._dragSelection === null) {
  85. const selections = this._editor.getSelections() || [];
  86. let possibleSelections = selections.filter(selection => target.position && selection.containsPosition(target.position));
  87. if (possibleSelections.length === 1) {
  88. this._dragSelection = possibleSelections[0];
  89. }
  90. else {
  91. return;
  92. }
  93. }
  94. if (hasTriggerModifier(mouseEvent.event)) {
  95. this._editor.updateOptions({
  96. mouseStyle: 'copy'
  97. });
  98. }
  99. else {
  100. this._editor.updateOptions({
  101. mouseStyle: 'default'
  102. });
  103. }
  104. if (target.position) {
  105. if (this._dragSelection.containsPosition(target.position)) {
  106. this._removeDecoration();
  107. }
  108. else {
  109. this.showAt(target.position);
  110. }
  111. }
  112. }
  113. _onEditorMouseDropCanceled() {
  114. this._editor.updateOptions({
  115. mouseStyle: 'text'
  116. });
  117. this._removeDecoration();
  118. this._dragSelection = null;
  119. this._mouseDown = false;
  120. }
  121. _onEditorMouseDrop(mouseEvent) {
  122. if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {
  123. let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);
  124. if (this._dragSelection === null) {
  125. let newSelections = null;
  126. if (mouseEvent.event.shiftKey) {
  127. let primarySelection = this._editor.getSelection();
  128. if (primarySelection) {
  129. const { selectionStartLineNumber, selectionStartColumn } = primarySelection;
  130. newSelections = [new Selection(selectionStartLineNumber, selectionStartColumn, newCursorPosition.lineNumber, newCursorPosition.column)];
  131. }
  132. }
  133. else {
  134. newSelections = (this._editor.getSelections() || []).map(selection => {
  135. if (selection.containsPosition(newCursorPosition)) {
  136. return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
  137. }
  138. else {
  139. return selection;
  140. }
  141. });
  142. }
  143. // Use `mouse` as the source instead of `api` and setting the reason to explicit (to behave like any other mouse operation).
  144. this._editor.setSelections(newSelections || [], 'mouse', 3 /* Explicit */);
  145. }
  146. else if (!this._dragSelection.containsPosition(newCursorPosition) ||
  147. ((hasTriggerModifier(mouseEvent.event) ||
  148. this._modifierPressed) && (this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition)) // we allow users to paste content beside the selection
  149. )) {
  150. this._editor.pushUndoStop();
  151. this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modifierPressed));
  152. this._editor.pushUndoStop();
  153. }
  154. }
  155. this._editor.updateOptions({
  156. mouseStyle: 'text'
  157. });
  158. this._removeDecoration();
  159. this._dragSelection = null;
  160. this._mouseDown = false;
  161. }
  162. showAt(position) {
  163. let newDecorations = [{
  164. range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),
  165. options: DragAndDropController._DECORATION_OPTIONS
  166. }];
  167. this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, newDecorations);
  168. this._editor.revealPosition(position, 1 /* Immediate */);
  169. }
  170. _removeDecoration() {
  171. this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, []);
  172. }
  173. _hitContent(target) {
  174. return target.type === 6 /* CONTENT_TEXT */ ||
  175. target.type === 7 /* CONTENT_EMPTY */;
  176. }
  177. _hitMargin(target) {
  178. return target.type === 2 /* GUTTER_GLYPH_MARGIN */ ||
  179. target.type === 3 /* GUTTER_LINE_NUMBERS */ ||
  180. target.type === 4 /* GUTTER_LINE_DECORATIONS */;
  181. }
  182. dispose() {
  183. this._removeDecoration();
  184. this._dragSelection = null;
  185. this._mouseDown = false;
  186. this._modifierPressed = false;
  187. super.dispose();
  188. }
  189. }
  190. DragAndDropController.ID = 'editor.contrib.dragAndDrop';
  191. DragAndDropController.TRIGGER_KEY_VALUE = isMacintosh ? 6 /* Alt */ : 5 /* Ctrl */;
  192. DragAndDropController._DECORATION_OPTIONS = ModelDecorationOptions.register({
  193. description: 'dnd-target',
  194. className: 'dnd-target'
  195. });
  196. registerEditorContribution(DragAndDropController.ID, DragAndDropController);