diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 572021c6e..e67360f96 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -4055,6 +4055,79 @@ describe('TextEditorComponent', () => { expect(component.refs.horizontalScrollbar.element.scrollLeft).toBe(Math.round(12 * component.getBaseCharacterWidth())) }) }) + + describe('handleMouseDragUntilMouseUp', () => { + it('repeatedly schedules `didDrag` calls on new animation frames after moving the mouse, and calls `didStopDragging` on mouseup', async () => { + const {component} = buildComponent() + + let dragEvents + let dragging = false + component.handleMouseDragUntilMouseUp({ + didDrag: (event) => { + dragging = true + dragEvents.push(event) + }, + didStopDragging: () => { dragging = false } + }) + expect(dragging).toBe(false) + + dragEvents = [] + const moveEvent1 = new MouseEvent('mousemove') + window.dispatchEvent(moveEvent1) + expect(dragging).toBe(false) + await getNextAnimationFramePromise() + expect(dragging).toBe(true) + expect(dragEvents).toEqual([moveEvent1]) + await getNextAnimationFramePromise() + expect(dragging).toBe(true) + expect(dragEvents).toEqual([moveEvent1, moveEvent1]) + + dragEvents = [] + const moveEvent2 = new MouseEvent('mousemove') + window.dispatchEvent(moveEvent2) + expect(dragging).toBe(true) + expect(dragEvents).toEqual([]) + await getNextAnimationFramePromise() + expect(dragging).toBe(true) + expect(dragEvents).toEqual([moveEvent2]) + await getNextAnimationFramePromise() + expect(dragging).toBe(true) + expect(dragEvents).toEqual([moveEvent2, moveEvent2]) + + dragEvents = [] + window.dispatchEvent(new MouseEvent('mouseup')) + expect(dragging).toBe(false) + expect(dragEvents).toEqual([]) + window.dispatchEvent(new MouseEvent('mousemove')) + await getNextAnimationFramePromise() + expect(dragging).toBe(false) + expect(dragEvents).toEqual([]) + }) + + it('calls `didStopDragging` if the buffer changes while dragging', async () => { + const {component, editor} = buildComponent() + + let dragging = false + component.handleMouseDragUntilMouseUp({ + didDrag: (event) => { dragging = true }, + didStopDragging: () => { dragging = false } + }) + + window.dispatchEvent(new MouseEvent('mousemove')) + await getNextAnimationFramePromise() + expect(dragging).toBe(true) + + editor.delete() + expect(dragging).toBe(false) + window.dispatchEvent(new MouseEvent('mousemove')) + await getNextAnimationFramePromise() + expect(dragging).toBe(false) + }) + + function getNextAnimationFramePromise () { + return new Promise((resolve) => requestAnimationFrame(resolve)) + } + }) }) function buildEditor (params = {}) { diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 3dec1f6ff..7e23e5745 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -1864,6 +1864,7 @@ class TextEditorComponent { handleMouseDragUntilMouseUp ({didDrag, didStopDragging}) { let dragging = false let lastMousemoveEvent + let bufferWillChangeDisposable const animationFrameLoop = () => { window.requestAnimationFrame(() => { @@ -1885,6 +1886,7 @@ class TextEditorComponent { function didMouseUp () { window.removeEventListener('mousemove', didMouseMove) window.removeEventListener('mouseup', didMouseUp) + bufferWillChangeDisposable.dispose() if (dragging) { dragging = false didStopDragging() @@ -1893,6 +1895,10 @@ class TextEditorComponent { window.addEventListener('mousemove', didMouseMove) window.addEventListener('mouseup', didMouseUp, {capture: true}) + // Simulate a mouse-up event if the buffer is about to change. This prevents + // unwanted selections when users perform edits while holding the left mouse + // button at the same time. + bufferWillChangeDisposable = this.props.model.getBuffer().onWillChange(didMouseUp) } autoscrollOnMouseDrag ({clientX, clientY}, verticalOnly = false) {