-
-
Notifications
You must be signed in to change notification settings - Fork 142
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
feat(core): support org-enforce-todo-dependencies #741
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -376,12 +376,35 @@ function OrgMappings:toggle_heading() | |
vim.fn.setline('.', line) | ||
end | ||
|
||
---@param headline OrgHeadline | ||
function OrgMappings:_has_unfinished_children(headline) | ||
for _, h in ipairs(headline:get_child_headlines()) do | ||
local was_done = h:is_done() | ||
if not was_done then | ||
return true | ||
end | ||
if OrgMappings:_has_unfinished_children(h) then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I stumbled over this recursion and after some moments I recognized, that not every headline is a todo. I think this is worth a comment. |
||
return true | ||
end | ||
end | ||
return false | ||
end | ||
|
||
function OrgMappings:_todo_change_state(direction) | ||
local headline = self.files:get_closest_headline() | ||
local old_state = headline:get_todo() | ||
local was_done = headline:is_done() | ||
local changed = self:_change_todo_state(direction, true) | ||
local force_dependent = config.org_enforce_todo_dependencies or false | ||
|
||
if force_dependent then | ||
local has_unfinished_children = OrgMappings:_has_unfinished_children(headline) | ||
if has_unfinished_children then | ||
utils.echo_warning(tostring(old_state) .. ' is blocked by unfinished sub-tasks.') | ||
return | ||
end | ||
end | ||
|
||
local changed = self:_change_todo_state(direction, true) | ||
if not changed then | ||
return | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,329 @@ | ||
local config = require('orgmode.config') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I appreciate, that you wrote tests. I saw that some of them are actually testing multiple things. I'm my experience this can make a regression harder to track, although it's much better than having no test. Would you mind to split the tests up a little bit? |
||
local TodoState = require('orgmode.objects.todo_state') | ||
local TodoKeyword = require('orgmode.objects.todo_keywords.todo_keyword') | ||
|
||
local helpers = require('tests.plenary.helpers') | ||
local api = require('orgmode.api') | ||
local Date = require('orgmode.objects.date') | ||
local OrgId = require('orgmode.org.id') | ||
local orgmode = require('orgmode') | ||
|
||
local M = {} | ||
-- @param headline OrgApiHeadline | ||
-- local M.vis_head = function (headline, indent) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove dead code. |
||
function M:vis_head(headline, indent) | ||
if headline == nil then | ||
return | ||
end | ||
print(string.rep('>', indent or 0) .. ' ' .. headline.title) | ||
for _, h in ipairs(headline.headlines) do | ||
M:vis_head(h, (indent or 0) + 1) | ||
end | ||
end | ||
|
||
function M:headline_has_unfinished_child(headline) | ||
for _, h in ipairs(headline.headlines) do | ||
if h.todo_type == 'TODO' then | ||
return true | ||
end | ||
if M:headline_has_unfinished_child(h) then | ||
return true | ||
end | ||
end | ||
return false | ||
end | ||
|
||
describe('Todo mappings unfer force dependency', function() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo? |
||
before_each(function() | ||
config:extend({ org_enforce_todo_dependencies = true }) | ||
end) | ||
after_each(function() | ||
vim.cmd([[silent! %bw!]]) | ||
end) | ||
it('should change todo state of a headline forward (org_todo)', function() | ||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
vim.fn.cursor(3, 1) | ||
|
||
-- Changing to DONE and adding closed date | ||
vim.cmd([[norm cit]]) | ||
|
||
assert.are.same({ | ||
'* DONE Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02> CLOSED: [' .. Date.now():to_string() .. ']', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Removing todo keyword and removing closed date | ||
vim.cmd([[norm cit]]) | ||
assert.are.same({ | ||
'* Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Setting TODO keyword, initial state | ||
vim.cmd([[norm cit]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
end) | ||
|
||
it('should change todo state of a headline forward (org_todo)', function() | ||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
vim.fn.cursor(3, 1) | ||
|
||
-- Changing to DONE and adding closed date | ||
vim.cmd([[norm cit]]) | ||
assert.are.same({ | ||
'* DONE Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02> CLOSED: [' .. Date.now():to_string() .. ']', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Removing todo keyword and removing closed date | ||
vim.cmd([[norm cit]]) | ||
assert.are.same({ | ||
'* Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Setting TODO keyword, initial state | ||
vim.cmd([[norm cit]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
end) | ||
|
||
it('should change todo state of repeatable task and add last repeat property and state change (org_todo)', function() | ||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-07 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}) | ||
|
||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-07 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 6, false)) | ||
vim.fn.cursor(3, 1) | ||
vim.cmd([[norm cit]]) | ||
vim.wait(50) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-14 Tue 12:00 +1w>', | ||
' :PROPERTIES:', | ||
' :LAST_REPEAT: [' .. Date.now():to_string() .. ']', | ||
' :END:', | ||
' - State "DONE" from "TODO" [' .. Date.now():to_string() .. ']', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 10, false)) | ||
end) | ||
|
||
it('should change todo state of repeatable task and not log last repeat date if disabled', function() | ||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-07 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}, { | ||
org_log_repeat = false, | ||
}) | ||
|
||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-07 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 6, false)) | ||
vim.fn.cursor(3, 1) | ||
vim.cmd([[norm cit]]) | ||
vim.wait(50) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-14 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 10, false)) | ||
|
||
config.org_log_repeat = 'time' | ||
end) | ||
|
||
it('should add last repeat property and state change to drawer (org_log_into_drawer)', function() | ||
config:extend({ | ||
org_log_into_drawer = 'LOGBOOK', | ||
}) | ||
|
||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-07 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}) | ||
|
||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-07 Tue 12:00 +1w>', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 6, false)) | ||
vim.fn.cursor(3, 1) | ||
vim.cmd([[norm cit]]) | ||
vim.wait(50) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-14 Tue 12:00 +1w>', | ||
' :PROPERTIES:', | ||
' :LAST_REPEAT: [' .. Date.now():to_string() .. ']', | ||
' :END:', | ||
' :LOGBOOK:', | ||
' - State "DONE" from "TODO" [' .. Date.now():to_string() .. ']', | ||
' :END:', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 12, false)) | ||
|
||
vim.fn.cursor(3, 1) | ||
vim.cmd([[norm cit]]) | ||
vim.wait(200) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-09-21 Tue 12:00 +1w>', | ||
' :PROPERTIES:', | ||
' :LAST_REPEAT: [' .. Date.now():to_string() .. ']', | ||
' :END:', | ||
' :LOGBOOK:', | ||
' - State "DONE" from "TODO" [' .. Date.now():to_string() .. ']', | ||
' - State "DONE" from "TODO" [' .. Date.now():to_string() .. ']', | ||
' :END:', | ||
'', | ||
'* TODO Another task', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 13, false)) | ||
end) | ||
|
||
it('should change todo state of a headline backward (org_todo_prev)', function() | ||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}) | ||
|
||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
vim.fn.cursor(3, 1) | ||
|
||
-- Removing todo keyword | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Changing to DONE and adding closed date | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* DONE Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02> CLOSED: [' .. Date.now():to_string() .. ']', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Setting TODO keyword, initial state | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
end) | ||
|
||
it('should change todo state of a headline backward (org_todo_prev)', function() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate test name? Can you reflect the difference to the former test in the name? |
||
helpers.create_agenda_file({ | ||
'#TITLE: Test', | ||
'', | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
'** TODO Test orgmode 1', | ||
}) | ||
|
||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
vim.fn.cursor(3, 1) | ||
|
||
-- Removing todo keyword, but will fail because of dependency | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- Changing to DONE and adding closed date, but will fail because of dependency | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- remove TODO | ||
vim.fn.cursor(5, 1) | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
'** Test orgmode 1', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 5, false)) | ||
|
||
-- toggle done | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* TODO Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
'** DONE Test orgmode 1', | ||
' CLOSED: [' .. Date.now():to_string() .. ']', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 6, false)) | ||
|
||
-- remove todo for parent | ||
vim.fn.cursor(3, 1) | ||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02>', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
|
||
-- toggle done | ||
|
||
vim.cmd([[norm ciT]]) | ||
assert.are.same({ | ||
'* DONE Test orgmode', | ||
' DEADLINE: <2021-07-21 Wed 22:02> CLOSED: [' .. Date.now():to_string() .. ']', | ||
}, vim.api.nvim_buf_get_lines(0, 2, 4, false)) | ||
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.
Do we need this local variable? It is not used later on, isn't it?
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.