Support multiple key input
Change-Id: I0cd0884e0e1f4f0fa82f93e5f7438ff00c5a992a
This commit is contained in:
parent
6f9e0ceb2f
commit
540c8dd7cc
@ -99,6 +99,10 @@ keymaps:
|
||||
review: ['r', 'R']
|
||||
- name: osx #OS X blocks ctrl+o
|
||||
change-search: 'ctrl s'
|
||||
# To specify a sequence of keys, they must be a list of keystrokes
|
||||
# within a list of key series. For example:
|
||||
- name: vi
|
||||
quit: [[':', 'q']]
|
||||
|
||||
# The default keymap may be selected with the '-k KEYMAP' command line
|
||||
# option, or with the following line:
|
||||
|
@ -77,18 +77,23 @@ class StatusHeader(urwid.WidgetWrap):
|
||||
self.error = None
|
||||
self.offline = None
|
||||
self.title = None
|
||||
self.message = None
|
||||
self.sync = None
|
||||
self.held = None
|
||||
self._error = False
|
||||
self._offline = False
|
||||
self._title = ''
|
||||
self._message = ''
|
||||
self._sync = 0
|
||||
self._held = 0
|
||||
self.held_key = self.app.config.keymap.formatKeys(keymap.LIST_HELD)
|
||||
|
||||
def update(self, title=None, error=None, offline=None, refresh=True, held=None):
|
||||
def update(self, title=None, message=None, error=None,
|
||||
offline=None, refresh=True, held=None):
|
||||
if title is not None:
|
||||
self.title = title
|
||||
if message is not None:
|
||||
self.message = message
|
||||
if error is not None:
|
||||
self.error = error
|
||||
if offline is not None:
|
||||
@ -100,9 +105,11 @@ class StatusHeader(urwid.WidgetWrap):
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
if self._title != self.title:
|
||||
if (self._title != self.title or self._message != self.message):
|
||||
self._title = self.title
|
||||
self.title_widget.set_text(self._title)
|
||||
self._message = self.message
|
||||
t = self.message or self.title
|
||||
self.title_widget.set_text(t)
|
||||
if self._held != self.held:
|
||||
self._held = self.held
|
||||
if self._held:
|
||||
@ -145,12 +152,14 @@ class SearchDialog(mywid.ButtonDialog):
|
||||
ring=app.ring)
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(SearchDialog, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(SearchDialog, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
if keymap.ACTIVATE in commands:
|
||||
self._emit('search')
|
||||
return None
|
||||
return r
|
||||
return key
|
||||
|
||||
# From: cpython/file/2.7/Lib/webbrowser.py with modification to
|
||||
# redirect stdin/out/err.
|
||||
@ -209,6 +218,7 @@ class App(object):
|
||||
self.log.debug("Starting")
|
||||
|
||||
self.ring = mywid.KillRing()
|
||||
self.input_buffer = []
|
||||
webbrowser.register('xdg-open', None, BackgroundBrowser("xdg-open"))
|
||||
|
||||
self.fetch_missing_refs = fetch_missing_refs
|
||||
@ -268,11 +278,17 @@ class App(object):
|
||||
|
||||
self.popup(dialog)
|
||||
|
||||
def clearInputBuffer(self):
|
||||
if self.input_buffer:
|
||||
self.input_buffer = []
|
||||
self.status.update(message='')
|
||||
|
||||
def changeScreen(self, widget, push=True):
|
||||
self.log.debug("Changing screen to %s" % (widget,))
|
||||
self.status.update(error=False, title=widget.title)
|
||||
if push:
|
||||
self.screens.append(self.loop.widget)
|
||||
self.clearInputBuffer()
|
||||
self.loop.widget = widget
|
||||
|
||||
def backScreen(self, target_widget=None):
|
||||
@ -285,6 +301,7 @@ class App(object):
|
||||
self.log.debug("Popping screen to %s" % (widget,))
|
||||
if hasattr(widget, 'title'):
|
||||
self.status.update(title=widget.title)
|
||||
self.clearInputBuffer()
|
||||
self.loop.widget = widget
|
||||
self.refresh(force=True)
|
||||
|
||||
@ -298,6 +315,7 @@ class App(object):
|
||||
self.log.debug("Clearing screen history")
|
||||
while self.screens:
|
||||
widget = self.screens.pop()
|
||||
self.clearInputBuffer()
|
||||
self.loop.widget = widget
|
||||
|
||||
def refresh(self, data=None, force=False):
|
||||
@ -329,6 +347,7 @@ class App(object):
|
||||
def popup(self, widget,
|
||||
relative_width=50, relative_height=25,
|
||||
min_width=20, min_height=8):
|
||||
self.clearInputBuffer()
|
||||
overlay = urwid.Overlay(widget, self.loop.widget,
|
||||
'center', ('relative', relative_width),
|
||||
'middle', ('relative', relative_height),
|
||||
@ -506,7 +525,9 @@ class App(object):
|
||||
return None
|
||||
|
||||
def unhandledInput(self, key):
|
||||
commands = self.config.keymap.getCommands(key)
|
||||
# get commands from buffer
|
||||
keys = self.input_buffer + [key]
|
||||
commands = self.config.keymap.getCommands(keys)
|
||||
if keymap.PREV_SCREEN in commands:
|
||||
self.backScreen()
|
||||
elif keymap.TOP_SCREEN in commands:
|
||||
@ -524,6 +545,11 @@ class App(object):
|
||||
d = self.config.dashboards[key]
|
||||
view = view_change_list.ChangeListView(self, d['query'], d['name'])
|
||||
self.changeScreen(view)
|
||||
elif keymap.FURTHER_INPUT in commands:
|
||||
self.input_buffer.append(key)
|
||||
self.status.update(message=''.join(self.input_buffer))
|
||||
return
|
||||
self.clearInputBuffer()
|
||||
|
||||
def openURL(self, url):
|
||||
self.log.debug("Open URL %s" % url)
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
import re
|
||||
import string
|
||||
import logging
|
||||
|
||||
import urwid
|
||||
|
||||
@ -29,10 +30,10 @@ CURSOR_PAGE_DOWN = urwid.CURSOR_PAGE_DOWN
|
||||
CURSOR_MAX_LEFT = urwid.CURSOR_MAX_LEFT
|
||||
CURSOR_MAX_RIGHT = urwid.CURSOR_MAX_RIGHT
|
||||
ACTIVATE = urwid.ACTIVATE
|
||||
# Global gertty commands:
|
||||
KILL = 'kill'
|
||||
YANK = 'yank'
|
||||
YANK_POP = 'yank pop'
|
||||
# Global gertty commands:
|
||||
PREV_SCREEN = 'previous screen'
|
||||
TOP_SCREEN = 'top screen'
|
||||
HELP = 'help'
|
||||
@ -74,6 +75,8 @@ SELECT_PATCHSETS = 'select patchsets'
|
||||
NEXT_SELECTABLE = 'next selectable'
|
||||
PREV_SELECTABLE = 'prev selectable'
|
||||
INTERACTIVE_SEARCH = 'interactive search'
|
||||
# Special:
|
||||
FURTHER_INPUT = 'further input'
|
||||
|
||||
DEFAULT_KEYMAP = {
|
||||
REDRAW_SCREEN: 'ctrl l',
|
||||
@ -93,7 +96,7 @@ DEFAULT_KEYMAP = {
|
||||
PREV_SCREEN: 'esc',
|
||||
TOP_SCREEN: 'meta home',
|
||||
HELP: ['f1', '?'],
|
||||
QUIT: 'ctrl q',
|
||||
QUIT: ['ctrl q'],
|
||||
CHANGE_SEARCH: 'ctrl o',
|
||||
REFINE_CHANGE_SEARCH: 'meta o',
|
||||
LIST_HELD: 'f12',
|
||||
@ -157,15 +160,32 @@ FORMAT_SUBS = (
|
||||
)
|
||||
|
||||
def formatKey(key):
|
||||
if type(key) == type([]):
|
||||
return ''.join([formatKey(k) for k in key])
|
||||
for subre, repl in FORMAT_SUBS:
|
||||
key = subre.sub(repl, key)
|
||||
return key
|
||||
|
||||
class Key(object):
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
self.keys = {}
|
||||
self.commands = []
|
||||
|
||||
def addKey(self, key):
|
||||
if key not in self.keys:
|
||||
self.keys[key] = Key(key)
|
||||
return self.keys[key]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s %s %s' % (self.__class__.__name__, self.key, self.keys.keys())
|
||||
|
||||
class KeyMap(object):
|
||||
def __init__(self, config):
|
||||
# key -> [commands]
|
||||
self.keymap = {}
|
||||
self.keytree = Key(None)
|
||||
self.commandmap = {}
|
||||
self.multikeys = ''
|
||||
self.update(DEFAULT_KEYMAP)
|
||||
self.update(config)
|
||||
|
||||
@ -178,26 +198,42 @@ class KeyMap(object):
|
||||
if type(keys) != type([]):
|
||||
keys = [keys]
|
||||
self.commandmap[command] = keys
|
||||
self.keymap = {}
|
||||
self.keytree = Key(None)
|
||||
for command, keys in self.commandmap.items():
|
||||
for key in keys:
|
||||
if key in self.keymap:
|
||||
self.keymap[key].append(command)
|
||||
if isinstance(key, list):
|
||||
# This is a command series
|
||||
tree = self.keytree
|
||||
for i, innerkey in enumerate(key):
|
||||
tree = tree.addKey(innerkey)
|
||||
if i+1 == len(key):
|
||||
tree.commands.append(command)
|
||||
else:
|
||||
self.keymap[key] = [command]
|
||||
tree = self.keytree.addKey(key)
|
||||
tree.commands.append(command)
|
||||
|
||||
def getCommands(self, key):
|
||||
return self.keymap.get(key, [])
|
||||
def getCommands(self, keys):
|
||||
if not keys:
|
||||
return []
|
||||
tree = self.keytree
|
||||
for key in keys:
|
||||
tree = tree.keys.get(key)
|
||||
if not tree:
|
||||
return []
|
||||
ret = tree.commands[:]
|
||||
if tree.keys:
|
||||
ret.append(FURTHER_INPUT)
|
||||
return ret
|
||||
|
||||
def getKeys(self, command):
|
||||
return self.commandmap.get(command, [])
|
||||
|
||||
def updateCommandMap(self):
|
||||
"Update the urwid command map with this keymap"
|
||||
for key, commands in self.keymap.items():
|
||||
for command in commands:
|
||||
for key in self.keytree.keys.values():
|
||||
for command in key.commands:
|
||||
if command in URWID_COMMANDS:
|
||||
urwid.command_map[key]=command
|
||||
urwid.command_map[key.key]=command
|
||||
|
||||
def formatKeys(self, command):
|
||||
keys = self.getKeys(command)
|
||||
|
@ -47,12 +47,14 @@ class EditTopicDialog(mywid.ButtonDialog):
|
||||
ring=app.ring)
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(EditTopicDialog, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(EditTopicDialog, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
if keymap.ACTIVATE in commands:
|
||||
self._emit('save')
|
||||
return None
|
||||
return r
|
||||
return key
|
||||
|
||||
class CherryPickDialog(urwid.WidgetWrap):
|
||||
signals = ['save', 'cancel']
|
||||
@ -187,12 +189,14 @@ class ReviewDialog(urwid.WidgetWrap):
|
||||
return (approvals, message)
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(ReviewDialog, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(ReviewDialog, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
if keymap.PREV_SCREEN in commands:
|
||||
self._emit('cancel')
|
||||
return None
|
||||
return r
|
||||
return key
|
||||
|
||||
class ReviewButton(mywid.FixedButton):
|
||||
def __init__(self, revision_row):
|
||||
@ -786,8 +790,10 @@ class ChangeView(urwid.WidgetWrap):
|
||||
return self.app.toggleHeldChange(self.change_key)
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(ChangeView, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(ChangeView, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
if keymap.TOGGLE_REVIEWED in commands:
|
||||
self.toggleReviewed()
|
||||
self.refresh()
|
||||
@ -870,10 +876,10 @@ class ChangeView(urwid.WidgetWrap):
|
||||
if keymap.CHERRY_PICK_CHANGE in commands:
|
||||
self.cherryPickChange()
|
||||
return None
|
||||
if r in self.app.config.reviewkeys:
|
||||
self.reviewKey(self.app.config.reviewkeys[r])
|
||||
if key in self.app.config.reviewkeys:
|
||||
self.reviewKey(self.app.config.reviewkeys[key])
|
||||
return None
|
||||
return r
|
||||
return key
|
||||
|
||||
def diff(self, revision_key):
|
||||
if self.app.config.diff_view == 'unified':
|
||||
|
@ -424,8 +424,10 @@ class ChangeListView(urwid.WidgetWrap):
|
||||
self.listbox.focus_position = pos
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(ChangeListView, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(ChangeListView, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
if keymap.TOGGLE_LIST_REVIEWED in commands:
|
||||
self.unreviewed = not self.unreviewed
|
||||
self.refresh()
|
||||
|
@ -451,7 +451,7 @@ class BaseDiffView(urwid.WidgetWrap):
|
||||
self.interactiveSearch(self.search)
|
||||
return None
|
||||
else:
|
||||
commands = self.app.config.keymap.getCommands(key)
|
||||
commands = self.app.config.keymap.getCommands([key])
|
||||
if keymap.INTERACTIVE_SEARCH in commands:
|
||||
self.nextSearchResult()
|
||||
return None
|
||||
@ -464,8 +464,11 @@ class BaseDiffView(urwid.WidgetWrap):
|
||||
return None
|
||||
|
||||
old_focus = self.listbox.focus
|
||||
r = super(BaseDiffView, self).keypress(size, key)
|
||||
if not self.app.input_buffer:
|
||||
r = super(BaseDiffView, self).keypress(size, key)
|
||||
new_focus = self.listbox.focus
|
||||
keys = self.app.input_buffer + [r]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
|
||||
context = self.getContextAtTop(size)
|
||||
if context:
|
||||
@ -474,7 +477,6 @@ class BaseDiffView(urwid.WidgetWrap):
|
||||
else:
|
||||
self.file_reminder.set('', '')
|
||||
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if (isinstance(old_focus, BaseDiffCommentEdit) and
|
||||
(old_focus != new_focus or (keymap.PREV_SCREEN in commands))):
|
||||
self.cleanupEdit(old_focus)
|
||||
|
@ -152,8 +152,12 @@ class ProjectListView(urwid.WidgetWrap):
|
||||
project_name, project_key=project_key, unreviewed=True))
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(ProjectListView, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(ProjectListView, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
if not self.app.input_buffer and keymap.FURTHER_INPUT not in commands:
|
||||
self.app.clearInputBuffer()
|
||||
if keymap.TOGGLE_LIST_REVIEWED in commands:
|
||||
self.unreviewed = not self.unreviewed
|
||||
self.refresh()
|
||||
@ -177,4 +181,4 @@ class ProjectListView(urwid.WidgetWrap):
|
||||
sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
|
||||
self.app.status.update()
|
||||
return None
|
||||
return r
|
||||
return key
|
||||
|
@ -49,8 +49,11 @@ class SideDiffCommentEdit(BaseDiffCommentEdit):
|
||||
self.focus_position = 1
|
||||
|
||||
def keypress(self, size, key):
|
||||
r = super(SideDiffCommentEdit, self).keypress(size, key)
|
||||
commands = self.app.config.keymap.getCommands(r)
|
||||
if not self.app.input_buffer:
|
||||
key = super(SideDiffCommentEdit, self).keypress(size, key)
|
||||
keys = self.app.input_buffer + [key]
|
||||
commands = self.app.config.keymap.getCommands(keys)
|
||||
|
||||
if ((keymap.NEXT_SELECTABLE in commands) or
|
||||
(keymap.PREV_SELECTABLE in commands)):
|
||||
if ((self.context.old_ln is not None and
|
||||
@ -61,7 +64,7 @@ class SideDiffCommentEdit(BaseDiffCommentEdit):
|
||||
else:
|
||||
self.focus_position = 3
|
||||
return None
|
||||
return r
|
||||
return key
|
||||
|
||||
class SideDiffComment(BaseDiffComment):
|
||||
def __init__(self, context, old, new):
|
||||
|
Loading…
x
Reference in New Issue
Block a user