Commit edc11eeb authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge branch 'editor'

parents 4570a210 f6a38845
...@@ -162,13 +162,12 @@ class EditCommandTest(CommandTest): ...@@ -162,13 +162,12 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.output, expected) self.assertEqual(self.output, expected)
self.assertEqual(self.todolist.print_todos(), u"Foo id:1\nFo\u00f3B\u0105\u017a\nLazy Cat\nLazy Dog") 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') @mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_archive(self, mock_call): def test_edit_archive(self, mock_call):
""" Edit archive file. """ """ Edit archive file. """
mock_call.return_value = 0 mock_call.return_value = 0
editor = 'vi'
os.environ['EDITOR'] = editor
archive = config().archive() archive = config().archive()
command = EditCommand(["-d"], self.todolist, self.out, self.error, command = EditCommand(["-d"], self.todolist, self.out, self.error,
...@@ -176,15 +175,14 @@ class EditCommandTest(CommandTest): ...@@ -176,15 +175,14 @@ class EditCommandTest(CommandTest):
command.execute() command.execute()
self.assertEqual(self.errors, "") 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') @mock.patch('topydo.commands.EditCommand.check_call')
def test_edit_todotxt(self, mock_call): def test_edit_todotxt(self, mock_call):
""" Edit todo file. """ """ Edit todo file. """
mock_call.return_value = 0 mock_call.return_value = 0
editor = 'vi'
os.environ['EDITOR'] = editor
todotxt = config().todotxt() todotxt = config().todotxt()
result = self.todolist.print_todos() # copy TodoList content *before* executing command result = self.todolist.print_todos() # copy TodoList content *before* executing command
...@@ -194,7 +192,91 @@ class EditCommandTest(CommandTest): ...@@ -194,7 +192,91 @@ class EditCommandTest(CommandTest):
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertEqual(self.todolist.print_todos(), result) 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): def test_edit_name(self):
name = EditCommand.name() name = EditCommand.name()
......
...@@ -36,6 +36,15 @@ append_parent_projects = 0 ...@@ -36,6 +36,15 @@ append_parent_projects = 0
; Add parent contexts when adding sub todo items ; Add parent contexts when adding sub todo items
append_parent_contexts = 0 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] [colorscheme]
; Configure colorscheme. Accepted values are: black, [light-]red, [light-]green, ; Configure colorscheme. Accepted values are: black, [light-]red, [light-]green,
; [light-]yellow, [light-]blue, [light-]magenta, [light-]cyan, white ; [light-]yellow, [light-]blue, [light-]magenta, [light-]cyan, white
......
...@@ -17,16 +17,13 @@ ...@@ -17,16 +17,13 @@
import os import os
import codecs import codecs
import tempfile import tempfile
import shlex
from subprocess import CalledProcessError, check_call from subprocess import CalledProcessError, check_call
from topydo.lib.Config import config from topydo.lib.Config import config
from topydo.lib.MultiCommand import MultiCommand from topydo.lib.MultiCommand import MultiCommand
from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers from topydo.lib.prettyprinters.Numbers import PrettyPrinterNumbers
from topydo.lib.Todo import Todo 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): def _get_file_mtime(p_file):
return os.stat(p_file.name).st_mtime return os.stat(p_file.name).st_mtime
...@@ -39,20 +36,32 @@ class EditCommand(MultiCommand): ...@@ -39,20 +36,32 @@ class EditCommand(MultiCommand):
super().__init__(p_args, p_todolist, p_output, super().__init__(p_args, p_todolist, p_output,
p_error, p_input) p_error, p_input)
if len(self.args) == 0: self.editor = config().editor()
self.multi_mode = False
self.is_expression = False self.is_expression = False
self.edit_archive = False self.edit_archive = False
self.last_argument = False self.last_argument = False
def get_flags(self): def get_flags(self):
return ("d", []) return ("dE:", [])
def process_flag(self, p_opt, p_value): def process_flag(self, p_opt, p_value):
if p_opt == '-d': if p_opt == '-d':
self.edit_archive = True self.edit_archive = True
self.multi_mode = False 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): def _todos_to_temp(self):
f = tempfile.NamedTemporaryFile(delete=False, suffix='.todo.txt') f = tempfile.NamedTemporaryFile(delete=False, suffix='.todo.txt')
...@@ -74,18 +83,13 @@ class EditCommand(MultiCommand): ...@@ -74,18 +83,13 @@ class EditCommand(MultiCommand):
def _open_in_editor(self, p_file): def _open_in_editor(self, p_file):
try: try:
editor = os.environ['EDITOR'] or DEFAULT_EDITOR return check_call(self.editor + [p_file])
except(KeyError):
editor = DEFAULT_EDITOR
try:
return check_call([editor, p_file])
except CalledProcessError: except CalledProcessError:
self.error('Something went wrong in the editor...') self.error('Something went wrong in the editor...')
return 1 return 1
except(OSError): except OSError:
self.error('There is no such editor as: ' + editor + '. ' self.error('There is no such editor as: ' + self.editor + '. '
'Check your $EDITOR and/or $PATH') 'Check your configuration file, $TOPYDO_EDITOR, $EDITOR and/or $PATH')
def _catch_todo_errors(self): def _catch_todo_errors(self):
errors = [] errors = []
...@@ -134,12 +138,13 @@ class EditCommand(MultiCommand): ...@@ -134,12 +138,13 @@ class EditCommand(MultiCommand):
return self._open_in_editor(todo) == 0 return self._open_in_editor(todo) == 0
def usage(self): def usage(self):
return """Synopsis: return """Synopsis:
edit edit [-E <EDITOR>]
edit <NUMBER 1> [<NUMBER 2> ...] edit [-E <EDITOR>] <NUMBER 1> [<NUMBER 2> ...]
edit -e [-x] [EXPRESSION] edit [-E <EDITOR>] -e [-x] [EXPRESSION]
edit -d""" edit [-E <EDITOR>] -d"""
def help(self): def help(self):
return """\ return """\
...@@ -150,10 +155,15 @@ edit todo item(s) with the given NUMBER(s) or edit relevant todos matching ...@@ -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 the given EXPRESSION. See `topydo help ls` for more information on relevant
todo items. It is also possible to open the archive file. todo items. It is also possible to open the archive file.
By default it will look to your environment variable $EDITOR, otherwise it will The editor is chosen as follows:
fall back to 'vi'. 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 : Treat the subsequent arguments as an EXPRESSION.
-E : Editor to start.
-x : Edit *all* todos matching the EXPRESSION (i.e. do not filter on -x : Edit *all* todos matching the EXPRESSION (i.e. do not filter on
dependencies or relevance). dependencies or relevance).
-d : Open the archive file.\ -d : Open the archive file.\
......
...@@ -101,6 +101,9 @@ class _Config: ...@@ -101,6 +101,9 @@ class _Config:
'append_parent_contexts': '0', 'append_parent_contexts': '0',
}, },
'edit': {
},
'colorscheme': { 'colorscheme': {
'project_color': 'red', 'project_color': 'red',
'context_color': 'magenta', 'context_color': 'magenta',
...@@ -448,6 +451,23 @@ class _Config: ...@@ -448,6 +451,23 @@ class _Config:
return (keymap_dict, keystates) 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): 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