Commit 2ece2812 authored by Jacek Sowiński's avatar Jacek Sowiński

Configurable keymap

User can now specify key-shortcuts in main topydo config file under
'column_keymap' section in form of:

`<SHORTCUT> = <ACTION>`

Two main types of action are supported:
- built-in (one of: 'home', 'end', 'up', 'down', 'postpone', 'postpone_s'
  and 'pri')
- topydo commands (aliases included). Prefixed with 'cmd'. Commands to
  call on selected todo item should contain '{}' placeholder to mark its
  place in final command call in similiar fashion as in aliases
  definitions.

postpone, postpone_s and pri shortcuts are sort of prefixes for
arguments for respective topydo commands triggered on selected todo item:
- `postpone<COUNT><PERIOD>` will translate to `cmd postpone {}
  <COUNT><PERIOD>`. postpone_s will do the same but with '-s' flag
  added.
- `pri<PRIORITY>` will translate to `cmd pri {} <PRIORITY>`

Default config as an example:

```ini
[column_keymap]
gg = home
G = end
j = down
k = up
d = cmd del {}
e = cmd edit {}
u = cmd revert
x = cmd do {}
pp = postpone
ps = postpone_s
pr = pri
```

- pp23d will postpone selected item by 23 days
- ps1m will postpone selected item (threshold date included) by 1 month
- prz will set priority of selected item to (Z)
parent 78f641b2
[column_keymap]
up = up
...@@ -140,5 +140,24 @@ class ConfigTest(TopydoTest): ...@@ -140,5 +140,24 @@ class ConfigTest(TopydoTest):
self.assertEqual(config("test/data/ConfigTest5.conf").link_color(), self.assertEqual(config("test/data/ConfigTest5.conf").link_color(),
config().defaults["colorscheme"]["link_color"]) config().defaults["colorscheme"]["link_color"])
def test_config24(self):
""" column_keymap test. """
keymap, keystates = config("test/data/ConfigTest6.conf").column_keymap()
self.assertEqual(keymap['pp'], 'postpone')
self.assertEqual(keymap['ps'], 'postpone_s')
self.assertEqual(keymap['pr'], 'pri')
self.assertEqual(keymap['pra'], 'cmd pri {} a')
self.assertIn('p', keystates)
self.assertIn('g', keystates)
self.assertIn('pp', keystates)
self.assertIn('ps', keystates)
self.assertIn('pr', keystates)
self.assertEqual(keymap['up'], 'up')
self.assertIn('u', keystates)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -63,3 +63,17 @@ append_parent_contexts = 0 ...@@ -63,3 +63,17 @@ append_parent_contexts = 0
;listcon = lscon ;listcon = lscon
;listcontext = lscon ;listcontext = lscon
;listcontexts = lscon ;listcontexts = lscon
[column_keymap]
; Keymap configuration for column-mode
gg = home
G = end
j = down
k = up
d = cmd del {}
e = cmd edit {}
u = cmd revert
x = cmd do {}
pp = postpone
ps = postpone_s
pr = pri
...@@ -18,6 +18,9 @@ import configparser ...@@ -18,6 +18,9 @@ import configparser
import os import os
import shlex import shlex
from itertools import accumulate
from string import ascii_lowercase
def home_config_path(p_filename): def home_config_path(p_filename):
return os.path.join(os.path.expanduser('~'), p_filename) return os.path.join(os.path.expanduser('~'), p_filename)
...@@ -109,11 +112,27 @@ class _Config: ...@@ -109,11 +112,27 @@ class _Config:
'listcontext': 'lscon', 'listcontext': 'lscon',
'listcontexts': 'lscon', 'listcontexts': 'lscon',
}, },
'column_keymap': {
'gg': 'home',
'G': 'end',
'j': 'down',
'k': 'up',
'd': 'cmd del {}',
'e': 'cmd edit {}',
'u': 'cmd revert',
'x': 'cmd do {}',
'pp': 'postpone',
'ps': 'postpone_s',
'pr': 'pri'
},
} }
self.config = {} self.config = {}
self.cp = configparser.RawConfigParser() self.cp = configparser.RawConfigParser()
# allow uppercase config keys
self.cp.optionxform = lambda option: option
for section in self.defaults: for section in self.defaults:
self.cp.add_section(section) self.cp.add_section(section)
...@@ -311,6 +330,28 @@ class _Config: ...@@ -311,6 +330,28 @@ class _Config:
""" Returns the list format used by `ls` """ """ Returns the list format used by `ls` """
return self.cp.get('ls', 'list_format') return self.cp.get('ls', 'list_format')
def column_keymap(self):
""" Returns keymap and keystates used in column mode """
keystates = set()
shortcuts = self.cp.items('column_keymap')
keymap_dict = dict(shortcuts)
for combo, action in shortcuts:
# add all possible prefixes to keystates
if len(combo) > 1:
keystates |= set(accumulate(combo[:-1]))
if action in ['pri', 'postpone', 'postpone_s']:
keystates.add(combo)
if action == 'pri':
for c in ascii_lowercase:
keymap_dict[combo + c] = 'cmd pri {} ' + c
return (keymap_dict, keystates)
def config(p_path=None, p_overrides=None): def config(p_path=None, p_overrides=None):
""" """
Retrieve the config instance. Retrieve the config instance.
......
...@@ -92,6 +92,9 @@ class UIApplication(CLIApplicationBase): ...@@ -92,6 +92,9 @@ class UIApplication(CLIApplicationBase):
self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH) self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH)
self.commandline = CommandLineWidget('topydo> ') self.commandline = CommandLineWidget('topydo> ')
self.keymap = config().column_keymap()
self._alarm = None
# console widget # console widget
self.console = ConsoleWidget() self.console = ConsoleWidget()
get_terminal_size(self._console_width) get_terminal_size(self._console_width)
...@@ -290,11 +293,14 @@ class UIApplication(CLIApplicationBase): ...@@ -290,11 +293,14 @@ class UIApplication(CLIApplicationBase):
before that position. before that position.
""" """
todolist = TodoListWidget(p_view, p_view.data['title']) todolist = TodoListWidget(p_view, p_view.data['title'], self.keymap)
no_output = lambda _: None no_output = lambda _: None
urwid.connect_signal(todolist, 'execute_command', urwid.connect_signal(todolist, 'execute_command_silent',
lambda cmd: self._execute_handler(cmd, no_output)) lambda cmd: self._execute_handler(cmd, no_output))
urwid.connect_signal(todolist, 'execute_command', self._execute_handler)
urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear) urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear)
urwid.connect_signal(todolist, 'add_pending_action', self._set_alarm)
urwid.connect_signal(todolist, 'remove_pending_action', self._remove_alarm)
options = self.columns.options( options = self.columns.options(
width_type='given', width_type='given',
...@@ -312,6 +318,22 @@ class UIApplication(CLIApplicationBase): ...@@ -312,6 +318,22 @@ class UIApplication(CLIApplicationBase):
self.columns.focus_position = p_pos self.columns.focus_position = p_pos
self._blur_commandline() self._blur_commandline()
def _set_alarm(self, p_action):
"""
Sets alarm to execute p_action specified in 0.5 sec.
Handle for this alarm is stored as _alarm attribute.
p_action must be an object with 'execute' method.
"""
self._alarm = self.mainloop.set_alarm_in(0.5, p_action.execute)
def _remove_alarm(self):
"""
Removes pending action alarm stored in _alarm attribute.
"""
self.mainloop.remove_alarm(self._alarm)
self._alarm = None
def _swap_column_left(self): def _swap_column_left(self):
pos = self.columns.focus_position pos = self.columns.focus_position
if pos > 0: if pos > 0:
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment