Skip to content

feat: add tasks command #29

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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions lua/obsidian/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2076,4 +2076,33 @@ Client.picker = function(self, picker_name)
return require("obsidian.pickers").get(self, picker_name)
end

---@class obsidian.Task
---
---@field path string The path to the note.
---@field line integer The line number (1-indexed) where the task was found.
---@field description string The text of the line where the task was found.
---@field status string The status of the task.

---@return obsidian.Task[]
Client.find_tasks = function(self)
local openTasks = search.search(self.dir, "- \\[(.)\\]", search.SearchOpts.default())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tasks can also be * [ ] like this.

One probably wants to adjust where to search for tasks: specific file, current file, folder, regex. Not everybody closes or moves their tasks.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And since recently also <number> [ ] like this. We probably want something like this:

'([-+*]|[0-9]+\.) \[[- x/<>?!*"lb~]\] .+'

That's grep -E syntax, I am not sure how it correctly translates to lua regexp string. The various states I got from https://publish.obsidian.md/tasks/Getting+Started/Statuses, perhaps that could be reduced to the ones supported by obsidian.nvim. I also included ~, which is supported by render-markdown. I used this to test the regex:

$ cat test.md
- [ ] normal
+ [ ] plus
* [ ] star
- [x] normal x-ed
- [!] normal !-ed
- [~] normal ~-ed (render-markdown)
1. [ ] ordered
2. [x] ordered x-ed

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are actually quite a few ways to define checkbox lists. We should consolidate once this #48 is merged.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The different types of list items should be supported now. I'm not restricting the supported statuses beside needing to be only one char since they are configurable in the ui.checkboxes config

--- @type obsidian.Task[]
local result = {}

--- @type MatchData|?
local taskMatch = openTasks()
while taskMatch do
-- matching in lua since ripgrep doesn't support capturing groups in the --json output
local status, description = string.match(taskMatch.lines.text, "- %[(.)%] (.*)")
result[#result + 1] = {
status = status,
description = description,
line = taskMatch.line_number,
path = taskMatch.path.text,
}
taskMatch = openTasks()
end
return result
end

return Client
3 changes: 3 additions & 0 deletions lua/obsidian/commands/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ local command_lookups = {
ObsidianExtractNote = "obsidian.commands.extract_note",
ObsidianDebug = "obsidian.commands.debug",
ObsidianTOC = "obsidian.commands.toc",
ObsidianTasks = "obsidian.commands.tasks",
}

local M = setmetatable({
Expand Down Expand Up @@ -191,4 +192,6 @@ M.register("ObsidianDebug", { opts = { nargs = 0, desc = "Log some information f

M.register("ObsidianTOC", { opts = { nargs = 0, desc = "Load the table of contents into a picker" } })

M.register("ObsidianTasks", { opts = { nargs = "?", desc = "List all tasks" } })

return M
77 changes: 77 additions & 0 deletions lua/obsidian/commands/tasks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---@class CheckboxConfig
---@field name string
---@field order integer

-- Build the list of task status names sorted by order
---@param checkbox_config table<string,CheckboxConfig>
local function get_task_status_names(checkbox_config)
-- index by status name
---@type table<string, CheckboxConfig>
local task_by_status_name = {}
local status_names = {}
for _, c in pairs(checkbox_config) do
task_by_status_name[c.name] = c
status_names[#status_names + 1] = c.name
end

-- sort list of status names
table.sort(status_names, function(a, b)
return (task_by_status_name[a].order or 0) < (task_by_status_name[b].order or 0)
end)

return status_names
end

---@param current string|nil
---@param status_names table<integer, string>
local function get_next_status(current, status_names)
for i, v in ipairs(status_names) do
if v == current then
return status_names[i + 1]
end
end
return status_names[1]
end

--- Show tasks with optional filtering
---@param client obsidian.Client
---@param data table
local function showTasks(client, data)
assert(client, "Client is required")

local filter = data.fargs[1]
local picker = assert(client:picker(), "No picker configured")

local checkboxes = client.opts.ui.checkboxes
local status_names = get_task_status_names(checkboxes)

local tasks = client:find_tasks()
local toShow = {}

for _, task in ipairs(tasks) do
local tStatus = checkboxes[task.status]
if tStatus and (not filter or tStatus.name == filter) then
table.insert(toShow, {
display = string.format(" %s", task.description),
filename = task.path,
lnum = task.line,
icon = tStatus.char,
})
end
end

picker:pick(toShow, {
prompt_title = filter and (filter .. " tasks") or "tasks",
query_mappings = {
["<C-n>"] = {
desc = "Toggle task status",
callback = function()
local next_state_name = get_next_status(filter, status_names)
showTasks(client, { fargs = { next_state_name } })
end,
},
},
})
end

return showTasks
11 changes: 6 additions & 5 deletions lua/obsidian/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ config.UIOpts = {}
---@field char string
---@field hl_group string
---@field order integer
---@field name string

---@class obsidian.config.UIStyleSpec
---
Expand All @@ -443,11 +444,11 @@ config.UIOpts.default = function()
update_debounce = 200,
max_file_length = 5000,
checkboxes = {
[" "] = { order = 1, char = "󰄱", hl_group = "ObsidianTodo" },
["~"] = { order = 2, char = "󰰱", hl_group = "ObsidianTilde" },
["!"] = { order = 3, char = "", hl_group = "ObsidianImportant" },
[">"] = { order = 4, char = "", hl_group = "ObsidianRightArrow" },
["x"] = { order = 5, char = "", hl_group = "ObsidianDone" },
[" "] = { order = 1, name = "todo", char = "󰄱", hl_group = "ObsidianTodo" },
["~"] = { order = 2, name = "doing", char = "󰰱", hl_group = "ObsidianTilde" },
["!"] = { order = 3, name = "important", char = "", hl_group = "ObsidianImportant" },
[">"] = { order = 4, name = "next", char = "", hl_group = "ObsidianRightArrow" },
["x"] = { order = 5, name = "done", char = "", hl_group = "ObsidianDone" },
},
bullets = { char = "•", hl_group = "ObsidianBullet" },
external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
Expand Down