Add a configurable keymap

Multiple keymaps may be added in either Gertty itself or in config
files, individual keys may be overriden in the standard map, and
the map can be selected via config file or command line option
just as palettes are.

Change the help text to be dynamically generated based on the
current keymap.

Change-Id: I5f8e63897fab3aa14493465256b5d4516cf47dcd
This commit is contained in:
James E. Blair 2014-08-23 18:37:04 -07:00
parent 9e38b52b63
commit 6815021f3f
9 changed files with 335 additions and 100 deletions

View File

@ -25,6 +25,7 @@ import urwid
from gertty import db from gertty import db
from gertty import config from gertty import config
from gertty import gitrepo from gertty import gitrepo
from gertty import keymap
from gertty import mywid from gertty import mywid
from gertty import sync from gertty import sync
from gertty import search from gertty import search
@ -36,9 +37,9 @@ import gertty.view
WELCOME_TEXT = """\ WELCOME_TEXT = """\
Welcome to Gertty! Welcome to Gertty!
To get started, you should subscribe to some projects. Press the "l" To get started, you should subscribe to some projects. Press the "L"
key to list all the projects, navigate to the ones you are interested key (shift-L) to list all the projects, navigate to the ones you are
in, and then press "s" to subscribe to them. Gertty will interested in, and then press "s" to subscribe to them. Gertty will
automatically sync changes in your subscribed projects. automatically sync changes in your subscribed projects.
Press the F1 key anywhere to get help. Your terminal emulator may Press the F1 key anywhere to get help. Your terminal emulator may
@ -74,7 +75,8 @@ class StatusHeader(urwid.WidgetWrap):
class SearchDialog(mywid.ButtonDialog): class SearchDialog(mywid.ButtonDialog):
signals = ['search', 'cancel'] signals = ['search', 'cancel']
def __init__(self): def __init__(self, app):
self.app = app
search_button = mywid.FixedButton('Search') search_button = mywid.FixedButton('Search')
cancel_button = mywid.FixedButton('Cancel') cancel_button = mywid.FixedButton('Cancel')
urwid.connect_signal(search_button, 'click', urwid.connect_signal(search_button, 'click',
@ -89,15 +91,18 @@ class SearchDialog(mywid.ButtonDialog):
def keypress(self, size, key): def keypress(self, size, key):
r = super(SearchDialog, self).keypress(size, key) r = super(SearchDialog, self).keypress(size, key)
if r == 'enter': commands = self.app.config.keymap.getCommands(r)
self.app.log.debug('search %s %s' % (r, commands))
if keymap.ACTIVATE in commands:
self._emit('search') self._emit('search')
return None return None
return r return r
class App(object): class App(object):
def __init__(self, server=None, palette='default', debug=False, disable_sync=False): def __init__(self, server=None, palette='default', keymap='default',
debug=False, disable_sync=False):
self.server = server self.server = server
self.config = config.Config(server, palette) self.config = config.Config(server, palette, keymap)
if debug: if debug:
level = logging.DEBUG level = logging.DEBUG
else: else:
@ -107,6 +112,7 @@ class App(object):
level=level) level=level)
self.log = logging.getLogger('gertty.App') self.log = logging.getLogger('gertty.App')
self.log.debug("Starting") self.log.debug("Starting")
self.config.keymap.updateCommandMap()
self.search = search.SearchCompiler(self) self.search = search.SearchCompiler(self)
self.db = db.Database(self) self.db = db.Database(self)
self.sync = sync.Sync(self) self.sync = sync.Sync(self)
@ -198,13 +204,25 @@ class App(object):
def help(self): def help(self):
if not hasattr(self.loop.widget, 'help'): if not hasattr(self.loop.widget, 'help'):
return return
text = mywid.GLOBAL_HELP global_help = [(self.config.keymap.formatKeys(k), t)
for (k, t) in mywid.GLOBAL_HELP]
for d in self.config.dashboards.values(): for d in self.config.dashboards.values():
space = max(9 - len(d['key']), 0) * ' ' global_help.append((keymap.formatKey(d['key']), d['name']))
text += '<%s>%s %s\n' % (d['key'], space, d['name']) parts = [('Global Keys', global_help),
text += "\nThis Screen\n" ('This Screen', self.loop.widget.help())]
text += "===========\n" keylen = 0
text += self.loop.widget.help() for title, items in parts:
for keys, text in items:
keylen = max(len(keys), keylen)
text = ''
for title, items in parts:
if text:
text += '\n'
text += title+'\n'
text += '%s\n' % ('='*len(title),)
for keys, cmdtext in items:
text += '{keys:{width}} {text}\n'.format(
keys=keys, width=keylen, text=cmdtext)
dialog = mywid.MessageDialog('Help', text) dialog = mywid.MessageDialog('Help', text)
lines = text.split('\n') lines = text.split('\n')
urwid.connect_signal(dialog, 'close', urwid.connect_signal(dialog, 'close',
@ -281,7 +299,7 @@ class App(object):
return self.error(e.message) return self.error(e.message)
def searchDialog(self): def searchDialog(self):
dialog = SearchDialog() dialog = SearchDialog(self)
urwid.connect_signal(dialog, 'cancel', urwid.connect_signal(dialog, 'cancel',
lambda button: self.backScreen()) lambda button: self.backScreen())
urwid.connect_signal(dialog, 'search', urwid.connect_signal(dialog, 'search',
@ -305,13 +323,14 @@ class App(object):
return None return None
def unhandledInput(self, key): def unhandledInput(self, key):
if key == 'esc': commands = self.config.keymap.getCommands(key)
if keymap.PREV_SCREEN in commands:
self.backScreen() self.backScreen()
elif key == 'f1' or key == '?': elif keymap.HELP in commands:
self.help() self.help()
elif key == 'ctrl q': elif keymap.QUIT in commands:
self.quit() self.quit()
elif key == 'ctrl o': elif keymap.CHANGE_SEARCH in commands:
self.searchDialog() self.searchDialog()
elif key in self.config.dashboards: elif key in self.config.dashboards:
d = self.config.dashboards[key] d = self.config.dashboards[key]
@ -339,10 +358,12 @@ def main():
help='disable remote syncing') help='disable remote syncing')
parser.add_argument('-p', dest='palette', default='default', parser.add_argument('-p', dest='palette', default='default',
help='Color palette to use') help='Color palette to use')
parser.add_argument('-k', dest='keymap', default='default',
help='Keymap to use')
parser.add_argument('server', nargs='?', parser.add_argument('server', nargs='?',
help='the server to use (as specified in config file)') help='the server to use (as specified in config file)')
args = parser.parse_args() args = parser.parse_args()
g = App(args.server, args.palette, args.debug, args.no_sync) g = App(args.server, args.palette, args.keymap, args.debug, args.no_sync)
g.run() g.run()

View File

@ -27,6 +27,7 @@ import voluptuous as v
import gertty.commentlink import gertty.commentlink
import gertty.palette import gertty.palette
import gertty.keymap
try: try:
OrderedDict = collections.OrderedDict OrderedDict = collections.OrderedDict
@ -89,10 +90,17 @@ class ConfigSchema(object):
hide_comments = [hide_comment] hide_comments = [hide_comment]
keymap = {v.Required('name'): str,
v.Match('(?!name)'): v.Any([str], str)}
keymaps = [keymap]
def getSchema(self, data): def getSchema(self, data):
schema = v.Schema({v.Required('servers'): self.servers, schema = v.Schema({v.Required('servers'): self.servers,
'palettes': self.palettes, 'palettes': self.palettes,
'palette': str, 'palette': str,
'keymaps': self.keymaps,
'keymap': str,
'commentlinks': self.commentlinks, 'commentlinks': self.commentlinks,
'dashboards': self.dashboards, 'dashboards': self.dashboards,
'reviewkeys': self.reviewkeys, 'reviewkeys': self.reviewkeys,
@ -103,7 +111,7 @@ class ConfigSchema(object):
return schema return schema
class Config(object): class Config(object):
def __init__(self, server=None, palette='default', def __init__(self, server=None, palette='default', keymap='default',
path=DEFAULT_CONFIG_PATH): path=DEFAULT_CONFIG_PATH):
self.path = os.path.expanduser(path) self.path = os.path.expanduser(path)
@ -144,6 +152,14 @@ class Config(object):
self.palettes[p['name']].update(p) self.palettes[p['name']].update(p)
self.palette = self.palettes[self.config.get('palette', palette)] self.palette = self.palettes[self.config.get('palette', palette)]
self.keymaps = {'default': gertty.keymap.KeyMap({})}
for p in self.config.get('keymaps', []):
if p['name'] not in self.keymaps:
self.keymaps[p['name']] = gertty.keymap.KeyMap(p)
else:
self.keymaps[p['name']].update(p)
self.keymap = self.keymaps[self.config.get('keymap', keymap)]
self.commentlinks = [gertty.commentlink.CommentLink(c) self.commentlinks = [gertty.commentlink.CommentLink(c)
for c in self.config.get('commentlinks', [])] for c in self.config.get('commentlinks', [])]
self.commentlinks.append( self.commentlinks.append(

162
gertty/keymap.py Normal file
View File

@ -0,0 +1,162 @@
# Copyright 2014 OpenStack Foundation
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
import string
import urwid
# urwid command map:
REDRAW_SCREEN = urwid.REDRAW_SCREEN
CURSOR_UP = urwid.CURSOR_UP
CURSOR_DOWN = urwid.CURSOR_DOWN
CURSOR_LEFT = urwid.CURSOR_LEFT
CURSOR_RIGHT = urwid.CURSOR_RIGHT
CURSOR_PAGE_UP = urwid.CURSOR_PAGE_UP
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:
PREV_SCREEN = 'previous screen'
HELP = 'help'
QUIT = 'quit'
CHANGE_SEARCH = 'change search'
# Change screen:
TOGGLE_REVIEWED = 'toggle reviewed'
TOGGLE_HIDDEN = 'toggle hidden'
REVIEW = 'review'
DIFF = 'diff'
CHECKOUT = 'checkout'
CHERRY_PICK = 'cherry pick'
SEARCH_RESULTS = 'search results'
NEXT_CHANGE = 'next change'
PREV_CHANGE = 'previous change'
TOGGLE_HIDDEN_COMMENTS = 'toggle hidden comments'
REFRESH = 'refresh'
# Project list screen:
TOGGLE_LIST_REVIEWED = 'toggle list reviewed'
TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed'
TOGGLE_SUBSCRIBED = 'toggle subscribed'
# Diff screens:
SELECT_PATCHSETS = 'select patchsets'
NEXT_SELECTABLE = 'next selectable'
PREV_SELECTABLE = 'prev selectable'
DEFAULT_KEYMAP = {
REDRAW_SCREEN: 'ctrl l',
CURSOR_UP: 'up',
CURSOR_DOWN: 'down',
CURSOR_LEFT: 'left',
CURSOR_RIGHT: 'right',
CURSOR_PAGE_UP: 'page up',
CURSOR_PAGE_DOWN: 'page down',
CURSOR_MAX_LEFT: 'home',
CURSOR_MAX_RIGHT: 'end',
ACTIVATE: 'enter',
PREV_SCREEN: 'esc',
HELP: ['f1', 'h'],
QUIT: 'ctrl q',
CHANGE_SEARCH: 'ctrl o',
TOGGLE_REVIEWED: 'v',
TOGGLE_HIDDEN: 'k',
REVIEW: 'r',
DIFF: 'd',
CHECKOUT: 'c',
CHERRY_PICK: 'x',
SEARCH_RESULTS: 'u',
NEXT_CHANGE: 'n',
PREV_CHANGE: 'p',
TOGGLE_HIDDEN_COMMENTS: 't',
REFRESH: 'ctrl r',
TOGGLE_LIST_REVIEWED: 'l',
TOGGLE_LIST_SUBSCRIBED: 'L',
TOGGLE_SUBSCRIBED: 's',
SELECT_PATCHSETS: 'p',
NEXT_SELECTABLE: 'tab',
PREV_SELECTABLE: 'shift tab',
}
URWID_COMMANDS = frozenset((
urwid.REDRAW_SCREEN,
urwid.CURSOR_UP,
urwid.CURSOR_DOWN,
urwid.CURSOR_LEFT,
urwid.CURSOR_RIGHT,
urwid.CURSOR_PAGE_UP,
urwid.CURSOR_PAGE_DOWN,
urwid.CURSOR_MAX_LEFT,
urwid.CURSOR_MAX_RIGHT,
urwid.ACTIVATE,
))
FORMAT_SUBS = (
(re.compile('ctrl '), 'CTRL-'),
(re.compile('meta '), 'META-'),
(re.compile('f(\d+)'), 'F\\1'),
(re.compile('([a-z][a-z]+)'), lambda x: string.upper(x.group(1))),
)
def formatKey(key):
for subre, repl in FORMAT_SUBS:
key = subre.sub(repl, key)
return key
class KeyMap(object):
def __init__(self, config):
# key -> [commands]
self.keymap = {}
self.commandmap = {}
self.update(DEFAULT_KEYMAP)
self.update(config)
def update(self, config):
# command -> [keys]
for command, keys in config.items():
if command == 'name':
continue
if type(keys) != type([]):
keys = [keys]
self.commandmap[command] = keys
self.keymap = {}
for command, keys in self.commandmap.items():
for key in keys:
if key in self.keymap:
self.keymap[key].append(command)
else:
self.keymap[key] = [command]
def getCommands(self, key):
return self.keymap.get(key, [])
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:
command = command.replace('-', ' ')
if command in URWID_COMMANDS:
urwid.command_map[key]=command
def formatKeys(self, command):
keys = self.getKeys(command)
keys = [formatKey(k) for k in keys]
return ' or '.join(keys)

View File

@ -14,14 +14,18 @@
import urwid import urwid
GLOBAL_HELP = """\ from gertty import keymap
Global Keys
=========== GLOBAL_HELP = (
<F1> or <?> Display help. (keymap.HELP,
<ESC> Back to previous screen. "Display help"),
<CTRL-Q> Quit Gertty. (keymap.PREV_SCREEN,
<CTRL-O> Search for changes. "Back to previous screen"),
""" (keymap.QUIT,
"Quit Gertty"),
(keymap.CHANGE_SEARCH,
"Search for changes"),
)
class TextButton(urwid.Button): class TextButton(urwid.Button):
def selectable(self): def selectable(self):
@ -176,7 +180,7 @@ class HyperText(urwid.Text):
if self.focusNextItem(): if self.focusNextItem():
return False return False
return key return key
elif key == 'enter': elif self._command_map[key] == urwid.ACTIVATE:
self.select() self.select()
return False return False
return key return key

View File

@ -18,6 +18,7 @@ import datetime
import urwid import urwid
from gertty import gitrepo from gertty import gitrepo
from gertty import keymap
from gertty import mywid from gertty import mywid
from gertty import sync from gertty import sync
from gertty.view import side_diff as view_side_diff from gertty.view import side_diff as view_side_diff
@ -97,7 +98,8 @@ class ReviewDialog(urwid.WidgetWrap):
def keypress(self, size, key): def keypress(self, size, key):
r = super(ReviewDialog, self).keypress(size, key) r = super(ReviewDialog, self).keypress(size, key)
if r=='esc': commands = self.app.config.keymap.getCommands(r)
if keymap.PREV_SCREEN in commands:
self._emit('cancel') self._emit('cancel')
return None return None
return r return r
@ -282,27 +284,38 @@ class CommitMessageBox(mywid.HyperText):
super(CommitMessageBox, self).set_text(text) super(CommitMessageBox, self).set_text(text)
class ChangeView(urwid.WidgetWrap): class ChangeView(urwid.WidgetWrap):
_help = """
<c> Checkout the most recent revision into the local repo.
<d> Show the diff of the mont recent revision.
<k> Toggle the hidden flag for the current change.
<n> Go to the next change in the list.
<p> Go to the previous change in the list.
<r> Leave a review for the most recent revision.
<t> Toggle display of hidden comments.
<u> Back to the list of changes.
<v> Toggle the reviewed flag for the current change.
<x> Cherry-pick the most recent revision onto the local repo.
<ctrl-r> Refresh this change.
"""
def help(self): def help(self):
text = self._help key = self.app.config.keymap.formatKeys
ret = [
(key(keymap.CHECKOUT),
"Checkout the most recent revision into the local repo"),
(key(keymap.DIFF),
"Show the diff of the mont recent revision"),
(key(keymap.TOGGLE_HIDDEN),
"Toggle the hidden flag for the current change"),
(key(keymap.NEXT_CHANGE),
"Go to the next change in the list"),
(key(keymap.PREV_CHANGE),
"Go to the previous change in the list"),
(key(keymap.REVIEW),
"Leave a review for the most recent revision"),
(key(keymap.TOGGLE_HIDDEN_COMMENTS),
"Toggle display of hidden comments"),
(key(keymap.SEARCH_RESULTS),
"Back to the list of changes"),
(key(keymap.TOGGLE_REVIEWED),
"Toggle the reviewed flag for the current change"),
(key(keymap.CHERRY_PICK),
"Cherry-pick the most recent revision onto the local repo"),
(key(keymap.REFRESH),
"Refresh this change"),
]
for k in self.app.config.reviewkeys.values(): for k in self.app.config.reviewkeys.values():
space = max(6 - len(k['key']), 0) * ' '
action = ', '.join(['{category}:{value}'.format(**a) for a in k['approvals']]) action = ', '.join(['{category}:{value}'.format(**a) for a in k['approvals']])
text += '<%s>%s %s\n' % (k['key'], space, action) ret.append((keymap.formatKey(k['key']), action))
return text
return ret
def __init__(self, app, change_key): def __init__(self, app, change_key):
super(ChangeView, self).__init__(urwid.Pile([])) super(ChangeView, self).__init__(urwid.Pile([]))
@ -608,37 +621,39 @@ class ChangeView(urwid.WidgetWrap):
def keypress(self, size, key): def keypress(self, size, key):
r = super(ChangeView, self).keypress(size, key) r = super(ChangeView, self).keypress(size, key)
if r == 'v': commands = self.app.config.keymap.getCommands(r)
if keymap.TOGGLE_REVIEWED in commands:
self.toggleReviewed() self.toggleReviewed()
self.refresh() self.refresh()
return None return None
if r == 'k': if keymap.TOGGLE_HIDDEN in commands:
self.toggleHidden() self.toggleHidden()
self.refresh() self.refresh()
return None return None
if r == 'r': if keymap.REVIEW in commands:
row = self.revision_rows[self.last_revision_key] row = self.revision_rows[self.last_revision_key]
row.review_button.openReview() row.review_button.openReview()
return None return None
if r == 'd': if keymap.DIFF in commands:
row = self.revision_rows[self.last_revision_key] row = self.revision_rows[self.last_revision_key]
row.diff(None) row.diff(None)
return None return None
if r == 'c': if keymap.CHECKOUT in commands:
row = self.revision_rows[self.last_revision_key] row = self.revision_rows[self.last_revision_key]
row.checkout(None) row.checkout(None)
return None return None
if r == 'x': if keymap.CHERRY_PICK in commands:
row = self.revision_rows[self.last_revision_key] row = self.revision_rows[self.last_revision_key]
row.cherryPick(None) row.cherryPick(None)
return None return None
if r == 'u': if keymap.SEARCH_RESULTS in commands:
widget = self.app.findChangeList() widget = self.app.findChangeList()
self.app.backScreen(widget) self.app.backScreen(widget)
return None return None
if r in ['n', 'p']: if ((keymap.NEXT_CHANGE in commands) or
(keymap.PREV_CHANGE in commands)):
widget = self.app.findChangeList() widget = self.app.findChangeList()
if r == 'n': if keymap.NEXT_CHANGE in commands:
new_change_key = widget.getNextChangeKey(self.change_key) new_change_key = widget.getNextChangeKey(self.change_key)
else: else:
new_change_key = widget.getPrevChangeKey(self.change_key) new_change_key = widget.getPrevChangeKey(self.change_key)
@ -649,11 +664,11 @@ class ChangeView(urwid.WidgetWrap):
except gertty.view.DisplayError as e: except gertty.view.DisplayError as e:
self.app.error(e.message) self.app.error(e.message)
return None return None
if r == 't': if keymap.TOGGLE_HIDDEN_COMMENTS in commands:
self.hide_comments = not self.hide_comments self.hide_comments = not self.hide_comments
self.refresh() self.refresh()
return None return None
if r == 'ctrl r': if keymap.REFRESH in commands:
self.app.sync.submitTask( self.app.sync.submitTask(
sync.SyncChangeTask(self.change_rest_id, priority=sync.HIGH_PRIORITY)) sync.SyncChangeTask(self.change_rest_id, priority=sync.HIGH_PRIORITY))
self.app.status.update() self.app.status.update()

View File

@ -15,6 +15,7 @@
import urwid import urwid
from gertty import keymap
from gertty import mywid from gertty import mywid
from gertty import sync from gertty import sync
from gertty.view import change as view_change from gertty.view import change as view_change
@ -68,15 +69,18 @@ class ChangeListHeader(urwid.WidgetWrap):
self._w.contents.append((urwid.Text(' %s' % category[0]), self._w.options('given', 3))) self._w.contents.append((urwid.Text(' %s' % category[0]), self._w.options('given', 3)))
class ChangeListView(urwid.WidgetWrap): class ChangeListView(urwid.WidgetWrap):
_help = """
<k> Toggle the hidden flag for the currently selected change.
<l> Toggle whether only unreviewed or all changes are displayed.
<v> Toggle the reviewed flag for the currently selected change.
<ctrl-r> Sync all projects.
"""
def help(self): def help(self):
return self._help key = self.app.config.keymap.formatKeys
return [
(key(keymap.TOGGLE_HIDDEN),
"Toggle the hidden flag for the currently selected change"),
(key(keymap.TOGGLE_LIST_REVIEWED),
"Toggle whether only unreviewed or all changes are displayed"),
(key(keymap.TOGGLE_REVIEWED),
"Toggle the reviewed flag for the currently selected change"),
(key(keymap.REFRESH),
"Sync all projects")
]
def __init__(self, app, query, query_desc=None, unreviewed=False): def __init__(self, app, query, query_desc=None, unreviewed=False):
super(ChangeListView, self).__init__(urwid.Pile([])) super(ChangeListView, self).__init__(urwid.Pile([]))
@ -152,30 +156,32 @@ class ChangeListView(urwid.WidgetWrap):
return ret return ret
def keypress(self, size, key): def keypress(self, size, key):
if key=='l': r = super(ChangeListView, self).keypress(size, key)
commands = self.app.config.keymap.getCommands(r)
if keymap.TOGGLE_LIST_REVIEWED in commands:
self.unreviewed = not self.unreviewed self.unreviewed = not self.unreviewed
self.refresh() self.refresh()
return None return None
if key=='v': if keymap.TOGGLE_REVIEWED in commands:
if not len(self.listbox.body): if not len(self.listbox.body):
return None return None
pos = self.listbox.focus_position pos = self.listbox.focus_position
reviewed = self.toggleReviewed(self.listbox.body[pos].change_key) reviewed = self.toggleReviewed(self.listbox.body[pos].change_key)
self.refresh() self.refresh()
return None return None
if key=='k': if keymap.TOGGLE_HIDDEN in commands:
if not len(self.listbox.body): if not len(self.listbox.body):
return None return None
pos = self.listbox.focus_position pos = self.listbox.focus_position
hidden = self.toggleHidden(self.listbox.body[pos].change_key) hidden = self.toggleHidden(self.listbox.body[pos].change_key)
self.refresh() self.refresh()
return None return None
if key == 'ctrl r': if keymap.REFRESH in commands:
self.app.sync.submitTask( self.app.sync.submitTask(
sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY)) sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
self.app.status.update() self.app.status.update()
return None return None
return super(ChangeListView, self).keypress(size, key) return key
def onSelect(self, button, change_key): def onSelect(self, button, change_key):
try: try:

View File

@ -17,6 +17,7 @@ import logging
import urwid import urwid
from gertty import keymap
from gertty import mywid from gertty import mywid
from gertty import gitrepo from gertty import gitrepo
@ -144,13 +145,14 @@ class DiffContextButton(urwid.WidgetWrap):
self.view.expandChunk(self.diff, self.chunk, from_end=-10) self.view.expandChunk(self.diff, self.chunk, from_end=-10)
class BaseDiffView(urwid.WidgetWrap): class BaseDiffView(urwid.WidgetWrap):
_help = """
<Enter> Add an inline comment
<p> Select old/new patchsets to diff
"""
def help(self): def help(self):
return self._help key = self.app.config.keymap.formatKeys
return [
(key(keymap.ACTIVATE),
"Add an inline comment"),
(key(keymap.SELECT_PATCHSETS),
"Select old/new patchsets to diff"),
]
def __init__(self, app, new_revision_key): def __init__(self, app, new_revision_key):
super(BaseDiffView, self).__init__(urwid.Pile([])) super(BaseDiffView, self).__init__(urwid.Pile([]))
@ -346,10 +348,11 @@ class BaseDiffView(urwid.WidgetWrap):
old_focus = self.listbox.focus old_focus = self.listbox.focus
r = super(BaseDiffView, self).keypress(size, key) r = super(BaseDiffView, self).keypress(size, key)
new_focus = self.listbox.focus new_focus = self.listbox.focus
commands = self.app.config.keymap.getCommands(r)
if (isinstance(old_focus, BaseDiffCommentEdit) and if (isinstance(old_focus, BaseDiffCommentEdit) and
(old_focus != new_focus or key == 'esc')): (old_focus != new_focus or (keymap.PREV_SCREEN in commands))):
self.cleanupEdit(old_focus) self.cleanupEdit(old_focus)
if r == 'p': if keymap.SELECT_PATCHSETS in commands:
self.openPatchsetDialog() self.openPatchsetDialog()
return None return None
return r return r

View File

@ -15,6 +15,7 @@
import urwid import urwid
from gertty import keymap
from gertty import mywid from gertty import mywid
from gertty import sync from gertty import sync
from gertty.view import change_list as view_change_list from gertty.view import change_list as view_change_list
@ -66,15 +67,18 @@ class ProjectListHeader(urwid.WidgetWrap):
super(ProjectListHeader, self).__init__(urwid.Columns(cols)) super(ProjectListHeader, self).__init__(urwid.Columns(cols))
class ProjectListView(urwid.WidgetWrap): class ProjectListView(urwid.WidgetWrap):
_help = """
<l> Toggle whether only subscribed projects or all projects are listed.
<L> Toggle listing of projects with unreviewed changes.
<s> Toggle the subscription flag for the currently selected project.
<ctrl-r> Sync all projects.
"""
def help(self): def help(self):
return self._help key = self.app.config.keymap.formatKeys
return [
(key(keymap.TOGGLE_LIST_SUBSCRIBED),
"Toggle whether only subscribed projects or all projects are listed"),
(key(keymap.TOGGLE_LIST_REVIEWED),
"Toggle listing of projects with unreviewed changes"),
(key(keymap.TOGGLE_SUBSCRIBED),
"Toggle the subscription flag for the currently selected project"),
(key(keymap.REFRESH),
"Sync all projects")
]
def __init__(self, app): def __init__(self, app):
super(ProjectListView, self).__init__(urwid.Pile([])) super(ProjectListView, self).__init__(urwid.Pile([]))
@ -138,15 +142,17 @@ class ProjectListView(urwid.WidgetWrap):
project_name, unreviewed=True)) project_name, unreviewed=True))
def keypress(self, size, key): def keypress(self, size, key):
if key=='L': r = super(ProjectListView, self).keypress(size, key)
commands = self.app.config.keymap.getCommands(r)
if keymap.TOGGLE_LIST_REVIEWED in commands:
self.unreviewed = not self.unreviewed self.unreviewed = not self.unreviewed
self.refresh() self.refresh()
return None return None
if key=='l': if keymap.TOGGLE_LIST_SUBSCRIBED in commands:
self.subscribed = not self.subscribed self.subscribed = not self.subscribed
self.refresh() self.refresh()
return None return None
if key=='s': if keymap.TOGGLE_SUBSCRIBED in commands:
if not len(self.listbox.body): if not len(self.listbox.body):
return None return None
pos = self.listbox.focus_position pos = self.listbox.focus_position
@ -156,11 +162,9 @@ class ProjectListView(urwid.WidgetWrap):
if subscribed: if subscribed:
self.app.sync.submitTask(sync.SyncProjectTask(project_key)) self.app.sync.submitTask(sync.SyncProjectTask(project_key))
return None return None
if key == 'ctrl r': if keymap.REFRESH in commands:
self.app.sync.submitTask( self.app.sync.submitTask(
sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY)) sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
self.app.status.update() self.app.status.update()
return None return None
return super(ProjectListView, self).keypress(size, key) return r

View File

@ -17,13 +17,15 @@ import logging
import urwid import urwid
from gertty import keymap
from gertty import mywid from gertty import mywid
from gertty import gitrepo from gertty import gitrepo
from gertty.view.diff import * from gertty.view.diff import *
class SideDiffCommentEdit(BaseDiffCommentEdit): class SideDiffCommentEdit(BaseDiffCommentEdit):
def __init__(self, context, old_key=None, new_key=None, old=u'', new=u''): def __init__(self, app, context, old_key=None, new_key=None, old=u'', new=u''):
super(SideDiffCommentEdit, self).__init__([]) super(SideDiffCommentEdit, self).__init__([])
self.app = app
self.context = context self.context = context
# If we save a comment, the resulting key will be stored here # If we save a comment, the resulting key will be stored here
self.old_key = old_key self.old_key = old_key
@ -38,7 +40,9 @@ class SideDiffCommentEdit(BaseDiffCommentEdit):
def keypress(self, size, key): def keypress(self, size, key):
r = super(SideDiffCommentEdit, self).keypress(size, key) r = super(SideDiffCommentEdit, self).keypress(size, key)
if r in ['tab', 'shift tab']: commands = self.app.config.keymap.getCommands(r)
if ((keymap.NEXT_SELECTABLE in commands) or
(keymap.PREV_SELECTABLE in commands)):
if self.focus_position == 3: if self.focus_position == 3:
self.focus_position = 1 self.focus_position = 1
else: else:
@ -137,7 +141,7 @@ class SideDiffView(BaseDiffView):
(old_comment_key, old_comment) = old_list.pop(0) (old_comment_key, old_comment) = old_list.pop(0)
if new_list: if new_list:
(new_comment_key, new_comment) = new_list.pop(0) (new_comment_key, new_comment) = new_list.pop(0)
lines.append(SideDiffCommentEdit(context, lines.append(SideDiffCommentEdit(self.app, context,
old_comment_key, old_comment_key,
new_comment_key, new_comment_key,
old_comment, new_comment)) old_comment, new_comment))
@ -178,14 +182,14 @@ class SideDiffView(BaseDiffView):
(old_comment_key, old_comment) = old_list.pop(0) (old_comment_key, old_comment) = old_list.pop(0)
if new_list: if new_list:
(new_comment_key, new_comment) = new_list.pop(0) (new_comment_key, new_comment) = new_list.pop(0)
lines.append(SideDiffCommentEdit(context, lines.append(SideDiffCommentEdit(self.app, context,
old_comment_key, old_comment_key,
new_comment_key, new_comment_key,
old_comment, new_comment)) old_comment, new_comment))
return lines return lines
def makeCommentEdit(self, edit): def makeCommentEdit(self, edit):
return SideDiffCommentEdit(edit.context) return SideDiffCommentEdit(self.app, edit.context)
def cleanupEdit(self, edit): def cleanupEdit(self, edit):
if edit.old_key: if edit.old_key: