Commit edc11eeb authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'editor'

parents 4570a210 f6a38845
......@@ -162,13 +162,12 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog")
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_archive(self, mock_call):
""" Edit archive file. """
mock_call.return_value = 0
editor = 'vi'
os.environ['EDITOR'] = editor
archive = config().archive()
command = EditCommand(["-d"], self.todolist, self.out, self.error,
......@@ -176,15 +175,14 @@ class EditCommandTest(CommandTest):
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with([editor, archive])
mock_call.assert_called_once_with(['vi', archive])
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_todotxt(self, mock_call):
""" Edit todo file. """
mock_call.return_value = 0
editor = 'vi'
os.environ['EDITOR'] = editor
todotxt = config().todotxt()
result = self.todolist.print_todos() # copy TodoList content *before* executing command
......@@ -194,7 +192,91 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), result)
mock_call.assert_called_once_with([editor, todotxt])
mock_call.assert_called_once_with(['vi', todotxt])
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch.dict(os.environ, {'TOPYDO_EDITOR': 'nano'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_editor1(self, mock_call):
""" $TOPYDO_EDITOR overrides $EDITOR """
mock_call.return_value = 0
todotxt = config().todotxt()
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with(['nano', todotxt])
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch.dict(os.environ, {'TOPYDO_EDITOR': 'nano'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_editor2(self, mock_call):
""" $TOPYDO_EDITOR overrides $EDITOR """
mock_call.return_value = 0
todotxt = config().todotxt()
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with(['nano', todotxt])
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch.dict(os.environ, {'TOPYDO_EDITOR': 'nano'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_editor3(self, mock_call):
""" Editor on commandline overrides $TOPYDO_EDITOR """
mock_call.return_value = 0
command = EditCommand(["-E", "foo"], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with(['foo', config().todotxt()])
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch.dict(os.environ, {'TOPYDO_EDITOR': 'nano'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_editor4(self, mock_call):
""" Editor in configuration file is overridden by $TOPYDO_EDITOR """
mock_call.return_value = 0
config(p_overrides={('edit', 'editor'): 'foo'})
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with(['nano', config().todotxt()])
@mock.patch.dict(os.environ, {'EDITOR': 'vi'})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_editor5(self, mock_call):
""" Editor in configuration file overrides $EDITOR """
mock_call.return_value = 0
config(p_overrides={('edit', 'editor'): 'foo'})
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with(['foo', config().todotxt()])
@mock.patch.dict(os.environ, {'EDITOR': ''})
@mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_editor6(self, mock_call):
""" Ultimate fallback is vi """
mock_call.return_value = 0
command = EditCommand([], self.todolist, self.out, self.error, None)
command.execute()
self.assertEqual(self.errors, "")
mock_call.assert_called_once_with(['vi', config().todotxt()])
def test_edit_name(self):
name = EditCommand.name()
......
......@@ -36,6 +36,15 @@ append_parent_projects = 0
; Add parent contexts when adding sub todo items
append_parent_contexts = 0
[edit]
; Editor to use for the 'edit' subcommand (overrides the EDITOR environment
; variable, but can be overridden with the TOPYDO_EDITOR environment variable
; or the -E flag in the edit command)
; editor = vi
; Vim tip: enable completion using your complete todo.txt file. Use this as
; editor command:
; vim -c 'autocmd Filetype todo set complete=.,w,b,u,t,i,k~/notes/todo.txt'
[colorscheme]
; Configure colorscheme. Accepted values are: black, [light-]red, [light-]green,
; [light-]yellow, [light-]blue, [light-]magenta, [light-]cyan, white
......
......@@ -17,16 +17,13 @@
import os
import codecs
import tempfile
import shlex
from subprocess import CalledProcessError, check_call
from topydo.lib.Config import config
from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Todo import Todo
from topydo.lib.TodoList import TodoList
# the true and only editor
DEFAULT_EDITOR = 'vi'
def _get_file_mtime(p_file):
return os.stat(p_file.name).st_mtime
......@@ -39,20 +36,32 @@ class EditCommand(MultiCommand):
super().__init__(p_args, p_todolist, p_output,
p_error, p_input)
if len(self.args) == 0:
self.multi_mode = False
self.editor = config().editor()
self.is_expression = False
self.edit_archive = False
self.last_argument = False
def get_flags(self):
return ("d", [])
return ("dE:", [])
def process_flag(self, p_opt, p_value):
if p_opt == '-d':
self.edit_archive = True
self.multi_mode = False
elif p_opt == '-E':
self.editor = shlex.split(p_value)
def _process_flags(self):
"""
Override to add an additional check after processing the flags: when
there are no flags left after argument parsing, then it means we'll be
editing the whole todo.txt file as a whole and therefore we're not in
multi mode.
"""
super()._process_flags()
if len(self.args) == 0:
self.multi_mode = False
def _todos_to_temp(self):
f = tempfile.NamedTemporaryFile(delete=False, suffix='.todo.txt')
......@@ -74,18 +83,13 @@ class EditCommand(MultiCommand):
def _open_in_editor(self, p_file):
try:
editor = os.environ['EDITOR'] or DEFAULT_EDITOR
except(KeyError):
editor = DEFAULT_EDITOR
try:
return check_call([editor, p_file])
return check_call(self.editor + [p_file])
except CalledProcessError:
self.error('Something went wrong in the editor...')
return 1
except(OSError):
self.error('There is no such editor as: ' + editor + '. '
'Check your $EDITOR and/or $PATH')
except OSError:
self.error('There is no such editor as: ' + self.editor + '. '
'Check your configuration file, $TOPYDO_EDITOR, $EDITOR and/or $PATH')
def _catch_todo_errors(self):
errors = []
......@@ -134,12 +138,13 @@ class EditCommand(MultiCommand):
return self._open_in_editor(todo) == 0
def usage(self):
return """Synopsis:
edit
edit <NUMBER 1> [<NUMBER 2> ...]
edit -e [-x] [EXPRESSION]
edit -d"""
edit [-E <EDITOR>]
edit [-E <EDITOR>] <NUMBER 1> [<NUMBER 2> ...]
edit [-E <EDITOR>] -e [-x] [EXPRESSION]
edit [-E <EDITOR>] -d"""
def help(self):
return """\
......@@ -150,10 +155,15 @@ edit todo item(s) with the given NUMBER(s) or edit relevant todos matching
the given EXPRESSION. See `topydo help ls` for more information on relevant
todo items. It is also possible to open the archive file.
By default it will look to your environment variable $EDITOR, otherwise it will
fall back to 'vi'.
The editor is chosen as follows:
1. Check whether the -E flag is given and use it;
2. Use the value of $TOPYDO_EDITOR in the environment;
3. Use the value in the configuration file;
4. Use the value of $EDITOR in the environment;
5. If all else fails, use 'vi'.
-e : Treat the subsequent arguments as an EXPRESSION.
-E : Editor to start.
-x : Edit *all* todos matching the EXPRESSION (i.e. do not filter on
dependencies or relevance).
-d : Open the archive file.\
......
......@@ -101,6 +101,9 @@ class _Config:
'append_parent_contexts': '0',
},
'edit': {
},
'colorscheme': {
'project_color': 'red',
'context_color': 'magenta',
......@@ -448,6 +451,23 @@ class _Config:
return (keymap_dict, keystates)
def editor(self):
"""
Returns the editor to invoke. It returns a list with the command in
the first position and its arguments in the remainder.
"""
result = 'vi'
if 'TOPYDO_EDITOR' in os.environ and os.environ['TOPYDO_EDITOR']:
result = os.environ['TOPYDO_EDITOR']
else:
try:
result = str(self.cp.get('edit', 'editor'))
except configparser.NoOptionError:
if 'EDITOR' in os.environ and os.environ['EDITOR']:
result = os.environ['EDITOR']
return shlex.split(result)
def config(p_path=None, p_overrides=None):
"""
......
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