Permalink
Browse files

Fix cursorManager selection bugs. Incubates #7131 (issues #5746, #6909)

  • Loading branch information...
jcsteh committed May 4, 2017
2 parents d601535 + 0eda1cb commit c80e529f5fb6bbbcfebfaf209dca9827ec663789
Showing with 143 additions and 15 deletions.
  1. +16 −8 source/cursorManager.py
  2. +127 −7 tests/unit/test_cursorManager.py
@@ -1,6 +1,6 @@
#cursorManager.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2016 NV Access Limited, Joseph Lee, Derek Riemer
#Copyright (C) 2006-2017 NV Access Limited, Joseph Lee, Derek Riemer
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
@@ -263,28 +263,34 @@ def _selectionMovementScriptHelper(self,unit=None,direction=None,toPosition=None
newInfo.move(unit, direction, endPoint="start" if direction < 0 else "end")
# Collapse this so we don't have to worry about which endpoint we used here.
newInfo.collapse(end=direction > 0)
if self._lastSelectionMovedStart:
# If we're selecting all, we're moving both endpoints.
# Otherwise, newInfo is the collapsed new active endpoint
# and we need to set the anchor endpoint.
movingSingleEndpoint = toPosition != textInfos.POSITION_ALL
if movingSingleEndpoint and self._lastSelectionMovedStart:
if newInfo.compareEndPoints(oldInfo, "startToEnd") > 0:
# We were selecting backwards, but now we're selecting forwards.
# For example:
# 1. Caret at 1
# 2. Shift+leftArrow: selection (0, 1)
# 3. Shift+control+rightArrow: next word at 3, so selection (1, 3)
newInfo.setEndPoint(oldInfo, "startToEnd")
self._lastSelectionMovedStart = False
else:
# We're selecting backwards.
# For example:
# 1. Caret at 1; selection (1, 1)
# 2. Shift+leftArrow: selection (0, 1)
newInfo.setEndPoint(oldInfo, "endToEnd")
else:
elif movingSingleEndpoint:
if newInfo.compareEndPoints(oldInfo, "startToStart") < 0:
# We were selecting forwards, but now we're selecting backwards.
# For example:
# 1. Caret at 1
# 2. Shift+rightArrow: selection (1, 2)
# 3. Shift+control+leftArrow: previous word at 0, so selection (0, 1)
newInfo.setEndPoint(oldInfo, "endToStart")
self._lastSelectionMovedStart = True
else:
# We're selecting forwards.
# For example:
@@ -325,11 +331,13 @@ def script_selectParagraph_back(self, gesture):
self._selectionMovementScriptHelper(unit=textInfos.UNIT_PARAGRAPH, direction=-1)
def script_selectToBeginningOfLine(self,gesture):
curInfo=self.makeTextInfo(textInfos.POSITION_SELECTION)
curInfo.collapse()
tempInfo=curInfo.copy()
tempInfo.expand(textInfos.UNIT_LINE)
if curInfo.compareEndPoints(tempInfo,"startToStart")>0:
# Make sure the active endpoint of the selection is after the start of the line.
sel=self.makeTextInfo(textInfos.POSITION_SELECTION)
line=sel.copy()
line.collapse()
line.expand(textInfos.UNIT_LINE)
compOp="startToStart" if self._lastSelectionMovedStart else "endToStart"
if sel.compareEndPoints(line,compOp)>0:
self._selectionMovementScriptHelper(unit=textInfos.UNIT_LINE,direction=-1)
def script_selectToEndOfLine(self,gesture):
@@ -29,43 +29,157 @@ def test_prevChar(self):
class TestSelection(unittest.TestCase):
def test_selectNextChar(self):
def test_selForward(self):
cm = CursorManager(text="abc") # Caret at "a"
cm.script_selectCharacter_forward(None)
self.assertEqual(cm.selectionOffsets, (0, 1)) # "a" selected
def test_selectPrevChar(self):
def test_selBackward(self):
"""Same as test_selForward, but with reversed direction.
"""
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectCharacter_back(None)
self.assertEqual(cm.selectionOffsets, (0, 1)) # "a" selected
def test_unselectPrevChar(self):
"""Depends on behavior tested by test_selectNextChar.
def test_selForwardThenUnsel(self):
"""Depends on behavior tested by test_selForward.
"""
cm = CursorManager(text="abc") # Caret at "a"
cm.script_selectCharacter_forward(None) # "a" selected
cm.script_selectCharacter_back(None) # "a" unselected
self.assertEqual(cm.selectionOffsets, (0, 0)) # Caret at "a", no selection
def test_selBackwardThenUnsel(self):
"""Depends on behavior tested by test_selBackward.
Same as test_selForwardThenUnsel, but with reversed directions.
"""
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectCharacter_back(None) # "a" selected
cm.script_selectCharacter_forward(None) # "a" unselected
self.assertEqual(cm.selectionOffsets, (1, 1)) # Caret at "b", no selection
def test_selForwardTwice(self):
"""Depends on behavior tested in test_selForward.
"""
cm = CursorManager(text="abc") # Caret at "a"
cm.script_selectCharacter_forward(None) # "a" selected
cm.script_selectCharacter_forward(None) # "b" selected
self.assertEqual(cm.selectionOffsets, (0, 2)) # "ab" selected
def test_selBackwardTwice(self):
"""Depends on behavior tested in test_selBackward.
Same as test_selForwardTwice, but with reversed directions.
"""
cm = CursorManager(text="abc", selection=(2, 2)) # Caret at "c"
cm.script_selectCharacter_back(None) # "b" selected
cm.script_selectCharacter_back(None) # "a" selected
self.assertEqual(cm.selectionOffsets, (0, 2)) # "ab" selected
def test_selForwardThenUnselThenSelBackward(self):
"""Test selecting forward, then unselecting and selecting backward.
Depends on behavior tested by test_unselectPrevChar.
Depends on behavior tested by test_selForwardThenUnsel.
"""
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectCharacter_forward(None) # "b" selected
cm.script_selectCharacter_back(None) # "b" unselected, caret at "b"
cm.script_selectCharacter_back(None)
self.assertEqual(cm.selectionOffsets, (0, 1)) # "a" selected
def test_selectForwardThenSelBackward(self):
def test_selBackwardThenUnselThenSelForward(self):
"""Test selecting backward, then unselecting and selecting forward.
Depends on behavior tested by test_selBackwardThenUnsel.
Same as test_selForwardThenUnselThenSelBackward, but with reversed directions.
"""
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectCharacter_back(None) # "a" selected
cm.script_selectCharacter_forward(None) # "a" unselected, caret at "b"
cm.script_selectCharacter_forward(None)
self.assertEqual(cm.selectionOffsets, (1, 2)) # "b" selected
def test_selForwardThenSelBackward(self):
"""Test selecting forward, then selecting backward without unselecting.
Depends on behavior tested by test_selectNextChar.
Depends on behavior tested by test_selForward.
"""
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectCharacter_forward(None) # "b" selected
cm.script_selectWord_back(None) # "b" unselected, "a" selected
self.assertEqual(cm.selectionOffsets, (0, 1)) # "a" selected
def test_selBackwardThenSelForward(self):
"""Test selecting backward, then selecting forward without unselecting.
Same as test_selForwardThenSelBackward, but with reversed directions.
"""
cm = CursorManager(text="abc", selection=(2, 2)) # Caret at "c"
cm.script_selectCharacter_back(None) # "b" selected
cm.script_selectWord_forward(None) # "b" unselected, "c" selected
self.assertEqual(cm.selectionOffsets, (2, 3)) # "c" selected
def test_selForwardThenSelBackwardThenUnsel(self):
"""Test selecting forward, then selecting backward without unselecting, then unselecting forward.
Depends on behavior tested by test_selForwardThenSelBackward.
"""
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectCharacter_forward(None) # "b" selected
cm.script_selectWord_back(None) # "b" unselected, "a" selected
cm.script_selectCharacter_forward(None) # "a" unselected
self.assertEqual(cm.selectionOffsets, (1, 1)) # Caret at "b", no selection
def test_selBackwardThenSelForwardThenUnsel(self):
"""Test selecting backward, then selecting forward without unselecting, then unselecting backward.
Same as test_selForwardThenSelBackwardThenUnsel, but with reversed directions.
Depends on behavior tested by test_selBackwardThenSelForward.
"""
cm = CursorManager(text="abc", selection=(2, 2)) # Caret at "c"
cm.script_selectCharacter_back(None) # "b" selected
cm.script_selectWord_forward(None) # "b" unselected, "c" selected
cm.script_selectCharacter_back(None) # "c" unselected
self.assertEqual(cm.selectionOffsets, (2, 2)) # Caret at "c", no selection
def test_selToBottom(self):
cm = CursorManager(text="abc", selection=(1, 1)) # Caret at "b"
cm.script_selectToBottomOfDocument(None)
self.assertEqual(cm.selectionOffsets, (1, 3)) # "bc" selected
def test_selToTop(self):
cm = CursorManager(text="abc", selection=(2, 2)) # Caret at "c"
cm.script_selectToTopOfDocument(None)
self.assertEqual(cm.selectionOffsets, (0, 2)) # "ab" selected
def test_selToEndOfLine(self):
cm = CursorManager(text="ab\ncd", selection=(1, 1)) # Caret at "b"
cm.script_selectToEndOfLine(None)
self.assertEqual(cm.selectionOffsets, (1, 3)) # "b\n" selected
def test_selToBeginningOfLine(self):
cm = CursorManager(text="ab\ncd", selection=(4, 4)) # Caret at "d"
cm.script_selectToBeginningOfLine(None)
self.assertEqual(cm.selectionOffsets, (3, 4)) # "c" selected
def test_selToBeginningOfLineAtBeginning(self):
"""Test selecting to the beginning of the line when the caret is already at the beginning of the line.
In this case, nothing should happen.
"""
cm = CursorManager(text="ab\ncd", selection=(3, 3)) # Caret at "c"
cm.script_selectToBeginningOfLine(None)
self.assertEqual(cm.selectionOffsets, (3, 3)) # No selection
def test_selForwardThenSelToBeginningOfLine(self):
"""Depends on behavior tested by test_selForward.
"""
cm = CursorManager(text="ab\ncd", selection=(3, 3)) # Caret at "c"
cm.script_selectCharacter_forward(None) # "c" selected
cm.script_selectToBeginningOfLine(None) # "c" unselected
self.assertEqual(cm.selectionOffsets, (3, 3)) # Caret at "c", no selection
def test_selToEndThenBeginningOfLine(self):
"""Test for #5746.
Depends on behavior tested in test_selToEndOfLine and test_selToBeginningOfLine.
"""
cm = CursorManager(text="ab") # Caret at "a"
cm.script_selectToEndOfLine(None)
cm.script_selectToBeginningOfLine(None)
self.assertEqual(cm.selectionOffsets, (0, 0)) # Caret at "a", no selection
class TestSelectAll(unittest.TestCase):
"""Tests the select all command starting from different caret positions.
"""
@@ -79,3 +193,9 @@ def _selectAllTest(self, caret):
def test_selectAllFromStart(self):
self._selectAllTest(0) # Caret at "a"
def test_selectAllFromMiddle(self):
self._selectAllTest(1) # Caret at "b"
def test_selectAllFromEnd(self):
self._selectAllTest(2) # Caret at "c"

0 comments on commit c80e529

Please sign in to comment.