# 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 logging 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 boartty commands: KILL = 'kill' YANK = 'yank' YANK_POP = 'yank pop' PREV_SCREEN = 'previous screen' TOP_SCREEN = 'top screen' HELP = 'help' QUIT = 'quit' STORY_SEARCH = 'story search' REFINE_STORY_SEARCH = 'refine story search' LIST_HELD = 'list held stories' NEW_STORY = 'new story' BOARD_LIST = 'board list' # Story screen: TOGGLE_HIDDEN = 'toggle hidden' TOGGLE_STARRED = 'toggle starred' TOGGLE_HELD = 'toggle held' TOGGLE_MARK = 'toggle process mark' LEAVE_COMMENT = 'leave comment' SEARCH_RESULTS = 'search results' NEXT_STORY = 'next story' PREV_STORY = 'previous story' TOGGLE_HIDDEN_COMMENTS = 'toggle hidden comments' NEW_TASK = 'new task' DELETE_TASK = 'delete task' REFRESH = 'refresh' EDIT_TITLE = 'edit title' EDIT_DESCRIPTION = 'edit description' SORT_BY_NUMBER = 'sort by number' SORT_BY_UPDATED = 'sort by updated' SORT_BY_LAST_SEEN = 'sort by last seen' SORT_BY_REVERSE = 'reverse the sort' # Project list screen: TOGGLE_LIST_ACTIVE = 'toggle list active' TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed' TOGGLE_SUBSCRIBED = 'toggle subscribed' NEW_PROJECT_TOPIC = 'new project topic' DELETE_PROJECT_TOPIC = 'delete project topic' MOVE_PROJECT_TOPIC = 'move to project topic' COPY_PROJECT_TOPIC = 'copy to project topic' REMOVE_PROJECT_TOPIC = 'remove from project topic' RENAME_PROJECT_TOPIC = 'rename project topic' # Special: FURTHER_INPUT = 'further input' NEXT_SELECTABLE = 'next selectable' PREV_SELECTABLE = 'prev selectable' INTERACTIVE_SEARCH = 'interactive search' 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', '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', BOARD_LIST: 'f6', HELP: ['f1', '?'], QUIT: ['ctrl q'], STORY_SEARCH: 'ctrl o', REFINE_STORY_SEARCH: 'meta o', LIST_HELD: 'f12', NEW_STORY: 'ctrl n', TOGGLE_HIDDEN: 'k', TOGGLE_STARRED: '*', TOGGLE_HELD: '!', TOGGLE_MARK: '%', LEAVE_COMMENT: 'r', SEARCH_RESULTS: 'u', NEXT_STORY: 'n', PREV_STORY: 'p', TOGGLE_HIDDEN_COMMENTS: 't', NEW_TASK: 'N', DELETE_TASK: 'delete', REFRESH: 'ctrl r', EDIT_TITLE: 'ctrl t', EDIT_DESCRIPTION: 'ctrl d', SORT_BY_NUMBER: [['S', 'n']], SORT_BY_UPDATED: [['S', 'u']], SORT_BY_LAST_SEEN: [['S', 's']], SORT_BY_REVERSE: [['S', 'r']], TOGGLE_LIST_ACTIVE: 'l', TOGGLE_LIST_SUBSCRIBED: 'L', TOGGLE_SUBSCRIBED: 's', NEW_PROJECT_TOPIC: [['T', 'n']], DELETE_PROJECT_TOPIC: [['T', 'delete']], MOVE_PROJECT_TOPIC: [['T', 'm']], COPY_PROJECT_TOPIC: [['T', 'c']], REMOVE_PROJECT_TOPIC: [['T', 'D']], RENAME_PROJECT_TOPIC: [['T', 'r']], NEXT_SELECTABLE: 'tab', PREV_SELECTABLE: 'shift tab', INTERACTIVE_SEARCH: 'ctrl s', } # Hi vi users! Add more things here! This overrides the default # keymap, so anything not defined here will just use what's defined # above. VI_KEYMAP = { QUIT: [[':', 'q']], CURSOR_LEFT: 'h', CURSOR_DOWN: 'j', CURSOR_UP: 'k', CURSOR_RIGHT: 'l', } 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, KILL, YANK, YANK_POP, )) FORMAT_SUBS = ( (re.compile('ctrl '), 'CTRL-'), (re.compile('meta '), 'META-'), (re.compile('f(\d+)'), 'F\\1'), (re.compile('([a-z][a-z]+)'), lambda x: x.group(1).upper()), ) 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.keytree = Key(None) self.commandmap = {} self.multikeys = '' self.update(DEFAULT_KEYMAP) self.update(config) def update(self, config): # command -> [keys] for command, keys in config.items(): if command == 'name': continue command = command.replace('-', ' ') if type(keys) != type([]): keys = [keys] self.commandmap[command] = keys self.keytree = Key(None) for command, keys in self.commandmap.items(): for key in keys: 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: tree = self.keytree.addKey(key) tree.commands.append(command) 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 getFurtherCommands(self, keys): if not keys: return [] tree = self.keytree for key in keys: tree = tree.keys.get(key) if not tree: return [] return self._getFurtherCommands('', tree) def _getFurtherCommands(self, keys, tree): if keys: ret = [(formatKey(keys), tree.commands[:])] else: ret = [] for subtree in tree.keys.values(): ret.extend(self._getFurtherCommands(keys + subtree.key, subtree)) return ret def getKeys(self, command): return self.commandmap.get(command, []) def updateCommandMap(self): "Update the urwid command map with this keymap" for key in self.keytree.keys.values(): for command in key.commands: if command in URWID_COMMANDS: urwid.command_map[key.key]=command def formatKeys(self, command): keys = self.getKeys(command) keys = [formatKey(k) for k in keys] return ' or '.join(keys)