D5146: histedit: import chistedit curses UI from hg-experimental

2018-11-13 Thread durin42 (Augie Fackler)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHGc36175456350: histedit: import chistedit curses UI from 
hg-experimental (authored by durin42, committed by ).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D5146?vs=12322=12513

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

AFFECTED FILES
  hgext/histedit.py
  mercurial/ui.py

CHANGE DETAILS

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1245,7 +1245,11 @@
 "chunkselector": [
 "text",
 "curses",
-]
+],
+"histedit": [
+"text",
+"curses",
+],
 }
 
 # Feature-specific interface
diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -183,7 +183,11 @@
 
 from __future__ import absolute_import
 
+import fcntl
+import functools
 import os
+import struct
+import termios
 
 from mercurial.i18n import _
 from mercurial import (
@@ -198,6 +202,7 @@
 extensions,
 hg,
 lock,
+logcmdutil,
 merge as mergemod,
 mergeutil,
 node,
@@ -235,6 +240,9 @@
 configitem('histedit', 'singletransaction',
 default=False,
 )
+configitem('ui', 'interface.histedit',
+default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' 
for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -915,6 +923,562 @@
 raise error.Abort(msg, hint=hint)
 return repo[roots[0]].node()
 
+# Curses Support
+try:
+import curses
+except ImportError:
+curses = None
+
+KEY_LIST = ['pick', 'edit', 'fold', 'drop', 'mess', 'roll']
+ACTION_LABELS = {
+'fold': '^fold',
+'roll': '^roll',
+}
+
+COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN  = 1, 2, 3, 4
+
+E_QUIT, E_HISTEDIT = 1, 2
+E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
+MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
+
+KEYTABLE = {
+'global': {
+'h': 'next-action',
+'KEY_RIGHT': 'next-action',
+'l': 'prev-action',
+'KEY_LEFT':  'prev-action',
+'q': 'quit',
+'c': 'histedit',
+'C': 'histedit',
+'v': 'showpatch',
+'?': 'help',
+},
+MODE_RULES: {
+'d': 'action-drop',
+'e': 'action-edit',
+'f': 'action-fold',
+'m': 'action-mess',
+'p': 'action-pick',
+'r': 'action-roll',
+' ': 'select',
+'j': 'down',
+'k': 'up',
+'KEY_DOWN':  'down',
+'KEY_UP':'up',
+'J': 'move-down',
+'K': 'move-up',
+'KEY_NPAGE': 'move-down',
+'KEY_PPAGE': 'move-up',
+'0': 'goto',  # Used for 0..9
+},
+MODE_PATCH: {
+' ': 'page-down',
+'KEY_NPAGE': 'page-down',
+'KEY_PPAGE': 'page-up',
+'j': 'line-down',
+'k': 'line-up',
+'KEY_DOWN':  'line-down',
+'KEY_UP':'line-up',
+'J': 'down',
+'K': 'up',
+},
+MODE_HELP: {
+},
+}
+
+def screen_size():
+return struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, ''))
+
+class histeditrule(object):
+def __init__(self, ctx, pos, action='pick'):
+self.ctx = ctx
+self.action = action
+self.origpos = pos
+self.pos = pos
+self.conflicts = []
+
+def __str__(self):
+# Some actions ('fold' and 'roll') combine a patch with a previous one.
+# Add a marker showing which patch they apply to, and also omit the
+# description for 'roll' (since it will get discarded). Example 
display:
+#
+#  #10 pick   316392:06a16c25c053   add option to skip tests
+#  #11 ^roll  316393:71313c964cc5
+#  #12 pick   316394:ab31f3973b0d   include mfbt for mozilla-config.h
+#  #13 ^fold  316395:14ce5803f4c3   fix warnings
+#
+# The carets point to the changeset being folded into ("roll this
+# changeset into the changeset above").
+action = ACTION_LABELS.get(self.action, self.action)
+h = self.ctx.hex()[0:12]
+r = self.ctx.rev()
+desc = self.ctx.description().splitlines()[0].strip()
+if self.action == 'roll':
+desc = ''
+return "#{0:<2} {1:<6} {2}:{3}   {4}".format(
+self.origpos, action, r, h, desc)
+
+def checkconflicts(self, other):
+if other.pos > self.pos and other.origpos <= self.origpos:
+if set(other.ctx.files()) & set(self.ctx.files()) != set():
+self.conflicts.append(other)
+return self.conflicts
+
+if other in self.conflicts:

D5146: histedit: import chistedit curses UI from hg-experimental

2018-11-13 Thread pulkit (Pulkit Goyal)
pulkit accepted this revision.
pulkit added a comment.


  Queueing this, many thanks for importing this in core.
  
  Can you add some documentation about the new config option to enable curses 
interface as followup?
  
  Also, how do you feel about histedit using curses interface if 
`ui.interface=curses` is set?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers, martinvonz, pulkit
Cc: martinvonz, pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-23 Thread pulkit (Pulkit Goyal)
pulkit added a comment.


  In https://phab.mercurial-scm.org/D5146#77493, @durin42 wrote:
  
  > In https://phab.mercurial-scm.org/D5146#77492, @pulkit wrote:
  >
  > > I remember using chistedit and finding that the `.hg/chisedit` file is 
not deleted after chistedit is completed. Looking at code, I am unable to find 
code which deletes that file. Can you please have a look and delete that file 
after a successfull or aborted chistedit?
  >
  >
  > That file is an implementation detail, so that histedit can pass rules to 
itself. It's a little gross, but not harmful.
  >
  > Could I persuade you to let us clean that up later as a follow-up?
  
  
  Yeah sure!

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers, martinvonz
Cc: martinvonz, pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-23 Thread durin42 (Augie Fackler)
durin42 added a comment.


  In https://phab.mercurial-scm.org/D5146#77492, @pulkit wrote:
  
  > I remember using chistedit and finding that the `.hg/chisedit` file is not 
deleted after chistedit is completed. Looking at code, I am unable to find code 
which deletes that file. Can you please have a look and delete that file after 
a successfull or aborted chistedit?
  
  
  That file is an implementation detail, so that histedit can pass rules to 
itself. It's a little gross, but not harmful.
  
  Could I persuade you to let us clean that up later as a follow-up?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers, martinvonz
Cc: martinvonz, pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-23 Thread pulkit (Pulkit Goyal)
pulkit added a comment.


  I remember using chistedit and finding that the `.hg/chisedit` file is not 
deleted after chistedit is completed. Looking at code, I am unable to find code 
which deletes that file. Can you please have a look and delete that file after 
a successfull or aborted chistedit?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers, martinvonz
Cc: martinvonz, pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-23 Thread durin42 (Augie Fackler)
durin42 updated this revision to Diff 12322.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D5146?vs=12221=12322

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

AFFECTED FILES
  hgext/histedit.py
  mercurial/ui.py

CHANGE DETAILS

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1203,7 +1203,11 @@
 "chunkselector": [
 "text",
 "curses",
-]
+],
+"histedit": [
+"text",
+"curses",
+],
 }
 
 # Feature-specific interface
diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -183,7 +183,11 @@
 
 from __future__ import absolute_import
 
+import fcntl
+import functools
 import os
+import struct
+import termios
 
 from mercurial.i18n import _
 from mercurial import (
@@ -198,6 +202,7 @@
 extensions,
 hg,
 lock,
+logcmdutil,
 merge as mergemod,
 mergeutil,
 node,
@@ -235,6 +240,9 @@
 configitem('histedit', 'singletransaction',
 default=False,
 )
+configitem('ui', 'interface.histedit',
+default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' 
for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -915,6 +923,562 @@
 raise error.Abort(msg, hint=hint)
 return repo[roots[0]].node()
 
+# Curses Support
+try:
+import curses
+except ImportError:
+curses = None
+
+KEY_LIST = ['pick', 'edit', 'fold', 'drop', 'mess', 'roll']
+ACTION_LABELS = {
+'fold': '^fold',
+'roll': '^roll',
+}
+
+COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN  = 1, 2, 3, 4
+
+E_QUIT, E_HISTEDIT = 1, 2
+E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
+MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
+
+KEYTABLE = {
+'global': {
+'h': 'next-action',
+'KEY_RIGHT': 'next-action',
+'l': 'prev-action',
+'KEY_LEFT':  'prev-action',
+'q': 'quit',
+'c': 'histedit',
+'C': 'histedit',
+'v': 'showpatch',
+'?': 'help',
+},
+MODE_RULES: {
+'d': 'action-drop',
+'e': 'action-edit',
+'f': 'action-fold',
+'m': 'action-mess',
+'p': 'action-pick',
+'r': 'action-roll',
+' ': 'select',
+'j': 'down',
+'k': 'up',
+'KEY_DOWN':  'down',
+'KEY_UP':'up',
+'J': 'move-down',
+'K': 'move-up',
+'KEY_NPAGE': 'move-down',
+'KEY_PPAGE': 'move-up',
+'0': 'goto',  # Used for 0..9
+},
+MODE_PATCH: {
+' ': 'page-down',
+'KEY_NPAGE': 'page-down',
+'KEY_PPAGE': 'page-up',
+'j': 'line-down',
+'k': 'line-up',
+'KEY_DOWN':  'line-down',
+'KEY_UP':'line-up',
+'J': 'down',
+'K': 'up',
+},
+MODE_HELP: {
+},
+}
+
+def screen_size():
+return struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, ''))
+
+class histeditrule(object):
+def __init__(self, ctx, pos, action='pick'):
+self.ctx = ctx
+self.action = action
+self.origpos = pos
+self.pos = pos
+self.conflicts = []
+
+def __str__(self):
+# Some actions ('fold' and 'roll') combine a patch with a previous one.
+# Add a marker showing which patch they apply to, and also omit the
+# description for 'roll' (since it will get discarded). Example 
display:
+#
+#  #10 pick   316392:06a16c25c053   add option to skip tests
+#  #11 ^roll  316393:71313c964cc5
+#  #12 pick   316394:ab31f3973b0d   include mfbt for mozilla-config.h
+#  #13 ^fold  316395:14ce5803f4c3   fix warnings
+#
+# The carets point to the changeset being folded into ("roll this
+# changeset into the changeset above").
+action = ACTION_LABELS.get(self.action, self.action)
+h = self.ctx.hex()[0:12]
+r = self.ctx.rev()
+desc = self.ctx.description().splitlines()[0].strip()
+if self.action == 'roll':
+desc = ''
+return "#{0:<2} {1:<6} {2}:{3}   {4}".format(
+self.origpos, action, r, h, desc)
+
+def checkconflicts(self, other):
+if other.pos > self.pos and other.origpos <= self.origpos:
+if set(other.ctx.files()) & set(self.ctx.files()) != set():
+self.conflicts.append(other)
+return self.conflicts
+
+if other in self.conflicts:
+self.conflicts.remove(other)
+return self.conflicts
+
+#  EVENTS ===
+def movecursor(state, oldpos, newpos):
+

D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-18 Thread durin42 (Augie Fackler)
durin42 added a comment.


  Correct, no tests in hg-experimental. I did a basic sniff test, but I don't 
really know how we do curses tests in general.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers, martinvonz
Cc: martinvonz, pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-18 Thread martinvonz (Martin von Zweigbergk)
martinvonz accepted this revision.
martinvonz added a comment.
This revision is now accepted and ready to land.


  Would be good to have tests, but I guess there are no tests in 
hg-experimental either? I'm not queuing because I'm biased.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers, martinvonz
Cc: martinvonz, pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-18 Thread durin42 (Augie Fackler)
durin42 added a comment.


  > pulkit added a comment.
  > 
  >   Is the goal is to include this in 4.8?
  
  Yes if possible.
  
  >

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers
Cc: pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-18 Thread pulkit (Pulkit Goyal)
pulkit added a comment.


  Is the goal is to include this in 4.8?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

To: durin42, #hg-reviewers
Cc: pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-17 Thread durin42 (Augie Fackler)
durin42 updated this revision to Diff 12221.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D5146?vs=12220=12221

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

AFFECTED FILES
  hgext/histedit.py
  mercurial/ui.py

CHANGE DETAILS

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1203,7 +1203,11 @@
 "chunkselector": [
 "text",
 "curses",
-]
+],
+"histedit": [
+"text",
+"curses",
+],
 }
 
 # Feature-specific interface
diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -183,7 +183,11 @@
 
 from __future__ import absolute_import
 
+import fcntl
+import functools
 import os
+import struct
+import termios
 
 from mercurial.i18n import _
 from mercurial import (
@@ -198,6 +202,7 @@
 extensions,
 hg,
 lock,
+logcmdutil,
 merge as mergemod,
 mergeutil,
 node,
@@ -235,6 +240,9 @@
 configitem('histedit', 'singletransaction',
 default=False,
 )
+configitem('ui', 'interface.histedit',
+default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' 
for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -916,6 +924,562 @@
 raise error.Abort(msg, hint=hint)
 return repo[roots[0]].node()
 
+# Curses Support
+try:
+import curses
+except ImportError:
+curses = None
+
+KEY_LIST = ['pick', 'edit', 'fold', 'drop', 'mess', 'roll']
+ACTION_LABELS = {
+'fold': '^fold',
+'roll': '^roll',
+}
+
+COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN  = 1, 2, 3, 4
+
+E_QUIT, E_HISTEDIT = 1, 2
+E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
+MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
+
+KEYTABLE = {
+'global': {
+'h': 'next-action',
+'KEY_RIGHT': 'next-action',
+'l': 'prev-action',
+'KEY_LEFT':  'prev-action',
+'q': 'quit',
+'c': 'histedit',
+'C': 'histedit',
+'v': 'showpatch',
+'?': 'help',
+},
+MODE_RULES: {
+'d': 'action-drop',
+'e': 'action-edit',
+'f': 'action-fold',
+'m': 'action-mess',
+'p': 'action-pick',
+'r': 'action-roll',
+' ': 'select',
+'j': 'down',
+'k': 'up',
+'KEY_DOWN':  'down',
+'KEY_UP':'up',
+'J': 'move-down',
+'K': 'move-up',
+'KEY_NPAGE': 'move-down',
+'KEY_PPAGE': 'move-up',
+'0': 'goto',  # Used for 0..9
+},
+MODE_PATCH: {
+' ': 'page-down',
+'KEY_NPAGE': 'page-down',
+'KEY_PPAGE': 'page-up',
+'j': 'line-down',
+'k': 'line-up',
+'KEY_DOWN':  'line-down',
+'KEY_UP':'line-up',
+'J': 'down',
+'K': 'up',
+},
+MODE_HELP: {
+},
+}
+
+def screen_size():
+return struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, ''))
+
+class histeditrule(object):
+def __init__(self, ctx, pos, action='pick'):
+self.ctx = ctx
+self.action = action
+self.origpos = pos
+self.pos = pos
+self.conflicts = []
+
+def __str__(self):
+# Some actions ('fold' and 'roll') combine a patch with a previous one.
+# Add a marker showing which patch they apply to, and also omit the
+# description for 'roll' (since it will get discarded). Example 
display:
+#
+#  #10 pick   316392:06a16c25c053   add option to skip tests
+#  #11 ^roll  316393:71313c964cc5
+#  #12 pick   316394:ab31f3973b0d   include mfbt for mozilla-config.h
+#  #13 ^fold  316395:14ce5803f4c3   fix warnings
+#
+# The carets point to the changeset being folded into ("roll this
+# changeset into the changeset above").
+action = ACTION_LABELS.get(self.action, self.action)
+h = self.ctx.hex()[0:12]
+r = self.ctx.rev()
+desc = self.ctx.description().splitlines()[0].strip()
+if self.action == 'roll':
+desc = ''
+return "#{0:<2} {1:<6} {2}:{3}   {4}".format(
+self.origpos, action, r, h, desc)
+
+def checkconflicts(self, other):
+if other.pos > self.pos and other.origpos <= self.origpos:
+if set(other.ctx.files()) & set(self.ctx.files()) != set():
+self.conflicts.append(other)
+return self.conflicts
+
+if other in self.conflicts:
+self.conflicts.remove(other)
+return self.conflicts
+
+#  EVENTS ===
+def movecursor(state, oldpos, newpos):
+

D5146: histedit: import chistedit curses UI from hg-experimental

2018-10-17 Thread durin42 (Augie Fackler)
durin42 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  I don't tend to like curses interfaces, but this gets enough use at
  work that it seems like it's worth bringing into core. This is a
  minimal import from hg-experimental revision 
https://phab.mercurial-scm.org/rFBHGX4c7f33bf5f00b91c2caf28f095c320873584dd34, 
in that
  I've done the smallest amount of code movement and editing in order to
  import the functionality.
  
  .. feature::
  
`hg histedit` will now present a curses UI if curses is available
and `ui.interface` or `ui.interface.histedit` is set to `curses`.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5146

AFFECTED FILES
  hgext/histedit.py
  mercurial/ui.py

CHANGE DETAILS

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1203,7 +1203,11 @@
 "chunkselector": [
 "text",
 "curses",
-]
+],
+"histedit": [
+"text",
+"curses",
+],
 }
 
 # Feature-specific interface
diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -183,6 +183,7 @@
 
 from __future__ import absolute_import
 
+import functools
 import os
 
 from mercurial.i18n import _
@@ -916,6 +917,565 @@
 raise error.Abort(msg, hint=hint)
 return repo[roots[0]].node()
 
+# Curses Support
+try:
+import curses
+except ImportError:
+curses = None
+
+KEY_LIST = ['pick', 'edit', 'fold', 'drop', 'mess', 'roll']
+ACTION_LABELS = {
+'fold': '^fold',
+'roll': '^roll',
+}
+
+COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN  = 1, 2, 3, 4
+
+E_QUIT, E_HISTEDIT = 1, 2
+E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
+MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
+
+KEYTABLE = {
+'global': {
+'h': 'next-action',
+'KEY_RIGHT': 'next-action',
+'l': 'prev-action',
+'KEY_LEFT':  'prev-action',
+'q': 'quit',
+'c': 'histedit',
+'C': 'histedit',
+'v': 'showpatch',
+'?': 'help',
+},
+MODE_RULES: {
+'d': 'action-drop',
+'e': 'action-edit',
+'f': 'action-fold',
+'m': 'action-mess',
+'p': 'action-pick',
+'r': 'action-roll',
+' ': 'select',
+'j': 'down',
+'k': 'up',
+'KEY_DOWN':  'down',
+'KEY_UP':'up',
+'J': 'move-down',
+'K': 'move-up',
+'KEY_NPAGE': 'move-down',
+'KEY_PPAGE': 'move-up',
+'0': 'goto',  # Used for 0..9
+},
+MODE_PATCH: {
+' ': 'page-down',
+'KEY_NPAGE': 'page-down',
+'KEY_PPAGE': 'page-up',
+'j': 'line-down',
+'k': 'line-up',
+'KEY_DOWN':  'line-down',
+'KEY_UP':'line-up',
+'J': 'down',
+'K': 'up',
+},
+MODE_HELP: {
+},
+}
+
+def screen_size():
+import termios
+from fcntl import ioctl
+from struct import unpack
+return unpack('hh', ioctl(1, termios.TIOCGWINSZ, ''))
+
+class histeditrule(object):
+def __init__(self, ctx, pos, action='pick'):
+self.ctx = ctx
+self.action = action
+self.origpos = pos
+self.pos = pos
+self.conflicts = []
+
+def __str__(self):
+# Some actions ('fold' and 'roll') combine a patch with a previous one.
+# Add a marker showing which patch they apply to, and also omit the
+# description for 'roll' (since it will get discarded). Example 
display:
+#
+#  #10 pick   316392:06a16c25c053   add option to skip tests
+#  #11 ^roll  316393:71313c964cc5
+#  #12 pick   316394:ab31f3973b0d   include mfbt for mozilla-config.h
+#  #13 ^fold  316395:14ce5803f4c3   fix warnings
+#
+# The carets point to the changeset being folded into ("roll this
+# changeset into the changeset above").
+action = ACTION_LABELS.get(self.action, self.action)
+h = self.ctx.hex()[0:12]
+r = self.ctx.rev()
+desc = self.ctx.description().splitlines()[0].strip()
+if self.action == 'roll':
+desc = ''
+return "#{0:<2} {1:<6} {2}:{3}   {4}".format(
+self.origpos, action, r, h, desc)
+
+def checkconflicts(self, other):
+if other.pos > self.pos and other.origpos <= self.origpos:
+if set(other.ctx.files()) & set(self.ctx.files()) != set():
+self.conflicts.append(other)
+return self.conflicts
+
+if other in self.conflicts:
+self.conflicts.remove(other)
+