mirror of
https://github.com/atom/atom.git
synced 2026-01-25 06:48:28 -05:00
Handle clicking, shift-clicking, cmd-clicking and dragging in gutter
This commit is contained in:
committed by
Antonio Scandurra
parent
17d579f949
commit
ffc2025df5
@@ -850,7 +850,7 @@ describe('TextEditorComponent', () => {
|
||||
}, clientPositionForCharacter(component, 1, 4)))
|
||||
|
||||
{
|
||||
const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[0]
|
||||
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))
|
||||
@@ -867,7 +867,7 @@ describe('TextEditorComponent', () => {
|
||||
metaKey: 1,
|
||||
}, clientPositionForCharacter(component, 8, 8)))
|
||||
{
|
||||
const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[1]
|
||||
const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[1][0]
|
||||
didDrag(clientPositionForCharacter(component, 2, 8))
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[1, 4], [4, 8]],
|
||||
@@ -903,7 +903,7 @@ describe('TextEditorComponent', () => {
|
||||
button: 0,
|
||||
}, clientPositionForCharacter(component, 1, 4)))
|
||||
|
||||
const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[1]
|
||||
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))
|
||||
@@ -919,13 +919,172 @@ describe('TextEditorComponent', () => {
|
||||
component.didMouseDownOnContent(Object.assign({detail: 2, button: 0}, tripleClickPosition))
|
||||
component.didMouseDownOnContent(Object.assign({detail: 3, button: 0}, tripleClickPosition))
|
||||
|
||||
const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[2]
|
||||
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]])
|
||||
})
|
||||
|
||||
describe('on the line number gutter', () => {
|
||||
it('selects all buffer rows intersecting the clicked screen row when a line number is clicked', async () => {
|
||||
const {component, editor} = buildComponent()
|
||||
spyOn(component, 'handleMouseDragUntilMouseUp')
|
||||
editor.setSoftWrapped(true)
|
||||
await setEditorWidthInCharacters(component, 50)
|
||||
editor.foldBufferRange([[4, Infinity], [7, Infinity]])
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
// Selects entire buffer line when clicked screen line is soft-wrapped
|
||||
component.didMouseDownOnLineNumberGutter({
|
||||
button: 0,
|
||||
clientY: clientTopForLine(component, 3)
|
||||
})
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[3, 0], [5, 0]])
|
||||
expect(editor.getSelectedBufferRange()).toEqual([[3, 0], [4, 0]])
|
||||
|
||||
// Selects entire screen line, even if folds cause that selection to
|
||||
// span multiple buffer lines
|
||||
component.didMouseDownOnLineNumberGutter({
|
||||
button: 0,
|
||||
clientY: clientTopForLine(component, 5)
|
||||
})
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[5, 0], [6, 0]])
|
||||
expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [8, 0]])
|
||||
})
|
||||
|
||||
it('adds new selections when a line number is meta-clicked', async () => {
|
||||
const {component, editor} = buildComponent()
|
||||
editor.setSoftWrapped(true)
|
||||
await setEditorWidthInCharacters(component, 50)
|
||||
editor.foldBufferRange([[4, Infinity], [7, Infinity]])
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
// Selects entire buffer line when clicked screen line is soft-wrapped
|
||||
component.didMouseDownOnLineNumberGutter({
|
||||
button: 0,
|
||||
metaKey: true,
|
||||
clientY: clientTopForLine(component, 3)
|
||||
})
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[0, 0], [0, 0]],
|
||||
[[3, 0], [5, 0]]
|
||||
])
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([
|
||||
[[0, 0], [0, 0]],
|
||||
[[3, 0], [4, 0]]
|
||||
])
|
||||
|
||||
// Selects entire screen line, even if folds cause that selection to
|
||||
// span multiple buffer lines
|
||||
component.didMouseDownOnLineNumberGutter({
|
||||
button: 0,
|
||||
metaKey: true,
|
||||
clientY: clientTopForLine(component, 5)
|
||||
})
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[0, 0], [0, 0]],
|
||||
[[3, 0], [5, 0]],
|
||||
[[5, 0], [6, 0]]
|
||||
])
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([
|
||||
[[0, 0], [0, 0]],
|
||||
[[3, 0], [4, 0]],
|
||||
[[4, 0], [8, 0]]
|
||||
])
|
||||
})
|
||||
|
||||
it('expands the last selection when a line number is shift-clicked', async () => {
|
||||
const {component, editor} = buildComponent()
|
||||
spyOn(component, 'handleMouseDragUntilMouseUp')
|
||||
editor.setSoftWrapped(true)
|
||||
await setEditorWidthInCharacters(component, 50)
|
||||
editor.foldBufferRange([[4, Infinity], [7, Infinity]])
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
editor.setSelectedScreenRange([[3, 4], [3, 8]])
|
||||
editor.addCursorAtScreenPosition([2, 10])
|
||||
component.didMouseDownOnLineNumberGutter({
|
||||
button: 0,
|
||||
shiftKey: true,
|
||||
clientY: clientTopForLine(component, 5)
|
||||
})
|
||||
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([
|
||||
[[3, 4], [3, 8]],
|
||||
[[2, 10], [8, 0]]
|
||||
])
|
||||
|
||||
// Original selection is preserved when shift-click-dragging
|
||||
const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][0]
|
||||
didDrag({
|
||||
clientY: clientTopForLine(component, 1)
|
||||
})
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([
|
||||
[[3, 4], [3, 8]],
|
||||
[[1, 0], [2, 10]]
|
||||
])
|
||||
|
||||
didDrag({
|
||||
clientY: clientTopForLine(component, 5)
|
||||
})
|
||||
|
||||
didStopDragging()
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([
|
||||
[[2, 10], [8, 0]]
|
||||
])
|
||||
})
|
||||
|
||||
it('expands the selection when dragging', async () => {
|
||||
const {component, editor} = buildComponent()
|
||||
spyOn(component, 'handleMouseDragUntilMouseUp')
|
||||
editor.setSoftWrapped(true)
|
||||
await setEditorWidthInCharacters(component, 50)
|
||||
editor.foldBufferRange([[4, Infinity], [7, Infinity]])
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
editor.setSelectedScreenRange([[3, 4], [3, 6]])
|
||||
|
||||
component.didMouseDownOnLineNumberGutter({
|
||||
button: 0,
|
||||
metaKey: true,
|
||||
clientY: clientTopForLine(component, 2)
|
||||
})
|
||||
|
||||
const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][0]
|
||||
|
||||
didDrag({
|
||||
clientY: clientTopForLine(component, 1)
|
||||
})
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[3, 4], [3, 6]],
|
||||
[[1, 0], [3, 0]]
|
||||
])
|
||||
|
||||
didDrag({
|
||||
clientY: clientTopForLine(component, 5)
|
||||
})
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[3, 4], [3, 6]],
|
||||
[[2, 0], [6, 0]]
|
||||
])
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
|
||||
|
||||
didDrag({
|
||||
clientY: clientTopForLine(component, 3)
|
||||
})
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[3, 4], [3, 6]],
|
||||
[[2, 0], [4, 4]]
|
||||
])
|
||||
|
||||
didStopDragging()
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([
|
||||
[[2, 0], [4, 4]]
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('autoscrolls the content when dragging near the edge of the screen', async () => {
|
||||
const {component, editor} = buildComponent({width: 200, height: 200})
|
||||
const {scroller} = component.refs
|
||||
@@ -948,7 +1107,7 @@ describe('TextEditorComponent', () => {
|
||||
}
|
||||
|
||||
component.didMouseDownOnContent({detail: 1, button: 0, clientX: 100, clientY: 100})
|
||||
const [didDrag, didStopDragging] = component.handleMouseDragUntilMouseUp.argsForCall[0]
|
||||
const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][0]
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
assertScrolledDownAndRight()
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const etch = require('etch')
|
||||
const {CompositeDisposable} = require('event-kit')
|
||||
const {Point} = require('text-buffer')
|
||||
const {Point, Range} = require('text-buffer')
|
||||
const resizeDetector = require('element-resize-detector')({strategy: 'scroll'})
|
||||
const TextEditor = require('./text-editor')
|
||||
const {isPairedCharacter} = require('./text-utils')
|
||||
@@ -14,6 +14,7 @@ const DOUBLE_WIDTH_CHARACTER = '我'
|
||||
const HALF_WIDTH_CHARACTER = 'ハ'
|
||||
const KOREAN_CHARACTER = '세'
|
||||
const NBSP_CHARACTER = '\u00a0'
|
||||
const ZERO_WIDTH_NBSP_CHARACTER = '\ufeff'
|
||||
const MOUSE_DRAG_AUTOSCROLL_MARGIN = 40
|
||||
|
||||
function scaleMouseDragAutoscrollDelta (delta) {
|
||||
@@ -796,29 +797,83 @@ class TextEditorComponent {
|
||||
break
|
||||
}
|
||||
|
||||
this.handleMouseDragUntilMouseUp(
|
||||
(event) => {
|
||||
this.handleMouseDragUntilMouseUp({
|
||||
didDrag: (event) => {
|
||||
this.autoscrollOnMouseDrag(event)
|
||||
const screenPosition = this.screenPositionForMouseEvent(event)
|
||||
model.selectToScreenPosition(screenPosition, {suppressSelectionMerge: true, autoscroll: false})
|
||||
this.updateSync()
|
||||
},
|
||||
() => {
|
||||
didStopDragging: () => {
|
||||
model.finalizeSelections()
|
||||
model.mergeIntersectingSelections()
|
||||
this.updateSync()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseDragUntilMouseUp (didDragCallback, didStopDragging) {
|
||||
didMouseDownOnLineNumberGutter (event) {
|
||||
if (global.debug) debugger
|
||||
|
||||
const {model} = this.props
|
||||
const {button, ctrlKey, shiftKey, metaKey} = event
|
||||
|
||||
// Only handle mousedown events for left mouse button
|
||||
if (button !== 0) return
|
||||
|
||||
const addOrRemoveSelection = metaKey || (ctrlKey && this.getPlatform() !== 'darwin')
|
||||
const clickedScreenRow = this.screenPositionForMouseEvent(event).row
|
||||
const startBufferRow = model.bufferPositionForScreenPosition([clickedScreenRow, 0]).row
|
||||
const endBufferRow = model.bufferPositionForScreenPosition([clickedScreenRow, Infinity]).row
|
||||
const clickedLineBufferRange = Range(Point(startBufferRow, 0), Point(endBufferRow + 1, 0))
|
||||
|
||||
let initialBufferRange
|
||||
if (shiftKey) {
|
||||
const lastSelection = model.getLastSelection()
|
||||
initialBufferRange = lastSelection.getBufferRange()
|
||||
lastSelection.setBufferRange(initialBufferRange.union(clickedLineBufferRange), {
|
||||
reversed: clickedScreenRow < lastSelection.getScreenRange().start.row,
|
||||
autoscroll: false,
|
||||
preserveFolds: true,
|
||||
suppressSelectionMerge: true
|
||||
})
|
||||
} else {
|
||||
initialBufferRange = clickedLineBufferRange
|
||||
if (addOrRemoveSelection) {
|
||||
model.addSelectionForBufferRange(clickedLineBufferRange, {autoscroll: false, preserveFolds: true})
|
||||
} else {
|
||||
model.setSelectedBufferRange(clickedLineBufferRange, {autoscroll: false, preserveFolds: true})
|
||||
}
|
||||
}
|
||||
|
||||
const initialScreenRange = model.screenRangeForBufferRange(initialBufferRange)
|
||||
this.handleMouseDragUntilMouseUp({
|
||||
didDrag: (event) => {
|
||||
const dragRow = this.screenPositionForMouseEvent(event).row
|
||||
const draggedLineScreenRange = Range(Point(dragRow, 0), Point(dragRow + 1, 0))
|
||||
model.getLastSelection().setScreenRange(draggedLineScreenRange.union(initialScreenRange), {
|
||||
reversed: dragRow < initialScreenRange.start.row,
|
||||
autoscroll: false,
|
||||
preserveFolds: true
|
||||
})
|
||||
this.updateSync()
|
||||
},
|
||||
didStopDragging: () => {
|
||||
model.mergeIntersectingSelections()
|
||||
this.updateSync()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
handleMouseDragUntilMouseUp ({didDrag, didStopDragging}) {
|
||||
let dragging = false
|
||||
let lastMousemoveEvent
|
||||
|
||||
const animationFrameLoop = () => {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (dragging && this.visible) {
|
||||
didDragCallback(lastMousemoveEvent)
|
||||
didDrag(lastMousemoveEvent)
|
||||
animationFrameLoop()
|
||||
}
|
||||
})
|
||||
@@ -1503,6 +1558,9 @@ class LineNumberGutterComponent {
|
||||
|
||||
children[tileIndex] = $.div({
|
||||
key: tileIndex,
|
||||
on: {
|
||||
mousedown: this.didMouseDown
|
||||
},
|
||||
style: {
|
||||
contain: 'strict',
|
||||
overflow: 'hidden',
|
||||
@@ -1547,6 +1605,10 @@ class LineNumberGutterComponent {
|
||||
if (!arraysEqual(oldProps.lineNumberDecorations, newProps.lineNumberDecorations)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
didMouseDown (event) {
|
||||
this.props.parentComponent.didMouseDownOnLineNumberGutter(event)
|
||||
}
|
||||
}
|
||||
|
||||
class LinesTileComponent {
|
||||
@@ -1718,7 +1780,7 @@ class LineComponent {
|
||||
// Insert a zero-width non-breaking whitespace, so that LinesYardstick can
|
||||
// take the fold-marker::after pseudo-element into account during
|
||||
// measurements when such marker is the last character on the line.
|
||||
const textNode = document.createTextNode(ZERO_WIDTH_NBSP)
|
||||
const textNode = document.createTextNode(ZERO_WIDTH_NBSP_CHARACTER)
|
||||
this.element.appendChild(textNode)
|
||||
textNodes.push(textNode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user