diff --git a/gertty/app.py b/gertty/app.py
index 77faacb..87aad46 100644
--- a/gertty/app.py
+++ b/gertty/app.py
@@ -25,6 +25,7 @@ import urwid
 from gertty import db
 from gertty import config
 from gertty import gitrepo
+from gertty import keymap
 from gertty import mywid
 from gertty import sync
 from gertty import search
@@ -36,9 +37,9 @@ import gertty.view
 WELCOME_TEXT = """\
 Welcome to Gertty!
 
-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
-in, and then press "s" to subscribe to them.  Gertty will
+To get started, you should subscribe to some projects.  Press the "L"
+key (shift-L) to list all the projects, navigate to the ones you are
+interested in, and then press "s" to subscribe to them.  Gertty will
 automatically sync changes in your subscribed projects.
 
 Press the F1 key anywhere to get help.  Your terminal emulator may
@@ -74,7 +75,8 @@ class StatusHeader(urwid.WidgetWrap):
 
 class SearchDialog(mywid.ButtonDialog):
     signals = ['search', 'cancel']
-    def __init__(self):
+    def __init__(self, app):
+        self.app = app
         search_button = mywid.FixedButton('Search')
         cancel_button = mywid.FixedButton('Cancel')
         urwid.connect_signal(search_button, 'click',
@@ -89,15 +91,18 @@ class SearchDialog(mywid.ButtonDialog):
 
     def keypress(self, 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')
             return None
         return r
 
 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.config = config.Config(server, palette)
+        self.config = config.Config(server, palette, keymap)
         if debug:
             level = logging.DEBUG
         else:
@@ -107,6 +112,7 @@ class App(object):
                             level=level)
         self.log = logging.getLogger('gertty.App')
         self.log.debug("Starting")
+        self.config.keymap.updateCommandMap()
         self.search = search.SearchCompiler(self)
         self.db = db.Database(self)
         self.sync = sync.Sync(self)
@@ -198,13 +204,25 @@ class App(object):
     def help(self):
         if not hasattr(self.loop.widget, 'help'):
             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():
-            space = max(9 - len(d['key']), 0) * ' '
-            text += '<%s>%s %s\n' % (d['key'], space, d['name'])
-        text += "\nThis Screen\n"
-        text += "===========\n"
-        text += self.loop.widget.help()
+            global_help.append((keymap.formatKey(d['key']), d['name']))
+        parts = [('Global Keys', global_help),
+                 ('This Screen', self.loop.widget.help())]
+        keylen = 0
+        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)
         lines = text.split('\n')
         urwid.connect_signal(dialog, 'close',
@@ -281,7 +299,7 @@ class App(object):
             return self.error(e.message)
 
     def searchDialog(self):
-        dialog = SearchDialog()
+        dialog = SearchDialog(self)
         urwid.connect_signal(dialog, 'cancel',
             lambda button: self.backScreen())
         urwid.connect_signal(dialog, 'search',
@@ -305,13 +323,14 @@ class App(object):
         return None
 
     def unhandledInput(self, key):
-        if key == 'esc':
+        commands = self.config.keymap.getCommands(key)
+        if keymap.PREV_SCREEN in commands:
             self.backScreen()
-        elif key == 'f1' or key == '?':
+        elif keymap.HELP in commands:
             self.help()
-        elif key == 'ctrl q':
+        elif keymap.QUIT in commands:
             self.quit()
-        elif key == 'ctrl o':
+        elif keymap.CHANGE_SEARCH in commands:
             self.searchDialog()
         elif key in self.config.dashboards:
             d = self.config.dashboards[key]
@@ -339,10 +358,12 @@ def main():
                         help='disable remote syncing')
     parser.add_argument('-p', dest='palette', default='default',
                         help='Color palette to use')
+    parser.add_argument('-k', dest='keymap', default='default',
+                        help='Keymap to use')
     parser.add_argument('server', nargs='?',
                         help='the server to use (as specified in config file)')
     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()
 
 
diff --git a/gertty/config.py b/gertty/config.py
index f8fe71c..3f34758 100644
--- a/gertty/config.py
+++ b/gertty/config.py
@@ -27,6 +27,7 @@ import voluptuous as v
 
 import gertty.commentlink
 import gertty.palette
+import gertty.keymap
 
 try:
     OrderedDict = collections.OrderedDict
@@ -89,10 +90,17 @@ class ConfigSchema(object):
 
     hide_comments = [hide_comment]
 
+    keymap = {v.Required('name'): str,
+              v.Match('(?!name)'): v.Any([str], str)}
+
+    keymaps = [keymap]
+
     def getSchema(self, data):
         schema = v.Schema({v.Required('servers'): self.servers,
                            'palettes': self.palettes,
                            'palette': str,
+                           'keymaps': self.keymaps,
+                           'keymap': str,
                            'commentlinks': self.commentlinks,
                            'dashboards': self.dashboards,
                            'reviewkeys': self.reviewkeys,
@@ -103,7 +111,7 @@ class ConfigSchema(object):
         return schema
 
 class Config(object):
-    def __init__(self, server=None, palette='default',
+    def __init__(self, server=None, palette='default', keymap='default',
                  path=DEFAULT_CONFIG_PATH):
         self.path = os.path.expanduser(path)
 
@@ -144,6 +152,14 @@ class Config(object):
                 self.palettes[p['name']].update(p)
         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)
                              for c in self.config.get('commentlinks', [])]
         self.commentlinks.append(
diff --git a/gertty/keymap.py b/gertty/keymap.py
new file mode 100644
index 0000000..803627b
--- /dev/null
+++ b/gertty/keymap.py
@@ -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)
diff --git a/gertty/mywid.py b/gertty/mywid.py
index 71263ca..42476f9 100644
--- a/gertty/mywid.py
+++ b/gertty/mywid.py
@@ -14,14 +14,18 @@
 
 import urwid
 
-GLOBAL_HELP = """\
-Global Keys
-===========
-<F1> or <?> Display help.
-<ESC>       Back to previous screen.
-<CTRL-Q>    Quit Gertty.
-<CTRL-O>    Search for changes.
-"""
+from gertty import keymap
+
+GLOBAL_HELP = (
+    (keymap.HELP,
+     "Display help"),
+    (keymap.PREV_SCREEN,
+     "Back to previous screen"),
+    (keymap.QUIT,
+     "Quit Gertty"),
+    (keymap.CHANGE_SEARCH,
+     "Search for changes"),
+    )
 
 class TextButton(urwid.Button):
     def selectable(self):
@@ -176,7 +180,7 @@ class HyperText(urwid.Text):
             if self.focusNextItem():
                 return False
             return key
-        elif key == 'enter':
+        elif self._command_map[key] == urwid.ACTIVATE:
             self.select()
             return False
         return key
diff --git a/gertty/view/change.py b/gertty/view/change.py
index 0fa7e1e..1f60b93 100644
--- a/gertty/view/change.py
+++ b/gertty/view/change.py
@@ -18,6 +18,7 @@ import datetime
 import urwid
 
 from gertty import gitrepo
+from gertty import keymap
 from gertty import mywid
 from gertty import sync
 from gertty.view import side_diff as view_side_diff
@@ -97,7 +98,8 @@ class ReviewDialog(urwid.WidgetWrap):
 
     def keypress(self, 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')
             return None
         return r
@@ -282,27 +284,38 @@ class CommitMessageBox(mywid.HyperText):
         super(CommitMessageBox, self).set_text(text)
 
 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):
-        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():
-            space = max(6 - len(k['key']), 0) * ' '
             action = ', '.join(['{category}:{value}'.format(**a) for a in k['approvals']])
-            text += '<%s>%s %s\n' % (k['key'], space, action)
-        return text
+            ret.append((keymap.formatKey(k['key']), action))
+
+        return ret
 
     def __init__(self, app, change_key):
         super(ChangeView, self).__init__(urwid.Pile([]))
@@ -608,37 +621,39 @@ class ChangeView(urwid.WidgetWrap):
 
     def keypress(self, 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.refresh()
             return None
-        if r == 'k':
+        if keymap.TOGGLE_HIDDEN in commands:
             self.toggleHidden()
             self.refresh()
             return None
-        if r == 'r':
+        if keymap.REVIEW in commands:
             row = self.revision_rows[self.last_revision_key]
             row.review_button.openReview()
             return None
-        if r == 'd':
+        if keymap.DIFF in commands:
             row = self.revision_rows[self.last_revision_key]
             row.diff(None)
             return None
-        if r == 'c':
+        if keymap.CHECKOUT in commands:
             row = self.revision_rows[self.last_revision_key]
             row.checkout(None)
             return None
-        if r == 'x':
+        if keymap.CHERRY_PICK in commands:
             row = self.revision_rows[self.last_revision_key]
             row.cherryPick(None)
             return None
-        if r == 'u':
+        if keymap.SEARCH_RESULTS in commands:
             widget = self.app.findChangeList()
             self.app.backScreen(widget)
             return None
-        if r in ['n', 'p']:
+        if ((keymap.NEXT_CHANGE in commands) or
+            (keymap.PREV_CHANGE in commands)):
             widget = self.app.findChangeList()
-            if r == 'n':
+            if keymap.NEXT_CHANGE in commands:
                 new_change_key = widget.getNextChangeKey(self.change_key)
             else:
                 new_change_key = widget.getPrevChangeKey(self.change_key)
@@ -649,11 +664,11 @@ class ChangeView(urwid.WidgetWrap):
                 except gertty.view.DisplayError as e:
                     self.app.error(e.message)
             return None
-        if r == 't':
+        if keymap.TOGGLE_HIDDEN_COMMENTS in commands:
             self.hide_comments = not self.hide_comments
             self.refresh()
             return None
-        if r == 'ctrl r':
+        if keymap.REFRESH in commands:
             self.app.sync.submitTask(
                 sync.SyncChangeTask(self.change_rest_id, priority=sync.HIGH_PRIORITY))
             self.app.status.update()
diff --git a/gertty/view/change_list.py b/gertty/view/change_list.py
index 5b3bf6f..3c55131 100644
--- a/gertty/view/change_list.py
+++ b/gertty/view/change_list.py
@@ -15,6 +15,7 @@
 
 import urwid
 
+from gertty import keymap
 from gertty import mywid
 from gertty import sync
 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)))
 
 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):
-        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):
         super(ChangeListView, self).__init__(urwid.Pile([]))
@@ -152,30 +156,32 @@ class ChangeListView(urwid.WidgetWrap):
         return ret
 
     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.refresh()
             return None
-        if key=='v':
+        if keymap.TOGGLE_REVIEWED in commands:
             if not len(self.listbox.body):
                 return None
             pos = self.listbox.focus_position
             reviewed = self.toggleReviewed(self.listbox.body[pos].change_key)
             self.refresh()
             return None
-        if key=='k':
+        if keymap.TOGGLE_HIDDEN in commands:
             if not len(self.listbox.body):
                 return None
             pos = self.listbox.focus_position
             hidden = self.toggleHidden(self.listbox.body[pos].change_key)
             self.refresh()
             return None
-        if key == 'ctrl r':
+        if keymap.REFRESH in commands:
             self.app.sync.submitTask(
                 sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
             self.app.status.update()
             return None
-        return super(ChangeListView, self).keypress(size, key)
+        return key
 
     def onSelect(self, button, change_key):
         try:
diff --git a/gertty/view/diff.py b/gertty/view/diff.py
index 4c22b85..78ce11e 100644
--- a/gertty/view/diff.py
+++ b/gertty/view/diff.py
@@ -17,6 +17,7 @@ import logging
 
 import urwid
 
+from gertty import keymap
 from gertty import mywid
 from gertty import gitrepo
 
@@ -144,13 +145,14 @@ class DiffContextButton(urwid.WidgetWrap):
         self.view.expandChunk(self.diff, self.chunk, from_end=-10)
 
 class BaseDiffView(urwid.WidgetWrap):
-    _help = """
-<Enter> Add an inline comment
-<p>     Select old/new patchsets to diff
-"""
-
     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):
         super(BaseDiffView, self).__init__(urwid.Pile([]))
@@ -346,10 +348,11 @@ class BaseDiffView(urwid.WidgetWrap):
         old_focus = self.listbox.focus
         r = super(BaseDiffView, self).keypress(size, key)
         new_focus = self.listbox.focus
+        commands = self.app.config.keymap.getCommands(r)
         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)
-        if r == 'p':
+        if keymap.SELECT_PATCHSETS in commands:
             self.openPatchsetDialog()
             return None
         return r
diff --git a/gertty/view/project_list.py b/gertty/view/project_list.py
index b6dfe2f..87dbceb 100644
--- a/gertty/view/project_list.py
+++ b/gertty/view/project_list.py
@@ -15,6 +15,7 @@
 
 import urwid
 
+from gertty import keymap
 from gertty import mywid
 from gertty import sync
 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))
 
 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):
-        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):
         super(ProjectListView, self).__init__(urwid.Pile([]))
@@ -138,15 +142,17 @@ class ProjectListView(urwid.WidgetWrap):
                 project_name, unreviewed=True))
 
     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.refresh()
             return None
-        if key=='l':
+        if keymap.TOGGLE_LIST_SUBSCRIBED in commands:
             self.subscribed = not self.subscribed
             self.refresh()
             return None
-        if key=='s':
+        if keymap.TOGGLE_SUBSCRIBED in commands:
             if not len(self.listbox.body):
                 return None
             pos = self.listbox.focus_position
@@ -156,11 +162,9 @@ class ProjectListView(urwid.WidgetWrap):
             if subscribed:
                 self.app.sync.submitTask(sync.SyncProjectTask(project_key))
             return None
-        if key == 'ctrl r':
+        if keymap.REFRESH in commands:
             self.app.sync.submitTask(
                 sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
             self.app.status.update()
             return None
-        return super(ProjectListView, self).keypress(size, key)
-
-
+        return r
diff --git a/gertty/view/side_diff.py b/gertty/view/side_diff.py
index 0dd491f..e5d2a54 100644
--- a/gertty/view/side_diff.py
+++ b/gertty/view/side_diff.py
@@ -17,13 +17,15 @@ import logging
 
 import urwid
 
+from gertty import keymap
 from gertty import mywid
 from gertty import gitrepo
 from gertty.view.diff import *
 
 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__([])
+        self.app = app
         self.context = context
         # If we save a comment, the resulting key will be stored here
         self.old_key = old_key
@@ -38,7 +40,9 @@ class SideDiffCommentEdit(BaseDiffCommentEdit):
 
     def keypress(self, 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:
                 self.focus_position = 1
             else:
@@ -137,7 +141,7 @@ class SideDiffView(BaseDiffView):
                     (old_comment_key, old_comment) = old_list.pop(0)
                 if new_list:
                     (new_comment_key, new_comment) = new_list.pop(0)
-                lines.append(SideDiffCommentEdit(context,
+                lines.append(SideDiffCommentEdit(self.app, context,
                                                  old_comment_key,
                                                  new_comment_key,
                                                  old_comment, new_comment))
@@ -178,14 +182,14 @@ class SideDiffView(BaseDiffView):
                 (old_comment_key, old_comment) = old_list.pop(0)
             if new_list:
                 (new_comment_key, new_comment) = new_list.pop(0)
-            lines.append(SideDiffCommentEdit(context,
+            lines.append(SideDiffCommentEdit(self.app, context,
                                              old_comment_key,
                                              new_comment_key,
                                              old_comment, new_comment))
         return lines
 
     def makeCommentEdit(self, edit):
-        return SideDiffCommentEdit(edit.context)
+        return SideDiffCommentEdit(self.app, edit.context)
 
     def cleanupEdit(self, edit):
         if edit.old_key: