mirror of
https://github.com/tbabej/taskwiki.git
synced 2025-08-19 06:43:06 +02:00
Merge branch 'master' of https://github.com/tbabej/taskwiki
This commit is contained in:
commit
e73aeb414e
17 changed files with 536 additions and 218 deletions
|
@ -1,2 +1,2 @@
|
|||
[report]
|
||||
omit = */python2.7/*,*/test*,*/__init__.py
|
||||
omit = */python2.7/*,*/test*,*/__init__.py,*/taskwiki/coverage.py
|
||||
|
|
|
@ -5,7 +5,8 @@ env:
|
|||
- TASK_VERSION=v2.4.2
|
||||
- TASK_VERSION=v2.4.3
|
||||
- TASK_VERSION=v2.4.4
|
||||
- TASK_VERSION=2.5.0
|
||||
- TASK_VERSION=v2.5.0
|
||||
- TASK_VERSION=2.5.1
|
||||
python:
|
||||
- "2.7"
|
||||
before_install:
|
||||
|
|
2
LICENCE
2
LICENCE
|
@ -1,4 +1,4 @@
|
|||
Copyright 2014-2015 Tomas Babej
|
||||
Copyright 2014-2016 Tomas Babej
|
||||
http://github.com/tbabej/taskwiki
|
||||
|
||||
This software is released under MIT licence.
|
||||
|
|
|
@ -1,59 +1,10 @@
|
|||
import vim # pylint: disable=F0401
|
||||
import re
|
||||
|
||||
import util
|
||||
import vwtask
|
||||
import viewport
|
||||
import regexp
|
||||
|
||||
from tasklib import TaskWarrior
|
||||
|
||||
|
||||
class WarriorStore(object):
|
||||
"""
|
||||
Stores all instances of TaskWarrior objects.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Determine defaults
|
||||
default_rc = vim.vars.get('taskwiki_taskrc_location') or '~/.taskrc'
|
||||
default_data = vim.vars.get('taskwiki_data_location') or '~/.task'
|
||||
|
||||
default_kwargs = dict(
|
||||
data_location=default_data,
|
||||
taskrc_location=default_rc,
|
||||
)
|
||||
|
||||
# Setup the store of TaskWarrior objects
|
||||
self.warriors = {'default': TaskWarrior(**default_kwargs)}
|
||||
extra_warrior_defs = vim.vars.get('taskwiki_extra_warriors', {})
|
||||
|
||||
for key in extra_warrior_defs.keys():
|
||||
current_kwargs = default_kwargs.copy()
|
||||
current_kwargs.update(extra_warrior_defs[key])
|
||||
self.warriors[key] = TaskWarrior(**current_kwargs)
|
||||
|
||||
# Make sure context is not respected in any TaskWarrior
|
||||
for tw in self.warriors.values():
|
||||
tw.overrides.update({'context':''})
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.warriors[key]
|
||||
except KeyError as e:
|
||||
raise util.TaskWikiException(
|
||||
"Taskwarrior with key '{0}' not available."
|
||||
.format(key))
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.warriors[key] = value
|
||||
|
||||
def values(self):
|
||||
return self.warriors.values()
|
||||
|
||||
def iteritems(self):
|
||||
return self.warriors.iteritems()
|
||||
import store
|
||||
|
||||
|
||||
class TaskCache(object):
|
||||
|
@ -63,89 +14,22 @@ class TaskCache(object):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.task_cache = dict()
|
||||
self.vimwikitask_cache = dict()
|
||||
self.viewport_cache = dict()
|
||||
self.warriors = WarriorStore()
|
||||
# Determine defaults
|
||||
default_rc = vim.vars.get('taskwiki_taskrc_location') or '~/.taskrc'
|
||||
default_data = vim.vars.get('taskwiki_data_location') or '~/.task'
|
||||
extra_warrior_defs = vim.vars.get('taskwiki_extra_warriors', {})
|
||||
|
||||
self.task = store.TaskStore(self)
|
||||
self.vwtask = store.VwtaskStore(self)
|
||||
self.viewport = store.ViewportStore(self)
|
||||
self.line = store.LineStore(self)
|
||||
self.warriors = store.WarriorStore(default_rc, default_data, extra_warrior_defs)
|
||||
self.buffer_has_authority = True
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Integer keys (line numbers) refer to the VimwikiTask objects
|
||||
if type(key) is int:
|
||||
vimwikitask = self.vimwikitask_cache.get(key)
|
||||
|
||||
if vimwikitask is None:
|
||||
vimwikitask = vwtask.VimwikiTask.from_line(self, key)
|
||||
|
||||
# If we successfully parsed a task, save it to the cache
|
||||
if vimwikitask is not None:
|
||||
self.vimwikitask_cache[key] = vimwikitask
|
||||
|
||||
return vimwikitask # May return None if the line has no task
|
||||
|
||||
# ShortUUID objects refer to Task cache
|
||||
elif type(key) == vwtask.ShortUUID:
|
||||
task = self.task_cache.get(key)
|
||||
|
||||
if task is None:
|
||||
task = key.tw.tasks.get(uuid=key.value)
|
||||
self.task_cache[key] = task
|
||||
|
||||
return task
|
||||
|
||||
# Anything else is wrong
|
||||
else:
|
||||
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# Never store None in the cache, treat it as deletion
|
||||
if value is None:
|
||||
if key in self:
|
||||
del self[key]
|
||||
return
|
||||
|
||||
# String keys refer to the Task objects
|
||||
if type(key) is vwtask.ShortUUID:
|
||||
self.task_cache[key] = value
|
||||
|
||||
# Integer keys (line numbers) refer to the VimwikiTask objects
|
||||
elif type(key) is int:
|
||||
self.vimwikitask_cache[key] = value
|
||||
|
||||
# Anything else is wrong
|
||||
else:
|
||||
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
|
||||
|
||||
def __delitem__(self, key):
|
||||
# String keys refer to the Task objects
|
||||
if type(key) is vwtask.ShortUUID:
|
||||
del self.task_cache[key]
|
||||
|
||||
# Integer keys (line numbers) refer to the VimwikiTask objects
|
||||
elif type(key) is int:
|
||||
del self.vimwikitask_cache[key]
|
||||
|
||||
# Anything else is wrong
|
||||
else:
|
||||
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
|
||||
|
||||
def __contains__(self, key):
|
||||
# String keys refer to the Task objects
|
||||
if type(key) is vwtask.ShortUUID:
|
||||
return key in self.task_cache
|
||||
|
||||
# Integer keys (line numbers) refer to the VimwikiTask objects
|
||||
elif type(key) is int:
|
||||
return key in self.vimwikitask_cache
|
||||
|
||||
# Anything else is wrong
|
||||
else:
|
||||
raise ValueError("Wrong key type: %s (%s)" % (key, type(key)))
|
||||
|
||||
@property
|
||||
def vimwikitask_dependency_order(self):
|
||||
iterated_cache = {
|
||||
k:v for k,v in self.vimwikitask_cache.iteritems()
|
||||
k:v for k,v in self.vwtask.iteritems()
|
||||
if v is not None
|
||||
}
|
||||
|
||||
|
@ -158,9 +42,10 @@ class TaskCache(object):
|
|||
yield task
|
||||
|
||||
def reset(self):
|
||||
self.task_cache = dict()
|
||||
self.vimwikitask_cache = dict()
|
||||
self.viewport_cache = dict()
|
||||
self.task.store = dict()
|
||||
self.vwtask.store = dict()
|
||||
self.viewport.store = dict()
|
||||
self.line.store = dict()
|
||||
|
||||
def load_vwtasks(self, buffer_has_authority=True):
|
||||
# Set the authority flag, which determines which data (Buffer or TW)
|
||||
|
@ -169,7 +54,7 @@ class TaskCache(object):
|
|||
self.buffer_has_authority = buffer_has_authority
|
||||
|
||||
for i in range(len(vim.current.buffer)):
|
||||
self[i] # Loads the line into the cache
|
||||
self.vwtask[i] # Loads the line into the cache
|
||||
|
||||
# Restore the old authority flag value
|
||||
self.buffer_has_authority = old_authority
|
||||
|
@ -184,10 +69,10 @@ class TaskCache(object):
|
|||
port.load_tasks()
|
||||
|
||||
# Save the viewport in the cache
|
||||
self.viewport_cache[i] = port
|
||||
self.viewport[i] = port
|
||||
|
||||
def update_vwtasks_in_buffer(self):
|
||||
for task in self.vimwikitask_cache.values():
|
||||
for task in self.vwtask.values():
|
||||
task.update_in_buffer()
|
||||
|
||||
def save_tasks(self):
|
||||
|
@ -227,14 +112,14 @@ class TaskCache(object):
|
|||
# Update each task in the cache
|
||||
for task in tasks:
|
||||
key = vwtask.ShortUUID(task['uuid'], tw)
|
||||
self.task_cache[key] = task
|
||||
self.task[key] = task
|
||||
|
||||
def update_vwtasks_from_tasks(self):
|
||||
for vwtask in self.vimwikitask_cache.values():
|
||||
for vwtask in self.vwtask.values():
|
||||
vwtask.update_from_task()
|
||||
|
||||
def evaluate_viewports(self):
|
||||
for port in self.viewport_cache.values():
|
||||
for port in self.viewport.values():
|
||||
port.sync_with_taskwarrior()
|
||||
|
||||
def get_viewport_by_task(self, task):
|
||||
|
@ -245,65 +130,50 @@ class TaskCache(object):
|
|||
Returns the viewport, or None if not found.
|
||||
"""
|
||||
|
||||
for port in self.viewport_cache.values():
|
||||
for port in self.viewport.values():
|
||||
if task in port.viewport_tasks:
|
||||
return port
|
||||
|
||||
def rebuild_vimwikitask_cache(self):
|
||||
new_cache = dict()
|
||||
|
||||
for vimwikitask in self.vimwikitask_cache.values():
|
||||
new_cache[vimwikitask['line_number']] = vimwikitask
|
||||
|
||||
self.vimwikitask_cache = new_cache
|
||||
|
||||
def insert_line(self, line, position):
|
||||
# Insert the line
|
||||
vim.current.buffer.append(line, position)
|
||||
|
||||
# Update the position of all the things shifted by the insertion
|
||||
for vimwikitask in self.vimwikitask_cache.values():
|
||||
if vimwikitask['line_number'] >= position:
|
||||
vimwikitask['line_number'] += 1
|
||||
self.vwtask.shift(position, 1)
|
||||
self.viewport.shift(position, 1)
|
||||
|
||||
# Rebuild cache keys
|
||||
self.rebuild_vimwikitask_cache()
|
||||
# Shift lines in the line cache
|
||||
self.line.shift(position, 1)
|
||||
|
||||
def remove_line(self, position):
|
||||
# Remove the line
|
||||
del vim.current.buffer[position]
|
||||
|
||||
# Remove the vimwikitask from cache
|
||||
del self.vimwikitask_cache[position]
|
||||
del self.vwtask[position]
|
||||
del self.viewport[position]
|
||||
|
||||
# Delete the line from the line cache
|
||||
del self.line[position]
|
||||
|
||||
# Update the position of all the things shifted by the removal
|
||||
for vimwikitask in self.vimwikitask_cache.values():
|
||||
if vimwikitask['line_number'] > position:
|
||||
vimwikitask['line_number'] -= 1
|
||||
self.vwtask.shift(position, -1)
|
||||
self.viewport.shift(position, -1)
|
||||
|
||||
# Rebuild cache keys
|
||||
self.rebuild_vimwikitask_cache()
|
||||
# Shift lines in the line cache
|
||||
self.line.shift(position, -1)
|
||||
|
||||
def swap_lines(self, position1, position2):
|
||||
buffer_size = len(vim.current.buffer)
|
||||
if position1 >= buffer_size or position2 >= buffer_size:
|
||||
raise ValueError("Tring to swap %d with %d" % (position1, position2))
|
||||
|
||||
# Swap both the lines and vimwikitasks indexes
|
||||
temp = vim.current.buffer[position2]
|
||||
vim.current.buffer[position2] = vim.current.buffer[position1]
|
||||
vim.current.buffer[position1] = temp
|
||||
# Swap lines in the line cache
|
||||
self.line.swap(position1, position2)
|
||||
|
||||
temp = self[position2]
|
||||
self[position2] = self.vimwikitask_cache.get(position1)
|
||||
self[position1] = temp
|
||||
|
||||
# Update the line numbers cached in the vimwikitasks
|
||||
for position in (position1, position2):
|
||||
if self[position] is not None:
|
||||
self[position]['line_number'] = position
|
||||
|
||||
# Rebuild of the cache is not necessary, only those two lines are affected
|
||||
# Swap both the viewport and vimwikitasks indexes
|
||||
self.vwtask.swap(position1, position2)
|
||||
self.viewport.swap(position1, position2)
|
||||
|
||||
def get_relevant_tw(self):
|
||||
# Find closest task
|
||||
|
|
8
taskwiki/errors.py
Normal file
8
taskwiki/errors.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
Contains TaskWiki-specific exceptions.
|
||||
"""
|
||||
|
||||
|
||||
class TaskWikiException(Exception):
|
||||
"""Used to interrupt a TaskWiki command/event and notify the user."""
|
||||
pass
|
|
@ -57,8 +57,6 @@ class TaskSorter(object):
|
|||
vwtasks_sorted[offset].vwtask['line_number']
|
||||
)
|
||||
|
||||
self.cache.rebuild_vimwikitask_cache()
|
||||
|
||||
|
||||
class CustomNodeComparator(object):
|
||||
"""
|
||||
|
|
198
taskwiki/store.py
Normal file
198
taskwiki/store.py
Normal file
|
@ -0,0 +1,198 @@
|
|||
from tasklib import TaskWarrior
|
||||
|
||||
import errors
|
||||
|
||||
|
||||
class WarriorStore(object):
|
||||
"""
|
||||
Stores all instances of TaskWarrior objects.
|
||||
"""
|
||||
|
||||
def __init__(self, default_rc, default_data, extra_warrior_defs):
|
||||
default_kwargs = dict(
|
||||
data_location=default_data,
|
||||
taskrc_location=default_rc,
|
||||
)
|
||||
|
||||
# Setup the store of TaskWarrior objects
|
||||
self.warriors = {'default': TaskWarrior(**default_kwargs)}
|
||||
|
||||
for key in extra_warrior_defs.keys():
|
||||
current_kwargs = default_kwargs.copy()
|
||||
current_kwargs.update(extra_warrior_defs[key])
|
||||
self.warriors[key] = TaskWarrior(**current_kwargs)
|
||||
|
||||
# Make sure context is not respected in any TaskWarrior
|
||||
for tw in self.warriors.values():
|
||||
tw.overrides.update({'context':''})
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.warriors[key]
|
||||
except KeyError:
|
||||
raise errors.TaskWikiException(
|
||||
"Taskwarrior with key '{0}' not available."
|
||||
.format(key))
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.warriors[key] = value
|
||||
|
||||
def values(self):
|
||||
return self.warriors.values()
|
||||
|
||||
def iteritems(self):
|
||||
return self.warriors.iteritems()
|
||||
|
||||
|
||||
class NoNoneStore(object):
|
||||
|
||||
def __init__(self, cache):
|
||||
self.cache = cache
|
||||
self.store = dict()
|
||||
|
||||
def __getitem__(self, key):
|
||||
item = self.store.get(key)
|
||||
|
||||
if item is None:
|
||||
item = self.get_method(key)
|
||||
|
||||
# If we successfully obtained an item, save it to the cache
|
||||
if item is not None:
|
||||
self.store[key] = item
|
||||
|
||||
return item # May return None if the line has no task
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# Never store None in the cache, treat it as deletion
|
||||
if value is None:
|
||||
if key in self:
|
||||
del self[key]
|
||||
return
|
||||
|
||||
# Otherwise store the given value
|
||||
self.store[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
if key in self.store:
|
||||
del self.store[key]
|
||||
|
||||
def values(self):
|
||||
return self.store.values()
|
||||
|
||||
def iteritems(self):
|
||||
return self.store.iteritems()
|
||||
|
||||
def clear(self):
|
||||
return self.store.clear()
|
||||
|
||||
class LineNumberedKeyedStoreMixin(object):
|
||||
|
||||
def shift(self, position, offset):
|
||||
self.store = {
|
||||
(i + offset if i >= position else i): self.store[i]
|
||||
for i in self.store.keys()
|
||||
}
|
||||
|
||||
def swap(self, position1, position2):
|
||||
temp = self.store.get(position1)
|
||||
self[position1] = self.store.get(position2)
|
||||
self[position2] = temp
|
||||
|
||||
class TaskStore(NoNoneStore):
|
||||
|
||||
def get_method(self, key):
|
||||
return key.tw.tasks.get(uuid=key.value)
|
||||
|
||||
|
||||
class VwtaskStore(LineNumberedKeyedStoreMixin, NoNoneStore):
|
||||
|
||||
def shift(self, position, offset):
|
||||
for line, vwtask in self.store.items():
|
||||
if line >= position:
|
||||
vwtask['line_number'] += offset
|
||||
|
||||
super(VwtaskStore, self).shift(position, offset)
|
||||
|
||||
def swap(self, position1, position2):
|
||||
super(VwtaskStore, self).swap(position1, position2)
|
||||
|
||||
for index in (position1, position2):
|
||||
if self.store.get(index) is not None:
|
||||
self[index]['line_number'] = index
|
||||
|
||||
def get_method(self, line):
|
||||
import vwtask
|
||||
return vwtask.VimwikiTask.from_line(self.cache, line)
|
||||
|
||||
|
||||
class ViewportStore(LineNumberedKeyedStoreMixin, NoNoneStore):
|
||||
|
||||
def shift(self, position, offset):
|
||||
for line, viewport in self.store.items():
|
||||
if line >= position:
|
||||
viewport.line_number += offset
|
||||
|
||||
super(ViewportStore, self).shift(position, offset)
|
||||
|
||||
def swap(self, position1, position2):
|
||||
super(ViewportStore, self).swap(position1, position2)
|
||||
|
||||
for index in (position1, position2):
|
||||
if self.store.get(index) is not None:
|
||||
self[index].line_number = index
|
||||
|
||||
def get_method(self, line):
|
||||
import viewport
|
||||
return viewport.ViewPort.from_line(line, self.cache)
|
||||
|
||||
|
||||
class LineStore(NoNoneStore):
|
||||
|
||||
def __delitem__(self, number):
|
||||
for cls, i in self.store.keys():
|
||||
if i == number:
|
||||
del self.store[(cls, i)]
|
||||
|
||||
def get_method(self, key):
|
||||
cls, line = key
|
||||
return cls.parse_line(line)
|
||||
|
||||
def shift(self, position, offset):
|
||||
new_store = {
|
||||
(cls, i + offset if i >= position else i): self.store[(cls, i)]
|
||||
for cls, i in self.store.keys()
|
||||
}
|
||||
|
||||
self.store = new_store
|
||||
|
||||
def swap(self, position1, position2):
|
||||
import vim
|
||||
|
||||
temp_store1 = {
|
||||
(cls, i): self.store[(cls, i)]
|
||||
for cls, i in self.store.keys()
|
||||
if i == position1
|
||||
}
|
||||
|
||||
temp_store2 = {
|
||||
(cls, i): self.store[(cls, i)]
|
||||
for cls, i in self.store.keys()
|
||||
if i == position2
|
||||
}
|
||||
|
||||
for cls, i in self.store.keys():
|
||||
if i == position1 or i == position2:
|
||||
del self.store[(cls, i)]
|
||||
|
||||
for cls, i in temp_store1.keys():
|
||||
self.store[(cls, position2)] = temp_store1[(cls, i)]
|
||||
|
||||
for cls, i in temp_store2.keys():
|
||||
self.store[(cls, position1)] = temp_store2[(cls, i)]
|
||||
|
||||
# Also change the actual line content
|
||||
temp = vim.current.buffer[position1]
|
||||
vim.current.buffer[position1] = vim.current.buffer[position2]
|
||||
vim.current.buffer[position2] = temp
|
||||
|
|
@ -5,15 +5,14 @@ import pickle
|
|||
import sys
|
||||
import vim # pylint: disable=F0401
|
||||
|
||||
from tasklib import TaskWarrior
|
||||
|
||||
# Insert the taskwiki on the python path
|
||||
BASE_DIR = vim.eval("s:plugin_path")
|
||||
sys.path.insert(0, os.path.join(BASE_DIR, 'taskwiki'))
|
||||
|
||||
import errors
|
||||
# Handle exceptions without traceback, if they're TaskWikiException
|
||||
def output_exception(exception_type, value, tb):
|
||||
if exception_type is util.TaskWikiException:
|
||||
if exception_type is errors.TaskWikiException:
|
||||
print(unicode(value), file=sys.stderr)
|
||||
else:
|
||||
sys.__excepthook__(exception_type, value, tb)
|
||||
|
@ -63,13 +62,14 @@ class WholeBuffer(object):
|
|||
|
||||
class SelectedTasks(object):
|
||||
def __init__(self):
|
||||
self.tw = cache.get_relevant_tw()
|
||||
|
||||
# Reset cache, otherwise old line content may be used
|
||||
cache.reset()
|
||||
|
||||
# Find relevant TaskWarrior instance
|
||||
self.tw = cache.get_relevant_tw()
|
||||
|
||||
# Load the current tasks
|
||||
range_tasks = [cache[i] for i in util.selected_line_numbers()]
|
||||
range_tasks = [cache.vwtask[i] for i in util.selected_line_numbers()]
|
||||
self.tasks = [t for t in range_tasks if t is not None]
|
||||
|
||||
if not self.tasks:
|
||||
|
@ -118,9 +118,6 @@ class SelectedTasks(object):
|
|||
if port:
|
||||
vim.command("TW rc:{0} rc.context: {1}"
|
||||
.format(port.tw.taskrc_location, port.raw_filter))
|
||||
|
||||
print("TW rc:{0} rc.context: {1}"
|
||||
.format(port.tw.taskrc_location, port.raw_filter))
|
||||
else:
|
||||
print("No viewport detected.", file=sys.stderr)
|
||||
|
||||
|
@ -199,7 +196,7 @@ class Mappings(object):
|
|||
# otherwise do the default VimwikiFollowLink
|
||||
position = util.get_current_line_number()
|
||||
|
||||
if cache[position] is not None:
|
||||
if cache.vwtask[position] is not None:
|
||||
SelectedTasks().info()
|
||||
else:
|
||||
port = viewport.ViewPort.from_line(position, cache)
|
||||
|
@ -369,6 +366,9 @@ class CallbackSplitMixin(object):
|
|||
# Close the split if the user leaves it
|
||||
vim.command('au BufLeave <buffer> :bwipe')
|
||||
|
||||
# SREMatch objecets cannot be pickled
|
||||
cache.line.clear()
|
||||
|
||||
# We can't save the current instance in vim variable
|
||||
# so save the pickled version
|
||||
vim.current.buffer.vars['taskwiki_callback'] = pickle.dumps(self)
|
||||
|
@ -509,7 +509,7 @@ class ChooseSplitTags(CallbackSplitMixin, SplitTags):
|
|||
if match:
|
||||
return match.group('name')
|
||||
else:
|
||||
raise util.TaskWikiException("No tag selected.")
|
||||
raise errors.TaskWikiException("No tag selected.")
|
||||
|
||||
def callback(self):
|
||||
tag = self.get_selected_tag()
|
||||
|
|
|
@ -10,15 +10,12 @@ import random
|
|||
import sys
|
||||
import vim # pylint: disable=F0401
|
||||
|
||||
from errors import TaskWikiException
|
||||
|
||||
# Detect if command AnsiEsc is available
|
||||
ANSI_ESC_AVAILABLE = vim.eval('exists(":AnsiEsc")') == '2'
|
||||
|
||||
|
||||
class TaskWikiException(Exception):
|
||||
"""Used to interrupt a TaskWiki command/event and notify the user."""
|
||||
pass
|
||||
|
||||
|
||||
def tw_modstring_to_args(line):
|
||||
output = []
|
||||
escape_global_chars = ('"', "'")
|
||||
|
|
|
@ -5,6 +5,7 @@ import vim # pylint: disable=F0401
|
|||
|
||||
import vwtask
|
||||
import regexp
|
||||
import errors
|
||||
import util
|
||||
import sort
|
||||
import constants
|
||||
|
@ -83,7 +84,7 @@ class ViewPort(object):
|
|||
context_args = util.tw_modstring_to_args(context_definition)
|
||||
detected_contexts.append((token, context_args))
|
||||
else:
|
||||
raise util.TaskWikiException("Context definition for '{0}' "
|
||||
raise errors.TaskWikiException("Context definition for '{0}' "
|
||||
"could not be found.".format(token[1:]))
|
||||
|
||||
for context_token, context_args in detected_contexts:
|
||||
|
@ -169,9 +170,13 @@ class ViewPort(object):
|
|||
# All syntactic processing done, return the resulting filter args
|
||||
return taskfilter_args, meta
|
||||
|
||||
@classmethod
|
||||
def parse_line(cls, number):
|
||||
return re.search(regexp.GENERIC_VIEWPORT, vim.current.buffer[number])
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, number, cache):
|
||||
match = re.search(regexp.GENERIC_VIEWPORT, vim.current.buffer[number])
|
||||
match = cache.line[(cls, number)]
|
||||
|
||||
if not match:
|
||||
return None
|
||||
|
@ -243,7 +248,7 @@ class ViewPort(object):
|
|||
# -VISIBLE virtual tag used
|
||||
elif self.meta.get('visible') is False:
|
||||
# Determine which tasks are outside the viewport
|
||||
all_vwtasks = set(self.cache.vimwikitask_cache.values())
|
||||
all_vwtasks = set(self.cache.vwtask.values())
|
||||
vwtasks_outside_viewport = all_vwtasks - set(self.tasks)
|
||||
tasks_outside_viewport = set(
|
||||
t.task for t in vwtasks_outside_viewport
|
||||
|
@ -274,7 +279,7 @@ class ViewPort(object):
|
|||
match = re.search(regexp.GENERIC_TASK, line)
|
||||
|
||||
if match:
|
||||
self.tasks.add(self.cache[i])
|
||||
self.tasks.add(self.cache.vwtask[i])
|
||||
else:
|
||||
# If we didn't found a valid task, terminate the viewport
|
||||
break
|
||||
|
@ -324,7 +329,7 @@ class ViewPort(object):
|
|||
added_at = self.line_number + existing_tasks + added_tasks
|
||||
|
||||
# Add the task object to cache
|
||||
self.cache[vwtask.ShortUUID(task['uuid'], self.tw)] = task
|
||||
self.cache.task[vwtask.ShortUUID(task['uuid'], self.tw)] = task
|
||||
|
||||
# Create the VimwikiTask
|
||||
vimwikitask = vwtask.VimwikiTask.from_task(self.cache, task)
|
||||
|
@ -335,6 +340,6 @@ class ViewPort(object):
|
|||
self.cache.insert_line(str(vimwikitask), added_at)
|
||||
|
||||
# Save it to cache
|
||||
self.cache[added_at] = vimwikitask
|
||||
self.cache.vwtask[added_at] = vimwikitask
|
||||
|
||||
sort.TaskSorter(self.cache, self.tasks, self.sort).execute()
|
||||
|
|
|
@ -105,9 +105,8 @@ class VimwikiTask(object):
|
|||
return vwtask
|
||||
|
||||
@classmethod
|
||||
def from_current_line(cls, cache):
|
||||
line_number = util.get_current_line_number()
|
||||
return cls.from_line(cache, line_number)
|
||||
def parse_line(cls, number):
|
||||
return re.search(regexp.GENERIC_TASK, vim.current.buffer[number])
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, cache, number):
|
||||
|
@ -118,7 +117,7 @@ class VimwikiTask(object):
|
|||
# Protected access is ok here
|
||||
# pylint: disable=W0212
|
||||
|
||||
match = re.search(regexp.GENERIC_TASK, vim.current.buffer[number])
|
||||
match = cache.line[(cls, number)]
|
||||
|
||||
if not match:
|
||||
return None
|
||||
|
@ -155,6 +154,8 @@ class VimwikiTask(object):
|
|||
due = match.group('due')
|
||||
if due:
|
||||
# With strptime, we get a native datetime object
|
||||
parsed_due = None
|
||||
|
||||
try:
|
||||
parsed_due = datetime.strptime(due, regexp.DATETIME_FORMAT)
|
||||
except ValueError:
|
||||
|
@ -167,7 +168,8 @@ class VimwikiTask(object):
|
|||
|
||||
# We need to interpret it as timezone aware object in user's
|
||||
# timezone, This properly handles DST and timezone offset.
|
||||
self.task['due'] = parsed_due
|
||||
if parsed_due:
|
||||
self.task['due'] = parsed_due
|
||||
|
||||
# After all line-data parsing, save the data in the buffer
|
||||
self._buffer_data = {key:self[key] for key in self.buffer_keys}
|
||||
|
@ -226,7 +228,7 @@ class VimwikiTask(object):
|
|||
# Else try to load it or create a new one
|
||||
if self.uuid:
|
||||
try:
|
||||
return self.cache[self.uuid]
|
||||
return self.cache.task[self.uuid]
|
||||
except Task.DoesNotExist:
|
||||
# Task with stale uuid, recreate
|
||||
self.__unsaved_task = Task(self.tw)
|
||||
|
@ -235,7 +237,7 @@ class VimwikiTask(object):
|
|||
'echom "UUID \'{0}\' not found, Task on line {1} will be '
|
||||
're-created in TaskWarrior."'.format(
|
||||
self.uuid,
|
||||
self['line_number']
|
||||
self['line_number'] + 1
|
||||
))
|
||||
self.uuid = None
|
||||
else:
|
||||
|
@ -285,7 +287,7 @@ class VimwikiTask(object):
|
|||
# the temporary reference
|
||||
if self.__unsaved_task is not None:
|
||||
self.uuid = ShortUUID(self.__unsaved_task['uuid'], self.tw)
|
||||
self.cache[self.uuid] = self.__unsaved_task
|
||||
self.cache.task[self.uuid] = self.__unsaved_task
|
||||
self.__unsaved_task = None
|
||||
|
||||
# Mark task as done.
|
||||
|
@ -344,11 +346,15 @@ class VimwikiTask(object):
|
|||
])
|
||||
|
||||
def find_parent_task(self):
|
||||
# If this task is not indented, we have nothing to do here
|
||||
if not self['indent']:
|
||||
return None
|
||||
|
||||
for i in reversed(range(0, self['line_number'])):
|
||||
# The from_line constructor returns None if line doesn't match a task
|
||||
task = self.cache[i]
|
||||
if task and len(task['indent']) < len(self['indent']):
|
||||
return task
|
||||
line = self.cache.line[(VimwikiTask, i)]
|
||||
if line and len(line.group('space')) < len(self['indent']):
|
||||
return self.cache.vwtask[i]
|
||||
|
||||
def apply_defaults(self):
|
||||
for i in reversed(range(0, self['line_number'])):
|
||||
|
|
|
@ -279,6 +279,13 @@ class MockCache(object):
|
|||
warriors = {'default': 'default'}
|
||||
buffer_has_authority = True
|
||||
|
||||
def __init__(self):
|
||||
from taskwiki import store
|
||||
self.line = store.LineStore(self)
|
||||
self.vwtask = dict()
|
||||
self.task = dict()
|
||||
self.viewport = dict()
|
||||
|
||||
def reset(self):
|
||||
self.warriors.clear()
|
||||
self.warriors.update({'default': 'default'})
|
||||
|
|
|
@ -142,8 +142,8 @@ class TestChooseTag(IntegrationTest):
|
|||
for task in self.tasks:
|
||||
task.refresh()
|
||||
|
||||
assert self.tasks[0]['tags'] == ["home"]
|
||||
assert self.tasks[1]['tags'] == ["home"]
|
||||
assert self.tasks[0]['tags'] == set(["home"])
|
||||
assert self.tasks[1]['tags'] == set(["home"])
|
||||
|
||||
|
||||
class TestChooseTagCancelled(IntegrationTest):
|
||||
|
@ -178,6 +178,44 @@ class TestChooseTagCancelled(IntegrationTest):
|
|||
for task in self.tasks:
|
||||
task.refresh()
|
||||
|
||||
assert self.tasks[0]['tags'] == ["home"]
|
||||
assert self.tasks[1]['project'] == None
|
||||
assert self.tasks[0]['tags'] == set(["home"])
|
||||
assert self.tasks[1]['tags'] == set()
|
||||
|
||||
|
||||
class TestChooseTagNoSelected(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
* [ ] test task 1 #{uuid}
|
||||
* [ ] test task 2 #{uuid}
|
||||
"""
|
||||
|
||||
vimoutput = """
|
||||
Tag Count
|
||||
---- -----
|
||||
home 1
|
||||
"""
|
||||
|
||||
tasks = [
|
||||
dict(description="test task 1", tags=["home"]),
|
||||
dict(description="test task 2"),
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
self.client.normal('1gg')
|
||||
sleep(0.5)
|
||||
|
||||
self.command("TaskWikiChooseTag")
|
||||
sleep(0.5)
|
||||
|
||||
# No tak on the 5th row
|
||||
self.client.normal('5gg')
|
||||
sleep(0.5)
|
||||
self.client.feedkeys("\\<CR>")
|
||||
sleep(0.5)
|
||||
|
||||
for task in self.tasks:
|
||||
task.refresh()
|
||||
|
||||
assert self.tasks[0]['tags'] == set(["home"])
|
||||
assert self.tasks[1]['tags'] == set()
|
||||
|
||||
|
|
|
@ -259,6 +259,35 @@ class TestInfoAction(IntegrationTest):
|
|||
assert re.search(data2, output, re.MULTILINE)
|
||||
|
||||
|
||||
class TestInfoActionTriggeredByEnter(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
* [ ] test task 1 #{uuid}
|
||||
* [ ] test task 2 #{uuid}
|
||||
"""
|
||||
|
||||
tasks = [
|
||||
dict(description="test task 1"),
|
||||
dict(description="test task 2"),
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
self.client.type('1gg')
|
||||
self.client.feedkeys("\\<CR>")
|
||||
sleep(0.5)
|
||||
|
||||
assert self.command(":py print vim.current.buffer", silent=False).startswith("<buffer info")
|
||||
output = '\n'.join(self.read_buffer())
|
||||
|
||||
header = r'\s*'.join(['Name', 'Value'])
|
||||
data = r'\s*'.join(['Description', 'test task 1'])
|
||||
data2 = r'\s*'.join(['Status', 'Pending'])
|
||||
|
||||
assert re.search(header, output, re.MULTILINE)
|
||||
assert re.search(data, output, re.MULTILINE)
|
||||
assert re.search(data2, output, re.MULTILINE)
|
||||
|
||||
|
||||
class TestInfoActionMoved(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
|
@ -692,8 +721,8 @@ class TestModInteractiveAction(IntegrationTest):
|
|||
for task in self.tasks:
|
||||
task.refresh()
|
||||
|
||||
assert self.tasks[0]['tags'] == ["work"]
|
||||
assert self.tasks[1]['tags'] == []
|
||||
assert self.tasks[0]['tags'] == set(["work"])
|
||||
assert self.tasks[1]['tags'] == set()
|
||||
|
||||
|
||||
class TestModVisibleAction(IntegrationTest):
|
||||
|
@ -832,6 +861,35 @@ class TestDoneAction(IntegrationTest):
|
|||
|
||||
assert self.tasks[1]['end'] == None
|
||||
|
||||
class TestDoneNoSelected(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
* [ ] test task 1 #{uuid}
|
||||
|
||||
* [ ] test task 2 #{uuid}
|
||||
"""
|
||||
|
||||
vimoutput = """
|
||||
* [ ] test task 1 #{uuid}
|
||||
|
||||
* [ ] test task 2 #{uuid}
|
||||
"""
|
||||
|
||||
tasks = [
|
||||
dict(description="test task 1"),
|
||||
dict(description="test task 2"),
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
self.client.type('2gg')
|
||||
self.command(
|
||||
"TaskWikiDone",
|
||||
regex="No tasks selected.",
|
||||
lines=1)
|
||||
|
||||
assert self.tasks[0]['status'] == "pending"
|
||||
assert self.tasks[1]['status'] == "pending"
|
||||
|
||||
|
||||
class TestDoneActionMoved(IntegrationTest):
|
||||
|
||||
|
@ -911,3 +969,30 @@ class TestDoneActionRange(IntegrationTest):
|
|||
|
||||
assert (now - self.tasks[1]['end']).total_seconds() < 5
|
||||
assert (self.tasks[1]['end'] - now).total_seconds() < 5
|
||||
|
||||
|
||||
class TestSortManually(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
* [ ] test task 1 #{uuid}
|
||||
* [ ] test task 2 #{uuid}
|
||||
"""
|
||||
|
||||
vimoutput = """
|
||||
* [ ] test task 2 #{uuid}
|
||||
* [ ] test task 1 #{uuid}
|
||||
"""
|
||||
|
||||
tasks = [
|
||||
dict(description="test task 1"),
|
||||
dict(description="test task 2"),
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
self.client.normal('1gg')
|
||||
sleep(0.5)
|
||||
self.client.normal('VG')
|
||||
sleep(0.5)
|
||||
self.client.feedkeys(":TaskWikiSort description-")
|
||||
self.client.type("<Enter>")
|
||||
sleep(0.5)
|
||||
|
|
|
@ -246,6 +246,30 @@ class TestTagsSimple(IntegrationTest):
|
|||
assert re.search(work, output, re.MULTILINE)
|
||||
|
||||
|
||||
class TestTagsSimpleFiltered(IntegrationTest):
|
||||
|
||||
tasks = [
|
||||
dict(description="home task"),
|
||||
dict(description="home chore task 1", tags=['chore']),
|
||||
dict(description="home chore task 2", tags=['chore']),
|
||||
dict(description="work task 1", tags=['work']),
|
||||
dict(description="work task 2", tags=['work']),
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
self.command("TaskWikiTags +chore")
|
||||
assert self.command(":py print vim.current.buffer", silent=False).startswith("<buffer tags")
|
||||
output = '\n'.join(self.read_buffer())
|
||||
|
||||
header = r'\s*'.join(['Tag', 'Count'])
|
||||
chores = r'\s*'.join(['chore', '2'])
|
||||
work = r'\s*'.join(['work', '2'])
|
||||
|
||||
assert re.search(header, output, re.MULTILINE)
|
||||
assert re.search(chores, output, re.MULTILINE)
|
||||
assert not re.search(work, output, re.MULTILINE)
|
||||
|
||||
|
||||
class TestSplitReplacement(IntegrationTest):
|
||||
|
||||
def execute(self):
|
||||
|
|
|
@ -147,7 +147,7 @@ class TestViewportDefaultsAssigment(IntegrationTest):
|
|||
task = self.tw.tasks.pending()[0]
|
||||
assert task['description'] == 'tag work task'
|
||||
assert task['status'] == 'pending'
|
||||
assert task['tags'] == ['work']
|
||||
assert task['tags'] == set(['work'])
|
||||
|
||||
|
||||
class TestViewportDefaultsExplicit(IntegrationTest):
|
||||
|
@ -169,7 +169,7 @@ class TestViewportDefaultsExplicit(IntegrationTest):
|
|||
assert task['description'] == 'home task'
|
||||
assert task['status'] == 'pending'
|
||||
assert task['project'] == 'Chores'
|
||||
assert task['tags'] == []
|
||||
assert task['tags'] == set()
|
||||
|
||||
|
||||
class TestViewportDefaultsExplicitEmpty(IntegrationTest):
|
||||
|
@ -191,7 +191,7 @@ class TestViewportDefaultsExplicitEmpty(IntegrationTest):
|
|||
assert task['description'] == 'home task'
|
||||
assert task['status'] == 'pending'
|
||||
assert task['project'] == None
|
||||
assert task['tags'] == []
|
||||
assert task['tags'] == set()
|
||||
|
||||
|
||||
class TestViewportInspection(IntegrationTest):
|
||||
|
@ -227,6 +227,44 @@ class TestViewportInspection(IntegrationTest):
|
|||
assert self.command(":py print vim.current.buffer", regex="<buffer taskwiki.")
|
||||
|
||||
|
||||
class TestViewportInspectionWithVisibleTag(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
=== Work tasks | +work -VISIBLE ===
|
||||
* [ ] tag work task #{uuid}
|
||||
|
||||
=== Home tasks | +home ===
|
||||
* [ ] tag work task #{uuid}
|
||||
"""
|
||||
|
||||
vimoutput = """
|
||||
ViewPort inspection:
|
||||
--------------------
|
||||
Name: Work tasks
|
||||
Filter used: -DELETED -PARENT +work
|
||||
Defaults used: tags:['work']
|
||||
Ordering used: due+,pri-,project+
|
||||
Matching taskwarrior tasks: 0
|
||||
Displayed tasks: 0
|
||||
Tasks to be added:
|
||||
Tasks to be deleted:
|
||||
"""
|
||||
|
||||
tasks = [
|
||||
dict(description="tag work task", tags=['work', 'home']),
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
self.command("w", regex="written$", lines=1)
|
||||
sleep(0.5)
|
||||
self.client.feedkeys('1gg')
|
||||
sleep(0.5)
|
||||
self.client.feedkeys(r'\<CR>')
|
||||
sleep(0.5)
|
||||
|
||||
assert self.command(":py print vim.current.buffer", regex="<buffer taskwiki.")
|
||||
|
||||
|
||||
class TestViewportsUnicodeTaskGeneration(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
|
|
|
@ -26,6 +26,27 @@ class TestSimpleTaskCreation(IntegrationTest):
|
|||
assert task['status'] == 'pending'
|
||||
|
||||
|
||||
class TestInvalidUUIDTask(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
* [ ] This is a test task #abc123ef
|
||||
"""
|
||||
|
||||
vimoutput = """
|
||||
* [ ] This is a test task #{uuid}
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
self.command("w", regex="on line 1 will be re-created")
|
||||
|
||||
# Check that only one tasks with this description exists
|
||||
assert len(self.tw.tasks.pending()) == 1
|
||||
|
||||
task = self.tw.tasks.pending()[0]
|
||||
assert task['description'] == 'This is a test task'
|
||||
assert task['status'] == 'pending'
|
||||
|
||||
|
||||
class TestSimpleTaskModification(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
|
@ -111,6 +132,28 @@ class TestSimpleTaskWithDueDatetimeCreation(IntegrationTest):
|
|||
assert task['due'] == local_zone.localize(due)
|
||||
|
||||
|
||||
class TestSimpleTaskWithFlawedDueDatetimeCreation(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
* [ ] This is a test task (2015-94-53 12:00)
|
||||
"""
|
||||
|
||||
vimoutput = """
|
||||
* [ ] This is a test task #{uuid}
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
self.command("w", regex="Invalid timestamp", lines=2)
|
||||
|
||||
# Check that only one tasks with this description exists
|
||||
assert len(self.tw.tasks.pending()) == 1
|
||||
|
||||
task = self.tw.tasks.pending()[0]
|
||||
assert task['description'] == 'This is a test task'
|
||||
assert task['status'] == 'pending'
|
||||
assert task['due'] == None
|
||||
|
||||
|
||||
class TestSimpleTaskWithDueDateCreation(IntegrationTest):
|
||||
|
||||
viminput = """
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue