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

Methods for modifying multiple tasks #240

Open
jmriego opened this issue Jun 5, 2020 · 22 comments
Open

Methods for modifying multiple tasks #240

jmriego opened this issue Jun 5, 2020 · 22 comments

Comments

@jmriego
Copy link

jmriego commented Jun 5, 2020

Hi! First of all, thank you for your hard work. I found out very recently about vit and I find it amazing. I can't believe it's not a lot more popular between the Taskwarrior tools!

I was trying to make changes in multiple tasks at the same time and was wondering if there's some way of doing this.

The two ways I would think of are similar as what you would do in vim:

  1. Mark multiple tasks with something like mt and then you run a command that would affect all of them. This should also work for when you have mappings that use the task UUID and would run them multiple times with a different UUID each
  2. Add a . mapping that will repeat the previous action. In my opinion itt would also have to work for things like :!rw task .... so it would be a bit different of the same mapping in vim that don't include :commands.

Does this sounds like something that could be added? Thanks!

@jmriego
Copy link
Author

jmriego commented Jun 5, 2020

I suppose another option would be a v or V mapping to start a selection but that would require the tasks you want to modify to be next to each other

@thehunmonkgroup
Copy link
Member

Multi-task operations have been on my wish list for awhile.

It's not something I have time to implement now, but I'd be happy to look at a PR for it.

I do suspect the implementation will be a lot trickier than you may imagine. Adjusting the interface to display multiple selections and operate on multiple tasks will likely involve some fairly major refactors, and a poor implementation would likely be pretty buggy.

@jmriego
Copy link
Author

jmriego commented Jun 5, 2020

I can imagine! That's why I mentioned several methods that could potentially work. As you said, there's the interface part with also changes in the highlighting. You have to think about multiple types of changes and running task commands that you don't know what could affect...
So I think the easiest method would probably be the dot, but what do you think? I might have time to have a look at the code if that makes sense

@thehunmonkgroup
Copy link
Member

The dot would probably be a good start. I still think that might have some dragons in it, but conceptually, "store last action, repeat when told" seems fairly straightforward.

Also, I think even if we start here, adding the multiple highlighting later wouldn't conflict, it would just be another usable method.

@thehunmonkgroup
Copy link
Member

#267 is taking a first stab at this, still a long ways to go...

@kevinstadler
Copy link

Since bulk-editing is also a feature I'd really like to be able to use from within vit I've had a little look at the code. Here's some ideas, would love to hear some feedback before I go forward and actually implement anything:

  • task's interface for bulk editing is simply task <filter> <command> ..., so in terms of the complexity of operations like modify, delete, start, stop etc. it doesn't matter whether the <filter> is a single uuid (that's the only use case invoked by vit operations at the moment), a space-separated list of uuids, or a logical filter expression. I'd therefore suggest working towards refactoring the existing editing operations to also process lists of tasks or filter expressions and pass them on to the already existing single self.execute_command(['task', ...]) command. This would mainly be around here in the code, where the commands would have to look at other arguments passed in data to see whether the command target is really just one uuid, or something else (although some code like for done might also be in the TaskListModel?):

    vit/vit/application.py

    Lines 358 to 370 in 62273a2

    elif op == 'modify':
    # TODO: Will this break if user clicks another list item
    # 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 == 'annotate':
    task = self.model.task_annotate(metadata['uuid'], data['text'])
    if task:
    self.table.flash_focus()
    self.update_report()
    self.activate_message_bar('Annotated task %s' % self.model.task_id(task['uuid']))
    elif op == 'tag':
    task = self.model.task_tags(metadata['uuid'], args)

  • Since bulk-editing is a pretty advanced feature I think it's also more useful to first focus on scriptability, UI implementations can always be added later. In particular I'm thinking about a "bulk-edit all tasks in current view" use case, which really only requires information about (1) what the default filter of the current report+context is and (2) what other custom filters were applied on top of that within vit. I know I can already implement the former using a custom variable replacement that invokes task to find out the relevant filters, but the latter needs access to vit's runtime application variables. I'd therefore propose to add support for (something like) the following variable replacements to vit:

    1. {REPORT_FILTER} (possibly also {CONTEXT_FILTER}?)
    2. {EXTRA_FILTER} (replaced by the application's self.extra_filters)
    3. {VIEW_FILTER} (this one's simply ({REPORT_FILTER}) and ({EXTRA_FILTER}))

With these two things in mind I think there could be a nice 3-step roadmap, each of which provides new functionality that builds/relies on the previous ones:

  1. expose current filter as well as the underlying filters of the current report via variables that users can use in their macros to make external calls like :!rw task {VIEW_FILTER} modify ...
  2. for each task operation that supports bulk editing, add a TASK_*_BULK action to the already existing ones, making sure that vit's underlying task invocation functions support filter expressions and task-list targets

    vit/vit/application.py

    Lines 737 to 784 in 62273a2

    def task_action_modify(self):
    uuid, _ = self.get_focused_task()
    if uuid:
    self.activate_command_bar('modify', 'Modify: ', {'uuid': uuid})
    self.task_list.focus_by_task_uuid(uuid, self.previous_focus_position)
    def task_action_start_stop(self):
    uuid, task = self.get_focused_task()
    if task:
    if self.confirm:
    self.activate_command_bar('start-stop', '%s task %s? (y/n): ' % (task.active and 'Stop' or 'Start', self.model.task_id(uuid)), {'uuid': uuid, 'choices': {'y': True}})
    else:
    self.task_start_stop(uuid)
    self.task_list.focus_by_task_uuid(uuid, self.previous_focus_position)
    def task_action_done(self):
    uuid, task = self.get_focused_task()
    if task:
    if self.confirm:
    self.activate_command_bar('done', 'Mark task %s done? (y/n): ' % self.model.task_id(uuid), {'uuid': uuid, 'choices': {'y': True}})
    else:
    self.task_done(uuid)
    self.task_list.focus_by_task_uuid(uuid, self.previous_focus_position)
    def task_action_priority(self):
    uuid, _ = self.get_focused_task()
    if uuid:
    choices = {}
    for choice in self.task_config.priority_values:
    key = choice.lower() or 'n'
    choices[key] = choice
    self.activate_command_bar('priority', 'Priority (%s): ' % '/'.join(choices), {'uuid': uuid, 'choices': choices})
    def task_action_project(self):
    uuid, _ = self.get_focused_task()
    if uuid:
    self.activate_command_bar('project', 'Project: ', {'uuid': uuid})
    def task_action_tags(self):
    uuid, _ = self.get_focused_task()
    if uuid:
    self.activate_command_bar('tag', 'Tag: ', {'uuid': uuid})
    def task_action_wait(self):
    uuid, _ = self.get_focused_task()
    if uuid:
    self.activate_command_bar('wait', 'Wait: ', {'uuid': uuid})
  3. add support for manual task selection (like in the pull request above), with the selection list populated by the UI seamlessly passed through to the newly added _BULK commands

I'd be keen to get some work done on this (especially step 1) soon, so let me know what you think!

@thehunmonkgroup
Copy link
Member

@kevinstadler on first read, this sounds like a solid plan. I'm a little concerned about ripping out the tasklib stuff and replacing it with direct calls to the task binary -- it's been so long since I coded that, I can't remember what the specific advantages were when using tasklib. I'm sure there were some ;)

At the same time, it's always felt clunky to me to have that hybrid model of tasklib/task binary. If we went the route you're proposing, perhaps we should expunge tasklib (at least for all the write operations).

If you want to take a crack at the PR, I'll commit the time to review and offer suggestions/improvements. I agree that having this feature would be a big plus, and I don't have the time to code it. As long as the code is clean and maintainable, we can get it in.

@scottkosty
Copy link
Member

Thanks for working on this!

I did not read the details, but would it make sense to add the under-the-hood work to tasklib? Is the library still maintained? It seems these features might be useful to other Taskwarrior tools that rely on tasklib. I don't know anything about it though so perhaps it is inherently out of the scope of tasklib.

Regarding the interface, two tools that I've used as inspiration for working on vit are mutt and ranger. Multiline (non-consecutive) selections are more natural in those two tools than in Vim so their interfaces might provide some good inspiration.

Again, thanks for your time on this! It is a feature that has been requested by a lot of users over the years and no one has dared to jump down the rabbit hole :)

@thehunmonkgroup
Copy link
Member

I did not read the details, but would it make sense to add the under-the-hood work to tasklib?

The problem with that is that the task binary is so darn flexible. I think it's going to be a challenge to account for all that we want to support only using tasklib. The project is still maintained, so it might be worth filing a ticket to see what the dev says.

@scottkosty
Copy link
Member

The problem with that is that the task binary is so darn flexible. I think it's going to be a challenge to account for all that we want to support only using tasklib. The project is still maintained, so it might be worth filing a ticket to see what the dev says.

Ah that is good to know. Thanks. Sounds like it might be one of those "sure that's the nice thing to do in theory but in practice it makes things a lot more complicated" things.

@tbabej
Copy link
Collaborator

tbabej commented Mar 27, 2021

Hey guys,

just randomly chiming in here, but I'm wondering if you want to jump on a call to discuss this? I wrote like ~70% of tasklib and if my memory serves me right, it covers a pretty broad range of use cases. I am using it quite successfully in taskwiki.

@kevinstadler
Copy link

kevinstadler commented Mar 27, 2021

I didn't even realise that tasklib was being used, as the modify command that I looked at already executes task directly via the application's self.execute_command() (same as at least 6 other commands, in particular undo, sync, edit, info, add and context). I wonder if the choice not to go through tasklib for these was the fact that many of them are quite complex so that their failure might require interactive prompting, which is then automatically handled by task?

Unless you want to make a push towards passing modify through tasklib as well, the current code:

if self.execute_command(['task', metadata['uuid'], 'modify'] + args, wait=self.wait):

can already support bulk-editing via any of the following minimal changes:

  • passing a filter string in place of metadata['uuid']
  • passing a space-separated string of uuids as metadata['uuid'] instead of just a single uuid
  • passing more uuid arguments between 'task' and 'modify' (I think all arguments just get joined with spaces for execution anyway)

@thehunmonkgroup
Copy link
Member

@tbabej @kevinstadler I'm game to jump on a call to discuss the best implementation.

Again, it's been so long since I built those pieces, I cannot recall the precise reasoning. I do remember that I tried to use tasklib as much as possible, and where I couldn't figure out how to use it, I fell back to the task binary directly.

I'm a little concerned about scope creep if we elect to run everything through tasklib. I guess it depends how hard that is, I haven't studied that code enough to have an opinion.

@kevinstadler
Copy link

Just to point out that any decision to change operation execution from task to tasklib is independent of the present multiple-task-modification issue, as the current implementation already supports batch operations (at least for modify). The biggest part of step 2 (adding TASK_*_BULK operations) concerns how command arguments should be represented within vit. If it turns out that tasklib is an option for batch modification as well, then the actual external execution can still be swapped in/out without affecting vit's internal processing of batch modifications.

I've had a go at streamlining access to all current filter information within vit and providing access to them via keybinding replacements. I wasn't 100% sure about naming conventions in the codebase, so please have a look and let me know if it looks alright to you so far!

kevinstadler added a commit to kevinstadler/vit that referenced this issue Apr 7, 2021
This action is a first use case (but really just one special case) of bulk editing, which  makes use of the new 'modify_multiple' operation by passing the filter expression that matches all tasks visible in the current view.
@kevinstadler
Copy link

After #290 is merged, it will actually be possible to achieve @jmriego's use case by means of a workaround that uses a tag (say selected) for marking tasks as selected, then filtering the view for all +selected tasks before calling {ACTION_TASK_MODIFY_ALL}:

[keybinding]

# select/de-select tasks by applying a `selected` tag (assumes default m = {ACTION_TASK_MODIFY} to work)
n = m+selected<Enter>
N = m-selected<Enter>

# could use a nice chunky `tag.selected.label = ✓` to visually mark selected tasks

# bind bulk modification to some key, just so it can be used by the actual command below
@ = {ACTION_TASK_MODIFY_ALL}
# prompt modification of all +selected tasks (assumes default f = {ACTION_REPORT_FILTER} to work)
M = f+selected<Enter>@-selected<Space>

Note that the above will also apply to any +selected tasks which were hidden from view because of extra filters that were active at the time of hitting M. Because {ACTION_REPORT_FILTER} overrides (rather than appends to) the extra filters, to be absolutely accurate one would still require access to the current extra filter information through variable replacement (see #289), then use the following:

M = f({REPORT_FILTER_EXTRA}) +selected<Enter>@-selected<Space>

@thehunmonkgroup
Copy link
Member

This hadn't occurred to me, and is a brilliant use of the existing tools.

Have a look at my proposal in #291 -- I think this could be fairly easy to implement, solve this issue more elegantly, and offer other benefits in the future.

@yukiohmiya
Copy link

yukiohmiya commented Jul 22, 2021

At first, Thank you for doing good job, and I've used vit everyday.
I implemented about multi-select operation.In my opinion, I think I want to use more simple method in this issue.

https://github.com/yukiohmiya/vit/tree/feature/multiple_select

I implemented this multi-select operation by following flow.

  1. Select a task by v command. Add selected task_id to bulk_id list.
  2. If you select same task again by v, Delete selected task_id from bulk_id list.
  3. If you push ESC key, Clear all bulk_id list.
  4. Output bulk_id list after :!rw task whne you push t command.

This implementation works well in my environment.
I want to hear your opinion.

Tanks.

Peek.2021-07-22.22-58.mp4

@jmriego
Copy link
Author

jmriego commented Jul 27, 2021

thanks a lot @yukiohmiya ! This looks really promising

Some feedback I can think of:
When I use the {bulk_id} in a mapping for some reason it seems to be using the id instead of the uuid. Even in your video you get something like :!rw task 4 5 instead of !rw task abd-1234-abc def-4567-dfe which would be safer

Also, do you think instead of creating {BULK_ID} it would make sense to make {TASK_UUID} have the value of the bulk list if there's a multiple selection or the current row if not? That way I can use the same mapping for both multi and single selection

@yukiohmiya
Copy link

@jmriego
Thanks for your feedback!!

I use buld_id because I want to display command more simply and I don't know difference task id from uuid. I realized maintainer use uuid in vit source code now.

I agree with your idea using uuid and {TASK_UUID}. I'm going to update my code.

kevinstadler added a commit to kevinstadler/vit that referenced this issue Dec 3, 2021
This action is a first use case (but really just one special case) of bulk editing, which  makes use of the new 'modify_multiple' operation by passing the filter expression that matches all tasks visible in the current view.
@lyndhurst
Copy link

Hi,

I was about to ask about the possibilities of adding this feature to vit, and I see people have already done some work on this. I was wondering if this feature plan was still alive...

Thanks again for this great tool, and all the work from the maintainer and contributors to make it even better !

@thehunmonkgroup
Copy link
Member

@lyndhurst #290 was the main thrust of this feature request, and if you read that issue, you'll see it got hung up on some key naming/convention decisions.

Someone will need to pick up that PR and continue it pushing it forward if we want to see this in.

@lyndhurst
Copy link

Thank you for the pointer, I am going to read it carefully so I will hopefully understand the issue better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants