From 956df8491a5c2996e123e9c40ea346a40c00c5d9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 24 Sep 2024 21:56:43 +0200 Subject: [PATCH 1/6] Add TODO's section to status buffer - WIP: - Adds about 800ms to the load time of the buffer, would scale poorly with bigger projects - Needs either some caching mechanism, or to run async with the repo state refreshings - Not 100% sold on the UI yet - This could be the foundation for a plugin system, allowing more custom sections to be added for users --- README.md | 4 + doc/neogit.txt | 9 ++ lua/neogit/buffers/status/actions.lua | 25 +++++ lua/neogit/buffers/status/ui.lua | 134 +++++++++++++++++++++++--- lua/neogit/config.lua | 24 +++++ lua/neogit/lib/collection.lua | 11 +++ lua/neogit/lib/ui/renderer.lua | 6 +- 7 files changed, 195 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 929cce584..35c522988 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,10 @@ neogit.setup { folded = true, hidden = false, }, + todo = { + folded = true, + hidden = false, + }, }, mappings = { commit_editor = { diff --git a/doc/neogit.txt b/doc/neogit.txt index fc44a6e05..d93860db1 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1835,6 +1835,15 @@ Untracked Files *neogit_status_buffer_untracked* untracked files entirely, "normal" to show files and directories (default), or "all" to show all files in all directories. +TODOs *neogit_status_buffer_todos* + Requires `ripgrep` and `sort` to be available on your system. By default, + finds all TODO notes in the codebase matching: + - " TODO: " + - " NOTE: " + - " FIXME: " + - " HACK: " + + Can be configured to find other/different keywords. ============================================================================== Editor Buffer *neogit_editor_buffer* diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index c378e718e..6a0f450cf 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -1124,6 +1124,13 @@ M.n_goto_file = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() + -- Goto PLUGIN (TODO, etc...) + if item and item.goto_path and item.goto_cursor then + self:close() + vim.schedule_wrap(open)("edit", item.goto_path, item.goto_cursor) + return + end + -- Goto FILE if item and item.absolute_path then local cursor = translate_cursor_location(self, item) @@ -1145,6 +1152,12 @@ M.n_tab_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() + -- Goto PLUGIN (TODO, etc...) + if item and item.goto_path and item.goto_cursor then + open("tabedit", item.goto_path, item.goto_cursor) + return + end + if item and item.absolute_path then open("tabedit", item.absolute_path, translate_cursor_location(self, item)) end @@ -1156,6 +1169,12 @@ M.n_split_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() + -- Goto PLUGIN (TODO, etc...) + if item and item.goto_path and item.goto_cursor then + open("split", item.goto_path, item.goto_cursor) + return + end + if item and item.absolute_path then open("split", item.absolute_path, translate_cursor_location(self, item)) end @@ -1167,6 +1186,12 @@ M.n_vertical_split_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() + -- Goto PLUGIN (TODO, etc...) + if item and item.goto_path and item.goto_cursor then + open("vsplit", item.goto_path, item.goto_cursor) + return + end + if item and item.absolute_path then open("vsplit", item.absolute_path, translate_cursor_location(self, item)) end diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index bfc26046e..a07cf912a 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -9,6 +9,7 @@ local row = Ui.row local text = Ui.text local map = util.map +local filter_map = util.filter_map local EmptyLine = common.EmptyLine local List = common.List @@ -158,6 +159,14 @@ local SectionTitleMerge = Component.new(function(props) end) local Section = Component.new(function(props) + if not props.visible then + return col {} + end + + if type(props.items) == "function" then + props.items = props.items() + end + local count if props.count then count = { text(" ("), text.highlight("NeogitSectionHeaderCount")(#props.items), text(")") } @@ -176,6 +185,10 @@ local Section = Component.new(function(props) end) local SequencerSection = Component.new(function(props) + if not props.visible then + return col {} + end + return col.tag("Section")({ row(util.merge(props.title)), col(map(props.items, props.render)), @@ -189,6 +202,10 @@ local SequencerSection = Component.new(function(props) end) local RebaseSection = Component.new(function(props) + if not props.visible then + return col {} + end + return col.tag("Section")({ row(util.merge(props.title, { text(" ("), @@ -430,6 +447,10 @@ local SectionItemBisect = Component.new(function(item) end) local BisectDetailsSection = Component.new(function(props) + if not props.visible then + return col {} + end + return col.tag("Section")({ row(util.merge(props.title, { text(" "), text.highlight("NeogitObjectId")(props.commit.oid) })), row { @@ -457,6 +478,36 @@ local BisectDetailsSection = Component.new(function(props) }) end) +local SectionItemTodo = Component.new(function(item) + local function present_item(item) + return row({ + text.highlight(item.highlight)(item.kind), + text(" "), + text(item.message), + }, { + yankable = ("%s:%d"):format(item.path, item.line), + item = { + goto_path = item.path, + goto_cursor = { item.line, item.column }, + }, + }) + end + + local grouped_items = map(item, present_item) + table.insert( + grouped_items, + 1, + row { + text.highlight("NeogitSubtleText")(item[1].path), + text.highlight("NeogitObjectId")(" ("), + text.highlight("NeogitObjectId")(#item), + text.highlight("NeogitObjectId")(")") + } + ) + + return col.tag("Section")(grouped_items, { foldable = true, folded = true }) +end) + function M.Status(state, config) -- stylua: ignore start local show_hint = not config.disable_hint @@ -516,6 +567,10 @@ function M.Status(state, config) local show_recent = #state.recent.items > 0 and not config.sections.recent.hidden + local show_todos = vim.fn.executable("rg") == 1 + and vim.fn.executable("sort") == 1 + and not config.sections.todo.hidden + return { List { items = { @@ -557,7 +612,8 @@ function M.Status(state, config) }, }, { foldable = true, folded = config.status.HEAD_folded }), EmptyLine(), - show_merge and SequencerSection { + SequencerSection { + visible = show_merge, title = SectionTitleMerge { title = "Merging", branch = state.merge.branch, @@ -568,7 +624,8 @@ function M.Status(state, config) folded = config.sections.sequencer.folded, name = "merge", }, - show_rebase and RebaseSection { + RebaseSection { + visible = show_rebase, title = SectionTitleRebase { title = "Rebasing", head = state.rebase.head, @@ -583,34 +640,39 @@ function M.Status(state, config) folded = config.sections.rebase.folded, name = "rebase", }, - show_cherry_pick and SequencerSection { + SequencerSection { + visible = show_cherry_pick, title = SectionTitle { title = "Cherry Picking", highlight = "NeogitPicking" }, render = SectionItemSequencer, items = util.reverse(state.sequencer.items), folded = config.sections.sequencer.folded, name = "cherry_pick", }, - show_revert and SequencerSection { + SequencerSection { + visible = show_revert, title = SectionTitle { title = "Reverting", highlight = "NeogitReverting" }, render = SectionItemSequencer, items = util.reverse(state.sequencer.items), folded = config.sections.sequencer.folded, name = "revert", }, - show_bisect and BisectDetailsSection { + BisectDetailsSection { + visible = show_bisect, title = SectionTitle { title = "Bisecting at", highlight = "NeogitBisecting" }, commit = state.bisect.current, folded = config.sections.bisect.folded, name = "bisect_details", }, - show_bisect and SequencerSection { + SequencerSection { + visible = show_bisect, title = SectionTitle { title = "Bisecting Log", highlight = "NeogitBisecting" }, render = SectionItemBisect, items = state.bisect.items, folded = config.sections.bisect.folded, name = "bisect", }, - show_untracked and Section { + Section { + visible = show_untracked, title = SectionTitle { title = "Untracked files", highlight = "NeogitUntrackedfiles" }, count = true, render = SectionItemFile("untracked", config), @@ -618,7 +680,8 @@ function M.Status(state, config) folded = config.sections.untracked.folded, name = "untracked", }, - show_unstaged and Section { + Section { + visible = show_unstaged, title = SectionTitle { title = "Unstaged changes", highlight = "NeogitUnstagedchanges" }, count = true, render = SectionItemFile("unstaged", config), @@ -626,7 +689,8 @@ function M.Status(state, config) folded = config.sections.unstaged.folded, name = "unstaged", }, - show_staged and Section { + Section { + visible = show_staged, title = SectionTitle { title = "Staged changes", highlight = "NeogitStagedchanges" }, count = true, render = SectionItemFile("staged", config), @@ -634,7 +698,8 @@ function M.Status(state, config) folded = config.sections.staged.folded, name = "staged", }, - show_stashes and Section { + Section { + visible = show_stashes, title = SectionTitle { title = "Stashes", highlight = "NeogitStashes" }, count = true, render = SectionItemStash, @@ -642,7 +707,8 @@ function M.Status(state, config) folded = config.sections.stashes.folded, name = "stashes", }, - show_upstream_unmerged and Section { + Section { + visible = show_upstream_unmerged, title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref, @@ -654,7 +720,8 @@ function M.Status(state, config) folded = config.sections.unmerged_upstream.folded, name = "upstream_unmerged", }, - show_pushRemote_unmerged and Section { + Section { + visible = show_pushRemote_unmerged, title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref, @@ -666,7 +733,8 @@ function M.Status(state, config) folded = config.sections.unmerged_pushRemote.folded, name = "pushRemote_unmerged", }, - not show_upstream_unmerged and show_recent and Section { + Section { + visible = not show_upstream_unmerged and show_recent, title = SectionTitle { title = "Recent Commits", highlight = "NeogitRecentcommits" }, count = false, render = SectionItemCommit, @@ -674,7 +742,8 @@ function M.Status(state, config) folded = config.sections.recent.folded, name = "recent", }, - show_upstream_unpulled and Section { + Section { + visible = show_upstream_unpulled, title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref, @@ -686,7 +755,8 @@ function M.Status(state, config) folded = config.sections.unpulled_upstream.folded, name = "upstream_unpulled", }, - show_pushRemote_unpulled and Section { + Section { + visible = show_pushRemote_unpulled, title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref, @@ -698,6 +768,40 @@ function M.Status(state, config) folded = config.sections.unpulled_pushRemote.folded, name = "pushRemote_unpulled", }, + Section { + visible = show_todos, + title = SectionTitle { title = "TODOs", highlight = "NeogitRecentcommits" }, + render = SectionItemTodo, + folded = true, + name = "todos_plugin", + items = function() + local Collection = require("neogit.lib.collection") + + local kinds = table.concat(vim.tbl_keys(config.sections.todo.keywords), "|") + local items = vim.system({ "rg", " (" .. kinds .. "): ", "--vimgrep" }, { text = true }):wait() + local lines = vim.split(items.stdout, "\n") + + local sorted_items = vim.system({ "sort", "-t:", "-k1,1", "-k2,2n" }, { text = true, stdin = lines }):wait() + local sorted_lines = vim.split(sorted_items.stdout, "\n") + + + items = filter_map(sorted_lines, function(line) + local path, linenr, column, kind, msg = line:match("^([^:]+):(%d+):(%d+):.- (%w+): (.+)$") + if path then + return { + path = path, + line = linenr, + column = column, + message = msg, + kind = kind, + highlight = config.sections.todo.keywords[kind] + } + end + end) + + return Collection.new(items):group_by("path") + end + } }, }, } diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 405e61757..944a450dc 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -120,6 +120,11 @@ end ---@field folded boolean Whether or not this section should be open or closed by default ---@field hidden boolean Whether or not this section should be shown +---@class NeogitTodoConfigSection A section to show in the Neogit Status buffer, e.g. Staged/Unstaged/Untracked +---@field folded boolean Whether or not this section should be open or closed by default +---@field hidden boolean Whether or not this section should be shown +---@field keywords { [string]: string } Dictionary of keywords to search for (key) and the highlight to apply (value) + ---@class NeogitConfigSections ---@field untracked NeogitConfigSection|nil ---@field unstaged NeogitConfigSection|nil @@ -133,6 +138,7 @@ end ---@field rebase NeogitConfigSection|nil ---@field sequencer NeogitConfigSection|nil ---@field bisect NeogitConfigSection|nil +---@field todo NeogitTodoConfigSection|nil ---@class HighlightOptions ---@field italic? boolean @@ -514,6 +520,16 @@ function M.get_default_values() folded = true, hidden = false, }, + todo = { + folded = true, + hidden = false, + keywords = { + ["TODO"] = "NeogitGraphBoldBlue", + ["NOTE"] = "NeogitGraphBoldGreen", + ["FIXME"] = "NeogitGraphBoldRed", + ["HACK"] = "NeogitGraphBoldOrange", + }, + }, }, ignored_settings = { "NeogitPushPopup--force-with-lease", @@ -794,6 +810,10 @@ function M.validate_config() validate_type(section, "section." .. section_name, "table") validate_type(section.folded, string.format("section.%s.folded", section_name), "boolean") validate_type(section.hidden, string.format("section.%s.hidden", section_name), "boolean") + + if section_name == "todo" then + validate_type(section.keywords, "section.todo.keywords", "table") + end end end @@ -1214,6 +1234,10 @@ function M.setup(opts) end end + if type(opts.sections.todo.keywords) == "table" then + M.values.sections.todo.keywords = opts.sections.todo.keywords + end + M.values = vim.tbl_deep_extend("force", M.values, opts) local config_errs = M.validate_config() diff --git a/lua/neogit/lib/collection.lua b/lua/neogit/lib/collection.lua index f5918d191..8a6507137 100644 --- a/lua/neogit/lib/collection.lua +++ b/lua/neogit/lib/collection.lua @@ -68,6 +68,17 @@ function M.find(tbl, func) return nil end +function M.group_by(tbl, key) + local result = {} + for _, item in ipairs(tbl) do + if not result[item[key]] then + result[item[key]] = {} + end + table.insert(result[item[key]], item) + end + return result +end + return setmetatable(M, { __call = function(_, tbl) return M.new(tbl) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 2c187bcdb..91d0c82f7 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -168,11 +168,11 @@ function Renderer:_render_child(child) child.position.row_end = #self.buffer.line - if child.options.section then + if child.options and child.options.section then self.index:add_section(child.options.section, child.position.row_start, child.position.row_end) end - if child.options.item then + if child.options and child.options.item then child.options.item.folded = child.options.folded self.index:add_item(child.options.item, child.position.row_start, child.position.row_end) end @@ -182,7 +182,7 @@ function Renderer:_render_child(child) table.insert(self.buffer.line_highlight, { #self.buffer.line - 1, line_hl }) end - if child.options.virtual_text then + if child.options and child.options.virtual_text then table.insert(self.buffer.extmark, { self.namespace, #self.buffer.line - 1, From 37947a2bdbf495257cc0c9e33d11e6ee995953d2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 22:35:40 +0100 Subject: [PATCH 2/6] Don't require `sort` --- lua/neogit/buffers/status/ui.lua | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index a07cf912a..79c95bb5a 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -568,7 +568,6 @@ function M.Status(state, config) and not config.sections.recent.hidden local show_todos = vim.fn.executable("rg") == 1 - and vim.fn.executable("sort") == 1 and not config.sections.todo.hidden return { @@ -778,14 +777,12 @@ function M.Status(state, config) local Collection = require("neogit.lib.collection") local kinds = table.concat(vim.tbl_keys(config.sections.todo.keywords), "|") - local items = vim.system({ "rg", " (" .. kinds .. "): ", "--vimgrep" }, { text = true }):wait() - local lines = vim.split(items.stdout, "\n") + local items = vim.system( + { "rg", " (" .. kinds .. "): ", "--vimgrep", "--sortr=path" }, + { text = true } + ):wait() - local sorted_items = vim.system({ "sort", "-t:", "-k1,1", "-k2,2n" }, { text = true, stdin = lines }):wait() - local sorted_lines = vim.split(sorted_items.stdout, "\n") - - - items = filter_map(sorted_lines, function(line) + items = filter_map(vim.split(items.stdout, "\n"), function(line) local path, linenr, column, kind, msg = line:match("^([^:]+):(%d+):(%d+):.- (%w+): (.+)$") if path then return { From 9593c6f61f6458469dc8d115a3781461845028d8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 22:35:54 +0100 Subject: [PATCH 3/6] Remove highlighting from filepath --- lua/neogit/buffers/status/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 79c95bb5a..39d50b689 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -498,7 +498,7 @@ local SectionItemTodo = Component.new(function(item) grouped_items, 1, row { - text.highlight("NeogitSubtleText")(item[1].path), + text(item[1].path), text.highlight("NeogitObjectId")(" ("), text.highlight("NeogitObjectId")(#item), text.highlight("NeogitObjectId")(")") From 82ba3cdcd363affdb43fd0e82636d27d99d335e9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 22:36:08 +0100 Subject: [PATCH 4/6] fix: when table is undefined in user config, don't error --- lua/neogit/config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 944a450dc..40c1d126d 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -1234,7 +1234,7 @@ function M.setup(opts) end end - if type(opts.sections.todo.keywords) == "table" then + if opts.sections.todo and type(opts.sections.todo.keywords) == "table" then M.values.sections.todo.keywords = opts.sections.todo.keywords end From 07a7002194a8d662012343d285b1e182cb3bb7af Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 22:40:40 +0100 Subject: [PATCH 5/6] Remove "TODO" section from status buffer --- README.md | 4 -- doc/neogit.txt | 9 ----- lua/neogit/buffers/status/ui.lua | 65 -------------------------------- lua/neogit/config.lua | 24 ------------ 4 files changed, 102 deletions(-) diff --git a/README.md b/README.md index 35c522988..929cce584 100644 --- a/README.md +++ b/README.md @@ -291,10 +291,6 @@ neogit.setup { folded = true, hidden = false, }, - todo = { - folded = true, - hidden = false, - }, }, mappings = { commit_editor = { diff --git a/doc/neogit.txt b/doc/neogit.txt index d93860db1..fc44a6e05 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1835,15 +1835,6 @@ Untracked Files *neogit_status_buffer_untracked* untracked files entirely, "normal" to show files and directories (default), or "all" to show all files in all directories. -TODOs *neogit_status_buffer_todos* - Requires `ripgrep` and `sort` to be available on your system. By default, - finds all TODO notes in the codebase matching: - - " TODO: " - - " NOTE: " - - " FIXME: " - - " HACK: " - - Can be configured to find other/different keywords. ============================================================================== Editor Buffer *neogit_editor_buffer* diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 39d50b689..5cb47a266 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -478,36 +478,6 @@ local BisectDetailsSection = Component.new(function(props) }) end) -local SectionItemTodo = Component.new(function(item) - local function present_item(item) - return row({ - text.highlight(item.highlight)(item.kind), - text(" "), - text(item.message), - }, { - yankable = ("%s:%d"):format(item.path, item.line), - item = { - goto_path = item.path, - goto_cursor = { item.line, item.column }, - }, - }) - end - - local grouped_items = map(item, present_item) - table.insert( - grouped_items, - 1, - row { - text(item[1].path), - text.highlight("NeogitObjectId")(" ("), - text.highlight("NeogitObjectId")(#item), - text.highlight("NeogitObjectId")(")") - } - ) - - return col.tag("Section")(grouped_items, { foldable = true, folded = true }) -end) - function M.Status(state, config) -- stylua: ignore start local show_hint = not config.disable_hint @@ -567,9 +537,6 @@ function M.Status(state, config) local show_recent = #state.recent.items > 0 and not config.sections.recent.hidden - local show_todos = vim.fn.executable("rg") == 1 - and not config.sections.todo.hidden - return { List { items = { @@ -767,38 +734,6 @@ function M.Status(state, config) folded = config.sections.unpulled_pushRemote.folded, name = "pushRemote_unpulled", }, - Section { - visible = show_todos, - title = SectionTitle { title = "TODOs", highlight = "NeogitRecentcommits" }, - render = SectionItemTodo, - folded = true, - name = "todos_plugin", - items = function() - local Collection = require("neogit.lib.collection") - - local kinds = table.concat(vim.tbl_keys(config.sections.todo.keywords), "|") - local items = vim.system( - { "rg", " (" .. kinds .. "): ", "--vimgrep", "--sortr=path" }, - { text = true } - ):wait() - - items = filter_map(vim.split(items.stdout, "\n"), function(line) - local path, linenr, column, kind, msg = line:match("^([^:]+):(%d+):(%d+):.- (%w+): (.+)$") - if path then - return { - path = path, - line = linenr, - column = column, - message = msg, - kind = kind, - highlight = config.sections.todo.keywords[kind] - } - end - end) - - return Collection.new(items):group_by("path") - end - } }, }, } diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 40c1d126d..405e61757 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -120,11 +120,6 @@ end ---@field folded boolean Whether or not this section should be open or closed by default ---@field hidden boolean Whether or not this section should be shown ----@class NeogitTodoConfigSection A section to show in the Neogit Status buffer, e.g. Staged/Unstaged/Untracked ----@field folded boolean Whether or not this section should be open or closed by default ----@field hidden boolean Whether or not this section should be shown ----@field keywords { [string]: string } Dictionary of keywords to search for (key) and the highlight to apply (value) - ---@class NeogitConfigSections ---@field untracked NeogitConfigSection|nil ---@field unstaged NeogitConfigSection|nil @@ -138,7 +133,6 @@ end ---@field rebase NeogitConfigSection|nil ---@field sequencer NeogitConfigSection|nil ---@field bisect NeogitConfigSection|nil ----@field todo NeogitTodoConfigSection|nil ---@class HighlightOptions ---@field italic? boolean @@ -520,16 +514,6 @@ function M.get_default_values() folded = true, hidden = false, }, - todo = { - folded = true, - hidden = false, - keywords = { - ["TODO"] = "NeogitGraphBoldBlue", - ["NOTE"] = "NeogitGraphBoldGreen", - ["FIXME"] = "NeogitGraphBoldRed", - ["HACK"] = "NeogitGraphBoldOrange", - }, - }, }, ignored_settings = { "NeogitPushPopup--force-with-lease", @@ -810,10 +794,6 @@ function M.validate_config() validate_type(section, "section." .. section_name, "table") validate_type(section.folded, string.format("section.%s.folded", section_name), "boolean") validate_type(section.hidden, string.format("section.%s.hidden", section_name), "boolean") - - if section_name == "todo" then - validate_type(section.keywords, "section.todo.keywords", "table") - end end end @@ -1234,10 +1214,6 @@ function M.setup(opts) end end - if opts.sections.todo and type(opts.sections.todo.keywords) == "table" then - M.values.sections.todo.keywords = opts.sections.todo.keywords - end - M.values = vim.tbl_deep_extend("force", M.values, opts) local config_errs = M.validate_config() From b65dce6c789e38f0c2f7ce759135e95dac2b68eb Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 2 Dec 2024 22:47:35 +0100 Subject: [PATCH 6/6] Revert 1 commits 07a7002 'Remove "TODO" section from status buffer' --- README.md | 4 ++ doc/neogit.txt | 9 +++++ lua/neogit/buffers/status/ui.lua | 65 ++++++++++++++++++++++++++++++++ lua/neogit/config.lua | 24 ++++++++++++ 4 files changed, 102 insertions(+) diff --git a/README.md b/README.md index 929cce584..35c522988 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,10 @@ neogit.setup { folded = true, hidden = false, }, + todo = { + folded = true, + hidden = false, + }, }, mappings = { commit_editor = { diff --git a/doc/neogit.txt b/doc/neogit.txt index fc44a6e05..d93860db1 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1835,6 +1835,15 @@ Untracked Files *neogit_status_buffer_untracked* untracked files entirely, "normal" to show files and directories (default), or "all" to show all files in all directories. +TODOs *neogit_status_buffer_todos* + Requires `ripgrep` and `sort` to be available on your system. By default, + finds all TODO notes in the codebase matching: + - " TODO: " + - " NOTE: " + - " FIXME: " + - " HACK: " + + Can be configured to find other/different keywords. ============================================================================== Editor Buffer *neogit_editor_buffer* diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 5cb47a266..39d50b689 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -478,6 +478,36 @@ local BisectDetailsSection = Component.new(function(props) }) end) +local SectionItemTodo = Component.new(function(item) + local function present_item(item) + return row({ + text.highlight(item.highlight)(item.kind), + text(" "), + text(item.message), + }, { + yankable = ("%s:%d"):format(item.path, item.line), + item = { + goto_path = item.path, + goto_cursor = { item.line, item.column }, + }, + }) + end + + local grouped_items = map(item, present_item) + table.insert( + grouped_items, + 1, + row { + text(item[1].path), + text.highlight("NeogitObjectId")(" ("), + text.highlight("NeogitObjectId")(#item), + text.highlight("NeogitObjectId")(")") + } + ) + + return col.tag("Section")(grouped_items, { foldable = true, folded = true }) +end) + function M.Status(state, config) -- stylua: ignore start local show_hint = not config.disable_hint @@ -537,6 +567,9 @@ function M.Status(state, config) local show_recent = #state.recent.items > 0 and not config.sections.recent.hidden + local show_todos = vim.fn.executable("rg") == 1 + and not config.sections.todo.hidden + return { List { items = { @@ -734,6 +767,38 @@ function M.Status(state, config) folded = config.sections.unpulled_pushRemote.folded, name = "pushRemote_unpulled", }, + Section { + visible = show_todos, + title = SectionTitle { title = "TODOs", highlight = "NeogitRecentcommits" }, + render = SectionItemTodo, + folded = true, + name = "todos_plugin", + items = function() + local Collection = require("neogit.lib.collection") + + local kinds = table.concat(vim.tbl_keys(config.sections.todo.keywords), "|") + local items = vim.system( + { "rg", " (" .. kinds .. "): ", "--vimgrep", "--sortr=path" }, + { text = true } + ):wait() + + items = filter_map(vim.split(items.stdout, "\n"), function(line) + local path, linenr, column, kind, msg = line:match("^([^:]+):(%d+):(%d+):.- (%w+): (.+)$") + if path then + return { + path = path, + line = linenr, + column = column, + message = msg, + kind = kind, + highlight = config.sections.todo.keywords[kind] + } + end + end) + + return Collection.new(items):group_by("path") + end + } }, }, } diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 405e61757..40c1d126d 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -120,6 +120,11 @@ end ---@field folded boolean Whether or not this section should be open or closed by default ---@field hidden boolean Whether or not this section should be shown +---@class NeogitTodoConfigSection A section to show in the Neogit Status buffer, e.g. Staged/Unstaged/Untracked +---@field folded boolean Whether or not this section should be open or closed by default +---@field hidden boolean Whether or not this section should be shown +---@field keywords { [string]: string } Dictionary of keywords to search for (key) and the highlight to apply (value) + ---@class NeogitConfigSections ---@field untracked NeogitConfigSection|nil ---@field unstaged NeogitConfigSection|nil @@ -133,6 +138,7 @@ end ---@field rebase NeogitConfigSection|nil ---@field sequencer NeogitConfigSection|nil ---@field bisect NeogitConfigSection|nil +---@field todo NeogitTodoConfigSection|nil ---@class HighlightOptions ---@field italic? boolean @@ -514,6 +520,16 @@ function M.get_default_values() folded = true, hidden = false, }, + todo = { + folded = true, + hidden = false, + keywords = { + ["TODO"] = "NeogitGraphBoldBlue", + ["NOTE"] = "NeogitGraphBoldGreen", + ["FIXME"] = "NeogitGraphBoldRed", + ["HACK"] = "NeogitGraphBoldOrange", + }, + }, }, ignored_settings = { "NeogitPushPopup--force-with-lease", @@ -794,6 +810,10 @@ function M.validate_config() validate_type(section, "section." .. section_name, "table") validate_type(section.folded, string.format("section.%s.folded", section_name), "boolean") validate_type(section.hidden, string.format("section.%s.hidden", section_name), "boolean") + + if section_name == "todo" then + validate_type(section.keywords, "section.todo.keywords", "table") + end end end @@ -1214,6 +1234,10 @@ function M.setup(opts) end end + if opts.sections.todo and type(opts.sections.todo.keywords) == "table" then + M.values.sections.todo.keywords = opts.sections.todo.keywords + end + M.values = vim.tbl_deep_extend("force", M.values, opts) local config_errs = M.validate_config()