Skip to content

Improve order of the todo agenda #842

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

Closed

Conversation

lyz-code
Copy link

As with all my PRs, I'm really a noob at Lua trying to do my best. I'd appreciate any suggestions to improve the code.

Until it's merged users can use my fork

As the order of the org_todo_keywords define the order of the todo in
the agenda, a user may want to have a default TODO state different than
the first one in the org_todo_keywords list
@lyz-code lyz-code force-pushed the feat/improve-todo-agenda-order branch from 47ba087 to 8a1acbf Compare December 24, 2024 15:12
@lyz-code
Copy link
Author

If you like the implementation I can add the documentation of the new configuration option

@kristijanhusak
Copy link
Member

Is this how Emacs does the sorting, or just something that you think would be good to add? IIRC sort is not changed when clocking in/out.

@lyz-code
Copy link
Author

lyz-code commented Jan 2, 2025

I've never used Emacs so it's my personal opinion :S , I can also add options to configure the non default behaviours if you want

@seflue
Copy link
Contributor

seflue commented Jan 3, 2025

Thanks @lyz-code for the effort. I like the attempt to provide a better agenda sorting then the current one. Because this implementation is the personal flavor of @lyz-code, my question to you, @kristijanhusak would be: Would you be open that we provide an option, where the user can define a closure, which does the sorting? Than we provide a default implementation, which mirrors the Emacs behavior and a couple of helper functions accessible from the options, so the user can easily implement their own sorting flavor?

@kristijanhusak
Copy link
Member

@seflue since I plan to add custom agenda commands in the following months, for now I'm thinking to attach the sorting function to the AgendaView class, and let the power users monkey patch it. With the custom agenda commands, there will be a way to do this as part of the configuration. Would that work @lyz-code ?

@seflue
Copy link
Contributor

seflue commented Jan 4, 2025

@kristijanhusak Would you also do that with the sort_agenda_items function in AgendaView which is currently just a local function? I would be really happy to make the agenda view a bit more custamizable, even if this means to write some code. The current behavior is most often too noisy and it is hard to let the important things stand out without losing track of the rest.

Can you elaborate a bit more on your plan of custom agenda commands? I have some requirements to the agenda which made me already thinking of writing my own agenda plugin - but I didn't find the time yet to seriously design something decent. But I would be happy to give feedback on what you have in mind.

@kristijanhusak
Copy link
Member

@seflue yes, for both todos and agenda views. I would just attach them to the exported module, and allow monkey patching. This would be a temporary thing.

Regarding the agenda commands, I plan to integrate them as similar as possible to what Emacs does. I'm still not 100% sure what it supports since I need to investigate further, but that's the idea. We can add some things on top of it, but it will need to align with the general structure.

@kristijanhusak
Copy link
Member

Ok, I attached both to the exported module in ad88620.

Now you can do this in your configuration to modify it, until we add the custom agenda commands:

  local agenda = require('orgmode.agenda.views.agenda')
  agenda._sort = function(agenda_items)
    table.sort(agenda_items, function(a, b)
	-- Sort logic
	end)

	return agenda_items
  end
  require('orgmode').setup({})

@lyz-code
Copy link
Author

lyz-code commented Jan 8, 2025

Wow what a new year's present (✿◠‿◠) thank you both @kristijanhusak and @seflue.

I've tried doing what you suggested but it doesn't work for me, probably I'm doing something wrong. Maybe the issue is that I use the agenda in the keys section of the LazyVim plugin configuration, and that has not yet loaded the agenda object?

return {
  {
    "nvim-orgmode/orgmode",
    url = "https://github.com/lyz-code/orgmode",
    event = "VeryLazy",
    keys = {
      {
        "gt",
        function()
          vim.notify("Opening today's agenda", vim.log.levels.INFO)
          require("orgmode.api.agenda").tags({
            query = "+t/-INACTIVE-DONE-REJECTED",
            todo_only = true,
          })
        end,
        desc = "Open orgmode agenda for today",
      },
      ...
    },
    config = function()
      local agenda = require("orgmode.agenda.views.agenda")
      agenda._sort = function(todos)
        table.sort(todos, function(a, b)
          -- Tasks marked as clocked_in appear first
          if a:is_clocked_in() then
            return true
          end
          if b:is_clocked_in() then
            return false
          end

          -- Then tasks are sorted by their priority
          if a:get_priority_sort_value() ~= b:get_priority_sort_value() then
            return a:get_priority_sort_value() > b:get_priority_sort_value()
          end

          -- Then tasks are sorted by their TODO keyword
          local a_keyword = a:get_todo()
          local b_keyword = b:get_todo()
          if (a_keyword and b_keyword) and (a_keyword ~= b_keyword) then
            return a:get_todo_sort_value() < b:get_todo_sort_value()
          end

          -- Then tasks which have a DEADLINE have priority over SCHEDULED over nothing
          local a_deadline = a:get_deadline_date()
          local a_scheduled = a:get_scheduled_date()
          local b_deadline = b:get_deadline_date()
          local b_scheduled = b:get_scheduled_date()

          -- If both have deadlines, earlier deadline comes first
          if a_deadline and b_deadline then
            return a_deadline < b_deadline
          end

          -- If only one has deadline, it comes first
          if a_deadline then
            return true
          end
          if b_deadline then
            return false
          end

          -- If both have scheduled dates, earlier date comes first
          if a_scheduled and b_scheduled then
            return a_scheduled < b_scheduled
          end

          -- If only one has scheduled date, it comes first
          if a_scheduled then
            return true
          end
          if b_scheduled then
            return false
          end

          -- Then tasks are sorted by their category keyword
          return a:get_category() < b:get_category()
        end)
        return todos
      end

      -- Setup orgmode
      require("orgmode").setup({
        org_agenda_files = {
          ...
        }
      })
  },

Also I've noticed that your commit has not added the functionality to sort the todo keywords. I feel it might be a useful thing to have, so for example you can sort DOING and WAITING over TODO in your agendas. Don't you think so?

@kristijanhusak
Copy link
Member

@lyz-code with the recent addition of custom commands you can set up custom sorting. Your case can be partially solved with this configuration:

org_agenda_sorting_strategy = {
  todo = { 'clocked-up', 'todo-state-up', 'priority-down', 'category-keep'}
}

Deadline and scheduled sorting is not yet added, but I plan to add it.

@lyz-code
Copy link
Author

OMG @kristijanhusak it's so beautiful!

One question though, is there a way to use these configurations when calling it from the api?

  • org_agenda_files
  • org_agenda_sorting_strategy
  • org_agenda_category_filter_preset / match
  • org_agenda_todo_ignore_deadlines

For example, how would I be able to edit the default values when using:

      {
        "gm",
        function()
          vim.notify("Opening the month's agenda", vim.log.levels.INFO)
          require("orgmode.api.agenda").tags({
            query = "+m/-INACTIVE-DONE-REJECTED",
            todo_only = true,
          })
        end,
        desc = "Open orgmode agenda for month objectives",
      },

I'm already used to my short bindings gm and prefer them over the agenda chooser menu.

An alternative question would be how could I launch a custom agenda command with a chosen keybindings from the api?

thank you so so much for developing this

@lyz-code
Copy link
Author

Another question (if you want me to open independent issues for each of them tell me please).

A side effect I've saw when I implemented the sort by TODO feature is that for example I have:

        org_todo_keywords = {
          "WAITING(w)",
          "DOING(d)",
          "READY(r)",
          "TODO(t)",
          "INACTIVE(i)",
          "|",
          "DONE(e)",
          "REJECTED(j)",
          "DUPLICATE(u)",
        },

Because that's the order I'd like the elements to show up in my agendas, the problem is that as the first element is WAITING each time you create a new heading or promote a checkbox into a heading instead of setting TODO it sets WAITING which is not the desired effect.

That's why in this PR I added the org_todo_default_state = nil, how have you solved this?

@kristijanhusak
Copy link
Member

I added all those options to the agenda api. Regarding your second question, I need to check how that's generally handled in emacs.

@kristijanhusak
Copy link
Member

From what I see, emacs also sets the first todo item when converting a checkbox to a heading, which does make sense. From my understanding, you use TODO as the first item, but you just need the sorting as in your previous comment, right?
I plan to allow custom functions for sorting, but I want to make sure I get it right.

@lyz-code
Copy link
Author

Yep @kristijanhusak that's right, I changed the order of my org_todo_keywords so that they were shown in that order in the agendas when sorting by todo-state-up, but I want TODO to be my first item.

I've checked that org_agenda_sorting_strategy works in the api too thanks.

Is there any way to launch a custom command from the api?

@kristijanhusak
Copy link
Member

Is there any way to launch a custom command from the api?

Yes, just pushed it. require('orgmode.api.agenda').open_by_key('a')

@lyz-code
Copy link
Author

In addition to custom functions for sorting it would be nice to have custom functions for filtering.

For example I was thinking of making an agenda view of the headlines that I have clocked in today or to get all headlines that have been done today (either state DONE or state TODO and a LAST_REPEAT>yesterday (btw I tried searching using +LAST_REPEAT>[2024-12-05] or LAST_REPEAT>2024-12-05 but it didn't work))

@kristijanhusak
Copy link
Member

kristijanhusak commented Jan 18, 2025

Clock in could be matched only with a custom function one it's added, but for last repeat you can do it.

Dates needs to be matched in this format according to spec:
PROPERTY_NAME="<DATE>". So it needs to be enclosed in double quotes and <>.
For your example, you would do LAST_REPEAT>"<2024-12-05>".
You can also use relative values, like <-1d>, <today>, <tomorrow>, <+3d>, etc.

@lyz-code
Copy link
Author

Hi @kristijanhusak is there anything I can do to help you in the implementation of the default TODO keyword on new created elements?

@kristijanhusak
Copy link
Member

@lyz-code I don't plan to add that feature directly, but I added an event to be fired when the heading is toggled, and you can write some custom code to do what you desire.
Here's an example:

  local EventManager = require('orgmode.events')
  EventManager.listen(EventManager.event.HeadingToggled, function(event)
  ---@cast event OrgHeadingToggledEvent
    if event.headline then
      event.headline:set_todo('PROGRESS')
    end
  end)

This will automatically set headline todo keyword to progress when created.
You can also check event.action which is union type 'line_to_headline' | 'headline_to_line' | 'line_to_child_headline'

  1. Plain line converted to top level headline
  2. Top level headline converted to line
  3. line (list item or plain text) converted to child headline (it has parent)

@lyz-code
Copy link
Author

lyz-code commented Jan 28, 2025

Thanks for the solution @kristijanhusak , I love that you're adding events, it will help a lot when developing plugins.

I've checked that your snippet worked when converting from plain line converted to top level headline or from line to child headline.

However when I use org_insert_todo_heading or org_insert_todo_heading_respect_content the event is not triggered. So the wrong TODO state is set.

Is there any event I can use to handle this case?

@lyz-code
Copy link
Author

lyz-code commented Feb 7, 2025

Hi @kristijanhusak do you want me to open a new issue to ask how to handle the case of the previous comment?

@kristijanhusak
Copy link
Member

Yes, please open up a separate issue.

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

Successfully merging this pull request may close these issues.

3 participants