diff --git a/mercurial/crecord.py b/mercurial/crecord.py --- a/mercurial/crecord.py +++ b/mercurial/crecord.py @@ -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']: diff --git a/tests/test-commit-interactive-curses.t b/tests/test-commit-interactive-curses.t --- a/tests/test-commit-interactive-curses.t +++ b/tests/test-commit-interactive-curses.t @@ -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 <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