Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new action for bulk-modifying all tasks currently in view (addresses #240) #290

Open
wants to merge 11 commits into
base: 2.x
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions vit/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def register(self):
self.action_registrar.register('TASK_DELETE', 'Delete task')
self.action_registrar.register('TASK_DENOTATE', 'Denotate a task')
self.action_registrar.register('TASK_MODIFY', 'Modify task (supports tab completion)')
self.action_registrar.register('TASK_MODIFY_ALL', 'Modify all tasks currently in view')
self.action_registrar.register('TASK_START_STOP', 'Start/stop task')
self.action_registrar.register('TASK_DONE', 'Mark task done')
self.action_registrar.register('TASK_PRIORITY', 'Modify task priority')
Expand Down
31 changes: 30 additions & 1 deletion vit/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ def setup_main_loop(self):
def set_active_context(self):
self.context = self.task_config.get_active_context()

def active_context_filter(self):
return self.contexts[self.context]['filter'] if self.context else []

def active_view_filters(self):
# precedence-preserving concatenation of context, report and extra filters
return self.model.build_task_filters(self.active_context_filter(), self.model.active_report_filter(), self.extra_filters)

def load_contexts(self):
self.contexts = self.task_config.get_contexts()

Expand Down Expand Up @@ -164,6 +171,7 @@ def register_managed_actions(self):
self.action_manager_registrar.register('TASK_DELETE', self.task_action_delete)
self.action_manager_registrar.register('TASK_DENOTATE', self.task_action_denotate)
self.action_manager_registrar.register('TASK_MODIFY', self.task_action_modify)
self.action_manager_registrar.register('TASK_MODIFY_ALL', self.task_action_modify_all)
self.action_manager_registrar.register('TASK_START_STOP', self.task_action_start_stop)
self.action_manager_registrar.register('TASK_DONE', self.task_action_done)
self.action_manager_registrar.register('TASK_PRIORITY', self.task_action_priority)
Expand Down Expand Up @@ -195,6 +203,7 @@ def _task_attribute_replace(task, attribute):
else:
return str(task[attribute])
return ''

replacements = [
{
'match_callback': _task_attribute_match,
Expand Down Expand Up @@ -360,6 +369,21 @@ def command_bar_keypress(self, data):
# before hitting enter?
if self.execute_command(['task', metadata['uuid'], 'modify'] + args, wait=self.wait):
self.activate_message_bar('Task %s modified' % self.model.task_id(metadata['uuid']))
elif op == 'modify_multiple':
# same underlying command is the modify command above, only the results is parsed
kevinstadler marked this conversation as resolved.
Show resolved Hide resolved
# differently and the message bar set accordingly

# to be absolutely safe, double-check whether the number of tasks matched by
# the filter is still the same (or has changed because of some other operations,
# due/schedule/wait timeouts etc)
ntasks = self.model.get_n_tasks(metadata['target'])
if ntasks != metadata['ntasks']:
self.activate_message_bar('Not applying the modification because the number of tasks has changed (was %s now %s)' % (metadata['ntasks'], ntasks))
elif self.execute_command(['task', metadata['target'], 'modify'] + args, wait=self.wait):
# TODO depending on interactive confirmation prompts, not all tasks
# might actually have been modified. for completeness one would need to extract
# the number of modified tasks from the stdout of the task command above!
self.activate_message_bar('Modified %s tasks' % ntasks)
kevinstadler marked this conversation as resolved.
Show resolved Hide resolved
elif op == 'annotate':
task = self.model.task_annotate(metadata['uuid'], data['text'])
if task:
Expand Down Expand Up @@ -740,6 +764,11 @@ def task_action_modify(self):
self.activate_command_bar('modify', 'Modify: ', {'uuid': uuid})
self.task_list.focus_by_task_uuid(uuid, self.previous_focus_position)

def task_action_modify_all(self):
currentviewfilter = self.active_view_filters()
kevinstadler marked this conversation as resolved.
Show resolved Hide resolved
ntasks = self.model.get_n_tasks(currentviewfilter)
self.activate_command_bar('modify_multiple', 'Modify all (%s tasks): ' % ntasks, {'target': currentviewfilter, 'ntasks': ntasks})

def task_action_start_stop(self):
uuid, task = self.get_focused_task()
if task:
Expand Down Expand Up @@ -909,7 +938,7 @@ def update_report(self, report=None):
self.task_config.get_projects()
self.refresh_blocking_task_uuids()
self.formatter.recalculate_due_datetimes()
context_filters = self.contexts[self.context]['filter'] if self.context else []
context_filters = self.active_context_filter()
try:
self.model.update_report(self.report, context_filters=context_filters, extra_filters=self.extra_filters)
except VitException as err:
Expand Down
10 changes: 8 additions & 2 deletions vit/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ def parse_error(self, err):

def update_report(self, report, context_filters=[], extra_filters=[]):
self.report = report
active_report = self.active_report()
report_filters = active_report['filter'] if 'filter' in active_report else []
report_filters = self.active_report_filter()
filters = self.build_task_filters(context_filters, report_filters, extra_filters)
try:
self.tasks = self.tw.tasks.filter(filters) if filters else self.tw.tasks.all()
Expand All @@ -45,6 +44,10 @@ def update_report(self, report, context_filters=[], extra_filters=[]):
except TaskWarriorException as err:
raise VitException(self.parse_error(err))

def active_report_filter(self):
active_report = self.active_report()
return active_report['filter'] if 'filter' in active_report else []

def build_task_filters(self, *all_filters):
def reducer(accum, filters):
if filters:
Expand All @@ -53,6 +56,9 @@ def reducer(accum, filters):
filter_parts = reduce(reducer, all_filters, [])
return ' '.join(filter_parts) if filter_parts else ''

def get_n_tasks(self, filter):
return len(self.tw.tasks.filter(filter))

def get_task(self, uuid):
try:
return self.tw.tasks.get(uuid=uuid)
Expand Down