Add a simple kill ring

Create a global app kill ring and a custom edit widget that can
kill and yank from it.  Also, add Emacs beginning/end of line
keys to the default keymap.

Change-Id: I18d8d47694c89ede4dcec7eaf5d3fb2210ef4438
This commit is contained in:
James E. Blair 2015-10-20 18:22:32 -07:00
parent b0f595b1c1
commit c8d81b7693
6 changed files with 92 additions and 21 deletions

View File

@ -141,7 +141,8 @@ class SearchDialog(mywid.ButtonDialog):
entry_prompt="Search: ",
entry_text=default,
buttons=[search_button,
cancel_button])
cancel_button],
ring=app.ring)
def keypress(self, size, key):
r = super(SearchDialog, self).keypress(size, key)
@ -207,6 +208,7 @@ class App(object):
self.log = logging.getLogger('gertty.App')
self.log.debug("Starting")
self.ring = mywid.KillRing()
webbrowser.register('xdg-open', None, BackgroundBrowser("xdg-open"))
self.fetch_missing_refs = fetch_missing_refs

View File

@ -29,6 +29,9 @@ CURSOR_PAGE_DOWN = urwid.CURSOR_PAGE_DOWN
CURSOR_MAX_LEFT = urwid.CURSOR_MAX_LEFT
CURSOR_MAX_RIGHT = urwid.CURSOR_MAX_RIGHT
ACTIVATE = urwid.ACTIVATE
KILL = 'kill'
YANK = 'yank'
YANK_POP = 'yank pop'
# Global gertty commands:
PREV_SCREEN = 'previous screen'
TOP_SCREEN = 'top screen'
@ -79,9 +82,12 @@ DEFAULT_KEYMAP = {
CURSOR_RIGHT: 'right',
CURSOR_PAGE_UP: 'page up',
CURSOR_PAGE_DOWN: 'page down',
CURSOR_MAX_LEFT: 'home',
CURSOR_MAX_RIGHT: 'end',
CURSOR_MAX_LEFT: ['home', 'ctrl a'],
CURSOR_MAX_RIGHT: ['end', 'ctrl e'],
ACTIVATE: 'enter',
KILL: 'ctrl k',
YANK: 'ctrl y',
YANK_POP: 'meta y',
PREV_SCREEN: 'esc',
TOP_SCREEN: 'meta home',
@ -136,6 +142,9 @@ URWID_COMMANDS = frozenset((
urwid.CURSOR_MAX_LEFT,
urwid.CURSOR_MAX_RIGHT,
urwid.ACTIVATE,
KILL,
YANK,
YANK_POP,
))
FORMAT_SUBS = (

View File

@ -74,9 +74,58 @@ class Table(urwid.WidgetWrap):
for i, widget in enumerate(cells):
self._w.contents[i][0].contents.append((widget, ('pack', None)))
class KillRing(object):
def __init__(self):
self.ring = []
def kill(self, text):
self.ring.append(text)
def yank(self, repeat=False):
if not self.ring:
return None
if repeat:
t = self.ring.pop()
self.ring.insert(0, t)
return self.ring[-1]
class MyEdit(urwid.Edit):
def __init__(self, *args, **kw):
self.ring = kw.pop('ring', None)
if not self.ring:
self.ring = KillRing()
self.last_yank = None
super(MyEdit, self).__init__(*args, **kw)
def keypress(self, size, key):
(maxcol,) = size
if self._command_map[key] == keymap.YANK:
text = self.ring.yank()
if text:
self.last_yank = (self.edit_pos, self.edit_pos+len(text))
self.insert_text(text)
return
if self._command_map[key] == keymap.YANK_POP:
if not self.last_yank:
return
text = self.ring.yank(True)
if text:
self.edit_text = (self.edit_text[:self.last_yank[0]] +
self.edit_text[self.last_yank[1]:])
self.last_yank = (self.edit_pos, self.edit_pos+len(text))
self.insert_text(text)
return
self.last_yank = None
if self._command_map[key] == keymap.KILL:
text = self.edit_text[self.edit_pos:]
self.edit_text = self.edit_text[:self.edit_pos]
self.ring.kill(text)
return super(MyEdit, self).keypress(size, key)
@mouse_scroll_decorator.ScrollByWheel
class ButtonDialog(urwid.WidgetWrap):
def __init__(self, title, message, entry_prompt=None, entry_text='', buttons=[]):
def __init__(self, title, message, entry_prompt=None,
entry_text='', buttons=[], ring=None):
button_widgets = []
for button in buttons:
button_widgets.append(('pack', button))
@ -84,7 +133,7 @@ class ButtonDialog(urwid.WidgetWrap):
rows = []
rows.append(urwid.Text(message))
if entry_prompt:
self.entry = urwid.Edit(entry_prompt, edit_text=entry_text)
self.entry = MyEdit(entry_prompt, edit_text=entry_text, ring=ring)
rows.append(self.entry)
else:
self.entry = None
@ -95,7 +144,7 @@ class ButtonDialog(urwid.WidgetWrap):
class TextEditDialog(urwid.WidgetWrap):
signals = ['save', 'cancel']
def __init__(self, title, prompt, button, text):
def __init__(self, title, prompt, button, text, ring=None):
save_button = FixedButton(button)
cancel_button = FixedButton('Cancel')
urwid.connect_signal(save_button, 'click',
@ -106,7 +155,7 @@ class TextEditDialog(urwid.WidgetWrap):
('pack', cancel_button)]
button_columns = urwid.Columns(button_widgets, dividechars=2)
rows = []
self.entry = urwid.Edit(edit_text=text, multiline=True)
self.entry = MyEdit(edit_text=text, multiline=True, ring=ring)
rows.append(urwid.Text(prompt))
rows.append(self.entry)
rows.append(urwid.Divider())

View File

@ -43,7 +43,8 @@ class EditTopicDialog(mywid.ButtonDialog):
entry_prompt="Topic: ",
entry_text=topic,
buttons=[save_button,
cancel_button])
cancel_button],
ring=app.ring)
def keypress(self, size, key):
r = super(EditTopicDialog, self).keypress(size, key)
@ -55,7 +56,7 @@ class EditTopicDialog(mywid.ButtonDialog):
class CherryPickDialog(urwid.WidgetWrap):
signals = ['save', 'cancel']
def __init__(self, change):
def __init__(self, app, change):
save_button = mywid.FixedButton('Propose Change')
cancel_button = mywid.FixedButton('Cancel')
urwid.connect_signal(save_button, 'click',
@ -66,7 +67,8 @@ class CherryPickDialog(urwid.WidgetWrap):
('pack', cancel_button)]
button_columns = urwid.Columns(button_widgets, dividechars=2)
rows = []
self.entry = urwid.Edit(edit_text=change.revisions[-1].message, multiline=True)
self.entry = mywid.MyEdit(edit_text=change.revisions[-1].message,
multiline=True, ring=app.ring)
self.branch_buttons = []
rows.append(urwid.Text(u"Branch:"))
for branch in change.project.branches:
@ -166,7 +168,8 @@ class ReviewDialog(urwid.WidgetWrap):
m = revision.getDraftMessage()
if m:
message = m.message
self.message = urwid.Edit("Message: \n", edit_text=message, multiline=True)
self.message = mywid.MyEdit("Message: \n", edit_text=message,
multiline=True, ring=app.ring)
rows.append(self.message)
rows.append(urwid.Divider())
rows.append(buttons)
@ -956,7 +959,7 @@ class ChangeView(urwid.WidgetWrap):
def cherryPickChange(self):
with self.app.db.getSession() as session:
change = session.getChange(self.change_key)
dialog = CherryPickDialog(change)
dialog = CherryPickDialog(self.app, change)
urwid.connect_signal(dialog, 'cancel', self.app.backScreen)
urwid.connect_signal(dialog, 'save', lambda button:
self.doCherryPickChange(dialog))

View File

@ -15,6 +15,7 @@
import urwid
from gertty import keymap
from gertty import mywid
from gertty.view.diff import BaseDiffComment, BaseDiffCommentEdit, BaseDiffLine
from gertty.view.diff import BaseFileHeader, BaseFileReminder, BaseDiffView
@ -28,8 +29,8 @@ class SideDiffCommentEdit(BaseDiffCommentEdit):
# If we save a comment, the resulting key will be stored here
self.old_key = old_key
self.new_key = new_key
self.old = urwid.Edit(edit_text=old, multiline=True)
self.new = urwid.Edit(edit_text=new, multiline=True)
self.old = mywid.MyEdit(edit_text=old, multiline=True, ring=app.ring)
self.new = mywid.MyEdit(edit_text=new, multiline=True, ring=app.ring)
self.contents.append((urwid.Text(u''), ('given', LN_COL_WIDTH, False)))
if context.old_file_key and (context.old_ln is not None or context.header):
self.contents.append((urwid.AttrMap(self.old, 'draft-comment'), ('weight', 1, False)))

View File

@ -16,19 +16,21 @@
import urwid
from gertty import gitrepo
from gertty import mywid
from gertty.view.diff import BaseDiffCommentEdit, BaseDiffComment, BaseDiffLine
from gertty.view.diff import BaseFileHeader, BaseFileReminder, BaseDiffView
LN_COL_WIDTH = 5
class UnifiedDiffCommentEdit(BaseDiffCommentEdit):
def __init__(self, context, oldnew, key=None, comment=u''):
def __init__(self, app, context, oldnew, key=None, comment=u''):
super(UnifiedDiffCommentEdit, self).__init__([])
self.context = context
self.oldnew = oldnew
# If we save a comment, the resulting key will be stored here
self.key = key
self.comment = urwid.Edit(edit_text=comment, multiline=True)
self.comment = mywid.MyEdit(edit_text=comment, multiline=True,
ring=app.ring)
self.contents.append((urwid.Text(u''), ('given', 8, False)))
self.contents.append((urwid.AttrMap(self.comment, 'draft-comment'),
('weight', 1, False)))
@ -135,7 +137,8 @@ class UnifiedDiffView(BaseDiffView):
old_list = comment_lists.pop(key, [])
while old_list:
(old_comment_key, old_comment) = old_list.pop(0)
lines.append(UnifiedDiffCommentEdit(context,
lines.append(UnifiedDiffCommentEdit(self.app,
context,
gitrepo.OLD,
old_comment_key,
old_comment))
@ -154,7 +157,8 @@ class UnifiedDiffView(BaseDiffView):
new_list = comment_lists.pop(key, [])
while new_list:
(new_comment_key, new_comment) = new_list.pop(0)
lines.append(UnifiedDiffCommentEdit(context,
lines.append(UnifiedDiffCommentEdit(self.app,
context,
gitrepo.NEW,
new_comment_key,
new_comment))
@ -180,7 +184,8 @@ class UnifiedDiffView(BaseDiffView):
old_list = comment_lists.pop(key, [])
while old_list:
(old_comment_key, old_comment) = old_list.pop(0)
lines.append(UnifiedDiffCommentEdit(context,
lines.append(UnifiedDiffCommentEdit(self.app,
context,
gitrepo.OLD,
old_comment_key,
old_comment))
@ -200,14 +205,16 @@ class UnifiedDiffView(BaseDiffView):
new_list = comment_lists.pop(key, [])
while new_list:
(new_comment_key, new_comment) = new_list.pop(0)
lines.append(UnifiedDiffCommentEdit(context,
lines.append(UnifiedDiffCommentEdit(self.app,
context,
gitrepo.NEW,
new_comment_key,
new_comment))
return lines
def makeCommentEdit(self, edit):
return UnifiedDiffCommentEdit(edit.context,
return UnifiedDiffCommentEdit(self.app,
edit.context,
edit.oldnew)
def cleanupEdit(self, edit):