crecord: provide 'X' as a range-select mechanism
authorKyle Lippincott <spectral@google.com>
Mon, 08 Jul 2019 13:10:34 -0700
changeset 42573 9ac1a5a4a64f
parent 42572 cd4f1b7c3192
child 42574 d28d91f9f35a
crecord: provide 'X' as a range-select mechanism Differential Revision: https://phab.mercurial-scm.org/D6621
mercurial/crecord.py
tests/test-commit-interactive-curses.t
--- a/mercurial/crecord.py	Mon Jul 08 13:06:46 2019 -0700
+++ b/mercurial/crecord.py	Mon Jul 08 13:10:34 2019 -0700
@@ -608,6 +608,7 @@
 
         # the currently selected header, hunk, or hunk-line
         self.currentselecteditem = self.headerlist[0]
+        self.lastapplieditem = None
 
         # updated when printing out patch-display -- the 'lines' here are the
         # line positions *in the pad*, not on the screen.
@@ -839,6 +840,8 @@
         """
         if item is None:
             item = self.currentselecteditem
+            # Only set this when NOT using 'toggleall'
+            self.lastapplieditem = item
 
         item.applied = not item.applied
 
@@ -932,6 +935,45 @@
                     self.toggleapply(item)
         self.waslasttoggleallapplied = not self.waslasttoggleallapplied
 
+    def toggleallbetween(self):
+        "toggle applied on or off for all items in range [lastapplied,current]."
+        if (not self.lastapplieditem or
+            self.currentselecteditem == self.lastapplieditem):
+            # Treat this like a normal 'x'/' '
+            self.toggleapply()
+            return
+
+        startitem = self.lastapplieditem
+        enditem = self.currentselecteditem
+        # Verify that enditem is "after" startitem, otherwise swap them.
+        for direction in ['forward', 'reverse']:
+            nextitem = startitem.nextitem()
+            while nextitem and nextitem != enditem:
+                nextitem = nextitem.nextitem()
+            if nextitem:
+                break
+            # Looks like we went the wrong direction :)
+            startitem, enditem = enditem, startitem
+
+        if not nextitem:
+            # We didn't find a path going either forward or backward? Don't know
+            # how this can happen, let's not crash though.
+            return
+
+        nextitem = startitem
+        # Switch all items to be the opposite state of the currently selected
+        # item. Specifically:
+        #  [ ] startitem
+        #  [x] middleitem
+        #  [ ] enditem  <-- currently selected
+        # This will turn all three on, since the currently selected item is off.
+        # This does *not* invert each item (i.e. middleitem stays marked/on)
+        desiredstate = not self.currentselecteditem.applied
+        while nextitem != enditem.nextitem():
+            if nextitem.applied != desiredstate:
+                self.toggleapply(item=nextitem)
+            nextitem = nextitem.nextitem()
+
     def togglefolded(self, item=None, foldparent=False):
         "toggle folded flag of specified item (defaults to currently selected)"
         if item is None:
@@ -1464,6 +1506,7 @@
               x [space] : (un-)select item ([~]/[x] = partly/fully applied)
                 [enter] : (un-)select item and go to next item of same type
                       A : (un-)select all items
+                      X : (un-)select all items between current and most-recent
     up/down-arrow [k/j] : go to previous/next unfolded item
         pgup/pgdn [K/J] : go to previous/next item of same type
  right/left-arrow [l/h] : go to child item / parent item
@@ -1755,6 +1798,8 @@
         elif keypressed in ['\n', 'KEY_ENTER']:
             self.toggleapply()
             self.nextsametype(test=test)
+        elif keypressed in ['X']:
+            self.toggleallbetween()
         elif keypressed in ['A']:
             self.toggleall()
         elif keypressed in ['e']:
--- a/tests/test-commit-interactive-curses.t	Mon Jul 08 13:06:46 2019 -0700
+++ b/tests/test-commit-interactive-curses.t	Mon Jul 08 13:10:34 2019 -0700
@@ -327,6 +327,50 @@
   hello world
   lower
 
+Test range select: unselect 3, 5, and 6, reselect 5, then go back up to 2 and
+press 'X', unselecting (because 2 is currently selected) 5 (because it's the
+start of the range) and 4, leaving 3 unselected.
+
+  $ hg init $TESTTMP/range_select
+  $ cd $TESTTMP/range_select
+  >>> open('range_select', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
+  $ hg add range_select
+  $ cat <<EOF >testModeCommands
+  > KEY_RIGHT
+  > KEY_RIGHT
+  > KEY_DOWN
+  > KEY_DOWN
+  > KEY_ENTER
+  > KEY_DOWN
+  > KEY_ENTER
+  > x
+  > KEY_UP
+  > x
+  > KEY_UP
+  > KEY_UP
+  > KEY_UP
+  > X
+  > c
+  > EOF
+  $ hg commit -i -m "range_select" -d "0 0"
+  $ hg cat -r tip range_select
+  1
+  7
+  8
+  9
+  10
+  $ cat range_select
+  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+  10
+
 Check ui.interface logic for the chunkselector
 
 The default interface is text