From 5594c9d82f26f024895181ddc88d0dd0d396297f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Mar 2017 14:31:54 -0700 Subject: [PATCH] Expand selections on mouse drag --- spec/text-editor-component-spec.js | 82 ++++++++++++++++++++++++++++++ src/text-editor-component.js | 47 +++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 04366bfdb..572c42f1f 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -819,6 +819,88 @@ describe('TextEditorComponent', () => { }, clientPositionForCharacter(component, 3, 11))) expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [4, 0]]) }) + + it('expands the last selection on drag', () => { + const {component, editor} = buildComponent() + spyOn(component, 'handleMouseDragUntilMouseUp') + + component.didMouseDownOnContent(Object.assign({ + detail: 1, + }, clientPositionForCharacter(component, 1, 4))) + + { + const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[0] + didDrag(clientPositionForCharacter(component, 8, 8)) + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [8, 8]]) + didDrag(clientPositionForCharacter(component, 4, 8)) + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [4, 8]]) + didStopDragging() + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [4, 8]]) + } + + // Click-drag a second selection... selections are not merged until the + // drag stops. + component.didMouseDownOnContent(Object.assign({ + detail: 1, + metaKey: 1, + }, clientPositionForCharacter(component, 8, 8))) + { + const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[1] + didDrag(clientPositionForCharacter(component, 2, 8)) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 4], [4, 8]], + [[2, 8], [8, 8]] + ]) + didDrag(clientPositionForCharacter(component, 6, 8)) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 4], [4, 8]], + [[6, 8], [8, 8]] + ]) + didDrag(clientPositionForCharacter(component, 2, 8)) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 4], [4, 8]], + [[2, 8], [8, 8]] + ]) + didStopDragging() + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 4], [8, 8]] + ]) + } + }) + + it('expands the selection word-wise on double-click-drag', () => { + const {component, editor} = buildComponent() + spyOn(component, 'handleMouseDragUntilMouseUp') + + component.didMouseDownOnContent(Object.assign({ + detail: 1, + }, clientPositionForCharacter(component, 1, 4))) + component.didMouseDownOnContent(Object.assign({ + detail: 2, + }, clientPositionForCharacter(component, 1, 4))) + + const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[1] + didDrag(clientPositionForCharacter(component, 0, 8)) + expect(editor.getSelectedScreenRange()).toEqual([[0, 4], [1, 5]]) + didDrag(clientPositionForCharacter(component, 2, 10)) + expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 13]]) + }) + + it('expands the selection line-wise on triple-click-drag', () => { + const {component, editor} = buildComponent() + spyOn(component, 'handleMouseDragUntilMouseUp') + + const tripleClickPosition = clientPositionForCharacter(component, 2, 8) + component.didMouseDownOnContent(Object.assign({detail: 1}, tripleClickPosition)) + component.didMouseDownOnContent(Object.assign({detail: 2}, tripleClickPosition)) + component.didMouseDownOnContent(Object.assign({detail: 3}, tripleClickPosition)) + + const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[2] + didDrag(clientPositionForCharacter(component, 1, 8)) + expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]) + didDrag(clientPositionForCharacter(component, 4, 10)) + expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [5, 0]]) + }) }) }) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 52e4f054e..02e49ecba 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -786,6 +786,53 @@ class TextEditorComponent { model.getLastSelection().selectLine(null, {autoscroll: false}) break } + + this.handleMouseDragUntilMouseUp( + (event) => { + const screenPosition = this.screenPositionForMouseEvent(event) + model.selectToScreenPosition(screenPosition, {suppressSelectionMerge: true, autoscroll: false}) + this.updateSync() + }, + () => { + model.finalizeSelections() + model.mergeIntersectingSelections() + this.updateSync() + } + ) + } + + handleMouseDragUntilMouseUp (didDragCallback, didStopDragging) { + let dragging = false + let lastMousemoveEvent + + const animationFrameLoop = () => { + window.requestAnimationFrame(() => { + if (dragging && this.visible) { + didDragCallback(lastMousemoveEvent) + animationFrameLoop() + } + }) + } + + function didMouseMove (event) { + lastMousemoveEvent = event + if (!dragging) { + dragging = true + animationFrameLoop() + } + } + + function didMouseUp () { + window.removeEventListener('mousemove', didMouseMove) + window.removeEventListener('mouseup', didMouseUp) + if (dragging) { + dragging = false + didStopDragging() + } + } + + window.addEventListener('mousemove', didMouseMove) + window.addEventListener('mouseup', didMouseUp) } screenPositionForMouseEvent ({clientX, clientY}) {