From e941dbd9be126174e3fd2fd3b2072bd9ca54530b Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Wed, 15 Nov 2017 11:51:54 +0100 Subject: [PATCH 1/3] Make ContextMenu async --- src/main-process/context-menu.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main-process/context-menu.coffee b/src/main-process/context-menu.coffee index 1bc9c29ba..ce1faf82d 100644 --- a/src/main-process/context-menu.coffee +++ b/src/main-process/context-menu.coffee @@ -5,7 +5,7 @@ class ContextMenu constructor: (template, @atomWindow) -> template = @createClickHandlers(template) menu = Menu.buildFromTemplate(template) - menu.popup(@atomWindow.browserWindow) + menu.popup(@atomWindow.browserWindow, {async: true}) # It's necessary to build the event handlers in this process, otherwise # closures are dragged across processes and failed to be garbage collected From 3569a11574260dc5609face8752327080fcae68c Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Wed, 15 Nov 2017 12:42:27 +0100 Subject: [PATCH 2/3] Rework didMouseDownOnContent to always position cursor --- src/text-editor-component.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 82ca7b676..da6ec452d 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -1756,33 +1756,28 @@ class TextEditorComponent { } } - // On Linux, position the cursor on middle mouse button click. A - // textInput event with the contents of the selection clipboard will be - // dispatched by the browser automatically on mouseup. - if (platform === 'linux' && button === 1) { - const selection = clipboard.readText('selection') - const screenPosition = this.screenPositionForMouseEvent(event) + const screenPosition = this.screenPositionForMouseEvent(event) + + // All clicks should set the cursor position, but only left-clicks should + // have additional logic. + // On macOS, ctrl-click brings up the context menu so also handle that case. + if (button !== 0 || (platform === 'darwin' && ctrlKey)) { model.setCursorScreenPosition(screenPosition, {autoscroll: false}) - model.insertText(selection) + + // On Linux, pasting happens on middle click. A textInput event with the + // contents of the selection clipboard will be dispatched by the browser + // automatically on mouseup. + if (platform === 'linux' && button === 1) model.insertText(clipboard.readText('selection')) return } - // Only handle mousedown events for left mouse button (or the middle mouse - // button on Linux where it pastes the selection clipboard). - if (button !== 0) return - - // Ctrl-click brings up the context menu on macOS - if (platform === 'darwin' && ctrlKey) return - - const screenPosition = this.screenPositionForMouseEvent(event) - if (target && target.matches('.fold-marker')) { const bufferPosition = model.bufferPositionForScreenPosition(screenPosition) model.destroyFoldsContainingBufferPositions([bufferPosition], false) return } - const addOrRemoveSelection = metaKey || (ctrlKey && platform !== 'darwin') + const addOrRemoveSelection = metaKey || ctrlKey switch (detail) { case 1: From a59913f51c122a62c96af502d2433e7121752ed3 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Wed, 15 Nov 2017 12:42:40 +0100 Subject: [PATCH 3/3] Update specs --- spec/text-editor-component-spec.js | 746 +++++++++++++++-------------- 1 file changed, 375 insertions(+), 371 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index fbc8eb0e1..c18e0d84a 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -2831,431 +2831,435 @@ describe('TextEditorComponent', () => { describe('mouse input', () => { describe('on the lines', () => { - it('positions the cursor on single-click', async () => { - const {component, element, editor} = buildComponent() - const {lineHeight} = component.measurements + it('positions the cursor on single-click or when middle/right-clicking', async () => { + for (const button of [0, 1, 2]) { + const {component, element, editor} = buildComponent() + const {lineHeight} = component.measurements - editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false}) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: clientLeftForCharacter(component, 0, 0) - 1, - clientY: clientTopForLine(component, 0) - 1 - }) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) + editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false}) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: clientLeftForCharacter(component, 0, 0) - 1, + clientY: clientTopForLine(component, 0) - 1 + }) + expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - const maxRow = editor.getLastScreenRow() - editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false}) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: clientLeftForCharacter(component, maxRow, editor.lineLengthForScreenRow(maxRow)) + 1, - clientY: clientTopForLine(component, maxRow) + 1 - }) - expect(editor.getCursorScreenPosition()).toEqual([maxRow, editor.lineLengthForScreenRow(maxRow)]) + const maxRow = editor.getLastScreenRow() + editor.setCursorScreenPosition([Infinity, Infinity], {autoscroll: false}) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: clientLeftForCharacter(component, maxRow, editor.lineLengthForScreenRow(maxRow)) + 1, + clientY: clientTopForLine(component, maxRow) + 1 + }) + expect(editor.getCursorScreenPosition()).toEqual([maxRow, editor.lineLengthForScreenRow(maxRow)]) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: clientLeftForCharacter(component, 0, editor.lineLengthForScreenRow(0)) + 1, - clientY: clientTopForLine(component, 0) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([0, editor.lineLengthForScreenRow(0)]) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: clientLeftForCharacter(component, 0, editor.lineLengthForScreenRow(0)) + 1, + clientY: clientTopForLine(component, 0) + lineHeight / 2 + }) + expect(editor.getCursorScreenPosition()).toEqual([0, editor.lineLengthForScreenRow(0)]) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: (clientLeftForCharacter(component, 3, 0) + clientLeftForCharacter(component, 3, 1)) / 2, - clientY: clientTopForLine(component, 1) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([1, 0]) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: (clientLeftForCharacter(component, 3, 0) + clientLeftForCharacter(component, 3, 1)) / 2, + clientY: clientTopForLine(component, 1) + lineHeight / 2 + }) + expect(editor.getCursorScreenPosition()).toEqual([1, 0]) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2, - clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 14]) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2, + clientY: clientTopForLine(component, 3) + lineHeight / 2 + }) + expect(editor.getCursorScreenPosition()).toEqual([3, 14]) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2 + 1, - clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 15]) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 15)) / 2 + 1, + clientY: clientTopForLine(component, 3) + lineHeight / 2 + }) + expect(editor.getCursorScreenPosition()).toEqual([3, 15]) - editor.getBuffer().setTextInRange([[3, 14], [3, 15]], '🐣') - await component.getNextUpdatePromise() + editor.getBuffer().setTextInRange([[3, 14], [3, 15]], '🐣') + await component.getNextUpdatePromise() - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2, - clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 14]) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2, + clientY: clientTopForLine(component, 3) + lineHeight / 2 + }) + expect(editor.getCursorScreenPosition()).toEqual([3, 14]) - component.didMouseDownOnContent({ - detail: 1, - button: 0, - clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2 + 1, - clientY: clientTopForLine(component, 3) + lineHeight / 2 - }) - expect(editor.getCursorScreenPosition()).toEqual([3, 16]) + component.didMouseDownOnContent({ + detail: 1, + button, + clientX: (clientLeftForCharacter(component, 3, 14) + clientLeftForCharacter(component, 3, 16)) / 2 + 1, + clientY: clientTopForLine(component, 3) + lineHeight / 2 + }) + expect(editor.getCursorScreenPosition()).toEqual([3, 16]) - expect(editor.testAutoscrollRequests).toEqual([]) + expect(editor.testAutoscrollRequests).toEqual([]) + } }) - it('selects words on double-click', () => { - const {component, editor} = buildComponent() - const {clientX, clientY} = clientPositionForCharacter(component, 1, 16) - component.didMouseDownOnContent({detail: 1, button: 0, clientX, clientY}) - component.didMouseDownOnContent({detail: 2, button: 0, clientX, clientY}) - expect(editor.getSelectedScreenRange()).toEqual([[1, 13], [1, 21]]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + describe('when the input is for the primary mouse button', () => { + it('selects words on double-click', () => { + const {component, editor} = buildComponent() + const {clientX, clientY} = clientPositionForCharacter(component, 1, 16) + component.didMouseDownOnContent({detail: 1, button: 0, clientX, clientY}) + component.didMouseDownOnContent({detail: 2, button: 0, clientX, clientY}) + expect(editor.getSelectedScreenRange()).toEqual([[1, 13], [1, 21]]) + expect(editor.testAutoscrollRequests).toEqual([]) + }) - it('selects lines on triple-click', () => { - const {component, editor} = buildComponent() - const {clientX, clientY} = clientPositionForCharacter(component, 1, 16) - component.didMouseDownOnContent({detail: 1, button: 0, clientX, clientY}) - component.didMouseDownOnContent({detail: 2, button: 0, clientX, clientY}) - component.didMouseDownOnContent({detail: 3, button: 0, clientX, clientY}) - expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [2, 0]]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + it('selects lines on triple-click', () => { + const {component, editor} = buildComponent() + const {clientX, clientY} = clientPositionForCharacter(component, 1, 16) + component.didMouseDownOnContent({detail: 1, button: 0, clientX, clientY}) + component.didMouseDownOnContent({detail: 2, button: 0, clientX, clientY}) + component.didMouseDownOnContent({detail: 3, button: 0, clientX, clientY}) + expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [2, 0]]) + expect(editor.testAutoscrollRequests).toEqual([]) + }) - it('adds or removes cursors when holding cmd or ctrl when single-clicking', () => { - const {component, editor} = buildComponent({platform: 'darwin'}) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0]]) + it('adds or removes cursors when holding cmd or ctrl when single-clicking', () => { + const {component, editor} = buildComponent({platform: 'darwin'}) + expect(editor.getCursorScreenPositions()).toEqual([[0, 0]]) - // add cursor at 1, 16 - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 1, 16), { + // add cursor at 1, 16 + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 1, 16), { + detail: 1, + button: 0, + metaKey: true + }) + ) + expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + + // remove cursor at 0, 0 + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 0, 0), { + detail: 1, + button: 0, + metaKey: true + }) + ) + expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]) + + // cmd-click cursor at 1, 16 but don't remove it because it's the last one + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 1, 16), { + detail: 1, + button: 0, + metaKey: true + }) + ) + expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]) + + // cmd-clicking within a selection destroys it + editor.addSelectionForScreenRange([[2, 10], [2, 15]], {autoscroll: false}) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 16], [1, 16]], + [[2, 10], [2, 15]] + ]) + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 2, 13), { + detail: 1, + button: 0, + metaKey: true + }) + ) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 16], [1, 16]] + ]) + + // ctrl-click does not add cursors on macOS, but it *does* move the cursor + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 1, 4), { + detail: 1, + button: 0, + ctrlKey: true + }) + ) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[1, 4], [1, 4]] + ]) + + // ctrl-click adds cursors on platforms *other* than macOS + component.props.platform = 'win32' + editor.setCursorScreenPosition([1, 4], {autoscroll: false}) + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 1, 16), { + detail: 1, + button: 0, + ctrlKey: true + }) + ) + expect(editor.getCursorScreenPositions()).toEqual([[1, 4], [1, 16]]) + + expect(editor.testAutoscrollRequests).toEqual([]) + }) + + it('adds word selections when holding cmd or ctrl when double-clicking', () => { + const {component, editor} = buildComponent() + editor.addCursorAtScreenPosition([1, 16], {autoscroll: false}) + expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 1, 16), { + detail: 1, + button: 0, + metaKey: true + }) + ) + component.didMouseDownOnContent( + Object.assign(clientPositionForCharacter(component, 1, 16), { + detail: 2, + button: 0, + metaKey: true + }) + ) + expect(editor.getSelectedScreenRanges()).toEqual([ + [[0, 0], [0, 0]], + [[1, 13], [1, 21]] + ]) + expect(editor.testAutoscrollRequests).toEqual([]) + }) + + it('adds line selections when holding cmd or ctrl when triple-clicking', () => { + const {component, editor} = buildComponent() + editor.addCursorAtScreenPosition([1, 16], {autoscroll: false}) + expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + + const {clientX, clientY} = clientPositionForCharacter(component, 1, 16) + component.didMouseDownOnContent({detail: 1, button: 0, metaKey: true, clientX, clientY}) + component.didMouseDownOnContent({detail: 2, button: 0, metaKey: true, clientX, clientY}) + component.didMouseDownOnContent({detail: 3, button: 0, metaKey: true, clientX, clientY}) + + expect(editor.getSelectedScreenRanges()).toEqual([ + [[0, 0], [0, 0]], + [[1, 0], [2, 0]] + ]) + expect(editor.testAutoscrollRequests).toEqual([]) + }) + + it('expands the last selection on shift-click', () => { + const {component, element, editor} = buildComponent() + + editor.setCursorScreenPosition([2, 18], {autoscroll: false}) + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - metaKey: true - }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + shiftKey: true + }, clientPositionForCharacter(component, 1, 4))) + expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [2, 18]]) - // remove cursor at 0, 0 - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 0, 0), { + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - metaKey: true - }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]) + shiftKey: true + }, clientPositionForCharacter(component, 4, 4))) + expect(editor.getSelectedScreenRange()).toEqual([[2, 18], [4, 4]]) - // cmd-click cursor at 1, 16 but don't remove it because it's the last one - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 1, 16), { + // reorients word-wise selections to keep the word selected regardless of + // where the subsequent shift-click occurs + editor.setCursorScreenPosition([2, 18], {autoscroll: false}) + editor.getLastSelection().selectWord({autoscroll: false}) + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - metaKey: true - }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[1, 16]]) + shiftKey: true + }, clientPositionForCharacter(component, 1, 4))) + expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 20]]) - // cmd-clicking within a selection destroys it - editor.addSelectionForScreenRange([[2, 10], [2, 15]], {autoscroll: false}) - expect(editor.getSelectedScreenRanges()).toEqual([ - [[1, 16], [1, 16]], - [[2, 10], [2, 15]] - ]) - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 2, 13), { + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - metaKey: true - }) - ) - expect(editor.getSelectedScreenRanges()).toEqual([ - [[1, 16], [1, 16]] - ]) + shiftKey: true + }, clientPositionForCharacter(component, 3, 11))) + expect(editor.getSelectedScreenRange()).toEqual([[2, 14], [3, 13]]) - // ctrl-click does not add cursors on macOS - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 1, 4), { + // reorients line-wise selections to keep the line selected regardless of + // where the subsequent shift-click occurs + editor.setCursorScreenPosition([2, 18], {autoscroll: false}) + editor.getLastSelection().selectLine(null, {autoscroll: false}) + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - ctrlKey: true - }) - ) - expect(editor.getSelectedScreenRanges()).toEqual([ - [[1, 16], [1, 16]] - ]) + shiftKey: true + }, clientPositionForCharacter(component, 1, 4))) + expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]) - // ctrl-click adds cursors on platforms *other* than macOS - component.props.platform = 'win32' - editor.setCursorScreenPosition([1, 4], {autoscroll: false}) - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 1, 16), { + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - ctrlKey: true - }) - ) - expect(editor.getCursorScreenPositions()).toEqual([[1, 4], [1, 16]]) + shiftKey: true + }, clientPositionForCharacter(component, 3, 11))) + expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [4, 0]]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + expect(editor.testAutoscrollRequests).toEqual([]) + }) - it('adds word selections when holding cmd or ctrl when double-clicking', () => { - const {component, editor} = buildComponent() - editor.addCursorAtScreenPosition([1, 16], {autoscroll: false}) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) + it('expands the last selection on drag', () => { + const {component, editor} = buildComponent() + spyOn(component, 'handleMouseDragUntilMouseUp') - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 1, 16), { + component.didMouseDownOnContent(Object.assign({ detail: 1, button: 0, - metaKey: true - }) - ) - component.didMouseDownOnContent( - Object.assign(clientPositionForCharacter(component, 1, 16), { + }, clientPositionForCharacter(component, 1, 4))) + + { + const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][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, + button: 0, + metaKey: 1, + }, clientPositionForCharacter(component, 8, 8))) + { + const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[1][0] + 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, + button: 0, + }, clientPositionForCharacter(component, 1, 4))) + component.didMouseDownOnContent(Object.assign({ detail: 2, button: 0, - metaKey: true - }) - ) - expect(editor.getSelectedScreenRanges()).toEqual([ - [[0, 0], [0, 0]], - [[1, 13], [1, 21]] - ]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) + }, clientPositionForCharacter(component, 1, 4))) - it('adds line selections when holding cmd or ctrl when triple-clicking', () => { - const {component, editor} = buildComponent() - editor.addCursorAtScreenPosition([1, 16], {autoscroll: false}) - expect(editor.getCursorScreenPositions()).toEqual([[0, 0], [1, 16]]) - - const {clientX, clientY} = clientPositionForCharacter(component, 1, 16) - component.didMouseDownOnContent({detail: 1, button: 0, metaKey: true, clientX, clientY}) - component.didMouseDownOnContent({detail: 2, button: 0, metaKey: true, clientX, clientY}) - component.didMouseDownOnContent({detail: 3, button: 0, metaKey: true, clientX, clientY}) - - expect(editor.getSelectedScreenRanges()).toEqual([ - [[0, 0], [0, 0]], - [[1, 0], [2, 0]] - ]) - expect(editor.testAutoscrollRequests).toEqual([]) - }) - - it('expands the last selection on shift-click', () => { - const {component, element, editor} = buildComponent() - - editor.setCursorScreenPosition([2, 18], {autoscroll: false}) - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - shiftKey: true - }, clientPositionForCharacter(component, 1, 4))) - expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [2, 18]]) - - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - shiftKey: true - }, clientPositionForCharacter(component, 4, 4))) - expect(editor.getSelectedScreenRange()).toEqual([[2, 18], [4, 4]]) - - // reorients word-wise selections to keep the word selected regardless of - // where the subsequent shift-click occurs - editor.setCursorScreenPosition([2, 18], {autoscroll: false}) - editor.getLastSelection().selectWord({autoscroll: false}) - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - shiftKey: true - }, clientPositionForCharacter(component, 1, 4))) - expect(editor.getSelectedScreenRange()).toEqual([[1, 2], [2, 20]]) - - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - shiftKey: true - }, clientPositionForCharacter(component, 3, 11))) - expect(editor.getSelectedScreenRange()).toEqual([[2, 14], [3, 13]]) - - // reorients line-wise selections to keep the word selected regardless of - // where the subsequent shift-click occurs - editor.setCursorScreenPosition([2, 18], {autoscroll: false}) - editor.getLastSelection().selectLine(null, {autoscroll: false}) - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - shiftKey: true - }, clientPositionForCharacter(component, 1, 4))) - expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]) - - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - shiftKey: true - }, clientPositionForCharacter(component, 3, 11))) - expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [4, 0]]) - - expect(editor.testAutoscrollRequests).toEqual([]) - }) - - it('expands the last selection on drag', () => { - const {component, editor} = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') - - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - }, clientPositionForCharacter(component, 1, 4))) - - { - const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][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, - button: 0, - metaKey: 1, - }, clientPositionForCharacter(component, 8, 8))) - { const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[1][0] - 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]] - ]) - } - }) + 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 word-wise on double-click-drag', () => { - const {component, editor} = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') + it('expands the selection line-wise on triple-click-drag', () => { + const {component, editor} = buildComponent() + spyOn(component, 'handleMouseDragUntilMouseUp') - component.didMouseDownOnContent(Object.assign({ - detail: 1, - button: 0, - }, clientPositionForCharacter(component, 1, 4))) - component.didMouseDownOnContent(Object.assign({ - detail: 2, - button: 0, - }, clientPositionForCharacter(component, 1, 4))) + const tripleClickPosition = clientPositionForCharacter(component, 2, 8) + component.didMouseDownOnContent(Object.assign({detail: 1, button: 0}, tripleClickPosition)) + component.didMouseDownOnContent(Object.assign({detail: 2, button: 0}, tripleClickPosition)) + component.didMouseDownOnContent(Object.assign({detail: 3, button: 0}, tripleClickPosition)) - const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[1][0] - 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]]) - }) + const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[2][0] + 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]]) + }) - it('expands the selection line-wise on triple-click-drag', () => { - const {component, editor} = buildComponent() - spyOn(component, 'handleMouseDragUntilMouseUp') + it('destroys folds when clicking on their fold markers', async () => { + const {component, element, editor} = buildComponent() + editor.foldBufferRow(1) + await component.getNextUpdatePromise() - const tripleClickPosition = clientPositionForCharacter(component, 2, 8) - component.didMouseDownOnContent(Object.assign({detail: 1, button: 0}, tripleClickPosition)) - component.didMouseDownOnContent(Object.assign({detail: 2, button: 0}, tripleClickPosition)) - component.didMouseDownOnContent(Object.assign({detail: 3, button: 0}, tripleClickPosition)) + const target = element.querySelector('.fold-marker') + const {clientX, clientY} = clientPositionForCharacter(component, 1, editor.lineLengthForScreenRow(1)) + component.didMouseDownOnContent({detail: 1, button: 0, target, clientX, clientY}) + expect(editor.isFoldedAtBufferRow(1)).toBe(false) + expect(editor.getCursorScreenPosition()).toEqual([0, 0]) + }) - const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[2][0] - 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]]) - }) + it('autoscrolls the content when dragging near the edge of the scroll container', async () => { + const {component, element, editor} = buildComponent({width: 200, height: 200}) + spyOn(component, 'handleMouseDragUntilMouseUp') - it('destroys folds when clicking on their fold markers', async () => { - const {component, element, editor} = buildComponent() - editor.foldBufferRow(1) - await component.getNextUpdatePromise() + let previousScrollTop = 0 + let previousScrollLeft = 0 + function assertScrolledDownAndRight () { + expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop) + previousScrollTop = component.getScrollTop() + expect(component.getScrollLeft()).toBeGreaterThan(previousScrollLeft) + previousScrollLeft = component.getScrollLeft() + } - const target = element.querySelector('.fold-marker') - const {clientX, clientY} = clientPositionForCharacter(component, 1, editor.lineLengthForScreenRow(1)) - component.didMouseDownOnContent({detail: 1, button: 0, target, clientX, clientY}) - expect(editor.isFoldedAtBufferRow(1)).toBe(false) - expect(editor.getCursorScreenPosition()).toEqual([0, 0]) - }) + function assertScrolledUpAndLeft () { + expect(component.getScrollTop()).toBeLessThan(previousScrollTop) + previousScrollTop = component.getScrollTop() + expect(component.getScrollLeft()).toBeLessThan(previousScrollLeft) + previousScrollLeft = component.getScrollLeft() + } - it('autoscrolls the content when dragging near the edge of the scroll container', async () => { - const {component, element, editor} = buildComponent({width: 200, height: 200}) - spyOn(component, 'handleMouseDragUntilMouseUp') + component.didMouseDownOnContent({detail: 1, button: 0, clientX: 100, clientY: 100}) + const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][0] - let previousScrollTop = 0 - let previousScrollLeft = 0 - function assertScrolledDownAndRight () { - expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop) - previousScrollTop = component.getScrollTop() - expect(component.getScrollLeft()).toBeGreaterThan(previousScrollLeft) - previousScrollLeft = component.getScrollLeft() - } + didDrag({clientX: 199, clientY: 199}) + assertScrolledDownAndRight() + didDrag({clientX: 199, clientY: 199}) + assertScrolledDownAndRight() + didDrag({clientX: 199, clientY: 199}) + assertScrolledDownAndRight() + didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) + assertScrolledUpAndLeft() + didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) + assertScrolledUpAndLeft() + didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) + assertScrolledUpAndLeft() - function assertScrolledUpAndLeft () { - expect(component.getScrollTop()).toBeLessThan(previousScrollTop) - previousScrollTop = component.getScrollTop() - expect(component.getScrollLeft()).toBeLessThan(previousScrollLeft) - previousScrollLeft = component.getScrollLeft() - } + // Don't artificially update scroll position beyond possible values + expect(component.getScrollTop()).toBe(0) + expect(component.getScrollLeft()).toBe(0) + didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) + expect(component.getScrollTop()).toBe(0) + expect(component.getScrollLeft()).toBe(0) - component.didMouseDownOnContent({detail: 1, button: 0, clientX: 100, clientY: 100}) - const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][0] + const maxScrollTop = component.getMaxScrollTop() + const maxScrollLeft = component.getMaxScrollLeft() + setScrollTop(component, maxScrollTop) + await setScrollLeft(component, maxScrollLeft) - didDrag({clientX: 199, clientY: 199}) - assertScrolledDownAndRight() - didDrag({clientX: 199, clientY: 199}) - assertScrolledDownAndRight() - didDrag({clientX: 199, clientY: 199}) - assertScrolledDownAndRight() - didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) - assertScrolledUpAndLeft() - didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) - assertScrolledUpAndLeft() - didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) - assertScrolledUpAndLeft() - - // Don't artificially update scroll position beyond possible values - expect(component.getScrollTop()).toBe(0) - expect(component.getScrollLeft()).toBe(0) - didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1}) - expect(component.getScrollTop()).toBe(0) - expect(component.getScrollLeft()).toBe(0) - - const maxScrollTop = component.getMaxScrollTop() - const maxScrollLeft = component.getMaxScrollLeft() - setScrollTop(component, maxScrollTop) - await setScrollLeft(component, maxScrollLeft) - - didDrag({clientX: 199, clientY: 199}) - didDrag({clientX: 199, clientY: 199}) - didDrag({clientX: 199, clientY: 199}) - expect(component.getScrollTop()).toBe(maxScrollTop) - expect(component.getScrollLeft()).toBe(maxScrollLeft) + didDrag({clientX: 199, clientY: 199}) + didDrag({clientX: 199, clientY: 199}) + didDrag({clientX: 199, clientY: 199}) + expect(component.getScrollTop()).toBe(maxScrollTop) + expect(component.getScrollLeft()).toBe(maxScrollLeft) + }) }) it('pastes the previously selected text when clicking the middle mouse button on Linux', async () => {