提交 66f06e0d 编写于 作者: A Alexandru Dima 提交者: GitHub

Merge pull request #20565 from rebornix/DragNDrop

Drag and drop selection in editor
......@@ -193,4 +193,4 @@ export class StandardMouseWheelEvent {
}
}
}
}
}
\ No newline at end of file
......@@ -402,7 +402,14 @@ class MouseDownOperation extends Disposable {
return;
}
this._dispatchMouse(position, true);
if (this._mouseState.isDragAndDrop) {
this._viewController.emitMouseDrag({
event: e,
target: this._createMouseTarget(e, true)
});
} else {
this._dispatchMouse(position, true);
}
}
public start(targetType: editorCommon.MouseTargetType, e: EditorMouseEvent): void {
......@@ -410,7 +417,6 @@ class MouseDownOperation extends Disposable {
this._mouseState.setStartedOnLineNumbers(targetType === editorCommon.MouseTargetType.GUTTER_LINE_NUMBERS);
this._mouseState.setModifiers(e);
let position = this._findMousePosition(e, true);
if (!position) {
// Ignoring because position is unknown
......@@ -422,6 +428,39 @@ class MouseDownOperation extends Disposable {
// Overwrite the detail of the MouseEvent, as it will be sent out in an event and contributions might rely on it.
e.detail = this._mouseState.count;
if (this._context.configuration.editor.enableDragAndDrop
&& !this._mouseState.altKey // we don't support multiple mouse
&& e.detail < 2 // only single click on a selection can work
&& !this._isActive // the mouse is not down yet
&& !this._currentSelection.isEmpty() // we don't drag single cursor
&& this._currentSelection.containsPosition(position.position) // single click on a selection
) {
this._mouseState.isDragAndDrop = true;
this._isActive = true;
this._viewController.emitMouseDrag({
event: e,
target: this._createMouseTarget(e, true)
});
this._mouseMoveMonitor.startMonitoring(
createMouseMoveEventMerger(null),
this._mouseDownThenMoveEventHandler.handler,
() => {
let position = this._findMousePosition(this._lastMouseEvent, true);
this._viewController.emitMouseDrop({
event: this._lastMouseEvent,
target: position ? this._createMouseTarget(this._lastMouseEvent, true) : null // Ignoring because position is unknown, e.g., Content View Zone
});
this._stop();
}
);
return;
}
this._mouseState.isDragAndDrop = false;
this._dispatchMouse(position, e.shiftKey);
if (!this._isActive) {
......@@ -449,6 +488,10 @@ class MouseDownOperation extends Disposable {
// Ignoring because position is unknown
return;
}
if (this._mouseState.isDragAndDrop) {
// Ignoring because users are dragging the text
return;
}
this._dispatchMouse(position, true);
}, 10);
}
......@@ -555,6 +598,7 @@ class MouseDownState {
private _lastMouseDownPositionEqualCount: number;
private _lastMouseDownCount: number;
private _lastSetMouseDownCountTime: number;
public isDragAndDrop: boolean;
constructor() {
this._altKey = false;
......@@ -566,6 +610,7 @@ class MouseDownState {
this._lastMouseDownPositionEqualCount = 0;
this._lastMouseDownCount = 0;
this._lastSetMouseDownCountTime = 0;
this.isDragAndDrop = false;
}
public get count(): number {
......
......@@ -22,6 +22,7 @@ import 'vs/editor/contrib/format/common/formatActions';
import 'vs/editor/contrib/goToDeclaration/browser/goToDeclaration';
import 'vs/editor/contrib/gotoError/browser/gotoError';
import 'vs/editor/contrib/hover/browser/hover';
import 'vs/editor/contrib/dnd/browser/dnd';
import 'vs/css!vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace';
import 'vs/editor/contrib/inPlaceReplace/common/inPlaceReplace';
import 'vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard';
......
......@@ -136,6 +136,8 @@ export interface IViewController {
emitMouseLeave(e: IEditorMouseEvent): void;
emitMouseUp(e: IEditorMouseEvent): void;
emitMouseDown(e: IEditorMouseEvent): void;
emitMouseDrag(e: IEditorMouseEvent): void;
emitMouseDrop(e: IEditorMouseEvent): void;
}
/**
......@@ -415,6 +417,18 @@ export interface ICodeEditor extends editorCommon.ICommonCodeEditor {
* @event
*/
onMouseDown(listener: (e: IEditorMouseEvent) => void): IDisposable;
/**
* An event emitted on a "mousedrag".
* @internal
* @event
*/
onMouseDrag(listener: (e: IEditorMouseEvent) => void): IDisposable;
/**
* An event emitted on a "mousedrop".
* @internal
* @event
*/
onMouseDrop(listener: (e: IEditorMouseEvent) => void): IDisposable;
/**
* An event emitted on a "contextmenu".
* @event
......
......@@ -273,4 +273,12 @@ export class ViewController implements IViewController {
public emitMouseDown(e: IEditorMouseEvent): void {
this.outgoingEvents.emitMouseDown(e);
}
public emitMouseDrag(e: IEditorMouseEvent): void {
this.outgoingEvents.emitMouseDrag(e);
}
public emitMouseDrop(e: IEditorMouseEvent): void {
this.outgoingEvents.emitMouseDrop(e);
}
}
......@@ -74,6 +74,14 @@ export class ViewOutgoingEvents extends Disposable {
this._actual.emit(EventType.MouseDown, this._convertViewToModelMouseEvent(e));
}
public emitMouseDrag(e: IEditorMouseEvent): void {
this._actual.emit(EventType.MouseDrag, this._convertViewToModelMouseEvent(e));
}
public emitMouseDrop(e: IEditorMouseEvent): void {
this._actual.emit(EventType.MouseDrop, this._convertViewToModelMouseEvent(e));
}
private _convertViewToModelMouseEvent(e: IEditorMouseEvent): IEditorMouseEvent {
if (e.target) {
return {
......
......@@ -33,6 +33,8 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
public readonly onMouseUp: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.MouseUp);
public readonly onMouseDown: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.MouseDown);
public readonly onMouseDrag: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.MouseDrag);
public readonly onMouseDrop: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.MouseDrop);
public readonly onContextMenu: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.ContextMenu);
public readonly onMouseMove: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.MouseMove);
public readonly onMouseLeave: Event<editorBrowser.IEditorMouseEvent> = fromEventEmitter(this, editorCommon.EventType.MouseLeave);
......
......@@ -870,6 +870,14 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom
this.emit(editorCommon.EventType.MouseUp, e);
break;
case editorCommon.EventType.MouseDrag:
this.emit(editorCommon.EventType.MouseDrag, e);
break;
case editorCommon.EventType.MouseDrop:
this.emit(editorCommon.EventType.MouseDrop, e);
break;
case editorCommon.EventType.KeyUp:
this.emit(editorCommon.EventType.KeyUp, e);
break;
......
......@@ -317,6 +317,7 @@ class InternalEditorOptionsHelper {
autoClosingBrackets: toBoolean(opts.autoClosingBrackets),
useTabStops: toBoolean(opts.useTabStops),
tabFocusMode: tabFocusMode,
enableDragAndDrop: toBoolean(opts.enableDragAndDrop),
layoutInfo: layoutInfo,
fontInfo: fontInfo,
viewInfo: viewInfo,
......@@ -842,6 +843,11 @@ const editorConfiguration: IConfigurationNode = {
'default': false,
'description': nls.localize('stablePeek', "Keep peek editors open even when double clicking their content or when hitting Escape.")
},
'editor.enableDragAndDrop': {
'type': 'boolean',
'default': DefaultConfig.editor.enableDragAndDrop,
'description': nls.localize('enableDragAndDrop', "Controls if the editor should allow to move selections via drag and drop.")
},
'diffEditor.renderSideBySide': {
'type': 'boolean',
'default': true,
......
......@@ -106,6 +106,7 @@ class ConfigClass implements IConfiguration {
renderLineHighlight: 'line',
useTabStops: true,
matchBrackets: true,
enableDragAndDrop: false,
fontFamily: (
platform.isMacintosh ? DEFAULT_MAC_FONT_FAMILY : (platform.isLinux ? DEFAULT_LINUX_FONT_FAMILY : DEFAULT_WINDOWS_FONT_FAMILY)
......
......@@ -419,6 +419,11 @@ export interface IEditorOptions {
* Defaults to false.
*/
formatOnPaste?: boolean;
/**
* Controls if the editor should allow to move selections via drag and drop.
* Defaults to false.
*/
enableDragAndDrop?: boolean;
/**
* Enable the suggestion box to pop-up on trigger characters.
* Defaults to true.
......@@ -1068,6 +1073,7 @@ export class InternalEditorOptions {
readonly autoClosingBrackets: boolean;
readonly useTabStops: boolean;
readonly tabFocusMode: boolean;
readonly enableDragAndDrop: boolean;
// ---- grouped options
readonly layoutInfo: EditorLayoutInfo;
readonly fontInfo: FontInfo;
......@@ -1085,6 +1091,7 @@ export class InternalEditorOptions {
autoClosingBrackets: boolean;
useTabStops: boolean;
tabFocusMode: boolean;
enableDragAndDrop: boolean;
layoutInfo: EditorLayoutInfo;
fontInfo: FontInfo;
viewInfo: InternalEditorViewOptions;
......@@ -1097,6 +1104,7 @@ export class InternalEditorOptions {
this.autoClosingBrackets = Boolean(source.autoClosingBrackets);
this.useTabStops = Boolean(source.useTabStops);
this.tabFocusMode = Boolean(source.tabFocusMode);
this.enableDragAndDrop = Boolean(source.enableDragAndDrop);
this.layoutInfo = source.layoutInfo.clone();
this.fontInfo = source.fontInfo.clone();
this.viewInfo = source.viewInfo.clone();
......@@ -1115,6 +1123,7 @@ export class InternalEditorOptions {
&& this.autoClosingBrackets === other.autoClosingBrackets
&& this.useTabStops === other.useTabStops
&& this.tabFocusMode === other.tabFocusMode
&& this.enableDragAndDrop === other.enableDragAndDrop
&& this.layoutInfo.equals(other.layoutInfo)
&& this.fontInfo.equals(other.fontInfo)
&& this.viewInfo.equals(other.viewInfo)
......@@ -1134,6 +1143,7 @@ export class InternalEditorOptions {
autoClosingBrackets: (this.autoClosingBrackets !== newOpts.autoClosingBrackets),
useTabStops: (this.useTabStops !== newOpts.useTabStops),
tabFocusMode: (this.tabFocusMode !== newOpts.tabFocusMode),
enableDragAndDrop: (this.enableDragAndDrop !== newOpts.enableDragAndDrop),
layoutInfo: (!this.layoutInfo.equals(newOpts.layoutInfo)),
fontInfo: (!this.fontInfo.equals(newOpts.fontInfo)),
viewInfo: this.viewInfo.createChangeEvent(newOpts.viewInfo),
......@@ -1160,6 +1170,7 @@ export interface IConfigurationChangedEvent {
readonly autoClosingBrackets: boolean;
readonly useTabStops: boolean;
readonly tabFocusMode: boolean;
readonly enableDragAndDrop: boolean;
readonly layoutInfo: boolean;
readonly fontInfo: boolean;
readonly viewInfo: IViewConfigurationChangedEvent;
......@@ -4136,6 +4147,8 @@ export var EventType = {
MouseUp: 'mouseup',
MouseMove: 'mousemove',
MouseLeave: 'mouseleave',
MouseDrag: 'mousedrag',
MouseDrop: 'mousedrop',
KeyDown: 'keydown',
KeyUp: 'keyup',
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor.vs .dnd-target {
border-right: 1px dotted black;
color: white; /* opposite of black */
}
.monaco-editor.vs-dark .dnd-target {
border-right: 1px dotted #AEAFAD;
color: #51504f; /* opposite of #AEAFAD */
}
.monaco-editor.hc-black .dnd-target {
border-right: 1px dotted #fff;
color: #000; /* opposite of #fff */
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./dnd';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { DragTargetHintWidget } from './dndHintWidget';
import { DragAndDropCommand } from '../common/dragAndDropCommand';
@editorContribution
export class DragAndDropController implements editorCommon.IEditorContribution {
private static ID = 'editor.contrib.dragAndDrop';
private _editor: ICodeEditor;
private _toUnhook: IDisposable[];
private _targetWidget: DragTargetHintWidget;
private _active: boolean;
private _dragSelection: Selection;
static get(editor: editorCommon.ICommonCodeEditor): DragAndDropController {
return editor.getContribution<DragAndDropController>(DragAndDropController.ID);
}
constructor(editor: ICodeEditor) {
this._editor = editor;
this._toUnhook = [];
this._toUnhook.push(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
this._toUnhook.push(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e)));
this._targetWidget = new DragTargetHintWidget(editor);
this._active = false;
}
private _onEditorMouseDrag(mouseEvent: IEditorMouseEvent): void {
let target = mouseEvent.target;
if (this._active) {
this._targetWidget.showAt(target.position);
} else {
let possibleSelections = this._editor.getSelections().filter(selection => selection.containsPosition(target.position));
if (possibleSelections.length === 1) {
this._active = true;
this._dragSelection = possibleSelections[0];
this._targetWidget.showAt(target.position);
}
}
}
private _onEditorMouseDrop(mouseEvent: IEditorMouseEvent): void {
if (mouseEvent.target &&
(mouseEvent.target.type === editorCommon.MouseTargetType.CONTENT_TEXT || mouseEvent.target.type === editorCommon.MouseTargetType.CONTENT_EMPTY) &&
mouseEvent.target.position) {
let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);
if (this._dragSelection.containsPosition(newCursorPosition)) {
let newSelections = this._editor.getSelections().map(selection => {
if (selection.equalsSelection(this._dragSelection)) {
return new Selection(newCursorPosition.lineNumber, newCursorPosition.column, newCursorPosition.lineNumber, newCursorPosition.column);
} else {
return selection;
}
});
this._editor.setSelections(newSelections);
} else {
this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition));
}
}
this._hideWidget();
this._active = false;
}
private _hideWidget(): void {
this._targetWidget.hide();
}
public getId(): string {
return DragAndDropController.ID;
}
public dispose(): void {
this._toUnhook = dispose(this._toUnhook);
if (this._targetWidget) {
this._targetWidget.dispose();
this._targetWidget = null;
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/styleMutator';
import { Widget } from 'vs/base/browser/ui/widget';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { Position } from 'vs/editor/common/core/position';
import { IPosition, TextEditorCursorStyle, IConfigurationChangedEvent } from 'vs/editor/common/editorCommon';
export class DragTargetHintWidget extends Widget implements editorBrowser.IContentWidget {
static ID = 'editor.contrib.dragTargetHintWidget';
protected _editor: editorBrowser.ICodeEditor;
protected _showAtPosition: Position;
private disposables: IDisposable[] = [];
private _cursorStyle: TextEditorCursorStyle;
private _lineHeight: number;
private _typicalHalfwidthCharacterWidth: number;
private readonly _domNode: FastDomNode<HTMLElement>;
private _isVisible: boolean = false;
protected get isVisible(): boolean {
return this._isVisible;
}
protected set isVisible(value: boolean) {
this._isVisible = value;
}
constructor(editor: editorBrowser.ICodeEditor) {
super();
this._editor = editor;
// Create the dom node
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setClassName('dnd-target');
this._domNode.setTop(0);
this._domNode.setLeft(0);
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
this._domNode.setVisibility('hidden');
this._editor.addContentWidget(this);
this._cursorStyle = this._editor.getConfiguration().viewInfo.cursorStyle;
this._lineHeight = this._editor.getConfiguration().lineHeight;
this._typicalHalfwidthCharacterWidth = this._editor.getConfiguration().fontInfo.typicalHalfwidthCharacterWidth;
this._register(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.fontInfo || e.viewInfo || e.lineHeight) {
this._typicalHalfwidthCharacterWidth = this._editor.getConfiguration().fontInfo.typicalHalfwidthCharacterWidth;
this._lineHeight = this._editor.getConfiguration().lineHeight;
this._cursorStyle = this._editor.getConfiguration().viewInfo.cursorStyle;
}
}));
// render cursor after preparing the dom node and fetching data from config.
this.renderCursor();
}
public getId(): string {
return DragTargetHintWidget.ID;
}
public getDomNode(): HTMLElement {
return this._domNode.domNode;
}
public showAt(position: IPosition): void {
// Position has changed
this._showAtPosition = new Position(position.lineNumber, position.column);
this.show();
this._editor.layoutContentWidget(this);
this._editor.render();
}
public show(): void {
if (!this._isVisible) {
this._domNode.setVisibility('inherit');
this._isVisible = true;
}
this.renderCursor();
}
public hide(): void {
if (this._isVisible) {
this._domNode.setVisibility('hidden');
this._isVisible = false;
this._editor.layoutContentWidget(this);
}
}
public getPosition(): editorBrowser.IContentWidgetPosition {
if (this._isVisible) {
return {
position: this._showAtPosition,
preference: [
editorBrowser.ContentWidgetPositionPreference.EXACT
]
};
}
return null;
}
public dispose(): void {
this._editor.removeContentWidget(this);
this.disposables = dispose(this.disposables);
super.dispose();
}
private renderCursor() {
if (!this.isVisible) {
return;
}
Configuration.applyFontInfo(this._domNode, this._editor.getConfiguration().fontInfo);
this._domNode.setHeight(this._lineHeight);
this._domNode.setWidth(0);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Selection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
export class DragAndDropCommand implements editorCommon.ICommand {
private selection: Selection;
private targetPosition: Position;
private targetSelection: Selection;
constructor(selection: Selection, targetPosition: Position) {
this.selection = selection;
this.targetPosition = targetPosition;
}
public getEditOperations(model: editorCommon.ITokenizedModel, builder: editorCommon.IEditOperationBuilder): void {
let text = model.getValueInRange(this.selection);
builder.addEditOperation(this.selection, null);
builder.addEditOperation(new Range(this.targetPosition.lineNumber, this.targetPosition.column, this.targetPosition.lineNumber, this.targetPosition.column), text);
if (this.targetPosition.lineNumber >= this.selection.endLineNumber) {
this.targetSelection = new Selection(
this.targetPosition.lineNumber - this.selection.endLineNumber + this.selection.startLineNumber,
this.targetPosition.column,
this.targetPosition.lineNumber,
this.selection.startLineNumber === this.selection.endLineNumber ?
this.targetPosition.column + this.selection.endColumn - this.selection.startColumn :
this.selection.endColumn
);
} else {
this.targetSelection = new Selection(
this.targetPosition.lineNumber,
this.targetPosition.column,
this.targetPosition.lineNumber + this.selection.endLineNumber - this.selection.startLineNumber,
this.selection.startLineNumber === this.selection.endLineNumber ?
this.targetPosition.column + this.selection.endColumn - this.selection.startColumn :
this.selection.endColumn
);
}
}
public computeCursorState(model: editorCommon.ITokenizedModel, helper: editorCommon.ICursorStateComputerData): Selection {
return this.targetSelection;
}
}
\ No newline at end of file
......@@ -1340,6 +1340,11 @@ declare module monaco.editor {
* Defaults to false.
*/
formatOnPaste?: boolean;
/**
* Controls if the editor should allow to move selections via drag and drop.
* Defaults to false.
*/
enableDragAndDrop?: boolean;
/**
* Enable the suggestion box to pop-up on trigger characters.
* Defaults to true.
......@@ -1599,6 +1604,7 @@ declare module monaco.editor {
readonly autoClosingBrackets: boolean;
readonly useTabStops: boolean;
readonly tabFocusMode: boolean;
readonly enableDragAndDrop: boolean;
readonly layoutInfo: EditorLayoutInfo;
readonly fontInfo: FontInfo;
readonly viewInfo: InternalEditorViewOptions;
......@@ -1616,6 +1622,7 @@ declare module monaco.editor {
readonly autoClosingBrackets: boolean;
readonly useTabStops: boolean;
readonly tabFocusMode: boolean;
readonly enableDragAndDrop: boolean;
readonly layoutInfo: boolean;
readonly fontInfo: boolean;
readonly viewInfo: IViewConfigurationChangedEvent;
......
......@@ -213,6 +213,7 @@ const configurationValueWhitelist = [
'editor.formatOnType',
'editor.formatOnSave',
'editor.formatOnPaste',
'editor.enableDragAndDrop',
'window.openFilesInNewWindow',
'javascript.validate.enable',
'editor.mouseWheelZoom',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册