-
-
Notifications
You must be signed in to change notification settings - Fork 160
feat: allow to define multiple todo keyword sequences #974
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
base: master
Are you sure you want to change the base?
feat: allow to define multiple todo keyword sequences #974
Conversation
fc18036
to
2d74d78
Compare
@kristijanhusak It seems, that the indentation test is a bit flaky (those are the both failing test). It also failed occasionally locally on my machine, but that is unrelated to my changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left few comments around the code, some around code styling changes (mostly around avoiding else
when there's early return), and some comments around the logic.
I think we should approach this in a slightly different , and potentially simpler way:
- Always store the todo keywords as sequences. You already did that here more-less, but there's no need have a separate logic like
parse_single_sequence
orparse_multiple_sequence
. WhenTodoKeywords
get'sorg_todo_keywords
value, just check if it's astring[]
orstring[][]
. If it's former, just convert it tostring[][]
and handle it accordingly. - Instead of keeping the sequence index on the
TodoKeyword
, andsequences
on theTodoKeywords
, we can just search things on the fly. These things are used only when mutating the document, so it's not that necessary to do these optimizations. Lua is fast enough to handle this, and I doubt users have a lot of todo sequences.
Another approach could be to keep the TodoKeywords
as they are, which is basically as single sequence
, and have a layer above that will know to figure out which of the sequence we need at the given point, and just return it and use it where necessary.
This might complicate some other things so it might not be the best idea, but I wanted to point it out.
Let me know what you think.
else | ||
for _, directive in ipairs(directives) do | ||
local name = directive:field('name')[1] | ||
local value = directive:field('value')[1] | ||
|
||
if name and value then | ||
local name_text = self:get_node_text(name) | ||
if name_text:lower() == directive_name:lower() then | ||
return self:get_node_text(value) | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can end the above if statement here since it has a return
else | |
for _, directive in ipairs(directives) do | |
local name = directive:field('name')[1] | |
local value = directive:field('value')[1] | |
if name and value then | |
local name_text = self:get_node_text(name) | |
if name_text:lower() == directive_name:lower() then | |
return self:get_node_text(value) | |
end | |
end | |
end | |
end | |
end | |
for _, directive in ipairs(directives) do | |
local name = directive:field('name')[1] | |
local value = directive:field('value')[1] | |
if name and value then | |
local name_text = self:get_node_text(name) | |
if name_text:lower() == directive_name:lower() then | |
return self:get_node_text(value) | |
end | |
end | |
end |
self:_parse_single_sequence(self.org_todo_keywords) | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can just return directly here, method returns void anyway. Same for the if below.
self:_parse_single_sequence(self.org_todo_keywords) | |
return | |
return self:_parse_single_sequence(self.org_todo_keywords) |
if #self.todos.sequences > 1 or self.todos:has_fast_access() then | ||
return true | ||
end | ||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if #self.todos.sequences > 1 or self.todos:has_fast_access() then | |
return true | |
end | |
return false | |
return #self.todos.sequences > 1 or self.todos:has_fast_access() |
return keyword | ||
-- When we're starting from an empty state and moving backward, | ||
-- go to the last todo keyword of the last sequence | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need for else
block when if
block returns. It removes one level of indentation.
-- Find the keyword by string value | ||
if type(current_state) == 'string' then | ||
opts.current_state = opts.todos:find(current_state) or TodoKeyword:empty() | ||
-- Direct assignment of a TodoKeyword |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do we create a TodoState
with the direct assignment? We just provide the todo keyword string in all usages.
---@param headline OrgHeadline | ||
---@param old_state string | ||
---@param new_state string | ||
function OrgMappings:_handle_repeating_task(headline, old_state, new_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this part of code changed?
used_shortcuts[todo_keyword.shortcut] = true | ||
elseif not used_shortcuts[todo_keyword.shortcut] then | ||
-- Mark it as a fast access key when we have multiple sequences | ||
if type(self.org_todo_keywords[1]) == 'table' and #self.org_todo_keywords > 1 then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having multiple sequences doesn't necessarily mean we use fast access. When there is no fast access defined in the config of the todo keywords, Emacs just cycles through the sequence it figured out that is being used.
There's a catch though. Emacs somehow keeps in memory which was the last sequence used. For example, if you have these keywords:
(setq org-todo-keywords
'((sequence "TODO" "|" "DONE")
(sequence "REPORT" "BUG" "TESTING" "|" "FIXED")))
And you have a headline with * REPORT foo
, cycling without the fast access will go REPORT -> BUG -> TESTING -> FIXED -> {EMPTY} -> REPORT -> etc.
. So it keeps in memory that the last sequence is 2nd one. Now, if you switch to an empty headline without todo keyword, restart emacs, and try switching the todo state, it will use the first sequence. So it's only in memory while the emacs is open.
I'm not sure what's the best way to access that issue, but I wanted to bring it up in case you have some ideas.
They can be defined in the config or within an org file.
2d74d78
to
68597a8
Compare
They can be defined in the config or within an org file.
Summary
This PR adds the ability to define multiple todo keyword sets as described in Orgmode manual.
Related Issues
Relates to #250, #157, PR #956
Closes #250
Changes
org_todo_keywords
allows to define a table of keyword sets#+TODO:
directivesorg_todo
keymap triggers fast access mode to select a keywordorg_todo_prev
behaves likeS-RIGHT
in Emacs OrgmodeFalling back to fast access mode when multiple sets are defined is a bit of a shortcut to get a first version of this feature out of the door. Emacs Orgmode defines some additional keybindings to switch between keyword sets. This is a bit more elaborated and could be implemented in a further PR.
Checklist
I confirm that I have:
Conventional Commits
specification (e.g.,
feat: add new feature
,fix: correct bug
,docs: update documentation
).make test
.