From 150b6db94de4a686c3aaaea6cfbb755fa06e9b35 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 12:36:28 +0100 Subject: [PATCH 001/443] Proof of concept for native folding --- lua/neogit.lua | 2 ++ lua/neogit/lib/folds.lua | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 lua/neogit/lib/folds.lua diff --git a/lua/neogit.lua b/lua/neogit.lua index d37aa40a6..c40d7d26c 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -11,6 +11,7 @@ function M.setup(opts) local hl = require("neogit.lib.hl") local state = require("neogit.lib.state") local logger = require("neogit.logger") + local folds = require("neogit.lib.folds") if did_setup then logger.debug("Already did setup!") @@ -40,6 +41,7 @@ function M.setup(opts) signs.setup() state.setup() autocmds.setup() + folds.setup() end ---@alias Popup "cherry_pick" | "commit" | "branch" | "diff" | "fetch" | "log" | "merge" | "remote" | "pull" | "push" | "rebase" | "revert" | "reset" | "stash" diff --git a/lua/neogit/lib/folds.lua b/lua/neogit/lib/folds.lua new file mode 100644 index 000000000..2d0979822 --- /dev/null +++ b/lua/neogit/lib/folds.lua @@ -0,0 +1,47 @@ +-- For Nightly, a function that constructs the fold text +local M = {} + +function M.fold_text() + local text = vim.fn.getline(vim.v.foldstart) + local bufnr = vim.fn.bufnr() + local ns = vim.api.nvim_get_namespaces()["neogit-buffer-" .. bufnr] + + local lnum = vim.v.foldstart - 1 + local startRange = { lnum, 0 } + local endRange = { lnum, -1 } + + local lastColEnd = 0 + local hlRes = {} + local marks = vim.api.nvim_buf_get_extmarks(bufnr, ns, startRange, endRange, { details = true }) + for i, m in ipairs(marks) do + local sc, details = m[3], m[4] + local ec = details.end_col or (sc + 1) + local hlGroup = details.hl_group + + if hlGroup then + if sc > lastColEnd then + table.insert(hlRes, { text:sub(lastColEnd + 1, sc), "NeogitGraphWhite" }) + end + + if i == 1 then + table.insert(hlRes, { text:sub(sc + 1, ec + 1), hlGroup }) + lastColEnd = ec + 1 + else + table.insert(hlRes, { text:sub(sc + 1, ec), hlGroup }) + lastColEnd = ec + end + end + end + + if #text > lastColEnd then + table.insert(hlRes, { text:sub(lastColEnd + 1, -1), "NeogitGraphWhite" }) + end + + return hlRes +end + +function M.setup() + _G.NeogitBufferFoldText = M.fold_text +end + +return M From 20e7e8611446c91d3817741aed8dbb1e7e8af686 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 12:36:52 +0100 Subject: [PATCH 002/443] Add "foldable" option --- lua/neogit/buffers/common.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index d714ee6cf..67609a7d5 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,3 +1,4 @@ + local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") @@ -202,7 +203,7 @@ M.CommitEntry = Component.new(function(commit, args) } end - return col({ + return col.tag("commit")({ row( util.merge({ text(commit.oid:sub(1, 7), { @@ -222,11 +223,11 @@ M.CommitEntry = Component.new(function(commit, args) } ), details, - }, { oid = commit.oid }) + }, { oid = commit.oid, foldable = args.details == true }) end) M.CommitGraph = Component.new(function(commit, _) - return col.padding_left(8) { row(build_graph(commit.graph)) } + return col.tag("graph").padding_left(8) { row(build_graph(commit.graph)) } end) M.Grid = Component.new(function(props) From e0c2734913917828ab9156e6e85cd4af5931fee6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 12:43:25 +0100 Subject: [PATCH 003/443] Update log-view commands to use native folding --- lua/neogit/buffers/log_view/init.lua | 51 +++++++++------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 8522a02a7..18e95737e 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -110,49 +110,30 @@ function M:open() [""] = function() CommitViewBuffer.new(self.buffer.ui:get_commit_under_cursor()):open() end, - [""] = function(buffer) - local stack = self.buffer.ui:get_component_stack_under_cursor() - local c = stack[#stack] - c.children[2].options.hidden = true - - local t_idx = math.max(c.index - 1, 1) - local target = c.parent.children[t_idx] - while not target.children[2] do - t_idx = t_idx - 1 - target = c.parent.children[t_idx] - end + [""] = function() + vim.cmd("normal! zc") - target.children[2].options.hidden = false + vim.cmd("normal! k") + while vim.fn.foldlevel(".") == 0 do + vim.cmd("normal! k") + end - buffer.ui:update() - self.buffer:move_cursor(target.position.row_start) + vim.cmd("normal! zo") + vim.cmd("normal! zz") end, - [""] = function(buffer) - local stack = self.buffer.ui:get_component_stack_under_cursor() - local c = stack[#stack] - c.children[2].options.hidden = true - - local t_idx = math.min(c.index + 1, #c.parent.children) - local target = c.parent.children[t_idx] - while not target.children[2] do - t_idx = t_idx + 1 - target = c.parent.children[t_idx] - end + [""] = function() + vim.cmd("normal! zc") - target.children[2].options.hidden = false + vim.cmd("normal! j") + while vim.fn.foldlevel(".") == 0 do + vim.cmd("normal! j") + end - buffer.ui:update() - buffer:move_cursor(target.position.row_start) + vim.cmd("normal! zo") vim.cmd("normal! zz") end, [""] = function() - local stack = self.buffer.ui:get_component_stack_under_cursor() - local c = stack[#stack] - - if c.children[2] then - c.children[2]:toggle_hidden() - self.buffer.ui:update() - end + vim.cmd("normal! za") end, ["d"] = function() if not config.check_integration("diffview") then From 6ba0fee6a8ccc672dc9a4a26372aac2803aa27e8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 12:44:16 +0100 Subject: [PATCH 004/443] Rewrite: Disregard "hidden" logic and use options.foldable to determine if a section is folded. --- lua/neogit/lib/buffer.lua | 27 +++-- lua/neogit/lib/ui/init.lua | 229 +++++++++++++++++-------------------- 2 files changed, 127 insertions(+), 129 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 7fead7030..e218db147 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -32,6 +32,7 @@ function Buffer:new(handle) hl_buffer = {}, sign_buffer = {}, ext_buffer = {}, + fold_buffer = {}, } this.ui = Ui.new(this) @@ -106,6 +107,10 @@ function Buffer:buffered_set_extmark(...) table.insert(self.ext_buffer, { ... }) end +function Buffer:buffered_create_fold(...) + table.insert(self.fold_buffer, { ... }) +end + function Buffer:resize(length) api.nvim_buf_set_lines(self.handle, length, -1, false, {}) end @@ -130,6 +135,11 @@ function Buffer:flush_buffers() self:set_extmark(unpack(ext)) end self.ext_buffer = {} + + for _, fold in ipairs(self.fold_buffer) do + self:create_fold(unpack(fold)) + end + self.fold_buffer = {} end function Buffer:set_text(first_line, last_line, first_col, last_col, lines) @@ -270,7 +280,7 @@ function Buffer:put(lines, after, follow) end function Buffer:create_fold(first, last) - vim.cmd(string.format(self.handle .. "bufdo %d,%dfold", first, last)) + vim.cmd(string.format("%d,%dfold", first, last)) end function Buffer:unlock() @@ -290,10 +300,6 @@ function Buffer:set_name(name) api.nvim_buf_set_name(self.handle, name) end -function Buffer:set_foldlevel(level) - vim.cmd("setlocal foldlevel=" .. level) -end - function Buffer:replace_content_with(lines) api.nvim_buf_set_lines(self.handle, 0, -1, false, lines) end @@ -382,8 +388,8 @@ function Buffer:set_extmark(...) return api.nvim_buf_set_extmark(self.handle, ...) end -function Buffer:get_extmark(ns, id) - return api.nvim_buf_get_extmark_by_id(self.handle, ns, id, { details = true }) +function Buffer:get_extmark(id, ns) + return api.nvim_buf_get_extmark_by_id(self.handle, ns or self.namespace, id, { details = true }) end function Buffer:del_extmark(ns, id) @@ -438,6 +444,12 @@ function Buffer.create(config) buffer:set_option("buftype", config.buftype or "nofile") buffer:set_option("swapfile", false) + buffer:set_option("foldenable", true) + buffer:set_option("foldmethod", "manual") + buffer:set_option("foldlevel", 1) + buffer:set_option("foldminlines", 0) + buffer:set_option("foldtext", "v:lua.NeogitBufferFoldText()") + if config.filetype then buffer:set_filetype(config.filetype) end @@ -493,6 +505,7 @@ function Buffer.create(config) buffer:call(function() -- Set fold styling for Neogit windows while preserving user styling vim.opt_local.winhl:append("Folded:NeogitFold") + vim.opt_local.fillchars:append("fold: ") -- Set signcolumn unless disabled by user settings if not config.disable_signs then diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index e31746a23..67c0bd49d 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -194,67 +194,65 @@ function Ui:_render(first_line, first_col, parent, components, flags) c.parent = parent c.index = i - if not c.options.hidden then - c.position = {} - c.position.row_start = curr_line - first_line + 1 - - local highlight = c:get_highlight() - - if c.tag == "text" then - local padding_left = flags.in_nested_row and "" or c:get_padding_left(i == 1) - table.insert(text, 1, padding_left) - - col_start = col_start + #padding_left - col_end = col_start + c:get_width() - c.position.col_start = col_start - c.position.col_end = col_end - 1 - - if c.options.align_right then - table.insert(text, c.value) - table.insert(text, (" "):rep(c.options.align_right - #c.value)) - else - table.insert(text, c.value) - end - - if highlight then - table.insert(highlights, { - from = col_start, - to = col_end, - name = highlight, - }) - end - - col_start = col_end - elseif c.tag == "row" then - flags.in_nested_row = true - - local padding_left = flags.in_nested_row and "" or c:get_padding_left(i == 1) - local res = self:_render(curr_line, col_start, c, c.children, flags) - - flags.in_nested_row = false - - if c.position.col_end then - c.position.col_end = c.position.col_end + #padding_left - end - - table.insert(text, padding_left) - table.insert(text, res.text) - - for _, h in ipairs(res.highlights) do - h.to = h.to + #padding_left - table.insert(highlights, h) - end - - col_end = col_start + vim.fn.strdisplaywidth(res.text) - c.position.col_start = col_start - c.position.col_end = col_end - col_start = col_end + c.position = {} + c.position.row_start = curr_line - first_line + 1 + + local highlight = c:get_highlight() + + if c.tag == "text" then + local padding_left = flags.in_nested_row and "" or c:get_padding_left(i == 1) + table.insert(text, 1, padding_left) + + col_start = col_start + #padding_left + col_end = col_start + c:get_width() + c.position.col_start = col_start + c.position.col_end = col_end - 1 + + if c.options.align_right then + table.insert(text, c.value) + table.insert(text, (" "):rep(c.options.align_right - #c.value)) else - error("The row component does not support having a `" .. c.tag .. "` as child") + table.insert(text, c.value) + end + + if highlight then + table.insert(highlights, { + from = col_start, + to = col_end, + name = highlight, + }) + end + + col_start = col_end + elseif c.tag == "row" then + flags.in_nested_row = true + + local padding_left = flags.in_nested_row and "" or c:get_padding_left(i == 1) + local res = self:_render(curr_line, col_start, c, c.children, flags) + + flags.in_nested_row = false + + if c.position.col_end then + c.position.col_end = c.position.col_end + #padding_left + end + + table.insert(text, padding_left) + table.insert(text, res.text) + + for _, h in ipairs(res.highlights) do + h.to = h.to + #padding_left + table.insert(highlights, h) end - c.position.row_end = c.position.row_start + col_end = col_start + vim.fn.strdisplaywidth(res.text) + c.position.col_start = col_start + c.position.col_end = col_end + col_start = col_end + else + error("The row component does not support having a `" .. c.tag .. "` as child") end + + c.position.row_end = c.position.row_start end if flags.in_nested_row then @@ -264,82 +262,69 @@ function Ui:_render(first_line, first_col, parent, components, flags) } end - if not flags.hidden then - self.buf:buffered_set_line(table.concat(text)) + self.buf:buffered_set_line(table.concat(text)) - for _, h in ipairs(highlights) do - self.buf:buffered_add_highlight(curr_line - 1, h.from, h.to, h.name) - end - - curr_line = curr_line + 1 + for _, h in ipairs(highlights) do + self.buf:buffered_add_highlight(curr_line - 1, h.from, h.to, h.name) end + + curr_line = curr_line + 1 else for i, c in ipairs(components) do c.parent = parent c.index = i - if not c.options.hidden then - c.position = {} - c.position.row_start = curr_line - first_line + 1 - c.position.col_start = 0 - c.position.col_end = -1 - local sign = c:get_sign() - local highlight = c:get_highlight() - - if c.tag == "text" then - if not flags.hidden then - self.buf:buffered_set_line(table.concat { c:get_padding_left(), c.value }) - - if highlight then - self.buf:buffered_add_highlight( - curr_line - 1, - c.position.col_start, - c.position.col_end, - highlight - ) - end - - if sign then - self.buf:buffered_place_sign(curr_line, sign, "hl") - end - - curr_line = curr_line + 1 - end - elseif c.tag == "col" then - curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) - elseif c.tag == "row" then - flags.in_row = true - curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) - - if not flags.hidden and sign then - self.buf:buffered_place_sign(curr_line - 1, sign, "hl") - end - - if not flags.hidden and c.options.virtual_text then - local ns = self.buf:create_namespace("NeogitBufferVirtualText") - self.buf:buffered_set_extmark(ns, curr_line - 2, 0, { - hl_mode = "combine", - virt_text = c.options.virtual_text, - virt_text_pos = "right_align", - }) - end - - flags.in_row = false + c.position = {} + c.position.row_start = curr_line - first_line + 1 + c.position.col_start = 0 + c.position.col_end = -1 + + local sign = c:get_sign() + local highlight = c:get_highlight() + + if c.tag == "text" then + self.buf:buffered_set_line(table.concat { c:get_padding_left(), c.value }) + + if highlight then + self.buf:buffered_add_highlight( + curr_line - 1, + c.position.col_start, + c.position.col_end, + highlight + ) end - c.position.row_end = curr_line - first_line - else - flags.hidden = true - - if c.tag == "col" then - self:_render(curr_line, 0, c, c.children, flags) - elseif c.tag == "row" then - flags.in_row = true - self:_render(curr_line, 0, c, c.children, flags) - flags.in_row = false + if sign then + self.buf:buffered_place_sign(curr_line, sign, "hl") end - flags.hidden = false + curr_line = curr_line + 1 + elseif c.tag == "col" then + curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) + elseif c.tag == "row" then + flags.in_row = true + curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) + + if sign then + self.buf:buffered_place_sign(curr_line - 1, sign, "hl") + end + + if c.options.virtual_text then + local ns = self.buf:create_namespace("NeogitBufferVirtualText") + self.buf:buffered_set_extmark(ns, curr_line - 2, 0, { + hl_mode = "combine", + virt_text = c.options.virtual_text, + virt_text_pos = "right_align", + }) + end + + flags.in_row = false + end + + c.position.row_end = curr_line - first_line + + if c.options.foldable then + self.buf:buffered_create_fold(c.position.row_start, c.position.row_end) end end end From c4d0552a264b4a68dca0286a319fa4d1a1ecab49 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 20:46:02 +0100 Subject: [PATCH 005/443] Set foldlevel to 99, cleanup --- lua/neogit/buffers/commit_view/init.lua | 18 +----------------- lua/neogit/buffers/common.lua | 11 +---------- lua/neogit/lib/buffer.lua | 2 +- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 945af594f..f4f158e5a 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -172,24 +172,8 @@ function M:open() ["q"] = function() self:close() end, - [""] = function() - self.buffer.ui:print_layout_tree { collapse_hidden_components = true } - end, [""] = function() - local c = self.buffer.ui:get_component_under_cursor() - - if c then - local c = c.parent - if c.options.tag == "HunkContent" then - c = c.parent - end - if vim.tbl_contains({ "Diff", "Hunk" }, c.options.tag) then - local first, _ = c:row_range_abs() - c.children[2]:toggle_hidden() - self.buffer.ui:update() - api.nvim_win_set_cursor(0, { first, 0 }) - end - end + pcall(vim.cmd, "normal! za") end, }, }, diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 67609a7d5..48ce7bbee 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,4 +1,3 @@ - local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") @@ -156,7 +155,7 @@ M.CommitEntry = Component.new(function(commit, args) local details if args.details then - details = col.hidden(true).padding_left(8) { + details = col.padding_left(8) { row(util.merge(graph, { text(" "), text("Author: ", { highlight = "NeogitGraphAuthor" }), @@ -267,14 +266,6 @@ M.Grid = Component.new(function(props) for i = 1, #props.items do local children = {} - -- TODO: seems to be a leftover from when the grid was column major - -- if i ~= 1 then - -- children = map(range(props.gap), function() - -- return text("") - -- end) - -- end - - -- current row local r = props.items[i] for j = 1, #r do diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e218db147..4d04701e3 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -446,7 +446,7 @@ function Buffer.create(config) buffer:set_option("foldenable", true) buffer:set_option("foldmethod", "manual") - buffer:set_option("foldlevel", 1) + buffer:set_option("foldlevel", 99) buffer:set_option("foldminlines", 0) buffer:set_option("foldtext", "v:lua.NeogitBufferFoldText()") From 6ceaf9b7b86d38d3844de90d3d92d59c2fe1f237 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 20:49:06 +0100 Subject: [PATCH 006/443] Remove traces of "hidden" --- lua/neogit/buffers/git_command_history.lua | 8 ++--- lua/neogit/lib/ui/component.lua | 18 +--------- lua/neogit/lib/ui/init.lua | 40 ++++++++-------------- 3 files changed, 19 insertions(+), 47 deletions(-) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index 0bcacba19..f78314b9b 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -87,11 +87,9 @@ function M:show() text(" "), text.highlight("NeogitCommandTime")(stdio), }, - col - .hidden(true) - .padding_left(" | ") - .highlight("NeogitCommandText")(map(is_err and item.stderr or item.stdout, text)), - } + col.padding_left(" | ") + .highlight("NeogitCommandText")(map(is_err and item.stderr or item.stdout, text)), + }, { foldable = true }) end) end, } diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index d32fe9e9f..2475507e0 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -1,8 +1,7 @@ local util = require("neogit.lib.util") local default_component_options = { - folded = false, - hidden = false, + foldable = false, } local Component = {} @@ -20,10 +19,6 @@ function Component:row_range_abs() return from, from + len end -function Component:toggle_hidden() - self.options.hidden = not self.options.hidden -end - function Component:get_padding_left(recurse) local padding_left = self.options.padding_left or 0 local padding_left_text = type(padding_left) == "string" and padding_left or (" "):rep(padding_left) @@ -33,14 +28,7 @@ function Component:get_padding_left(recurse) return padding_left_text .. (self.parent and self.parent:get_padding_left() or "") end -function Component:is_hidden() - return self.options.hidden or (self.parent and self.parent:is_hidden()) -end - function Component:is_under_cursor(cursor) - if self:is_hidden() then - return false - end local row = cursor[1] local col = cursor[2] local from, to = self:row_range_abs() @@ -51,10 +39,6 @@ function Component:is_under_cursor(cursor) end function Component:is_in_linewise_range(start, stop) - if self:is_hidden() then - return false - end - local from, to = self:row_range_abs() return from >= start and from <= stop and to >= start and to <= stop end diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 67c0bd49d..39b688a41 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -24,9 +24,7 @@ end function Ui._print_component(indent, c, _options) local output = string.rep(" ", indent) - if c.options.hidden then - output = output .. "(H)" - elseif c.position then + if c.position then local text = "" if c.position.row_start == c.position.row_end then text = c.position.row_start @@ -48,7 +46,7 @@ function Ui._print_component(indent, c, _options) end for k, v in pairs(c.options) do - if k ~= "tag" and k ~= "hidden" then + if k ~= "tag" then output = output .. " " .. k .. "=" .. tostring(v) end end @@ -59,10 +57,7 @@ end function Ui._visualize_tree(indent, components, options) for _, c in ipairs(components) do Ui._print_component(indent, c, options) - if - (c.tag == "col" or c.tag == "row") - and not (options.collapse_hidden_components and c.options.hidden) - then + if c.tag == "col" or c.tag == "row" then Ui._visualize_tree(indent + 1, c.children, options) end end @@ -70,18 +65,16 @@ end function Ui._find_component(components, f, options) for _, c in ipairs(components) do - if (options.include_hidden and c.options.hidden) or not c.options.hidden then - if c.tag == "col" or c.tag == "row" then - local res = Ui._find_component(c.children, f, options) + if c.tag == "col" or c.tag == "row" then + local res = Ui._find_component(c.children, f, options) - if res then - return res - end + if res then + return res end + end - if f(c) then - return c - end + if f(c) then + return c end end @@ -89,7 +82,6 @@ function Ui._find_component(components, f, options) end ---@class FindOptions ----@field include_hidden boolean --- Finds a ui component in the buffer --- @@ -286,12 +278,7 @@ function Ui:_render(first_line, first_col, parent, components, flags) self.buf:buffered_set_line(table.concat { c:get_padding_left(), c.value }) if highlight then - self.buf:buffered_add_highlight( - curr_line - 1, - c.position.col_start, - c.position.col_end, - highlight - ) + self.buf:buffered_add_highlight(curr_line - 1, c.position.col_start, c.position.col_end, highlight) end if sign then @@ -324,7 +311,10 @@ function Ui:_render(first_line, first_col, parent, components, flags) c.position.row_end = curr_line - first_line if c.options.foldable then - self.buf:buffered_create_fold(c.position.row_start, c.position.row_end) + self.buf:buffered_create_fold( + #self.buf.line_buffer - (c.position.row_end - c.position.row_start), + #self.buf.line_buffer + ) end end end From df337cc184eff080611c348c70f4cb297d4fa0b7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 21:16:37 +0100 Subject: [PATCH 007/443] Not needed --- ftplugin/NeogitGitCommandHistory.vim | 15 --------------- lua/neogit/buffers/common.lua | 8 ++++---- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 ftplugin/NeogitGitCommandHistory.vim diff --git a/ftplugin/NeogitGitCommandHistory.vim b/ftplugin/NeogitGitCommandHistory.vim deleted file mode 100644 index 475230fb7..000000000 --- a/ftplugin/NeogitGitCommandHistory.vim +++ /dev/null @@ -1,15 +0,0 @@ -" Only do this when not done yet for this buffer -if exists("b:did_ftplugin") - finish -endif -let b:did_ftplugin = 1 - -function! NeogitFoldFunction() - return getline(v:foldstart) -endfunction - -setlocal foldmethod=manual -setlocal foldlevel=1 -setlocal fillchars=fold:\ -setlocal foldminlines=0 -setlocal foldtext=NeogitFoldFunction() diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 48ce7bbee..12858fe18 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -30,13 +30,13 @@ M.Diff = Component.new(function(diff) } end) - return col.tag("Diff") { + return col.tag("Diff")({ text(string.format("%s %s", diff.kind, diff.file), { sign = "NeogitDiffHeader" }), col.tag("DiffContent") { col.tag("DiffInfo")(map(diff.info, text)), col.tag("HunkList")(map(hunk_props, M.Hunk)), }, - } + }, { foldable = true }) end) local HunkLine = Component.new(function(line) @@ -54,10 +54,10 @@ local HunkLine = Component.new(function(line) end) M.Hunk = Component.new(function(props) - return col.tag("Hunk") { + return col.tag("Hunk")({ text.sign("NeogitHunkHeader")(props.header), col.tag("HunkContent")(map(props.content, HunkLine)), - } + }, { foldable = true }) end) M.List = Component.new(function(props) From c0e64fdb6f8cf66f19e72a63be2c3a38dd9899cd Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 21:48:51 +0100 Subject: [PATCH 008/443] Open all folds for commit view --- lua/neogit/buffers/commit_view/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index f4f158e5a..671091725 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -180,6 +180,9 @@ function M:open() render = function() return ui.CommitView(self.commit_info, self.commit_overview, self.commit_signature) end, + after = function() + vim.cmd("normal! zR") + end, } end From b2f8115634680bbca4665e814b0e6ef77a979c6a Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 21:49:13 +0100 Subject: [PATCH 009/443] Add folds for git command history view --- lua/neogit/buffers/git_command_history.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index f78314b9b..7c63d490a 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -77,7 +77,7 @@ function M:show() local spacing = string.rep(" ", win_width - #code - #command - #time - #stdio - 6) - return col { + return col({ row { text.highlight(highlight_code)(code), text(" "), @@ -87,8 +87,9 @@ function M:show() text(" "), text.highlight("NeogitCommandTime")(stdio), }, - col.padding_left(" | ") - .highlight("NeogitCommandText")(map(is_err and item.stderr or item.stdout, text)), + col + .padding_left(" | ") + .highlight("NeogitCommandText")(map(is_err and item.stderr or item.stdout, text)), }, { foldable = true }) end) end, From a699b0a3aecd7daa4d123f20e13ed371ca7f6bf7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 21:49:24 +0100 Subject: [PATCH 010/443] Add tab/navigation for git command history --- lua/neogit/buffers/git_command_history.lua | 28 +++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index 7c63d490a..c0cf17540 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -46,14 +46,30 @@ function M:show() [""] = function() self:close() end, - [""] = function() - local stack = self.buffer.ui:get_component_stack_under_cursor() - local c = stack[#stack] + [""] = function() + vim.cmd("normal! zc") + + vim.cmd("normal! k") + while vim.fn.foldlevel(".") == 0 do + vim.cmd("normal! k") + end + + vim.cmd("normal! zo") + vim.cmd("normal! zz") + end, + [""] = function() + vim.cmd("normal! zc") - if c then - c.children[2]:toggle_hidden() - self.buffer.ui:update() + vim.cmd("normal! j") + while vim.fn.foldlevel(".") == 0 do + vim.cmd("normal! j") end + + vim.cmd("normal! zo") + vim.cmd("normal! zz") + end, + [""] = function() + pcall(vim.cmd, "normal! za") end, }, }, From 681a99c89cfa0eeb6daec1f5d489496b6687d4c7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 21:49:33 +0100 Subject: [PATCH 011/443] pcall this in log view for safety --- lua/neogit/buffers/log_view/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 18e95737e..64555db4b 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -133,7 +133,7 @@ function M:open() vim.cmd("normal! zz") end, [""] = function() - vim.cmd("normal! za") + pcall(vim.cmd, "normal! za") end, ["d"] = function() if not config.check_integration("diffview") then From 93b2a3091b428a615653c8d6a30bbbb581048b32 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 21:49:45 +0100 Subject: [PATCH 012/443] Refactor folds lib and add guard clause for < nvim 0.10 --- lua/neogit/lib/folds.lua | 51 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/lua/neogit/lib/folds.lua b/lua/neogit/lib/folds.lua index 2d0979822..8a5d5422a 100644 --- a/lua/neogit/lib/folds.lua +++ b/lua/neogit/lib/folds.lua @@ -1,43 +1,44 @@ --- For Nightly, a function that constructs the fold text local M = {} function M.fold_text() local text = vim.fn.getline(vim.v.foldstart) + + if not vim.fn.has("nvim-0.10") == 1 then + return text + end + local bufnr = vim.fn.bufnr() local ns = vim.api.nvim_get_namespaces()["neogit-buffer-" .. bufnr] local lnum = vim.v.foldstart - 1 - local startRange = { lnum, 0 } - local endRange = { lnum, -1 } - - local lastColEnd = 0 - local hlRes = {} - local marks = vim.api.nvim_buf_get_extmarks(bufnr, ns, startRange, endRange, { details = true }) - for i, m in ipairs(marks) do - local sc, details = m[3], m[4] - local ec = details.end_col or (sc + 1) - local hlGroup = details.hl_group - - if hlGroup then - if sc > lastColEnd then - table.insert(hlRes, { text:sub(lastColEnd + 1, sc), "NeogitGraphWhite" }) - end + local start_range = { lnum, 0 } + local end_range = { lnum, -1 } - if i == 1 then - table.insert(hlRes, { text:sub(sc + 1, ec + 1), hlGroup }) - lastColEnd = ec + 1 - else - table.insert(hlRes, { text:sub(sc + 1, ec), hlGroup }) - lastColEnd = ec + local last_col_end = 0 + local res = {} + + local marks = vim.api.nvim_buf_get_extmarks(bufnr, ns, start_range, end_range, { details = true }) + + for _, m in ipairs(marks) do + local start_col, details = m[3], m[4] + local end_col = details.end_col or (start_col + 1) + local hl_group = details.hl_group + + if hl_group then + if start_col > last_col_end then + table.insert(res, { text:sub(last_col_end + 1, start_col), "NeogitGraphWhite" }) end + + last_col_end = end_col + table.insert(res, { text:sub(start_col + 1, end_col), hl_group }) end end - if #text > lastColEnd then - table.insert(hlRes, { text:sub(lastColEnd + 1, -1), "NeogitGraphWhite" }) + if #text > last_col_end then + table.insert(res, { text:sub(last_col_end + 1, -1), "NeogitGraphWhite" }) end - return hlRes + return res end function M.setup() From 164d71ca431adf2635eb0b07166aee406b25ff77 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 22:35:36 +0100 Subject: [PATCH 013/443] Extract signature block --- lua/neogit/buffers/commit_view/ui.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index 7eec96720..487030e72 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -46,16 +46,21 @@ function M.CommitHeader(info) } end -function M.CommitView(info, overview, signature_block) - local hide_signature = vim.tbl_isempty(signature_block) +function M.SignatureBlock(signature_block) + if vim.tbl_isempty(signature_block or {}) then + return text("") + end + return col(util.merge(map(signature_block, text), { text("") }), { tag = "Signature" }) +end + +function M.CommitView(info, overview, signature_block) return { M.CommitHeader(info), text(""), col(map(info.description, text), { sign = "NeogitCommitViewDescription", tag = "Description" }), text(""), - col(map(signature_block or {}, text), { tag = "Signature", hidden = hide_signature }), - text("", { hidden = hide_signature }), + M.SignatureBlock(signature_block), text(overview.summary), col(map(overview.files, M.OverviewFile), { tag = "OverviewFileList" }), text(""), From e1d77419a65e1b08f6f8c0827d148a30759664f1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 22:39:25 +0100 Subject: [PATCH 014/443] Shush, daddy needs this --- lua/neogit/lib/folds.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/folds.lua b/lua/neogit/lib/folds.lua index 8a5d5422a..85286f9c2 100644 --- a/lua/neogit/lib/folds.lua +++ b/lua/neogit/lib/folds.lua @@ -42,6 +42,7 @@ function M.fold_text() end function M.setup() + -- selene: allow(global_usage) _G.NeogitBufferFoldText = M.fold_text end From 11b709ffc715909eef6b5434320981539a7b7e94 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 21 Nov 2023 22:43:03 +0100 Subject: [PATCH 015/443] Sky computer happy now --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 4d04701e3..406e7bf94 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -444,7 +444,7 @@ function Buffer.create(config) buffer:set_option("buftype", config.buftype or "nofile") buffer:set_option("swapfile", false) - buffer:set_option("foldenable", true) + vim.opt_local.foldenable = true buffer:set_option("foldmethod", "manual") buffer:set_option("foldlevel", 99) buffer:set_option("foldminlines", 0) From 4d6eee6c2cd4013c1a1137bee54e83f51a42aefb Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 09:58:05 +0100 Subject: [PATCH 016/443] Set window opt --- lua/neogit/lib/buffer.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 406e7bf94..42bd51f95 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -444,11 +444,12 @@ function Buffer.create(config) buffer:set_option("buftype", config.buftype or "nofile") buffer:set_option("swapfile", false) - vim.opt_local.foldenable = true - buffer:set_option("foldmethod", "manual") - buffer:set_option("foldlevel", 99) - buffer:set_option("foldminlines", 0) - buffer:set_option("foldtext", "v:lua.NeogitBufferFoldText()") + if win then + vim.api.nvim_set_option_value("foldenable", true, { win = win }) + vim.api.nvim_set_option_value("foldlevel", 99, { win = win }) + vim.api.nvim_set_option_value("foldminlines", 0, { win = win }) + vim.api.nvim_set_option_value("foldtext", "v:lua.NeogitBufferFoldText()", { win = win }) + end if config.filetype then buffer:set_filetype(config.filetype) From 209d60eff1915b433146e66b3244264f8ad23669 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 10:03:25 +0100 Subject: [PATCH 017/443] Update log spec to open fold so we can get the text properly --- tests/specs/neogit/popups/log_spec.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/specs/neogit/popups/log_spec.lua b/tests/specs/neogit/popups/log_spec.lua index 316dca279..6a0a79e6b 100644 --- a/tests/specs/neogit/popups/log_spec.lua +++ b/tests/specs/neogit/popups/log_spec.lua @@ -76,6 +76,7 @@ describe("log popup", function() act("l=n1l") operations.wait("log_current") vim.fn.feedkeys("G", "x") + vim.fn.feedkeys("zo", "x") eq("e2c2a1c * master origin/second-branch b.txt", vim.api.nvim_get_current_line()) end) ) From 23e4cfd24b71cd0cb3ebfb1bd19fe026db2beedf Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 10:33:15 +0100 Subject: [PATCH 018/443] Make these calls more robust. With -G flag, the first item can be "...", which isn't a fold. Prevent errors in this case, and prevent infinite loops if the top/bottom of buffer is a "...". --- lua/neogit/buffers/log_view/init.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 64555db4b..04b79b20a 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -111,25 +111,33 @@ function M:open() CommitViewBuffer.new(self.buffer.ui:get_commit_under_cursor()):open() end, [""] = function() - vim.cmd("normal! zc") + pcall(vim.cmd, "normal! zc") vim.cmd("normal! k") - while vim.fn.foldlevel(".") == 0 do + for _ = vim.fn.line("."), 0, -1 do + if vim.fn.foldlevel(".") > 0 then + break + end + vim.cmd("normal! k") end - vim.cmd("normal! zo") + pcall(vim.cmd, "normal! zo") vim.cmd("normal! zz") end, [""] = function() - vim.cmd("normal! zc") + pcall(vim.cmd, "normal! zc") vim.cmd("normal! j") - while vim.fn.foldlevel(".") == 0 do + for _ = vim.fn.line("."), vim.fn.line("$"), 1 do + if vim.fn.foldlevel(".") > 0 then + break + end + vim.cmd("normal! j") end - vim.cmd("normal! zo") + pcall(vim.cmd, "normal! zo") vim.cmd("normal! zz") end, [""] = function() From 44a0b83e72a7bbc66b29fa939cc143a4f51e1ac9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 10:34:21 +0100 Subject: [PATCH 019/443] Fix up spec to work with folds --- tests/specs/neogit/popups/log_spec.lua | 100 +++++++++++++++++++++---- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/tests/specs/neogit/popups/log_spec.lua b/tests/specs/neogit/popups/log_spec.lua index 6a0a79e6b..964c9c820 100644 --- a/tests/specs/neogit/popups/log_spec.lua +++ b/tests/specs/neogit/popups/log_spec.lua @@ -14,6 +14,10 @@ local function act(normal_cmd) vim.fn.feedkeys("", "x") -- flush typeahead end +local function actual() + return vim.api.nvim_buf_get_lines(0, 0, -1, true) +end + describe("log popup", function() before_each(function() -- Reset all switches. @@ -75,9 +79,19 @@ describe("log popup", function() in_prepared_repo(function() act("l=n1l") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - vim.fn.feedkeys("zo", "x") - eq("e2c2a1c * master origin/second-branch b.txt", vim.api.nvim_get_current_line()) + + local expected = { + "e2c2a1c * master origin/second-branch b.txt", + " * Author: Florian Proksch ", + " * AuthorDate: Tue Feb 9 20:33:33 2021 +0100", + " * Commit: Florian Proksch ", + " * CommitDate: Tue Feb 9 20:33:33 2021 +0100", + " *", + " * b.txt", + " * ", + } + + eq(expected, actual()) end) ) @@ -92,8 +106,8 @@ describe("log popup", function() ]]) act("l=APersonl") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - assert.is_not.Nil(string.find(vim.api.nvim_get_current_line(), "Empty commit", 1, true)) + + assert.is_not.Nil(string.find(actual()[1], "Empty commit", 1, true)) end) ) @@ -102,8 +116,19 @@ describe("log popup", function() in_prepared_repo(function() act("l=Fa.txtl") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - eq("d86fa0e * a.txt", vim.api.nvim_get_current_line()) + + local expected = { + "d86fa0e * a.txt", + " * Author: Florian Proksch ", + " * AuthorDate: Sat Feb 6 08:08:32 2021 +0100", + " * Commit: Florian Proksch ", + " * CommitDate: Sat Feb 6 21:20:33 2021 +0100", + " *", + " * a.txt", + " * ", + } + + eq(expected, actual()) end) ) @@ -112,8 +137,19 @@ describe("log popup", function() in_prepared_repo(function() act("l=sFeb 8 2021l") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - eq("e2c2a1c * master origin/second-branch b.txt", vim.api.nvim_get_current_line()) + + local expected = { + "e2c2a1c * master origin/second-branch b.txt", + " * Author: Florian Proksch ", + " * AuthorDate: Tue Feb 9 20:33:33 2021 +0100", + " * Commit: Florian Proksch ", + " * CommitDate: Tue Feb 9 20:33:33 2021 +0100", + " *", + " * b.txt", + " * ", + } + + eq(expected, actual()) end) ) @@ -122,8 +158,19 @@ describe("log popup", function() in_prepared_repo(function() act("l=uFeb 7 2021l") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - eq("d86fa0e * a.txt", vim.api.nvim_get_current_line()) + + local expected = { + "d86fa0e * a.txt", + " * Author: Florian Proksch ", + " * AuthorDate: Sat Feb 6 08:08:32 2021 +0100", + " * Commit: Florian Proksch ", + " * CommitDate: Sat Feb 6 21:20:33 2021 +0100", + " *", + " * a.txt", + " * ", + } + + eq(expected, actual()) end) ) @@ -133,8 +180,20 @@ describe("log popup", function() input.values = { "text file" } act("l-Gl") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - eq("d86fa0e * a.txt", vim.api.nvim_get_current_line()) + + local expected = { + " ...", + "d86fa0e * a.txt", + " * Author: Florian Proksch ", + " * AuthorDate: Sat Feb 6 08:08:32 2021 +0100", + " * Commit: Florian Proksch ", + " * CommitDate: Sat Feb 6 21:20:33 2021 +0100", + " *", + " * a.txt", + " * ", + } + + eq(expected, actual()) end) ) @@ -144,8 +203,19 @@ describe("log popup", function() input.values = { "test file" } act("l-Sl") operations.wait("log_current") - vim.fn.feedkeys("G", "x") - eq("e2c2a1c * master origin/second-branch b.txt", vim.api.nvim_get_current_line()) + + local expected = { + "e2c2a1c * master origin/second-branch b.txt", + " * Author: Florian Proksch ", + " * AuthorDate: Tue Feb 9 20:33:33 2021 +0100", + " * Commit: Florian Proksch ", + " * CommitDate: Tue Feb 9 20:33:33 2021 +0100", + " *", + " * b.txt", + " * ", + } + + eq(expected, actual()) end) ) From cf461c35fc62ee8b602b79520a8f340fbad237ca Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 10:51:18 +0100 Subject: [PATCH 020/443] Properly check for nvim 0.10 --- lua/neogit/lib/folds.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/folds.lua b/lua/neogit/lib/folds.lua index 85286f9c2..1ed494027 100644 --- a/lua/neogit/lib/folds.lua +++ b/lua/neogit/lib/folds.lua @@ -3,7 +3,8 @@ local M = {} function M.fold_text() local text = vim.fn.getline(vim.v.foldstart) - if not vim.fn.has("nvim-0.10") == 1 then + -- TODO: Remove this once nvim-0.10 is released + if vim.fn.has("nvim-0.10") == 0 then return text end From b49ed374aeacee3bc797e88022e67172799417a7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 8 Oct 2023 19:19:16 +0200 Subject: [PATCH 021/443] This can be removed. Fixed in 44bd94625c6fa95d33f7a2ddf077b50a59cc75c6. --- lua/neogit/config.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 5d6674156..69bf0a228 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -264,7 +264,6 @@ function M.get_default_values() "NeogitPullPopup--rebase", "NeogitLogPopup--", "NeogitCommitPopup--allow-empty", - "NeogitRevertPopup--no-edit", -- TODO: Fix incompatible switches with default enables }, mappings = { finder = { From e2ffb33cdf7da9c82f24edaf521c9164fd5432e1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Oct 2023 10:48:15 +0200 Subject: [PATCH 022/443] Remove this from ignored settings --- lua/neogit/config.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 69bf0a228..7aca55fc5 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -262,7 +262,6 @@ function M.get_default_values() "NeogitPushPopup--force-with-lease", "NeogitPushPopup--force", "NeogitPullPopup--rebase", - "NeogitLogPopup--", "NeogitCommitPopup--allow-empty", }, mappings = { From 91c001cf41413e9a28bf55acae582c115a2acd31 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Oct 2023 10:48:29 +0200 Subject: [PATCH 023/443] If opts has a setup function, call it --- lua/neogit/lib/popup/builder.lua | 4 ++++ lua/neogit/popups/log/init.lua | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 4a184bee0..071104f95 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -194,6 +194,10 @@ function M:option(key, cli, value, description, opts) opts.separator = "=" end + if opts.setup then + opts.setup(self) + end + table.insert(self.state.args, { type = "option", id = opts.key_prefix .. key, diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index e60823209..a5c98dd37 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -30,6 +30,16 @@ function M.create() key_prefix = "-", separator = "", fn = actions.limit_to_files, + setup = function(popup) + local value = require("neogit.lib.state").get { "NeogitLogPopup", "" } + value = vim.split(value, " ", { trimempty = true }) + value = require("neogit.lib.util").map(value, function(v) + local result, _ = v:gsub([["]], "") + return result + end) + + popup.state.env.files = value + end, }) :switch("f", "follow", "Follow renames when showing single-file log") :arg_heading("Commit Ordering") From d33fd945af319dd873289abad1d06bb205ad9e1a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Oct 2023 10:48:44 +0200 Subject: [PATCH 024/443] Nullify settings if empty string is passed --- lua/neogit/lib/state.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/state.lua b/lua/neogit/lib/state.lua index 9664112f9..722769d6a 100644 --- a/lua/neogit/lib/state.lua +++ b/lua/neogit/lib/state.lua @@ -74,15 +74,21 @@ local function gen_key(key_table) end ---Set option and write to disk ----@param key table +---@param key string[] ---@param value any function M.set(key, value) if not M.enabled() then return end - if not vim.tbl_contains(config.values.ignored_settings, gen_key(key)) then - M.state[gen_key(key)] = value + local cache_key = gen_key(key) + if not vim.tbl_contains(config.values.ignored_settings, cache_key) then + if value == "" then + M.state[cache_key] = nil + else + M.state[cache_key] = value + end + M.write() end end From b60ea2b906f98da0bef65deba2e18aa24fd93f02 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 10:56:33 +0100 Subject: [PATCH 025/443] Update status message when discarding selection --- lua/neogit/status.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 31145bfac..758ac32f7 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -924,7 +924,9 @@ local unstage = operation("unstage", function() end) local function discard_message(files, hunk_count) - if hunk_count > 0 then + if vim.api.nvim_get_mode() == "V" then + return string.format("Discard selection?") + elseif hunk_count > 0 then return string.format("Discard %d hunks?", hunk_count) elseif #files > 1 then return string.format("Discard %d files?", #files) From 1ec1a476797b82dedf8e82adc75ac3cf0ebad251 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 11:00:26 +0100 Subject: [PATCH 026/443] Don't string format --- lua/neogit/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 758ac32f7..aadc0478b 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -925,7 +925,7 @@ end) local function discard_message(files, hunk_count) if vim.api.nvim_get_mode() == "V" then - return string.format("Discard selection?") + return "Discard selection?" elseif hunk_count > 0 then return string.format("Discard %d hunks?", hunk_count) elseif #files > 1 then From b515b236feff5a98dc6f5ffcb8ef19079f716aee Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Thu, 16 Nov 2023 08:04:09 +0100 Subject: [PATCH 027/443] Show HEAD as `NeogitBranchHead` in Log Graph In the log graph it's non intuitive where the currently checked out branch is. To make this more customizable for the user, we add the `NeogitBranchHead` highlight group. --- doc/neogit.txt | 1 + lua/neogit/buffers/common.lua | 9 ++++++--- lua/neogit/lib/hl.lua | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index d7f3cc7b0..402c357f0 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -200,6 +200,7 @@ exist, they will be created with sensible defaults based on your colorscheme. STATUS BUFFER NeogitBranch Header showing currently checked out branch +NeogitBranchHead Currently checkoued branch (in log graph) NeogitRemote Header showing current branch's remote (if set) NeogitObjectId Object's SHA hash NeogitStash Stash name diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 12858fe18..d1eee7f54 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -120,16 +120,19 @@ M.CommitEntry = Component.new(function(commit, args) remote_name, local_name = local_name, remote_name end + local is_head = string.match(commit.ref_name, "HEAD") ~= nil + local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" + if local_name and remote_name and vim.endswith(remote_name, local_name) then local remote = remote_name:match("^([^/]*)/.*$") table.insert(ref, text(remote .. "/", { highlight = "NeogitRemote" })) - table.insert(ref, text(local_name, { highlight = "NeogitBranch" })) + table.insert(ref, text(local_name, { highlight = branch_highlight })) table.insert(ref, text(" ")) else if local_name then table.insert( ref, - text(local_name, { highlight = local_name:match("/") and "NeogitRemote" or "NeogitBranch" }) + text(local_name, { highlight = local_name:match("/") and "NeogitRemote" or branch_highlight }) ) table.insert(ref, text(" ")) end @@ -137,7 +140,7 @@ M.CommitEntry = Component.new(function(commit, args) if remote_name then table.insert( ref, - text(remote_name, { highlight = remote_name:match("/") and "NeogitRemote" or "NeogitBranch" }) + text(remote_name, { highlight = remote_name:match("/") and "NeogitRemote" or branch_highlight }) ) table.insert(ref, text(" ")) end diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 2b966f893..f53970d4f 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -164,6 +164,7 @@ function M.setup() NeogitCommandCodeNormal = { link = "String" }, NeogitCommandCodeError = { link = "Error" }, NeogitBranch = { fg = palette.blue, bold = true }, + NeogitBranchHead = { fg = palette.blue, bold = true, underline = true }, NeogitRemote = { fg = palette.green, bold = true }, NeogitUnmergedInto = { fg = palette.bg_purple, bold = true }, NeogitUnpushedTo = { fg = palette.bg_purple, bold = true }, From 7b61fbc71ce69b96c083c4f6124e0b4ec4083615 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Fri, 17 Nov 2023 08:41:31 +0100 Subject: [PATCH 028/443] FIX: Don't highlight `origin/HEAD` with `NeogitBranchHead` --- lua/neogit/buffers/common.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index d1eee7f54..a09892efe 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -120,7 +120,7 @@ M.CommitEntry = Component.new(function(commit, args) remote_name, local_name = local_name, remote_name end - local is_head = string.match(commit.ref_name, "HEAD") ~= nil + local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" if local_name and remote_name and vim.endswith(remote_name, local_name) then From b6001dea8a8267398a775419a05138b3bb2ee1d6 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Mon, 20 Nov 2023 08:09:13 +0100 Subject: [PATCH 029/443] Reformulate highlight groups in docs --- doc/neogit.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 402c357f0..8cd549819 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -199,9 +199,9 @@ these yourself before the plugin loads, that will be respected. If they do not exist, they will be created with sensible defaults based on your colorscheme. STATUS BUFFER -NeogitBranch Header showing currently checked out branch -NeogitBranchHead Currently checkoued branch (in log graph) -NeogitRemote Header showing current branch's remote (if set) +NeogitBranch Local branches +NeogitBranchHead Accent highlight for current HEAD in LogBuffer +NeogitRemote Remote branches NeogitObjectId Object's SHA hash NeogitStash Stash name NeogitFold Folded text highlight From b98595be7c0c09d45a08528eb2898161c530a250 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 11:19:29 +0100 Subject: [PATCH 030/443] Add --tags option to pull popup --- lua/neogit/popups/pull/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/popups/pull/init.lua b/lua/neogit/popups/pull/init.lua index 313a2a442..dcba8a281 100755 --- a/lua/neogit/popups/pull/init.lua +++ b/lua/neogit/popups/pull/init.lua @@ -23,6 +23,7 @@ function M.create() :switch("f", "ff-only", "Fast-forward only") :switch("r", "rebase", "Rebase local commits") :switch("a", "autostash", "Autostash") + :switch("t", "tags", "Fetch tags") :group_heading_if(current, "Pull into " .. current .. " from") :group_heading_if(not current, "Pull from") :action_if(current, "p", git.branch.pushRemote_label(), actions.from_pushremote) From c08367cee785e27f2da04537bdfeb02a1a900f7a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:56:36 +0100 Subject: [PATCH 031/443] Mark TODOs --- doc/neogit.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 8cd549819..a427fd1b5 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -502,9 +502,11 @@ Branch Popup *neogit_branch_popup* Variables: *neogit_branch_popup_variables* • branch..description + (TODO) • branch..merge branch..remote + (TODO) • branch..rebase Cycles branch..rebase value between true, false, and the value of From b5808f8bf046affd9d8cb4b100f07e2b43c33c5e Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:56:59 +0100 Subject: [PATCH 032/443] Add docs for Pull popup --- doc/neogit.txt | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index a427fd1b5..9dd9a6b23 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -946,7 +946,62 @@ Remote: ============================================================================== Pull Popup *neogit_pull_popup* -(TODO) +Variables: *neogit_pull_popup_variables* + • branch..rebase + When true, rebase the branch on top of the fetched branch, instead + of merging the default branch from the default remote when "git pull" is + run. See "pull.rebase" for doing this in a non branch-specific manner. + +Arguments: *neogit_pull_popup_args* + • --ff-only + Only update to the new history if there is no divergent local history. + This is the default when no method for reconciling divergent histories is + provided (via the --rebase=* flags). + + • --rebase + When enabled, rebase the current branch on top of the upstream branch + after fetching. If there is a remote-tracking branch corresponding to the + upstream branch and the upstream branch was rebased since last fetched, + the rebase uses that information to avoid rebasing non-local changes. + + See pull.rebase, branch..rebase and branch.autoSetupRebase if you + want to make git pull always use --rebase instead of merging. + + Note: + This is a potentially dangerous mode of operation. It rewrites history, + which does not bode well when you published that history already. Do not + use this option unless you understand the consequences. + + • --autostash + Automatically create a temporary stash entry before the operation begins, + record it in the special ref MERGE_AUTOSTASH and apply it after the + operation ends. This means that you can run the operation on a dirty + worktree. However, use with care: the final stash application after a + successful merge might result in non-trivial conflicts. + + • --tags + Fetch all tags from the remote (i.e., fetch remote tags refs/tags/* into + local tags with the same name), in addition to whatever else would + otherwise be fetched. + +Actions: *neogit_pull_popup_actions* + • Pull into from pushRemote *neogit_pull_pushRemote* + Pulls into the current branch from `branch..pushRemote`. If that is + unset, the user will be prompted to select a remote branch, which will + pulled from and used to set `branch..pushRemote. + + • Pull into from @{upstream} *neogit_pull_upstream* + Pulls into the current branch from it's upstream counterpart. If that is + unset, the user will be prompted to select a remote branch, which will + pulled from and set as the upstream. + + • Pull into from elsewhere *neogit_pull_elsewhere* + Prompts the user to select a branch (local or remote), which is then + pulled in the current branch. + + • Set Variables *neogit_pull_set_variables* + Opens Branch Config popup for the current branch. + See |neogit_branch_config_popup|. ============================================================================== Push Popup *neogit_push_popup* From d3c87a49b1802586a5ed7cc8505b60859bcc76b7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:57:06 +0100 Subject: [PATCH 033/443] Allow pulling from ALL branches, not just remote --- lua/neogit/popups/pull/actions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index c63c02ed4..8f2dbff0f 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -62,7 +62,7 @@ function M.from_upstream(popup) end function M.from_elsewhere(popup) - local target = FuzzyFinderBuffer.new(git.branch.get_remote_branches()) + local target = FuzzyFinderBuffer.new(git.branch.get_all_branches(false)) :open_async { prompt_prefix = "pull > " } if not target then return From f827920c01c72c6ebc45ca6c6cc2e85054d5e348 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 15:59:21 +0100 Subject: [PATCH 034/443] Typo --- doc/neogit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 9dd9a6b23..0a51b337f 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -988,7 +988,7 @@ Actions: *neogit_pull_popup_actions* • Pull into from pushRemote *neogit_pull_pushRemote* Pulls into the current branch from `branch..pushRemote`. If that is unset, the user will be prompted to select a remote branch, which will - pulled from and used to set `branch..pushRemote. + pulled from and used to set `branch..pushRemote`. • Pull into from @{upstream} *neogit_pull_upstream* Pulls into the current branch from it's upstream counterpart. If that is From bb0f5e295b8bb8c6d29718255618d94272002bdb Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Fri, 17 Nov 2023 08:33:53 +0100 Subject: [PATCH 035/443] Parse `tag: ...` and render with `NeogitTagName` in Log Graph The `commit.ref_name` might also contain a tag name, which the log graph is currently rendering in the `NeogitBranch` highlight group. The refname of HEAD with a tag placed on it might look like this: > HEAD -> main, tag: 1.0.0, origin/main, origin/HEAD A refname of a tag not associated to any branch might look like this: > tag: 1.0.0 This makes the parsing of `local_name`, `remote_name` a bit more sophisticated, since we don't know the exact order of them. Naive solution might be to just split `ref_name` by comma and pattern match: * contains `tag: ...` -> `tag_name` * contains `/` -> `remote_name` * otherwise -> `local_name` --- lua/neogit/buffers/common.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index a09892efe..85afcb5a9 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -113,11 +113,21 @@ M.CommitEntry = Component.new(function(commit, args) -- Parse out ref names if args.decorate and commit.ref_name ~= "" then local ref_name, _ = commit.ref_name:gsub("HEAD %-> ", "") - local remote_name, local_name = unpack(vim.split(ref_name, ", ")) - -- Sometimes the log output will list remote/local names in reverse order - if (local_name and local_name:match("/")) and (remote_name and not remote_name:match("/")) then - remote_name, local_name = local_name, remote_name + local names = vim.split(ref_name, ", ") + local local_name = nil + local remote_name = nil + local tag_name = nil + for _, name in ipairs(names) do + if name:match("^tag: .*") ~= nil then + tag_name = name:gsub("tag: ", "") + end + if local_name == nil and not name:match("/") then + local_name = name + end + if remote_name == nil and name:match("/") then + remote_name = name + end end local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil @@ -145,6 +155,11 @@ M.CommitEntry = Component.new(function(commit, args) table.insert(ref, text(" ")) end end + + if tag_name ~= nil then + table.insert(ref, text(tag_name, { highlight = "NeogitTagName" })) + table.insert(ref, text(" ")) + end end if commit.rel_date:match(" years?,") then From 28b88e6c5635d49d6cde9227339b023c85915451 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Mon, 20 Nov 2023 11:23:39 +0100 Subject: [PATCH 036/443] Properly interprete branch names, remotes & tags Parsing the the `ref_name` manually by regex is finicky and error prone. To better identify, which of the parts of `ref_name` are remotes and which are branch names one can not rely on finding the `/`, since branches might also contain `/`. Also the order of the fields within the `ref_name` can vary. For a robust solution I extracted this parsing into the `lib/git/log.lua` module and added unit tests as well. This simplifies the logic of the logging function as well. --- lua/neogit/buffers/common.lua | 51 +++++------------- lua/neogit/lib/git/log.lua | 44 ++++++++++++++++ tests/specs/neogit/lib/git/log_spec.lua | 70 +++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 39 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 85afcb5a9..521d5e377 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,6 +1,8 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") +local log = require("neogit.lib.git.log") +local remote = require("neogit.lib.git.remote") local text = Ui.text local col = Ui.col @@ -113,51 +115,22 @@ M.CommitEntry = Component.new(function(commit, args) -- Parse out ref names if args.decorate and commit.ref_name ~= "" then local ref_name, _ = commit.ref_name:gsub("HEAD %-> ", "") - - local names = vim.split(ref_name, ", ") - local local_name = nil - local remote_name = nil - local tag_name = nil - for _, name in ipairs(names) do - if name:match("^tag: .*") ~= nil then - tag_name = name:gsub("tag: ", "") - end - if local_name == nil and not name:match("/") then - local_name = name - end - if remote_name == nil and name:match("/") then - remote_name = name - end - end - local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - if local_name and remote_name and vim.endswith(remote_name, local_name) then - local remote = remote_name:match("^([^/]*)/.*$") - table.insert(ref, text(remote .. "/", { highlight = "NeogitRemote" })) - table.insert(ref, text(local_name, { highlight = branch_highlight })) - table.insert(ref, text(" ")) - else - if local_name then - table.insert( - ref, - text(local_name, { highlight = local_name:match("/") and "NeogitRemote" or branch_highlight }) - ) - table.insert(ref, text(" ")) + local items = log.interprete(ref_name, remote.list()) + for branch, remotes in pairs(items.branches) do + if #remotes == 1 then + table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) end - - if remote_name then - table.insert( - ref, - text(remote_name, { highlight = remote_name:match("/") and "NeogitRemote" or branch_highlight }) - ) - table.insert(ref, text(" ")) + if #remotes > 1 then + table.insert(ref, text("{" .. table.concat(remotes, ",") .. "}/", { highlight = "NeogitRemote" })) end + table.insert(ref, text(branch, { highlight = branch_highlight })) + table.insert(ref, text(" ")) end - - if tag_name ~= nil then - table.insert(ref, text(tag_name, { highlight = "NeogitTagName" })) + for _, tag in pairs(items.tags) do + table.insert(ref, text(tag, { highlight = "NeogitTagName" })) table.insert(ref, text(" ")) end end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index ac7e114b1..d22fdcaeb 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -438,4 +438,48 @@ function M.verify_commit(commit) return cli["verify-commit"].args(commit).call_sync_ignoring_exit_code():trim().stderr end +---@class CommitBranchInfo +---@field branches table Mapping from (local) branch names to list of remotes where this branch is present (empty for local branches) +---@field tags string[] List of tags placed on this commit + +--- Parse information of branches, tags and remotes from a given commit's ref output +--- @param ref string comma separated list of branches, tags and remotes, e.g.: +--- * "origin/main, main, origin/HEAD, tag: 1.2.3, fork/develop" +--- @param remotes string[] list of remote names, e.g. by calling `require("neogit.lib.git.remote").list()` +--- @return CommitBranchInfo +function M.interprete(ref, remotes) + local parts = vim.split(ref, ", ") + local result = { + branches = {}, + tags = {}, + } + for _, part in ipairs(parts) do + if part:match("^tag: .*") ~= nil then + local tag = part:gsub("tag: ", "") + table.insert(result.tags, tag) + -- No need to annotate tags with remotes, probably too cluttered + goto continue + end + local has_remote = false + for _, remote in ipairs(remotes) do + if part:match("^" .. remote .. "/") then + has_remote = true + local name = part:gsub("^" .. remote .. "/", "") + if name == "HEAD" then + goto continue + end + if result.branches[name] == nil then + result.branches[name] = {} + end + table.insert(result.branches[name], remote) + end + end + if not has_remote and result.branches[part] == nil then + result.branches[part] = {} + end + ::continue:: + end + return result +end + return M diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 73036cf7f..80d02954b 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -2,6 +2,7 @@ local status = require("neogit.status") local plenary_async = require("plenary.async") local git_harness = require("tests.util.git_harness") local util = require("tests.util.util") +local remote = require("neogit.lib.git.remote") local subject = require("neogit.lib.git.log") @@ -321,4 +322,73 @@ describe("lib.git.log.parse", function() assert.are.same(v, expected[k]) end end) + + it("lib.git.log.interprete extracts local branch name", function() + local remotes = remote.list() + assert.are.same({ branches = { main = {} }, tags = {} }, subject.interprete("main", remotes)) + assert.are.same( + { branches = { main = {}, develop = {} }, tags = {} }, + subject.interprete("main, develop", remotes) + ) + end) + + it("lib.git.log.interprete extracts local & remote branch names", function() + local remotes = { "origin" } + assert.are.same( + { branches = { main = { "origin" } }, tags = {} }, + subject.interprete("origin/main", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + subject.interprete("origin/main, origin/develop", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + subject.interprete("origin/main, main, origin/develop", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin" }, foo = {} }, tags = {} }, + subject.interprete("main, origin/main, origin/develop, develop, foo", remotes) + ) + end) + + it("lib.git.log.interprete can deal with multiple remotes", function() + local remotes = { "origin", "fork" } + assert.are.same( + { branches = { main = { "origin", "fork" } }, tags = {} }, + subject.interprete("origin/main, main, fork/main", remotes) + ) + assert.are.same( + { branches = { main = { "origin" }, develop = { "origin", "fork" }, foo = {} }, tags = {} }, + subject.interprete("origin/main, develop, origin/develop, fork/develop, foo", remotes) + ) + end) + + it("lib.git.log.interprete can deal with slashes in branch names", function() + local remotes = { "origin" } + assert.are.same( + { branches = { ["feature/xyz"] = { "origin" }, ["foo/bar/baz"] = {} }, tags = {} }, + subject.interprete("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) + ) + end) + + it("lib.git.log.interprete ignores HEAD references", function() + local remotes = { "origin", "fork" } + assert.are.same( + { branches = { main = { "origin", "fork" }, develop = {} }, tags = {} }, + subject.interprete("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) + ) + end) + + it("lib.git.log.interprete parses tags", function() + local remotes = { "origin" } + assert.are.same({ + branches = {}, + tags = { "0.1.0" }, + }, subject.interprete("tag: 0.1.0", remotes)) + assert.are.same({ + branches = {}, + tags = { "0.5.7", "foo-bar" }, + }, subject.interprete("tag: 0.5.7, tag: foo-bar", remotes)) + end) end) From c59e6a93759a3578cb48f2ccb141b7545ff77357 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Mon, 20 Nov 2023 15:04:46 +0100 Subject: [PATCH 037/443] fix lint because selene doesn't support `goto`s =( --- lua/neogit/lib/git/log.lua | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index d22fdcaeb..3ceed1e55 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -454,30 +454,33 @@ function M.interprete(ref, remotes) tags = {}, } for _, part in ipairs(parts) do + local skip = false if part:match("^tag: .*") ~= nil then local tag = part:gsub("tag: ", "") table.insert(result.tags, tag) -- No need to annotate tags with remotes, probably too cluttered - goto continue + skip = true end local has_remote = false for _, remote in ipairs(remotes) do - if part:match("^" .. remote .. "/") then - has_remote = true - local name = part:gsub("^" .. remote .. "/", "") - if name == "HEAD" then - goto continue + if not skip then + if part:match("^" .. remote .. "/") then + has_remote = true + local name = part:gsub("^" .. remote .. "/", "") + if name == "HEAD" then + skip = true + else + if result.branches[name] == nil then + result.branches[name] = {} + end + table.insert(result.branches[name], remote) + end end - if result.branches[name] == nil then - result.branches[name] = {} - end - table.insert(result.branches[name], remote) end end - if not has_remote and result.branches[part] == nil then + if not skip and not has_remote and result.branches[part] == nil then result.branches[part] = {} end - ::continue:: end return result end From a2ab3ee607d6e7e6a38f8d5b15ca171b14493716 Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 21 Nov 2023 08:21:14 +0100 Subject: [PATCH 038/443] Rename `interprete()` -> `branch_info()` --- lua/neogit/buffers/common.lua | 2 +- lua/neogit/lib/git/log.lua | 2 +- tests/specs/neogit/lib/git/log_spec.lua | 36 ++++++++++++------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 521d5e377..b1d62b035 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -118,7 +118,7 @@ M.CommitEntry = Component.new(function(commit, args) local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - local items = log.interprete(ref_name, remote.list()) + local items = log.branch_info(ref_name, remote.list()) for branch, remotes in pairs(items.branches) do if #remotes == 1 then table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 3ceed1e55..e9a44e477 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -447,7 +447,7 @@ end --- * "origin/main, main, origin/HEAD, tag: 1.2.3, fork/develop" --- @param remotes string[] list of remote names, e.g. by calling `require("neogit.lib.git.remote").list()` --- @return CommitBranchInfo -function M.interprete(ref, remotes) +function M.branch_info(ref, remotes) local parts = vim.split(ref, ", ") local result = { branches = {}, diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 80d02954b..f699cc40c 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -323,72 +323,72 @@ describe("lib.git.log.parse", function() end end) - it("lib.git.log.interprete extracts local branch name", function() + it("lib.git.log.branch_info extracts local branch name", function() local remotes = remote.list() - assert.are.same({ branches = { main = {} }, tags = {} }, subject.interprete("main", remotes)) + assert.are.same({ branches = { main = {} }, tags = {} }, subject.branch_info("main", remotes)) assert.are.same( { branches = { main = {}, develop = {} }, tags = {} }, - subject.interprete("main, develop", remotes) + subject.branch_info("main, develop", remotes) ) end) - it("lib.git.log.interprete extracts local & remote branch names", function() + it("lib.git.log.branch_info extracts local & remote branch names", function() local remotes = { "origin" } assert.are.same( { branches = { main = { "origin" } }, tags = {} }, - subject.interprete("origin/main", remotes) + subject.branch_info("origin/main", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, - subject.interprete("origin/main, origin/develop", remotes) + subject.branch_info("origin/main, origin/develop", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, - subject.interprete("origin/main, main, origin/develop", remotes) + subject.branch_info("origin/main, main, origin/develop", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin" }, foo = {} }, tags = {} }, - subject.interprete("main, origin/main, origin/develop, develop, foo", remotes) + subject.branch_info("main, origin/main, origin/develop, develop, foo", remotes) ) end) - it("lib.git.log.interprete can deal with multiple remotes", function() + it("lib.git.log.branch_info can deal with multiple remotes", function() local remotes = { "origin", "fork" } assert.are.same( { branches = { main = { "origin", "fork" } }, tags = {} }, - subject.interprete("origin/main, main, fork/main", remotes) + subject.branch_info("origin/main, main, fork/main", remotes) ) assert.are.same( { branches = { main = { "origin" }, develop = { "origin", "fork" }, foo = {} }, tags = {} }, - subject.interprete("origin/main, develop, origin/develop, fork/develop, foo", remotes) + subject.branch_info("origin/main, develop, origin/develop, fork/develop, foo", remotes) ) end) - it("lib.git.log.interprete can deal with slashes in branch names", function() + it("lib.git.log.branch_info can deal with slashes in branch names", function() local remotes = { "origin" } assert.are.same( { branches = { ["feature/xyz"] = { "origin" }, ["foo/bar/baz"] = {} }, tags = {} }, - subject.interprete("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) + subject.branch_info("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) ) end) - it("lib.git.log.interprete ignores HEAD references", function() + it("lib.git.log.branch_info ignores HEAD references", function() local remotes = { "origin", "fork" } assert.are.same( { branches = { main = { "origin", "fork" }, develop = {} }, tags = {} }, - subject.interprete("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) + subject.branch_info("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) ) end) - it("lib.git.log.interprete parses tags", function() + it("lib.git.log.branch_info parses tags", function() local remotes = { "origin" } assert.are.same({ branches = {}, tags = { "0.1.0" }, - }, subject.interprete("tag: 0.1.0", remotes)) + }, subject.branch_info("tag: 0.1.0", remotes)) assert.are.same({ branches = {}, tags = { "0.5.7", "foo-bar" }, - }, subject.interprete("tag: 0.5.7, tag: foo-bar", remotes)) + }, subject.branch_info("tag: 0.5.7, tag: foo-bar", remotes)) end) end) From 3447b39c53d3524c432f70e17ae0b80195002d5d Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 21 Nov 2023 08:22:39 +0100 Subject: [PATCH 039/443] Better greppable imports for `neogit.lib.git` --- lua/neogit/buffers/common.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index b1d62b035..7ec546234 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -1,8 +1,7 @@ local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") -local log = require("neogit.lib.git.log") -local remote = require("neogit.lib.git.remote") +local git = require("neogit.lib.git") local text = Ui.text local col = Ui.col @@ -118,7 +117,7 @@ M.CommitEntry = Component.new(function(commit, args) local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - local items = log.branch_info(ref_name, remote.list()) + local items = git.log.branch_info(ref_name, git.remote.list()) for branch, remotes in pairs(items.branches) do if #remotes == 1 then table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) From affc1b32314a5f80ecc7f1b386d333d0f14ef02c Mon Sep 17 00:00:00 2001 From: Thore Goll Date: Tue, 21 Nov 2023 10:09:57 +0100 Subject: [PATCH 040/443] Fix ordering of tracked/untracked branches in log graph Since lua tables are not sorted, the exact order in which the branch names are placed in the log graph can vary and depdends on the output of `git log...`. For example in Git version 2.34 it would: > e2c2a1c * origin/tracked-branch untracked-branch MESSAGE whereas in Git 2.39 it would be reversed: > e2c2a1c * untracked-branch origin/tracked-branch MESSAGE To keep the behaviour consistent between git versions, I introduced the notion of `tracked` and `untracked` branches in the `git.log.branch_info()`. * `untracked`: List of all branches which have no remote counterpart (i.e. are local only) * `tracked`: Mapping of local branch names (keys) which a list of remote names (values) which contain such branches. --- lua/neogit/buffers/common.lua | 10 +++++-- lua/neogit/lib/git/log.lua | 23 ++++++++++----- tests/specs/neogit/lib/git/log_spec.lua | 38 +++++++++++++------------ 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 7ec546234..d5fab3e0e 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -117,8 +117,12 @@ M.CommitEntry = Component.new(function(commit, args) local is_head = string.match(commit.ref_name, "HEAD %->") ~= nil local branch_highlight = is_head and "NeogitBranchHead" or "NeogitBranch" - local items = git.log.branch_info(ref_name, git.remote.list()) - for branch, remotes in pairs(items.branches) do + local info = git.log.branch_info(ref_name, git.remote.list()) + for _, branch in ipairs(info.untracked) do + table.insert(ref, text(branch, { highlight = "NeogitBranch" })) + table.insert(ref, text(" ")) + end + for branch, remotes in pairs(info.tracked) do if #remotes == 1 then table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) end @@ -128,7 +132,7 @@ M.CommitEntry = Component.new(function(commit, args) table.insert(ref, text(branch, { highlight = branch_highlight })) table.insert(ref, text(" ")) end - for _, tag in pairs(items.tags) do + for _, tag in pairs(info.tags) do table.insert(ref, text(tag, { highlight = "NeogitTagName" })) table.insert(ref, text(" ")) end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index e9a44e477..62557d110 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -439,7 +439,8 @@ function M.verify_commit(commit) end ---@class CommitBranchInfo ----@field branches table Mapping from (local) branch names to list of remotes where this branch is present (empty for local branches) +---@field untracked string[] List of local branches on without any remote counterparts +---@field tracked table Mapping from (local) branch names to list of remotes where this branch is present ---@field tags string[] List of tags placed on this commit --- Parse information of branches, tags and remotes from a given commit's ref output @@ -450,9 +451,11 @@ end function M.branch_info(ref, remotes) local parts = vim.split(ref, ", ") local result = { - branches = {}, + untracked = {}, + tracked = {}, tags = {}, } + local untracked = {} for _, part in ipairs(parts) do local skip = false if part:match("^tag: .*") ~= nil then @@ -470,16 +473,22 @@ function M.branch_info(ref, remotes) if name == "HEAD" then skip = true else - if result.branches[name] == nil then - result.branches[name] = {} + if result.tracked[name] == nil then + result.tracked[name] = {} end - table.insert(result.branches[name], remote) + table.insert(result.tracked[name], remote) end end end end - if not skip and not has_remote and result.branches[part] == nil then - result.branches[part] = {} + -- if not skip and not has_remote and result.tracked[part] == nil then + if not skip and not has_remote then + table.insert(untracked, part) + end + end + for _, branch in ipairs(untracked) do + if result.tracked[branch] == nil then + table.insert(result.untracked, branch) end end return result diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index f699cc40c..948fd3c74 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -325,9 +325,9 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info extracts local branch name", function() local remotes = remote.list() - assert.are.same({ branches = { main = {} }, tags = {} }, subject.branch_info("main", remotes)) + assert.are.same({ untracked = { "main" }, tracked = {}, tags = {} }, subject.branch_info("main", remotes)) assert.are.same( - { branches = { main = {}, develop = {} }, tags = {} }, + { untracked = { "main", "develop" }, tracked = {}, tags = {} }, subject.branch_info("main, develop", remotes) ) end) @@ -335,19 +335,19 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info extracts local & remote branch names", function() local remotes = { "origin" } assert.are.same( - { branches = { main = { "origin" } }, tags = {} }, + { tracked = { main = { "origin" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main", remotes) ) assert.are.same( - { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + { tracked = { main = { "origin" }, develop = { "origin" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main, origin/develop", remotes) ) assert.are.same( - { branches = { main = { "origin" }, develop = { "origin" } }, tags = {} }, + { tracked = { main = { "origin" }, develop = { "origin" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main, main, origin/develop", remotes) ) assert.are.same( - { branches = { main = { "origin" }, develop = { "origin" }, foo = {} }, tags = {} }, + { tracked = { main = { "origin" }, develop = { "origin" } }, tags = {}, untracked = { "foo" } }, subject.branch_info("main, origin/main, origin/develop, develop, foo", remotes) ) end) @@ -355,19 +355,20 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info can deal with multiple remotes", function() local remotes = { "origin", "fork" } assert.are.same( - { branches = { main = { "origin", "fork" } }, tags = {} }, + { tracked = { main = { "origin", "fork" } }, tags = {}, untracked = {} }, subject.branch_info("origin/main, main, fork/main", remotes) ) - assert.are.same( - { branches = { main = { "origin" }, develop = { "origin", "fork" }, foo = {} }, tags = {} }, - subject.branch_info("origin/main, develop, origin/develop, fork/develop, foo", remotes) - ) + assert.are.same({ + tracked = { main = { "origin" }, develop = { "origin", "fork" } }, + tags = {}, + untracked = { "foo" }, + }, subject.branch_info("origin/main, develop, origin/develop, fork/develop, foo", remotes)) end) it("lib.git.log.branch_info can deal with slashes in branch names", function() local remotes = { "origin" } assert.are.same( - { branches = { ["feature/xyz"] = { "origin" }, ["foo/bar/baz"] = {} }, tags = {} }, + { tracked = { ["feature/xyz"] = { "origin" } }, untracked = { "foo/bar/baz" }, tags = {} }, subject.branch_info("feature/xyz, foo/bar/baz, origin/feature/xyz", remotes) ) end) @@ -375,19 +376,20 @@ describe("lib.git.log.parse", function() it("lib.git.log.branch_info ignores HEAD references", function() local remotes = { "origin", "fork" } assert.are.same( - { branches = { main = { "origin", "fork" }, develop = {} }, tags = {} }, + { tracked = { main = { "origin", "fork" } }, untracked = { "develop" }, tags = {} }, subject.branch_info("origin/main, fork/main, develop, origin/HEAD, fork/HEAD", remotes) ) end) it("lib.git.log.branch_info parses tags", function() local remotes = { "origin" } + assert.are.same( + { tracked = {}, untracked = {}, tags = { "0.1.0" } }, + subject.branch_info("tag: 0.1.0", remotes) + ) assert.are.same({ - branches = {}, - tags = { "0.1.0" }, - }, subject.branch_info("tag: 0.1.0", remotes)) - assert.are.same({ - branches = {}, + tracked = {}, + untracked = {}, tags = { "0.5.7", "foo-bar" }, }, subject.branch_info("tag: 0.5.7, tag: foo-bar", remotes)) end) From be8d37e20665185531a8f29d2955386b84dc38b8 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Mon, 20 Nov 2023 00:54:50 +0100 Subject: [PATCH 041/443] remove dependencies on cwd --- lua/neogit.lua | 8 +-- lua/neogit/integrations/diffview.lua | 42 ++-------------- lua/neogit/lib/fs.lua | 6 +-- lua/neogit/lib/git/cli.lua | 50 +++++-------------- lua/neogit/lib/git/config.lua | 4 +- lua/neogit/lib/git/index.lua | 8 +-- lua/neogit/lib/git/init.lua | 2 +- lua/neogit/lib/git/merge.lua | 7 +-- lua/neogit/lib/git/rebase.lua | 7 +-- lua/neogit/lib/git/repository.lua | 61 +++++++++++------------ lua/neogit/lib/git/sequencer.lua | 13 ++--- lua/neogit/lib/git/status.lua | 1 - lua/neogit/popups/ignore/actions.lua | 2 +- lua/neogit/popups/init.lua | 2 +- lua/neogit/process.lua | 3 -- lua/neogit/status.lua | 24 ++++----- tests/README.md | 2 +- tests/specs/neogit/lib/git/cli_spec.lua | 4 +- tests/specs/neogit/popups/branch_spec.lua | 1 + tests/specs/neogit/popups/remote_spec.lua | 2 + 20 files changed, 86 insertions(+), 163 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index c40d7d26c..c0ad65ba1 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -69,7 +69,7 @@ function M.open(opts) end if not opts.cwd then - opts.cwd = require("neogit.lib.git.cli").git_root() + opts.cwd = require("neogit.lib.git.cli").git_root_of_cwd() end if not did_setup then @@ -78,7 +78,7 @@ function M.open(opts) return end - if not cli.git_is_repository_sync(opts.cwd) then + if not cli.is_inside_worktree(opts.cwd) then if input.get_confirmation( string.format("Initialize repository in %s?", opts.cwd or vim.fn.getcwd()), @@ -172,10 +172,6 @@ function M.complete(arglead) end, { "kind=", "cwd=", "commit" }) end -function M.get_repo() - return require("neogit.lib.git").repo -end - function M.get_log_file_path() return vim.fn.stdpath("cache") .. "/neogit.log" end diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index aab3da75d..343662da2 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -9,6 +9,7 @@ local dv_lib = require("diffview.lib") local dv_utils = require("diffview.utils") local neogit = require("neogit") +local repo = require("neogit.lib.git.repository") local status = require("neogit.status") local a = require("plenary.async") @@ -35,47 +36,12 @@ local function cb(name) return string.format(":lua require('neogit.integrations.diffview').diffview_mappings['%s']()", name) end ----Resolves a cwd local file to git root relative -local function root_prefix(git_root, cwd, path) - local t = {} - for part in string.gmatch(cwd .. "/" .. path, "[^/\\]+") do - if part == ".." then - if #t > 0 and t[#t] ~= ".." then - table.remove(t, #t) - else - table.insert(t, "..") - end - else - table.insert(t, part) - end - end - - local git_root_parts = {} - for part in git_root:gmatch("[^/\\]+") do - table.insert(git_root_parts, part) - end - - local s = {} - local skipping = true - for i = 1, #t do - if not skipping or git_root_parts[i] ~= t[i] then - table.insert(s, t[i]) - skipping = false - end - end - - path = table.concat(s, "/") - return path -end - local function get_local_diff_view(selected_file_name) local left = Rev(RevType.STAGE) local right = Rev(RevType.LOCAL) - local git_root = neogit.cli.git_root_sync() local function update_files() local files = {} - local repo = neogit.get_repo() local sections = { conflicting = { items = vim.tbl_filter(function(o) @@ -89,9 +55,7 @@ local function get_local_diff_view(selected_file_name) files[kind] = {} for _, item in ipairs(section.items) do local file = { - -- use the repo.cwd instead of current as it may change since the - -- status was refreshed - path = root_prefix(git_root, repo.cwd, item.name), + path = item.name, status = item.mode and item.mode:sub(1, 1), stats = (item.diff and item.diff.stats) and { additions = item.diff.stats.additions or 0, @@ -112,7 +76,7 @@ local function get_local_diff_view(selected_file_name) local files = update_files() local view = CDiffView { - git_root = git_root, + git_root = repo.git_root, left = left, right = right, files = files, diff --git a/lua/neogit/lib/fs.lua b/lua/neogit/lib/fs.lua index 23bc9fc0b..126a8abcc 100644 --- a/lua/neogit/lib/fs.lua +++ b/lua/neogit/lib/fs.lua @@ -3,11 +3,7 @@ local cli = require("neogit.lib.git.cli") local M = {} function M.relpath_from_repository(path) - local result = cli["ls-files"].others.cached.modified.deleted.full_name - .cwd("") - .args(path) - .show_popup(false) - .call() + local result = cli["ls-files"].others.cached.modified.deleted.full_name.args(path).show_popup(false).call() return result.stdout[1] end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index b1ef82884..224c9848f 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -521,8 +521,11 @@ local configurations = { ["verify-commit"] = config {}, } --- TODO: Consider returning a Path object, since consumers of this function tend to need that anyways. -local function git_root() +-- NOTE: Use require("neogit.lib.git.repository").git_root instead of calling this function. +-- repository.git_root is used by all other library functions, so it's most likely the one you want to use. +-- git_root_of_cwd() returns the git repo of the cwd, which can change anytime +-- after git_root_of_cwd() has been called. +local function git_root_of_cwd() local process = process.new({ cmd = { "git", "rev-parse", "--show-toplevel" }, ignore_code = true }):spawn_blocking() @@ -533,15 +536,7 @@ local function git_root() end end -local git_root_sync = function() - return util.trim(vim.fn.system("git rev-parse --show-toplevel")) -end - -local git_dir_path_sync = function() - return util.trim(vim.fn.system("git rev-parse --git-dir")) -end - -local git_is_repository_sync = function(cwd) +local is_inside_worktree = function(cwd) if not cwd then vim.fn.system("git rev-parse --is-inside-work-tree") else @@ -634,13 +629,6 @@ local mt_builder = { end end - if action == "cwd" then - return function(cwd) - tbl[k_state].cwd = cwd - return tbl - end - end - if action == "prefix" then return function(x) tbl[k_state].prefix = x @@ -791,7 +779,6 @@ local function new_builder(subcommand) input = nil, show_popup = true, in_pty = false, - cwd = nil, env = {}, } @@ -825,9 +812,10 @@ local function new_builder(subcommand) logger.trace(string.format("[CLI]: Executing '%s': '%s'", subcommand, table.concat(cmd, " "))) + local repo = require("neogit.lib.git.repository") return process.new { cmd = cmd, - cwd = state.cwd, + cwd = repo.git_root, env = state.env, pty = state.in_pty, verbose = verbose, @@ -962,7 +950,6 @@ local function new_parallel_builder(calls) calls = calls, show_popup = true, in_pty = true, - cwd = nil, } local function call() @@ -970,15 +957,13 @@ local function new_parallel_builder(calls) return end - if not state.cwd then - state.cwd = git_root() - end - if not state.cwd or state.cwd == "" then + local repo = require("neogit.lib.git.repository") + if not repo.git_root then return end for _, c in ipairs(state.calls) do - c.cwd(state.cwd).show_popup(state.show_popup) + c.show_popup(state.show_popup) end local processes = {} @@ -993,13 +978,6 @@ local function new_parallel_builder(calls) call = call, }, { __index = function(tbl, action) - if action == "cwd" then - return function(cwd) - state.cwd = cwd - return tbl - end - end - if action == "show_popup" then return function(show_popup) state.show_popup = show_popup @@ -1031,10 +1009,8 @@ local meta = { local cli = setmetatable({ history = history, insert = handle_new_cmd, - git_root = git_root, - git_root_sync = git_root_sync, - git_dir_path_sync = git_dir_path_sync, - git_is_repository_sync = git_is_repository_sync, + git_root_of_cwd = git_root_of_cwd, + is_inside_worktree = is_inside_worktree, in_parallel = function(...) local calls = { ... } return new_parallel_builder(calls) diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index 91762f27d..f786d4ea4 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -70,10 +70,10 @@ end ---@type table local config_cache = {} local cache_key = nil -local git_root = cli.git_root() local function make_cache_key() - local stat = vim.loop.fs_stat(git_root .. "/.git/config") + local repo = require("neogit.lib.git.repository") + local stat = vim.loop.fs_stat(repo.git_root .. "/.git/config") if stat then return stat.mtime.sec end diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index c708472e2..1ff33d6d9 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -82,7 +82,7 @@ end ---@param opts table ---@return table function M.apply(patch, opts) - opts = opts or { reverse = false, cached = false, index = false, use_git_root = false } + opts = opts or { reverse = false, cached = false, index = false } local cmd = cli.apply @@ -98,12 +98,6 @@ function M.apply(patch, opts) cmd = cmd.index end - if opts.use_git_root then - local repository = require("neogit.lib.git.repository") - - cmd = cmd.cwd(repository.git_root) - end - return cmd.with_patch(patch).call() end diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index a8d1aa0fb..1d5d0232c 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -32,7 +32,7 @@ M.init_repo = function() status.cwd_changed = true vim.cmd.lcd(directory) - if cli.git_is_repository_sync() then + if cli.is_inside_worktree() then if not input.get_confirmation( string.format("Reinitialize existing repository %s?", directory), diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index b614b4f60..47cc1d834 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -31,20 +31,21 @@ function M.abort() end function M.update_merge_status(state) - if state.git_root == "" then + local repo = require("neogit.lib.git.repository") + if repo.git_root == "" then return end state.merge = { head = nil, msg = "", items = {} } - local merge_head = state.git_path("MERGE_HEAD") + local merge_head = repo:git_path("MERGE_HEAD") if not merge_head:exists() then return end state.merge.head = merge_head:read():match("([^\r\n]+)") - local message = state.git_path("MERGE_MSG") + local message = repo:git_path("MERGE_MSG") if message:exists() then state.merge.msg = message:read():match("([^\r\n]+)") -- we need \r? to support windows end diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 9899e334e..457aa814f 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -46,15 +46,16 @@ function M.skip() end function M.update_rebase_status(state) - if state.git_root == "" then + local repo = require("neogit.lib.git.repository") + if repo.git_root == "" then return end state.rebase = { items = {}, head = nil, current = nil } local rebase_file - local rebase_merge = state.git_path("rebase-merge") - local rebase_apply = state.git_path("rebase-apply") + local rebase_merge = repo:git_path("rebase-merge") + local rebase_apply = repo:git_path("rebase-apply") if rebase_merge:exists() then rebase_file = rebase_merge diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index db67066c8..d151f115b 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -1,23 +1,12 @@ local a = require("plenary.async") local logger = require("neogit.logger") +local Path = require("plenary.path") +local cli = require("neogit.lib.git.cli") --- git-status outputs files relative to the cwd. --- --- Save the working directory to allow resolution to absolute paths since the --- cwd may change after the status is refreshed and used, especially if using --- rooter plugins with lsp integration --- stylua: ignore start local function empty_state() - local root = require("neogit.lib.git.cli").git_root() - local Path = require("plenary.path") - return { - git_path = function(...) - return Path.new(root):joinpath(".git", ...) - end, - cwd = vim.fn.getcwd(), - git_root = root, - head = { + git_root = require("neogit.lib.git.cli").git_root_of_cwd(), + head = { branch = nil, commit_message = nil, tag = { @@ -25,27 +14,27 @@ local function empty_state() distance = nil, }, }, - upstream = { - branch = nil, + upstream = { + branch = nil, commit_message = nil, - remote = nil, - ref = nil, - unmerged = { items = {} }, - unpulled = { items = {} }, + remote = nil, + ref = nil, + unmerged = { items = {} }, + unpulled = { items = {} }, }, - pushRemote = { + pushRemote = { commit_message = nil, - unmerged = { items = {} }, - unpulled = { items = {} }, + unmerged = { items = {} }, + unpulled = { items = {} }, }, - untracked = { items = {} }, - unstaged = { items = {} }, - staged = { items = {} }, - stashes = { items = {} }, - recent = { items = {} }, - rebase = { items = {}, head = nil }, - sequencer = { items = {}, head = nil }, - merge = { items = {}, head = nil, msg = nil }, + untracked = { items = {} }, + unstaged = { items = {} }, + staged = { items = {} }, + stashes = { items = {} }, + recent = { items = {} }, + rebase = { items = {}, head = nil }, + sequencer = { items = {}, head = nil }, + merge = { items = {}, head = nil, msg = nil }, } end -- stylua: ignore end @@ -68,6 +57,10 @@ end function M.refresh(self, lib) local refreshes = {} + if lib then + self.state.git_root = cli.git_root_of_cwd() + end + if lib and type(lib) == "table" then if lib.status then self.lib.update_status(self.state) @@ -157,6 +150,10 @@ function M.refresh(self, lib) logger.debug("[REPO]: Refreshes completed") end +function M.git_path(self, ...) + return Path.new(self.state.git_root):joinpath(".git", ...) +end + if not M.initialized then logger.debug("[REPO]: Initializing Repository") M.initialized = true diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 620277f24..6cb1764b2 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -20,23 +20,24 @@ function M.pick_or_revert_in_progress() end function M.update_sequencer_status(state) + local repo = require("neogit.lib.git.repository") state.sequencer = { items = {}, head = nil, head_oid = nil } - local revert_head = state.git_path("REVERT_HEAD") - local cherry_head = state.git_path("CHERRY_PICK_HEAD") + local revert_head = repo:git_path("REVERT_HEAD") + local cherry_head = repo:git_path("CHERRY_PICK_HEAD") if cherry_head:exists() then state.sequencer.head = "CHERRY_PICK_HEAD" - state.sequencer.head_oid = state.git_path("CHERRY_PICK_HEAD"):read() + state.sequencer.head_oid = repo:git_path("CHERRY_PICK_HEAD"):read() state.sequencer.cherry_pick = true elseif revert_head:exists() then state.sequencer.head = "REVERT_HEAD" - state.sequencer.head_oid = state.git_path("REVERT_HEAD"):read() + state.sequencer.head_oid = repo:git_path("REVERT_HEAD"):read() state.sequencer.revert = true end - local todo = state.git_path("sequencer/todo") - local orig = state.git_path("ORIG_HEAD") + local todo = repo:git_path("sequencer/todo") + local orig = repo:git_path("ORIG_HEAD") if todo:exists() then for line in todo:iter() do if not line:match("^#") then diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 87904490c..a4a497dca 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -160,7 +160,6 @@ local function update_status(state) else head.tag = { name = nil, distance = nil } end - state.cwd = cwd state.head = head state.upstream = upstream state.untracked.items = untracked_files diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 3f71e1ea0..7b57e25e3 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -51,7 +51,7 @@ M.shared_subdirectory = operation("ignore_subdirectory", function(popup) end) M.private_local = operation("ignore_private", function(popup) - local ignore_file = git.repo.git_path("info", "exclude") + local ignore_file = git.repo:git_path("info", "exclude") local rules = make_rules(popup, git.repo.git_root) add_rules(ignore_file, rules) diff --git a/lua/neogit/popups/init.lua b/lua/neogit/popups/init.lua index 54c96de2e..1624d718c 100644 --- a/lua/neogit/popups/init.lua +++ b/lua/neogit/popups/init.lua @@ -90,7 +90,7 @@ function M.mappings_table() paths = util.filter_map(require("neogit.status").get_selection().items, function(v) return v.absolute_path end), - git_root = git.repo.state.git_root, + git_root = git.repo.git_root, } end), }, diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index f5c3bf605..05f8ef398 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -271,9 +271,6 @@ function Process:spawn(cb) -- An empty table is treated as an array self.env = self.env or {} self.env.TERM = "xterm-256color" - if self.cwd == "" then - self.cwd = nil - end local start = vim.loop.now() self.start = start diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index aadc0478b..3ed2abe6d 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -537,7 +537,7 @@ local function refresh(which, reason) a.util.scheduler() local s, f, h = save_cursor_location() - if cli.git_root() ~= "" then + if cli.git_root_of_cwd() ~= "" then git.repo:refresh(which) refresh_status_buffer() vim.api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) @@ -632,7 +632,9 @@ local function close(skip_close) M.status_buffer:close() end - M.watcher:stop() + if M.watcher then + M.watcher:stop() + end notification.delete_all() M.status_buffer = nil vim.o.autochdir = M.prev_autochdir @@ -841,7 +843,7 @@ local stage = operation("stage", function() for _, hunk in ipairs(hunks) do -- Apply works for both tracked and untracked local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to) - git.index.apply(patch, { cached = true, use_git_root = true }) + git.index.apply(patch, { cached = true }) end else git.status.stage { item.name } @@ -850,10 +852,7 @@ local stage = operation("stage", function() if #hunks > 0 then for _, hunk in ipairs(hunks) do -- Apply works for both tracked and untracked - git.index.apply( - git.index.generate_patch(item, hunk, hunk.from, hunk.to), - { cached = true, use_git_root = true } - ) + git.index.apply(git.index.generate_patch(item, hunk, hunk.from, hunk.to), { cached = true }) end else table.insert(files, item.name) @@ -901,7 +900,7 @@ local unstage = operation("unstage", function() -- Apply works for both tracked and untracked git.index.apply( git.index.generate_patch(item, hunk, hunk.from, hunk.to, true), - { cached = true, reverse = true, use_git_root = true } + { cached = true, reverse = true } ) end else @@ -967,9 +966,9 @@ local discard = operation("discard", function() if section_name == "staged" then --- Apply both to the worktree and the staging area - git.index.apply(patch, { index = true, reverse = true, use_git_root = true }) + git.index.apply(patch, { index = true, reverse = true }) else - git.index.apply(patch, { reverse = true, use_git_root = true }) + git.index.apply(patch, { reverse = true }) end end) end @@ -978,7 +977,7 @@ local discard = operation("discard", function() table.insert(t, function() if section_name == "untracked" then a.util.scheduler() - vim.fn.delete(cli.git_root() .. "/" .. item.name) + vim.fn.delete(git.repo.git_root .. "/" .. item.name) elseif section_name == "unstaged" then git.index.checkout { item.name } elseif section_name == "staged" then @@ -1238,7 +1237,6 @@ local cmd_func_map = function() end end, ["GoToFile"] = a.void(function() - -- local repo_root = cli.git_root() a.util.scheduler() local section, item = get_current_section_item() if not section then @@ -1448,7 +1446,7 @@ function M.create(kind, cwd) refresh(true, "Buffer.create") end, after = function() - M.watcher = watcher.new(git.repo.git_path():absolute()) + M.watcher = watcher.new(git.repo:git_path():absolute()) end, } end diff --git a/tests/README.md b/tests/README.md index fbdd5aec7..7c3c4be97 100644 --- a/tests/README.md +++ b/tests/README.md @@ -91,7 +91,7 @@ describe("git cli", function() it( "finds the correct git root for a non symlinked directory", in_prepared_repo(function(root_dir) - local detected_root_dir = git_cli.git_root() + local detected_root_dir = git_cli.git_root_of_cwd() eq(detected_root_dir, root_dir) end) ) diff --git a/tests/specs/neogit/lib/git/cli_spec.lua b/tests/specs/neogit/lib/git/cli_spec.lua index f446b8f64..a02164742 100644 --- a/tests/specs/neogit/lib/git/cli_spec.lua +++ b/tests/specs/neogit/lib/git/cli_spec.lua @@ -8,7 +8,7 @@ describe("git cli", function() it( "finds the correct git root for a non symlinked directory", in_prepared_repo(function(root_dir) - local detected_root_dir = git_cli.git_root() + local detected_root_dir = git_cli.git_root_of_cwd() eq(detected_root_dir, root_dir) end) ) @@ -35,7 +35,7 @@ describe("git cli", function() vim.fn.system(cmd) vim.api.nvim_set_current_dir(symlink_dir) - local detected_root_dir = git_cli.git_root() + local detected_root_dir = git_cli.git_root_of_cwd() eq(detected_root_dir, git_dir) end) ) diff --git a/tests/specs/neogit/popups/branch_spec.lua b/tests/specs/neogit/popups/branch_spec.lua index fb81daace..81592d27f 100644 --- a/tests/specs/neogit/popups/branch_spec.lua +++ b/tests/specs/neogit/popups/branch_spec.lua @@ -172,6 +172,7 @@ describe("branch popup", function() input.confirmed = true local remote = harness.prepare_repository() + async.util.block_on(status.reset) util.system("git remote add upstream " .. remote) util.system([[ git stash --include-untracked diff --git a/tests/specs/neogit/popups/remote_spec.lua b/tests/specs/neogit/popups/remote_spec.lua index 5e4a36bc9..9c36972a0 100644 --- a/tests/specs/neogit/popups/remote_spec.lua +++ b/tests/specs/neogit/popups/remote_spec.lua @@ -1,4 +1,5 @@ require("plenary.async").tests.add_to_env() +local async = require("plenary.async") local eq = assert.are.same local operations = require("neogit.operations") local harness = require("tests.util.git_harness") @@ -19,6 +20,7 @@ describe("remote popup", function() in_prepared_repo(function() local remote_a = harness.prepare_repository() local remote_b = harness.prepare_repository() + async.util.block_on(status.reset) input.values = { "foo", remote_a } act("Ma") From aef9da7b47930504dd302cacb81b858a84d6e035 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 23:04:04 +0100 Subject: [PATCH 042/443] Cleanup and notes --- lua/neogit/popups/log/init.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index a5c98dd37..9e4ca548b 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -31,14 +31,16 @@ function M.create() separator = "", fn = actions.limit_to_files, setup = function(popup) - local value = require("neogit.lib.state").get { "NeogitLogPopup", "" } - value = vim.split(value, " ", { trimempty = true }) - value = require("neogit.lib.util").map(value, function(v) - local result, _ = v:gsub([["]], "") - return result - end) - - popup.state.env.files = value + local state = require("neogit.lib.state").get { "NeogitLogPopup", "" } + if state then + -- State for this option is saved with quotes around each filepath, which cannot + -- get passed into the CLI lib. So, to handle things internally, we need to strip + -- them out when loading the popup. + popup.state.env.files = vim.tbl_map(function(value) + local result, _ = value:gsub([["]], "") + return result + end, vim.split(state, " ", { trimempty = true })) + end end, }) :switch("f", "follow", "Follow renames when showing single-file log") From e30bccfd537da9ac3ae22ef80d72a75501aebbb6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 22 Nov 2023 23:35:14 +0100 Subject: [PATCH 043/443] Standardize prompt prefixes --- lua/neogit/lib/finder.lua | 8 ++++++-- lua/neogit/lib/git/branch.lua | 2 +- lua/neogit/popups/branch/actions.lua | 6 +++--- lua/neogit/popups/branch_config/actions.lua | 2 +- lua/neogit/popups/fetch/actions.lua | 6 +++--- lua/neogit/popups/ignore/actions.lua | 2 +- lua/neogit/popups/pull/actions.lua | 4 ++-- lua/neogit/popups/push/actions.lua | 10 +++++----- lua/neogit/popups/tag/actions.lua | 2 +- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index b197d2e30..d2be9d037 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -171,6 +171,10 @@ Finder.__index = Finder ---@param opts FinderOpts ---@return Finder function Finder:new(opts) + if opts.prompt_prefix then + opts.prompt_prefix = string.format(" %s > ", opts.prompt_prefix) + end + local this = { opts = vim.tbl_deep_extend("keep", opts, default_opts()), entries = {}, @@ -210,7 +214,7 @@ function Finder:find(on_select) elseif config.check_integration("fzf_lua") then local fzf_lua = require("fzf-lua") fzf_lua.fzf_exec(self.entries, { - prompt = self.opts.prompt_prefix, + prompt = string.format(" %s > ", self.opts.prompt_prefix), fzf_opts = fzf_opts(self.opts), winopts = { height = self.opts.layout_config.height, @@ -219,7 +223,7 @@ function Finder:find(on_select) }) else vim.ui.select(self.entries, { - prompt = self.opts.prompt_prefix, + prompt = string.format(" %s > ", self.opts.prompt_prefix), format_item = function(entry) return entry end, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 44889fbd9..27608f367 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -158,7 +158,7 @@ function M.set_pushRemote() elseif pushDefault:is_set() then pushRemote = pushDefault:read() else - pushRemote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "set pushRemote > " } + pushRemote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "set pushRemote" } end if pushRemote then diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 463dedd23..b8e5e20a8 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -82,7 +82,7 @@ M.checkout_local_branch = operation("checkout_local_branch", function(popup) end) local target = FuzzyFinderBuffer.new(util.merge(local_branches, remote_branches)):open_async { - prompt_prefix = " branch > ", + prompt_prefix = "branch", } if target then @@ -116,7 +116,7 @@ M.checkout_create_branch = operation("checkout_create_branch", function() end name, _ = name:gsub("%s", "-") - local base_branch = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = " base branch > " } + local base_branch = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "base branch" } if not base_branch then return end @@ -180,7 +180,7 @@ M.reset_branch = operation("reset_branch", function() local current = git.branch.current() local branches = git.branch.get_all_branches(false) local to = FuzzyFinderBuffer.new(branches):open_async { - prompt_prefix = string.format(" reset %s to > ", current), + prompt_prefix = string.format("reset %s to", current), } if not to then diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index da3fba957..2799abce1 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -46,7 +46,7 @@ function M.merge_config(branch) local branches = util.merge(local_branches, remote_branches) return a.void(function(popup, c) - local target = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "Upstream > " } + local target = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "upstream" } if not target then return end diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index bd812fac3..ffc469860 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -9,7 +9,7 @@ local util = require("neogit.lib.util") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local function select_remote() - return FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote > " } + return FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote" } end local function fetch_from(name, remote, branch, args) @@ -86,7 +86,7 @@ function M.fetch_another_branch(popup) end) local branch = FuzzyFinderBuffer.new(branches):open_async { - prompt_prefix = remote .. "/{branch} > ", + prompt_prefix = remote .. "/{branch}", } if not branch then return @@ -107,7 +107,7 @@ function M.fetch_refspec(popup) end) notification.delete_all() - local refspec = FuzzyFinderBuffer.new(refspecs):open_async { prompt_prefix = "refspec > " } + local refspec = FuzzyFinderBuffer.new(refspecs):open_async { prompt_prefix = "refspec" } if not refspec then return end diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 7b57e25e3..cc2168198 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -22,7 +22,7 @@ end ---@param rules string[] local function add_rules(path, rules) local selected = FuzzyFinderBuffer.new(rules) - :open_async { allow_multi = true, prompt_prefix = " File or pattern to ignore > " } + :open_async { allow_multi = true, prompt_prefix = "File or pattern to ignore" } if not selected or #selected == 0 then return diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index 8f2dbff0f..1122795a9 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -49,7 +49,7 @@ function M.from_upstream(popup) if not upstream then set_upstream = true upstream = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async { - prompt_prefix = "set upstream > ", + prompt_prefix = "set upstream", } if not upstream then @@ -63,7 +63,7 @@ end function M.from_elsewhere(popup) local target = FuzzyFinderBuffer.new(git.branch.get_all_branches(false)) - :open_async { prompt_prefix = "pull > " } + :open_async { prompt_prefix = "pull" } if not target then return end diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index d61b3cbc2..3fa07044d 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -57,7 +57,7 @@ function M.to_upstream(popup) set_upstream = true branch = git.branch.current() remote = git.branch.upstream_remote() - or FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote > " } + or FuzzyFinderBuffer.new(git.remote.list()):open_async { prompt_prefix = "remote" } end if remote then @@ -69,7 +69,7 @@ end function M.to_elsewhere(popup) local target = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async { - prompt_prefix = "push > ", + prompt_prefix = "push", } if target then @@ -87,7 +87,7 @@ function M.push_other(popup) table.insert(sources, 1, popup.state.env.commit) end - local source = FuzzyFinderBuffer.new(sources):open_async { prompt_prefix = "push > " } + local source = FuzzyFinderBuffer.new(sources):open_async { prompt_prefix = "push" } if not source then return end @@ -98,7 +98,7 @@ function M.push_other(popup) end local destination = FuzzyFinderBuffer.new(destinations) - :open_async { prompt_prefix = "push " .. source .. " to > " } + :open_async { prompt_prefix = "push " .. source .. " to" } if not destination then return end @@ -114,7 +114,7 @@ function M.push_tags(popup) if #remotes == 1 then remote = remotes[1] else - remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "push tags to > " } + remote = FuzzyFinderBuffer.new(remotes):open_async { prompt_prefix = "push tags to" } end if remote then diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index 710b12b27..183994841 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -62,7 +62,7 @@ end ---@param _ table function M.prune(_) local selected_remote = FuzzyFinderBuffer.new(git.remote.list()):open_async { - prompt_prefix = " Prune tags using remote > ", + prompt_prefix = "Prune tags using remote", } if (selected_remote or "") == "" then From a6f99da5d40b68a324c4e0a9dd997e39c7089dff Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 24 Nov 2023 01:03:26 +0100 Subject: [PATCH 044/443] Fix bug: non-zero processes cause crashes Apparently running vim.notify with error level inside on_exit causes the the vim job to error. --- lua/neogit/process.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 05f8ef398..a4d682605 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -344,7 +344,7 @@ function Process:spawn(cb) table.concat(output, "\n") ) - notification.error(message) + notification.warn(message) end -- vim.schedule(Process.show_console) end From 2889e83487b128ef5bf1cc547d43d74e825a233f Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 23 Nov 2023 09:30:55 +0100 Subject: [PATCH 045/443] pass filenames through fnameescape before trying to edit them --- lua/neogit/status.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 3ed2abe6d..4fac5342a 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1029,30 +1029,25 @@ end ---@see section_has_hunks local function handle_section_item(item) local path = item.absolute_path - if not path then notification.error("Cannot open file. No path found.") return end local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) - local hunk = M.get_item_hunks(item, cursor_row, cursor_row, false)[1] notification.delete_all() M.status_buffer:close() - local relpath = vim.fn.fnamemodify(path, ":.") - if not vim.o.hidden and vim.bo.buftype == "" and not vim.bo.readonly and vim.fn.bufname() ~= "" then vim.cmd("update") end - vim.cmd("e " .. relpath) + vim.cmd(string.format("edit %s", vim.fn.fnameescape(vim.fn.fnamemodify(path, ":~:.")))) if hunk then local line_offset = cursor_row - hunk.first - local row = hunk.disk_from + line_offset - 1 for i = 1, line_offset do if string.sub(hunk.lines[i], 1, 1) == "-" then From e6158f4ca91f3a437a97d8b3e402d85c5e7f242e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 23 Nov 2023 09:47:27 +0100 Subject: [PATCH 046/443] Restructure --- lua/neogit/status.lua | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 4fac5342a..6f97a6d44 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1028,14 +1028,25 @@ end ---@param item File ---@see section_has_hunks local function handle_section_item(item) - local path = item.absolute_path - if not path then + if not item.absolute_path then notification.error("Cannot open file. No path found.") return end + local row, col local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) local hunk = M.get_item_hunks(item, cursor_row, cursor_row, false)[1] + if hunk then + local line_offset = cursor_row - hunk.first + row = hunk.disk_from + line_offset - 1 + for i = 1, line_offset do + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + -- adjust for diff sign column + col = math.floor(0, cursor_col - 1) + end notification.delete_all() M.status_buffer:close() @@ -1044,18 +1055,10 @@ local function handle_section_item(item) vim.cmd("update") end - vim.cmd(string.format("edit %s", vim.fn.fnameescape(vim.fn.fnamemodify(path, ":~:.")))) + local path = vim.fn.fnameescape(vim.fn.fnamemodify(item.absolute_path, ":~:.")) + vim.cmd(string.format("edit %s", path)) - if hunk then - local line_offset = cursor_row - hunk.first - local row = hunk.disk_from + line_offset - 1 - for i = 1, line_offset do - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - -- adjust for diff sign column - local col = cursor_col == 0 and 0 or cursor_col - 1 + if row and col then vim.api.nvim_win_set_cursor(0, { row, col }) end end From 19f01bdb1e2151f6d19b44093260d24aad3fe09a Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 23 Nov 2023 09:50:45 +0100 Subject: [PATCH 047/443] Max, not floor --- lua/neogit/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 6f97a6d44..57a4b5572 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1045,7 +1045,7 @@ local function handle_section_item(item) end end -- adjust for diff sign column - col = math.floor(0, cursor_col - 1) + col = math.max(0, cursor_col - 1) end notification.delete_all() From 0bd106ddc1638ee21c670def4a0a014d2eda5ed9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 26 Nov 2023 13:50:56 +0100 Subject: [PATCH 048/443] Add pull popup mapping to log/reflog/commit views --- lua/neogit/buffers/commit_view/init.lua | 1 + lua/neogit/buffers/log_view/init.lua | 2 ++ lua/neogit/buffers/reflog_view/init.lua | 2 ++ 3 files changed, 5 insertions(+) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 671091725..cb5089736 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -169,6 +169,7 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.commit_info.oid } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["q"] = function() self:close() end, diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 04b79b20a..c7641e80a 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -66,6 +66,7 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["d"] = function() if not config.check_integration("diffview") then notification.error("Diffview integration must be enabled for log diff") @@ -101,6 +102,7 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["q"] = function() self:close() end, diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index c52125dd3..41936b91f 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -61,6 +61,7 @@ function M:open(_) [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), }, n = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) @@ -87,6 +88,7 @@ function M:open(_) [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), ["q"] = function() self:close() end, From 161b48fdcb73992541f5e05d8573eed9aa6fa94c Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 26 Nov 2023 22:07:38 +0100 Subject: [PATCH 049/443] Add basic refs view buffer --- lua/neogit/buffers/refs_view/init.lua | 76 +++++++++++++++++++++++++++ lua/neogit/buffers/refs_view/ui.lua | 76 +++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 lua/neogit/buffers/refs_view/init.lua create mode 100644 lua/neogit/buffers/refs_view/ui.lua diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua new file mode 100644 index 000000000..c56e689c5 --- /dev/null +++ b/lua/neogit/buffers/refs_view/init.lua @@ -0,0 +1,76 @@ +local Buffer = require("neogit.lib.buffer") +local ui = require("neogit.buffers.refs_view.ui") +local config = require("neogit.config") +-- local popups = require("neogit.popups") + +--- @class RefsViewBuffer +--- @field is_open boolean whether the buffer is currently shown +--- @field buffer Buffer +--- @field open fun() +--- @field close fun() +--- @see RefsInfo +--- @see Buffer +--- @see Ui +local M = { + instance = nil, +} + +--- Creates a new RefsViewBuffer +--- @return RefsViewBuffer +function M.new(refs) + local instance = { + refs = refs, + is_open = false, + buffer = nil, + } + + setmetatable(instance, { __index = M }) + return instance +end + +--- Closes the RefsViewBuffer +function M:close() + self.is_open = false + self.buffer:close() + self.buffer = nil +end + +--- Opens the RefsViewBuffer +--- If already open will close the buffer +function M:open() + if M.instance and M.instance.is_open then + M.instance:close() + end + + M.instance = self + + if self.is_open then + return + end + + self.hovered_component = nil + self.is_open = true + + self.buffer = Buffer.create { + name = "NeogitRefsView", + filetype = "NeogitRefsView", + kind = "auto", + context_highlight = false, + autocmds = { + ["BufUnload"] = function() + M.instance.is_open = false + end, + }, + mappings = { + n = {}, + }, + render = function() + return ui.RefsView(self.refs) + end, + after = function() + vim.cmd([[setlocal nowrap nospell]]) + end, + } +end + +return M diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua new file mode 100644 index 000000000..5e0833684 --- /dev/null +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -0,0 +1,76 @@ +local M = {} + +local Ui = require("neogit.lib.ui") +local util = require("neogit.lib.util") +local git = require("neogit.lib.git") + +local text = Ui.text +local col = Ui.col +local row = Ui.row + +local highlights = { + local_branch = "NeogitBranch", + remote_branch = "NeogitRemote", + tag = "NeogitTagName", +} + +local function section(refs, heading) + local rows = {} + for _, ref in ipairs(refs) do + table.insert( + rows, + row({ + text.highlight(highlights[ref.type])( + util.str_truncate(ref.name, 34), + { align_right = 35 } + ), + text(ref.subject), + }, { oid = ref.oid }) + ) + end + + table.insert(rows, row({ text("") })) + + return col({ + row(util.merge(heading, { + text.highlight("NeogitGraphWhite")(string.format(" (%d)", #refs)), + })), + col.padding_left(2)(rows), + }, { foldable = true }) +end + +function M.Branches(branches) + return section(branches, { text.highlight("NeogitBranch")("Branches") }) +end + +function M.Remotes(remotes) + local out = {} + local max_len = util.max_length(vim.tbl_keys(remotes)) + + for name, branches in pairs(remotes) do + table.insert( + out, + section(branches, { + text.highlight("NeogitBranch")("Remote "), + text.highlight("NeogitRemote")(name, { align_right = max_len }), + text.highlight("NeogitBranch")( + string.format(" (%s)", git.config.get(string.format("remote.%s.url", name)):read()) + ), + }) + ) + end + + return out +end + +function M.Tags(tags) + return section(tags, { text.highlight("NeogitBranch")("Tags") }) +end + +function M.RefsView(refs) + refs = refs or git.refs.list_parsed() + + return util.merge({ M.Branches(refs.local_branch) }, M.Remotes(refs.remote_branch), { M.Tags(refs.tag) }) +end + +return M From d21946ecfd53c2c5092406ec7b06859742e5f1bf Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 26 Nov 2023 22:07:55 +0100 Subject: [PATCH 050/443] Add git.refs.list_parsed function --- lua/neogit/lib/git/refs.lua | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 74728585a..b171d7d0d 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -12,4 +12,61 @@ function M.list() return revisions end +function M.list_parsed() + local refs = cli["for-each-ref"].format( + [[{"oid":"%(objectname)","ref":"%(refname)","name":"%(refname:short)","subject":"%(subject)"},]] + ).call_sync():trim().stdout + + -- Wrap list of refs in an Array + refs = "[" .. table.concat(refs, "\\n") .. "]" + + -- Remove trailing comma from last object in array + refs, _ = refs:gsub(",]", "]") + + -- Remove escaped newlines from in-between objects + refs, _ = refs:gsub("},\\n{", "},{") + + -- Escape any double-quote characters, or escape codes, in the subject + refs, _ = refs:gsub( + [[(,"subject":")(.-)("})]], + function(before, subject, after) + return table.concat({ before, vim.fn.escape(subject, [[\"]]), after }, "") + end + ) + + local ok, result = pcall(vim.json.decode, refs, { luanil = { object = true, array = true } }) + if not ok then + assert(ok, "Failed to parse log json!: " .. result) + end + + local output = { + local_branch = {}, + remote_branch = {}, + tag = {} + } + + for _, ref in ipairs(result) do + if ref.ref:match("^refs/heads/") then + ref.type = "local_branch" + table.insert(output.local_branch, ref) + + elseif ref.ref:match("^refs/remotes/") then + local remote, branch = ref.ref:match("^refs/remotes/([^/]*)/(.*)$") + if not output.remote_branch[remote] then + output.remote_branch[remote] = {} + end + + ref.type = "remote_branch" + ref.name = branch + table.insert(output.remote_branch[remote], ref) + + elseif ref.ref:match("^refs/tags/") then + ref.type = "tag" + table.insert(output.tag, ref) + end + end + + return output +end + return M From e7d6461719fc8166f0f3b62e01f3664499c6b25b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 26 Nov 2023 22:08:10 +0100 Subject: [PATCH 051/443] Add util function for getting longest string in table. --- lua/neogit/lib/util.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index ddcffad03..9dc6c151c 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -143,6 +143,19 @@ function M.filter(tbl, f) return vim.tbl_filter(f, tbl) end +---Finds length of longest string in table +---@param tbl table +---@return integer +function M.max_length(tbl) + local max = 0 + for _, v in ipairs(tbl) do + if #v > max then + max = #v + end + end + return max +end + -- function M.print_tbl(tbl) -- for _, x in pairs(tbl) do -- print("| " .. x) From 3acad22da235592ddfc063eddc57730b88e04f70 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 26 Nov 2023 22:08:24 +0100 Subject: [PATCH 052/443] Workaround for UFO. It messes up the folds. --- lua/neogit/lib/buffer.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 42bd51f95..3eafd3043 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -267,6 +267,12 @@ function Buffer:show() vim.cmd("setlocal nornu") end + -- Workaround UFO getting folds wrong. + local ufo, _ = pcall(require, "ufo") + if ufo then + ufo.detach() + end + return win end From b575b48261976c4f3549df0bfdcdaee6195aea54 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 27 Nov 2023 16:27:09 +0100 Subject: [PATCH 053/443] Add mappings to refs view --- lua/neogit/buffers/refs_view/init.lua | 121 +++++++++++++++++++++++++- lua/neogit/buffers/refs_view/ui.lua | 15 ++-- 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index c56e689c5..4dab0be47 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -1,7 +1,9 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.refs_view.ui") +local popups = require("neogit.popups") +local CommitViewBuffer = require("neogit.buffers.commit_view") local config = require("neogit.config") --- local popups = require("neogit.popups") +local notification = require("neogit.lib.notification") --- @class RefsViewBuffer --- @field is_open boolean whether the buffer is currently shown @@ -54,7 +56,7 @@ function M:open() self.buffer = Buffer.create { name = "NeogitRefsView", filetype = "NeogitRefsView", - kind = "auto", + kind = "tab", context_highlight = false, autocmds = { ["BufUnload"] = function() @@ -62,7 +64,120 @@ function M:open() end, }, mappings = { - n = {}, + v = { + [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("PushPopup")] = popups.open("push", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), + ["d"] = function() + if not config.check_integration("diffview") then + notification.error("Diffview integration must be enabled for log diff") + return + end + + local dv = require("neogit.integrations.diffview") + dv.open("log", self.buffer.ui:get_commits_in_selection()) + end, + }, + n = { + [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("PushPopup")] = popups.open("push", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) + p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), + ["q"] = function() + self:close() + end, + [""] = function() + self:close() + end, + [""] = function() + CommitViewBuffer.new(self.buffer.ui:get_commits_in_selection()[1]):open() + end, + ["{"] = function() + pcall(vim.cmd, "normal! zc") + + vim.cmd("normal! k") + for _ = vim.fn.line("."), 0, -1 do + if vim.fn.foldlevel(".") > 0 then + break + end + + vim.cmd("normal! k") + end + + pcall(vim.cmd, "normal! zo") + vim.cmd("normal! zz") + end, + ["}"] = function() + pcall(vim.cmd, "normal! zc") + + vim.cmd("normal! j") + for _ = vim.fn.line("."), vim.fn.line("$"), 1 do + if vim.fn.foldlevel(".") > 0 then + break + end + + vim.cmd("normal! j") + end + + pcall(vim.cmd, "normal! zo") + vim.cmd("normal! zz") + end, + [""] = function() + pcall(vim.cmd, "normal! za") + end, + ["d"] = function() + if not config.check_integration("diffview") then + notification.error("Diffview integration must be enabled for log diff") + return + end + + local dv = require("neogit.integrations.diffview") + dv.open("log", self.buffer.ui:get_commits_in_selection()[1]) + end, + }, }, render = function() return ui.RefsView(self.refs) diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 5e0833684..9ef8bce9b 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -19,17 +19,16 @@ local function section(refs, heading) for _, ref in ipairs(refs) do table.insert( rows, - row({ - text.highlight(highlights[ref.type])( - util.str_truncate(ref.name, 34), - { align_right = 35 } - ), - text(ref.subject), + col({ + row { + text.highlight(highlights[ref.type])(util.str_truncate(ref.name, 34), { align_right = 35 }), + text(ref.subject), + }, }, { oid = ref.oid }) ) end - table.insert(rows, row({ text("") })) + table.insert(rows, row { text("") }) return col({ row(util.merge(heading, { @@ -68,8 +67,6 @@ function M.Tags(tags) end function M.RefsView(refs) - refs = refs or git.refs.list_parsed() - return util.merge({ M.Branches(refs.local_branch) }, M.Remotes(refs.remote_branch), { M.Tags(refs.tag) }) end From 597aafd010fca6b09d486c7b1877bf08d55fbb8f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 27 Nov 2023 16:27:15 +0100 Subject: [PATCH 054/443] Add status mapping for RefsShow --- lua/neogit/config.lua | 3 ++- lua/neogit/status.lua | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 7aca55fc5..c0cff0a1f 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -86,7 +86,7 @@ end ---@alias NeogitConfigMappingsFinder "Select" | "Close" | "Next" | "Previous" | "MultiselectToggleNext" | "MultiselectTogglePrevious" | "NOP" | false ----@alias NeogitConfigMappingsStatus "Close" | "Depth1" | "Depth2" | "Depth3" | "Depth4" | "Toggle" | "Discard" | "Stage" | "StageUnstaged" | "StageAll" | "Unstage" | "UnstageStaged" | "DiffAtFile" | "RefreshBuffer" | "GoToFile" | "VSplitOpen" | "SplitOpen" | "TabOpen" | "GoToPreviousHunkHeader" | "GoToNextHunkHeader" | "Console" | "CommandHistory" | "InitRepo" | false | fun() +---@alias NeogitConfigMappingsStatus "Close" | "Depth1" | "Depth2" | "Depth3" | "Depth4" | "Toggle" | "Discard" | "Stage" | "StageUnstaged" | "StageAll" | "Unstage" | "UnstageStaged" | "DiffAtFile" | "RefreshBuffer" | "GoToFile" | "VSplitOpen" | "SplitOpen" | "TabOpen" | "GoToPreviousHunkHeader" | "GoToNextHunkHeader" | "Console" | "CommandHistory" | "ShowRefs" | "InitRepo" | false | fun() ---@alias NeogitConfigMappingsPopup "HelpPopup" | "DiffPopup" | "PullPopup" | "RebasePopup" | "MergePopup" | "PushPopup" | "CommitPopup" | "LogPopup" | "RevertPopup" | "StashPopup" | "IgnorePopup" | "CherryPickPopup" | "BranchPopup" | "FetchPopup" | "ResetPopup" | "RemotePopup" | "TagPopup" | false @@ -311,6 +311,7 @@ function M.get_default_values() ["u"] = "Unstage", ["U"] = "UnstageStaged", ["d"] = "DiffAtFile", + ["y"] = "ShowRefs", ["$"] = "CommandHistory", ["#"] = "Console", [""] = "RefreshBuffer", diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 57a4b5572..69ef5b61d 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1,5 +1,6 @@ local Buffer = require("neogit.lib.buffer") local GitCommandHistory = require("neogit.buffers.git_command_history") +local RefsViewBuffer = require("neogit.buffers.refs_view") local CommitView = require("neogit.buffers.commit_view") local git = require("neogit.lib.git") local cli = require("neogit.lib.git.cli") @@ -1152,6 +1153,9 @@ local cmd_func_map = function() ["CommandHistory"] = function() GitCommandHistory:new():show() end, + ["ShowRefs"] = function() + RefsViewBuffer.new(git.refs.list_parsed()):open() + end, ["Console"] = function() local process = require("neogit.process") process.show_console() From 7ce99b0c32225245f243dc4d6ce7cc1a342d5d67 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 27 Nov 2023 22:18:36 +0100 Subject: [PATCH 055/443] Asyncify buffer loading --- lua/neogit/buffers/refs_view/init.lua | 3 +- lua/neogit/buffers/refs_view/ui.lua | 79 ++++++++++++++++++++------- lua/neogit/lib/git.lua | 1 + lua/neogit/lib/git/cherry.lua | 13 +++++ lua/neogit/lib/git/refs.lua | 35 ++++++++---- lua/neogit/lib/util.lua | 20 +++---- lua/neogit/status.lua | 4 +- 7 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 lua/neogit/lib/git/cherry.lua diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 4dab0be47..e5feab2c4 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -22,6 +22,7 @@ local M = { function M.new(refs) local instance = { refs = refs, + head = "HEAD", is_open = false, buffer = nil, } @@ -180,7 +181,7 @@ function M:open() }, }, render = function() - return ui.RefsView(self.refs) + return ui.RefsView(self.refs, self.head) end, after = function() vim.cmd([[setlocal nowrap nospell]]) diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 9ef8bce9b..c514c2931 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -12,37 +12,74 @@ local highlights = { local_branch = "NeogitBranch", remote_branch = "NeogitRemote", tag = "NeogitTagName", + ["+"] = "NeogitGraphCyan", + ["-"] = "NeogitGraphPurple", + ["<>"] = "NeogitGraphYellow", + ["="] = "NeogitGraphGreen", + ["<"] = "NeogitGraphPurple", + [">"] = "NeogitGraphCyan", + [""] = "NeogitGraphRed", } -local function section(refs, heading) +local function Cherries(ref, head) + local cherries = util.map(git.cherry.list(head, ref.oid), function(cherry) + return row({ + text.highlight(highlights[cherry.status])(cherry.status), + text(" "), + text.highlight("Comment")(cherry.oid:sub(1, 7)), + text(" "), + text.highlight("NeogitGraphWhite")(cherry.subject), + }, { oid = cherry.oid }) + end) + + if cherries[1] then + table.insert(cherries, row { text("") }) + end + + return col.padding_left(2)(cherries) +end + +local function Ref(ref) + return row { + text.highlight("NeogitGraphBoldPurple")(ref.head and "@ " or " "), + text.highlight(highlights[ref.type])(util.str_truncate(ref.name, 34), { align_right = 35 }), + text.highlight(highlights[ref.upstream_status])(ref.upstream_name), + text(ref.upstream_name ~= "" and " " or ""), + text(ref.subject), + } +end + +local function section(refs, heading, head) local rows = {} for _, ref in ipairs(refs) do table.insert( rows, - col({ - row { - text.highlight(highlights[ref.type])(util.str_truncate(ref.name, 34), { align_right = 35 }), - text(ref.subject), - }, - }, { oid = ref.oid }) + col({ Ref(ref) }, { + oid = ref.oid, + foldable = true, + callback = a.void(function(c) + vim.cmd("echomsg 'Getting cherries for " .. ref.oid .. "'") + local cherries = Cherries(ref, head) + vim.cmd("echomsg ''") + P { cherries } + end), + }) ) end table.insert(rows, row { text("") }) return col({ - row(util.merge(heading, { - text.highlight("NeogitGraphWhite")(string.format(" (%d)", #refs)), - })), - col.padding_left(2)(rows), + row(util.merge(heading, { text.highlight("NeogitGraphWhite")(string.format(" (%d)", #refs)) })), + col(rows), }, { foldable = true }) end -function M.Branches(branches) - return section(branches, { text.highlight("NeogitBranch")("Branches") }) +function M.Branches(branches, head) + return { section(branches, { text.highlight("NeogitBranch")("Branches") }, head) } end -function M.Remotes(remotes) +function M.Remotes(remotes, head) local out = {} local max_len = util.max_length(vim.tbl_keys(remotes)) @@ -55,19 +92,23 @@ function M.Remotes(remotes) text.highlight("NeogitBranch")( string.format(" (%s)", git.config.get(string.format("remote.%s.url", name)):read()) ), - }) + }, head) ) end return out end -function M.Tags(tags) - return section(tags, { text.highlight("NeogitBranch")("Tags") }) +function M.Tags(tags, head) + return { section(tags, { text.highlight("NeogitBranch")("Tags") }, head) } end -function M.RefsView(refs) - return util.merge({ M.Branches(refs.local_branch) }, M.Remotes(refs.remote_branch), { M.Tags(refs.tag) }) +function M.RefsView(refs, head) + return util.merge( + M.Branches(refs.local_branch, head), + M.Remotes(refs.remote_branch, head), + M.Tags(refs.tag, head) + ) end return M diff --git a/lua/neogit/lib/git.lua b/lua/neogit/lib/git.lua index 83dd44ab6..4ae6ad18d 100644 --- a/lua/neogit/lib/git.lua +++ b/lua/neogit/lib/git.lua @@ -2,6 +2,7 @@ return { repo = require("neogit.lib.git.repository"), rev_parse = require("neogit.lib.git.rev_parse"), cli = require("neogit.lib.git.cli"), + cherry = require("neogit.lib.git.cherry"), init = require("neogit.lib.git.init"), status = require("neogit.lib.git.status"), stash = require("neogit.lib.git.stash"), diff --git a/lua/neogit/lib/git/cherry.lua b/lua/neogit/lib/git/cherry.lua new file mode 100644 index 000000000..15e70fda2 --- /dev/null +++ b/lua/neogit/lib/git/cherry.lua @@ -0,0 +1,13 @@ +local M = {} +local cli = require("neogit.lib.git.cli") +local util = require("neogit.lib.util") + +function M.list(upstream, head) + local result = cli.cherry.verbose.args(upstream, head).call():trim().stdout + return util.reverse(util.map(result, function(cherry) + local status, oid, subject = cherry:match("([%+%-]) (%x+) (.*)") + return { status = status, oid = oid, subject = subject } + end)) +end + +return M diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index b171d7d0d..4d896d6a4 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -12,10 +12,24 @@ function M.list() return revisions end +local function json_format() + local template = { + [["head":"%(HEAD)"]], + [["oid":"%(objectname)"]], + [["ref":"%(refname)"]], + [["name":"%(refname:short)"]], + [["upstream_status":"%(upstream:trackshort)"]], + [["upstream_name":"%(upstream:short)"]], + [["subject":"%(subject)"]], + } + + return string.format("{%s},", table.concat(template, ",")) +end + +local json = json_format() + function M.list_parsed() - local refs = cli["for-each-ref"].format( - [[{"oid":"%(objectname)","ref":"%(refname)","name":"%(refname:short)","subject":"%(subject)"},]] - ).call_sync():trim().stdout + local refs = cli["for-each-ref"].format(json).call_sync():trim().stdout -- Wrap list of refs in an Array refs = "[" .. table.concat(refs, "\\n") .. "]" @@ -27,12 +41,9 @@ function M.list_parsed() refs, _ = refs:gsub("},\\n{", "},{") -- Escape any double-quote characters, or escape codes, in the subject - refs, _ = refs:gsub( - [[(,"subject":")(.-)("})]], - function(before, subject, after) - return table.concat({ before, vim.fn.escape(subject, [[\"]]), after }, "") - end - ) + refs, _ = refs:gsub([[(,"subject":")(.-)("})]], function(before, subject, after) + return table.concat({ before, vim.fn.escape(subject, [[\"]]), after }, "") + end) local ok, result = pcall(vim.json.decode, refs, { luanil = { object = true, array = true } }) if not ok then @@ -42,14 +53,15 @@ function M.list_parsed() local output = { local_branch = {}, remote_branch = {}, - tag = {} + tag = {}, } for _, ref in ipairs(result) do + ref.head = ref.head == "*" + if ref.ref:match("^refs/heads/") then ref.type = "local_branch" table.insert(output.local_branch, ref) - elseif ref.ref:match("^refs/remotes/") then local remote, branch = ref.ref:match("^refs/remotes/([^/]*)/(.*)$") if not output.remote_branch[remote] then @@ -59,7 +71,6 @@ function M.list_parsed() ref.type = "remote_branch" ref.name = branch table.insert(output.remote_branch[remote], ref) - elseif ref.ref:match("^refs/tags/") then ref.type = "tag" table.insert(output.tag, ref) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 9dc6c151c..e13404971 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -323,16 +323,16 @@ function M.deduplicate(tbl) return res end --- function M.find(tbl, cond) --- local res --- for i = 1, #tbl do --- if cond(tbl[i]) then --- res = tbl[i] --- break --- end --- end --- return res --- end +function M.find(tbl, cond) + local res + for i = 1, #tbl do + if cond(tbl[i]) then + res = tbl[i] + break + end + end + return res +end function M.build_reverse_lookup(tbl) local result = {} diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 69ef5b61d..03398967f 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1153,9 +1153,9 @@ local cmd_func_map = function() ["CommandHistory"] = function() GitCommandHistory:new():show() end, - ["ShowRefs"] = function() + ["ShowRefs"] = a.void(function() RefsViewBuffer.new(git.refs.list_parsed()):open() - end, + end), ["Console"] = function() local process = require("neogit.process") process.show_console() From 5fa79e37e6f8ddeaa682f72a5ec6099564ac3037 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:53:53 +0100 Subject: [PATCH 056/443] Set fold state in component --- lua/neogit/buffers/common.lua | 6 +++--- lua/neogit/buffers/git_command_history.lua | 2 +- lua/neogit/lib/ui/component.lua | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index d5fab3e0e..3a09d35df 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -37,7 +37,7 @@ M.Diff = Component.new(function(diff) col.tag("DiffInfo")(map(diff.info, text)), col.tag("HunkList")(map(hunk_props, M.Hunk)), }, - }, { foldable = true }) + }, { foldable = true, folded = false }) end) local HunkLine = Component.new(function(line) @@ -58,7 +58,7 @@ M.Hunk = Component.new(function(props) return col.tag("Hunk")({ text.sign("NeogitHunkHeader")(props.header), col.tag("HunkContent")(map(props.content, HunkLine)), - }, { foldable = true }) + }, { foldable = true, folded = false }) end) M.List = Component.new(function(props) @@ -216,7 +216,7 @@ M.CommitEntry = Component.new(function(commit, args) } ), details, - }, { oid = commit.oid, foldable = args.details == true }) + }, { oid = commit.oid, foldable = args.details == true, folded = true }) end) M.CommitGraph = Component.new(function(commit, _) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index c0cf17540..c7d40dd0d 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -106,7 +106,7 @@ function M:show() col .padding_left(" | ") .highlight("NeogitCommandText")(map(is_err and item.stderr or item.stdout, text)), - }, { foldable = true }) + }, { foldable = true, folded = true }) end) end, } diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 2475507e0..7aaa6fad5 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -2,8 +2,25 @@ local util = require("neogit.lib.util") local default_component_options = { foldable = false, + folded = false, } +---@class ComponentPosition +---@field row_start integer +---@field row_end integer +---@field col_start integer +---@field col_end integer + +---@class ComponentOptions +---@field padding_left integer +---@field tag string +---@field foldable boolean +---@field folded boolean + +---@class Component +---@field position ComponentPosition +---@field parent Component + local Component = {} function Component:row_range_abs() From 422aa3468e46e737578950543cc5118b136da52f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:54:03 +0100 Subject: [PATCH 057/443] Allow setting fold state in render --- lua/neogit/lib/buffer.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 3eafd3043..89b4e791c 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -138,6 +138,7 @@ function Buffer:flush_buffers() for _, fold in ipairs(self.fold_buffer) do self:create_fold(unpack(fold)) + self:set_fold_state(unpack(fold)) end self.fold_buffer = {} end @@ -285,10 +286,17 @@ function Buffer:put(lines, after, follow) api.nvim_put(lines, "l", after, follow) end -function Buffer:create_fold(first, last) +function Buffer:create_fold(first, last, _) vim.cmd(string.format("%d,%dfold", first, last)) end +function Buffer:set_fold_state(first, last, open) + if open then + vim.cmd(string.format("%d,%dfoldopen", first, last)) + else + vim.cmd(string.format("%d,%dfoldclose", first, last)) + end +end function Buffer:unlock() self:set_option("readonly", false) self:set_option("modifiable", true) From ec64f37360bcb0eb1e9baa6cab1680c4d416a859 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:54:18 +0100 Subject: [PATCH 058/443] Fix UFO patch --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 89b4e791c..8a56c35be 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -271,7 +271,7 @@ function Buffer:show() -- Workaround UFO getting folds wrong. local ufo, _ = pcall(require, "ufo") if ufo then - ufo.detach() + require("ufo").detach() end return win From c1ff05ebc5788fadd56d3920d29f2665767513d5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:54:47 +0100 Subject: [PATCH 059/443] Add component append --- lua/neogit/lib/ui/component.lua | 5 +++++ lua/neogit/lib/ui/init.lua | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 7aaa6fad5..8be1643a1 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -108,6 +108,11 @@ function Component:get_highlight() return self.options.highlight or (self.parent and self.parent:get_highlight() or nil) end +function Component:append(c) + table.insert(self.children, c) + return self +end + function Component.new(f) local x = {} setmetatable(x, { diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 39b688a41..bdd537bd3 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -129,6 +129,13 @@ function Ui:get_component_stack_under_cursor() end) end +function Ui:get_fold_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) + return self:find_component(function(c) + return c.options.foldable and c:is_under_cursor(cursor) + end) +end + function Ui:get_component_stack_in_linewise_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } table.sort(range) @@ -313,7 +320,8 @@ function Ui:_render(first_line, first_col, parent, components, flags) if c.options.foldable then self.buf:buffered_create_fold( #self.buf.line_buffer - (c.position.row_end - c.position.row_start), - #self.buf.line_buffer + #self.buf.line_buffer, + not c.options.folded ) end end From a177fac03eb8d4854abc3cef2f2be0fcf221f652 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:55:12 +0100 Subject: [PATCH 060/443] Rename callback to on_open --- lua/neogit/buffers/refs_view/ui.lua | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index c514c2931..609fb69ff 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -1,5 +1,6 @@ local M = {} +local a = require("plenary.async") local Ui = require("neogit.lib.ui") local util = require("neogit.lib.util") local git = require("neogit.lib.git") @@ -54,14 +55,26 @@ local function section(refs, heading, head) for _, ref in ipairs(refs) do table.insert( rows, - col({ Ref(ref) }, { + col.tag("Ref")({ Ref(ref) }, { oid = ref.oid, foldable = true, - callback = a.void(function(c) - vim.cmd("echomsg 'Getting cherries for " .. ref.oid .. "'") + on_open = a.void(function(this, ui) + vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.oid:sub(1, 7))) + local cherries = Cherries(ref, head) - vim.cmd("echomsg ''") - P { cherries } + if cherries.children[1] then + this.options.on_open = nil -- Don't call this again + this.options.foldable = true + this.options.folded = false + + vim.cmd("norm! zE") -- Eliminate all existing folds + this:append(cherries) + ui:update() + + vim.cmd(string.format("redraw | echomsg 'Got %d cherries for %s'", #cherries.children - 1, ref.oid:sub(1, 7))) + else + vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.oid:sub(1, 7))) + end end), }) ) From f95809ac8a33c8910c0e19ed7483153f4867c94b Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:55:30 +0100 Subject: [PATCH 061/443] Tag section --- lua/neogit/buffers/refs_view/init.lua | 1 + lua/neogit/buffers/refs_view/ui.lua | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index e5feab2c4..e682cd770 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -4,6 +4,7 @@ local popups = require("neogit.popups") local CommitViewBuffer = require("neogit.buffers.commit_view") local config = require("neogit.config") local notification = require("neogit.lib.notification") +local util = require("neogit.lib.util") --- @class RefsViewBuffer --- @field is_open boolean whether the buffer is currently shown diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 609fb69ff..80516a41a 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -82,10 +82,12 @@ local function section(refs, heading, head) table.insert(rows, row { text("") }) - return col({ - row(util.merge(heading, { text.highlight("NeogitGraphWhite")(string.format(" (%d)", #refs)) })), - col(rows), - }, { foldable = true }) + return col.tag("Section")({ + row.tag("SectionHeading")( + util.merge(heading, { text.highlight("NeogitGraphWhite")(string.format(" (%d)", #refs)) }) + ), + col.tag("SectionBody")(rows), + }, { foldable = true, folded = false }) end function M.Branches(branches, head) From 185c69bfb6fbd3f7ec971115d3f481699e20fc23 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 11:55:54 +0100 Subject: [PATCH 062/443] Update tab callback --- lua/neogit/buffers/refs_view/init.lua | 70 +++++++++++++++------------ 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index e682cd770..6b0a9e2cd 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -137,38 +137,48 @@ function M:open() [""] = function() CommitViewBuffer.new(self.buffer.ui:get_commits_in_selection()[1]):open() end, - ["{"] = function() - pcall(vim.cmd, "normal! zc") - - vim.cmd("normal! k") - for _ = vim.fn.line("."), 0, -1 do - if vim.fn.foldlevel(".") > 0 then - break - end - - vim.cmd("normal! k") - end - - pcall(vim.cmd, "normal! zo") - vim.cmd("normal! zz") - end, - ["}"] = function() - pcall(vim.cmd, "normal! zc") - - vim.cmd("normal! j") - for _ = vim.fn.line("."), vim.fn.line("$"), 1 do - if vim.fn.foldlevel(".") > 0 then - break + -- ["{"] = function() + -- pcall(vim.cmd, "normal! zc") + -- + -- vim.cmd("normal! k") + -- for _ = vim.fn.line("."), 0, -1 do + -- if vim.fn.foldlevel(".") > 0 then + -- break + -- end + -- + -- vim.cmd("normal! k") + -- end + -- + -- pcall(vim.cmd, "normal! zo") + -- vim.cmd("normal! zz") + -- end, + -- ["}"] = function() + -- pcall(vim.cmd, "normal! zc") + -- + -- vim.cmd("normal! j") + -- for _ = vim.fn.line("."), vim.fn.line("$"), 1 do + -- if vim.fn.foldlevel(".") > 0 then + -- break + -- end + -- + -- vim.cmd("normal! j") + -- end + -- + -- pcall(vim.cmd, "normal! zo") + -- vim.cmd("normal! zz") + -- end, + [""] = function() + local fold = self.buffer.ui:get_fold_under_cursor() + if fold then + if fold.options.on_open then + fold.options.on_open(fold, self.buffer.ui) + else + local ok, _ = pcall(vim.cmd, "normal! za") + if ok then + fold.options.folded = not fold.options.folded + end end - - vim.cmd("normal! j") end - - pcall(vim.cmd, "normal! zo") - vim.cmd("normal! zz") - end, - [""] = function() - pcall(vim.cmd, "normal! za") end, ["d"] = function() if not config.check_integration("diffview") then From 68136c7e304c2d71735ae66f55b7992c9ef980d6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 12:11:48 +0100 Subject: [PATCH 063/443] Lint --- lua/neogit/lib/buffer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 8a56c35be..d4069ae4d 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -297,6 +297,7 @@ function Buffer:set_fold_state(first, last, open) vim.cmd(string.format("%d,%dfoldclose", first, last)) end end + function Buffer:unlock() self:set_option("readonly", false) self:set_option("modifiable", true) From d7be4a0726aba479cb2c13ded56b763460a5cdff Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 12:15:07 +0100 Subject: [PATCH 064/443] Didn't use this --- lua/neogit/lib/util.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index e13404971..9dc6c151c 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -323,16 +323,16 @@ function M.deduplicate(tbl) return res end -function M.find(tbl, cond) - local res - for i = 1, #tbl do - if cond(tbl[i]) then - res = tbl[i] - break - end - end - return res -end +-- function M.find(tbl, cond) +-- local res +-- for i = 1, #tbl do +-- if cond(tbl[i]) then +-- res = tbl[i] +-- break +-- end +-- end +-- return res +-- end function M.build_reverse_lookup(tbl) local result = {} From 2617cca6b9f39d252003e2990d8ea96eec1331bf Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Nov 2023 22:21:41 +0100 Subject: [PATCH 065/443] Lint --- lua/neogit/buffers/refs_view/init.lua | 1 - lua/neogit/buffers/refs_view/ui.lua | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 6b0a9e2cd..eff7d6461 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -4,7 +4,6 @@ local popups = require("neogit.popups") local CommitViewBuffer = require("neogit.buffers.commit_view") local config = require("neogit.config") local notification = require("neogit.lib.notification") -local util = require("neogit.lib.util") --- @class RefsViewBuffer --- @field is_open boolean whether the buffer is currently shown diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 80516a41a..b0baab802 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -71,7 +71,13 @@ local function section(refs, heading, head) this:append(cherries) ui:update() - vim.cmd(string.format("redraw | echomsg 'Got %d cherries for %s'", #cherries.children - 1, ref.oid:sub(1, 7))) + vim.cmd( + string.format( + "redraw | echomsg 'Got %d cherries for %s'", + #cherries.children - 1, + ref.oid:sub(1, 7) + ) + ) else vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.oid:sub(1, 7))) end From 5e937a11d8d964496f1ca2caf1f6fa97200d71a5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 30 Nov 2023 22:48:36 +0100 Subject: [PATCH 066/443] Move wrap and spell options to all buffers --- lua/neogit/buffers/commit_select_view/init.lua | 1 - lua/neogit/buffers/log_view/init.lua | 2 -- lua/neogit/buffers/reflog_view/init.lua | 3 --- lua/neogit/buffers/refs_view/init.lua | 1 - lua/neogit/lib/buffer.lua | 3 +++ 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 62e794ae1..5a69f6a3d 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -95,7 +95,6 @@ function M:open(action) vim.api.nvim_win_set_cursor(win, { found.position.row_start, 0 }) end end - vim.cmd([[setlocal nowrap]]) end, render = function() return ui.View(self.commits) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 84d3a4fec..0c9265460 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -174,8 +174,6 @@ function M:open() vim.api.nvim_win_set_cursor(win, { found.position.row_start, 0 }) end end - - vim.cmd([[setlocal nowrap]]) end, render = function() return ui.View(self.commits, self.internal_args) diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 4a92990cb..542e5ac82 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -118,9 +118,6 @@ function M:open(_) end, }, }, - after = function() - vim.cmd([[setlocal nowrap]]) - end, render = function() return ui.View(self.entries) end, diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index eff7d6461..0e1550c9b 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -194,7 +194,6 @@ function M:open() return ui.RefsView(self.refs, self.head) end, after = function() - vim.cmd([[setlocal nowrap nospell]]) end, } end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index d4069ae4d..bef49df53 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -512,6 +512,9 @@ function Buffer.create(config) buffer:set_option("readonly", true) end + buffer:set_option("spell", false) + buffer:set_option("wrap", false) + if config.after then buffer:call(function() config.after(buffer, win) From d42040b1db097538f489ed2d6bb3888ad250c764 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 30 Nov 2023 22:59:46 +0100 Subject: [PATCH 067/443] lint --- lua/neogit/buffers/refs_view/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 0e1550c9b..5811214b2 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -193,8 +193,6 @@ function M:open() render = function() return ui.RefsView(self.refs, self.head) end, - after = function() - end, } end From 46b00374b3eff5802a923109e6ffb2ab2a4f6e87 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 30 Nov 2023 23:04:25 +0100 Subject: [PATCH 068/443] Gate features --- lua/neogit/lib/buffer.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index bef49df53..cd5e51f9e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -512,8 +512,10 @@ function Buffer.create(config) buffer:set_option("readonly", true) end - buffer:set_option("spell", false) - buffer:set_option("wrap", false) + if vim.fn.has("nvim-0.10") then + buffer:set_option("spell", false) + buffer:set_option("wrap", false) + end if config.after then buffer:call(function() From 09beb2eafde56117e7bb178a43c3ab7d7e4fbb91 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 11:52:32 +0100 Subject: [PATCH 069/443] Use line-highlight instead of signs for most things --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/buffers/commit_view/ui.lua | 4 ++-- lua/neogit/buffers/common.lua | 14 +++++++------- lua/neogit/lib/ui/component.lua | 4 ++++ lua/neogit/lib/ui/init.lua | 9 +++++++++ 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 6614129cd..12f1c1c4c 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -130,7 +130,7 @@ function M:open(kind) end end else - if layout.options.sign == "NeogitDiffHeader" then + if layout.options.line_hl == "NeogitDiffHeader" then return { layout.value, layout:row_range_abs() } end end diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index 487030e72..98d8333fc 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -31,7 +31,7 @@ end function M.CommitHeader(info) return col { - text.sign("NeogitCommitViewHeader")("Commit " .. info.commit_arg), + text.line_hl("NeogitCommitViewHeader")("Commit " .. info.commit_arg), commit_header_arg(info), row { text.highlight("Comment")("Author: "), @@ -58,7 +58,7 @@ function M.CommitView(info, overview, signature_block) return { M.CommitHeader(info), text(""), - col(map(info.description, text), { sign = "NeogitCommitViewDescription", tag = "Description" }), + col(map(info.description, text), { line_hl = "NeogitCommitViewDescription", tag = "Description" }), text(""), M.SignatureBlock(signature_block), text(overview.summary), diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index c7cf7cf20..0b6a51e0c 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -32,7 +32,7 @@ M.Diff = Component.new(function(diff) end) return col.tag("Diff")({ - text(string.format("%s %s", diff.kind, diff.file), { sign = "NeogitDiffHeader" }), + text(string.format("%s %s", diff.kind, diff.file), { line_hl = "NeogitDiffHeader" }), col.tag("DiffContent") { col.tag("DiffInfo")(map(diff.info, text)), col.tag("HunkList")(map(hunk_props, M.Hunk)), @@ -41,22 +41,22 @@ M.Diff = Component.new(function(diff) end) local HunkLine = Component.new(function(line) - local sign + local line_hl if string.sub(line, 1, 1) == diff_add_start then - sign = "NeogitDiffAdd" + line_hl = "NeogitDiffAdd" elseif string.sub(line, 1, 1) == diff_delete_start then - sign = "NeogitDiffDelete" + line_hl = "NeogitDiffDelete" else - sign = "NeogitDiffContext" + line_hl = "NeogitDiffContext" end - return text(line, { sign = sign }) + return text(line, { line_hl = line_hl }) end) M.Hunk = Component.new(function(props) return col.tag("Hunk")({ - text.sign("NeogitHunkHeader")(props.header), + text.line_hl("NeogitHunkHeader")(props.header), col.tag("HunkContent")(map(props.content, HunkLine)), }, { foldable = true, folded = false }) end) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 6ec292178..4c7d15334 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -105,6 +105,10 @@ function Component:get_sign() return self.options.sign or (self.parent and self.parent:get_sign() or nil) end +function Component:get_line_highlight() + return self.options.line_hl or (self.parent and self.parent:get_line_highlight() or nil) +end + function Component:get_highlight() return self.options.highlight or (self.parent and self.parent:get_highlight() or nil) end diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index bdd537bd3..fa2acee8c 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -279,6 +279,7 @@ function Ui:_render(first_line, first_col, parent, components, flags) c.position.col_end = -1 local sign = c:get_sign() + local line_hl = c:get_line_highlight() local highlight = c:get_highlight() if c.tag == "text" then @@ -292,6 +293,10 @@ function Ui:_render(first_line, first_col, parent, components, flags) self.buf:buffered_place_sign(curr_line, sign, "hl") end + if line_hl then + self.buf:buffered_add_line_highlight(curr_line - 1, line_hl) + end + curr_line = curr_line + 1 elseif c.tag == "col" then curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) @@ -303,6 +308,10 @@ function Ui:_render(first_line, first_col, parent, components, flags) self.buf:buffered_place_sign(curr_line - 1, sign, "hl") end + if line_hl then + self.buf:buffered_add_line_highlight(curr_line - 2, line_hl) + end + if c.options.virtual_text then local ns = self.buf:create_namespace("NeogitBufferVirtualText") self.buf:buffered_set_extmark(ns, curr_line - 2, 0, { From 9a95e1847f1b90ddb646ca4338581e84238b411b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 21:01:48 +0100 Subject: [PATCH 070/443] Replace buffer methods for setting signs with new extmark api --- lua/neogit/lib/buffer.lua | 151 +++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 67 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index d9232c8f7..7c36604cf 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -1,14 +1,13 @@ local api = vim.api local fn = vim.fn -package.loaded["neogit.buffer"] = nil - -__BUFFER_AUTOCMD_STORE = {} local mappings_manager = require("neogit.lib.mappings_manager") +local signs = require("neogit.lib.signs") local Ui = require("neogit.lib.ui") ---@class Buffer ---@field handle number +---@field namespaces table ---@field mmanager MappingsManager ---@field ui Ui ---@field kind string @@ -27,10 +26,12 @@ function Buffer:new(handle) border = nil, mmanager = mappings_manager.new(handle), kind = nil, -- how the buffer was opened. For more information look at the create function - namespace = api.nvim_create_namespace("neogit-buffer-" .. handle), + namespaces = { + default = api.nvim_create_namespace("neogit-buffer-" .. handle), + }, line_buffer = {}, hl_buffer = {}, - sign_buffer = {}, + line_hl_buffer = {}, ext_buffer = {}, fold_buffer = {}, } @@ -104,10 +105,6 @@ function Buffer:buffered_add_highlight(...) table.insert(self.hl_buffer, { ... }) end -function Buffer:buffered_place_sign(...) - table.insert(self.sign_buffer, { ... }) -end - function Buffer:buffered_set_extmark(...) table.insert(self.ext_buffer, { ... }) end @@ -116,31 +113,43 @@ function Buffer:buffered_create_fold(...) table.insert(self.fold_buffer, { ... }) end +function Buffer:buffered_add_line_highlight(...) + table.insert(self.line_hl_buffer, { ... }) +end + function Buffer:resize(length) api.nvim_buf_set_lines(self.handle, length, -1, false, {}) end -function Buffer:flush_buffers() - self:clear_namespace(self.namespace) - - api.nvim_buf_set_lines(self.handle, 0, -1, false, self.line_buffer) - self.line_buffer = {} - - for _, sign in ipairs(self.sign_buffer) do - self:place_sign(unpack(sign)) +function Buffer:flush_line_buffer() + if self.line_buffer[1] then + api.nvim_buf_set_lines(self.handle, 0, -1, false, self.line_buffer) + self.line_buffer = {} end - self.sign_buffer = {} +end - for _, hl in ipairs(self.hl_buffer) do - self:add_highlight(unpack(hl)) +function Buffer:flush_highlight_buffer() + for _, highlight in ipairs(self.hl_buffer) do + self:add_highlight(unpack(highlight)) end self.hl_buffer = {} +end +function Buffer:flush_extmark_buffer() for _, ext in ipairs(self.ext_buffer) do self:set_extmark(unpack(ext)) end self.ext_buffer = {} +end + +function Buffer:flush_line_highlight_buffer() + for _, hl in ipairs(self.line_hl_buffer) do + self:add_line_highlight(unpack(hl)) + end + self.line_hl_buffer = {} +end +function Buffer:flush_fold_buffer() for _, fold in ipairs(self.fold_buffer) do self:create_fold(unpack(fold)) self:set_fold_state(unpack(fold)) @@ -148,6 +157,15 @@ function Buffer:flush_buffers() self.fold_buffer = {} end +function Buffer:flush_buffers() + self:clear_namespace("default") + self:flush_line_buffer() + self:flush_highlight_buffer() + self:flush_extmark_buffer() + self:flush_line_highlight_buffer() + self:flush_fold_buffer() +end + function Buffer:set_text(first_line, last_line, first_col, last_col, lines) api.nvim_buf_set_text(self.handle, first_line, first_col, last_line, last_col, lines) end @@ -338,58 +356,65 @@ function Buffer:open_fold(line, reset_pos) end end -function Buffer:add_highlight(line, col_start, col_end, name, ns_id) - local ns_id = ns_id or self.namespace - - api.nvim_buf_add_highlight(self.handle, ns_id, name, line, col_start, col_end) +function Buffer:add_highlight(line, col_start, col_end, name, namespace) + api.nvim_buf_add_highlight(self.handle, self:get_namespace_id(namespace), name, line, col_start, col_end) end -function Buffer:unplace_sign(id) - vim.cmd("sign unplace " .. id) +function Buffer:place_sign(line, name, opts) + opts = opts or {} + + api.nvim_buf_set_extmark( + self.handle, + self:get_namespace_id(opts.namespace), + line - 1, + 0, + { sign_text = signs.get(name) } + ) end -function Buffer:place_sign(line, name, group, id) - -- Sign IDs should be unique within a group, however there's no downside as - -- long as we don't want to uniquely identify the placed sign later. Thus, - -- we leave the choice to the caller - local sign_id = id or 1 +function Buffer:add_line_highlight(line, hl_group, opts) + opts = opts or {} - -- There's an equivalent function sign_place() which can automatically use - -- a free ID, but is considerable slower, so we use the command for now - local cmd = { - string.format("sign place %d", sign_id), - string.format("line=%d", line), - string.format("name=%s", name), - } + api.nvim_buf_set_extmark( + self.handle, + self:get_namespace_id(opts.namespace), + line, + 0, + { line_hl_group = hl_group, priority = opts.priority or 190 } + ) +end - if group then - table.insert(cmd, string.format("group=%s", group)) - end +function Buffer:clear_namespace(name) + assert(name, "Cannot clear namespace without specifying which") - table.insert(cmd, string.format("buffer=%d", self.handle)) + if not self:is_focused() then + return + end - vim.cmd(table.concat(cmd, " ")) - return sign_id + api.nvim_buf_clear_namespace(self.handle, self:get_namespace_id(name), 0, -1) end -function Buffer:get_sign_at_line(line, group) - group = group or "*" - return fn.sign_getplaced(self.handle, { - group = group, - lnum = line, - })[1] -end +function Buffer:create_namespace(name) + assert(name, "Namespace must have a name") -function Buffer:clear_sign_group(group) - vim.cmd(string.format("sign unplace * group=%s buffer=%s", group, self.handle)) -end + local namespace = "neogit-buffer-" .. self.handle .. "-" .. name + if not self.namespaces[namespace] then + self.namespaces[namespace] = api.nvim_create_namespace(namespace) + end -function Buffer:clear_namespace(namespace) - api.nvim_buf_clear_namespace(self.handle, namespace, 0, -1) + return self.namespaces[namespace] end -function Buffer:create_namespace(name) - return api.nvim_create_namespace(name) +function Buffer:get_namespace_id(name) + local ns_id + if name and name ~= "default" then + ns_id = self.namespaces["neogit-buffer-" .. self.handle .. "-" .. name] + assert(ns_id, "Namespace ID should never be nil! Create '" .. name .. "' namespace before using it") + else + ns_id = self.namespaces.default + end + + return ns_id end function Buffer:set_filetype(ft) @@ -408,16 +433,8 @@ function Buffer:set_extmark(...) return api.nvim_buf_set_extmark(self.handle, ...) end -function Buffer:get_extmark(id, ns) - return api.nvim_buf_get_extmark_by_id(self.handle, ns or self.namespace, id, { details = true }) -end - -function Buffer:del_extmark(ns, id) - return api.nvim_buf_del_extmark(self.handle, ns, id) -end - function Buffer:set_decorations(namespace, opts) - return api.nvim_set_decoration_provider(namespace, opts) + return api.nvim_set_decoration_provider(self:get_namespace_id(namespace), opts) end local uv_utils = require("neogit.lib.uv") From bf99fe604e1380d232592ae0b7a140ad7ec07bf6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 21:02:30 +0100 Subject: [PATCH 071/443] Use new buffer extmark/namespace methods --- lua/neogit/lib/buffer.lua | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 7c36604cf..2e52107a8 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -425,8 +425,8 @@ function Buffer:call(f) api.nvim_buf_call(self.handle, f) end -function Buffer.exists(name) - return fn.bufnr(name) ~= -1 +function Buffer:exists() + return fn.bufnr(self.handle) ~= -1 end function Buffer:set_extmark(...) @@ -557,10 +557,10 @@ function Buffer.create(config) end) if config.context_highlight then - buffer:call(function() - local decor_ns = api.nvim_create_namespace("NeogitBufferViewDecor" .. config.name) - local context_ns = api.nvim_create_namespace("NeogitBufferitViewContext" .. config.name) + buffer:create_namespace("ViewContext") + buffer:create_namespace("ViewDecor") + buffer:call(function() local function frame_key() return table.concat { fn.line("w0"), fn.line("w$"), fn.getcurpos()[2], buffer:get_changedtick() } end @@ -576,7 +576,7 @@ function Buffer.create(config) end local function on_win() - buffer:clear_namespace(context_ns) + buffer:clear_namespace("ViewContext") -- TODO: this is WAY to slow to be called so frequently, especially in a large buffer local stack = buffer.ui:get_component_stack_under_cursor() @@ -590,19 +590,19 @@ function Buffer.create(config) for line = fn.line("w0"), fn.line("w$") do if first and last and line >= first and line <= last and not top_level then - local sign = buffer.ui:get_component_stack_on_line(line)[1].options.sign - - buffer:set_extmark( - context_ns, + local line_hl = buffer.ui:get_component_stack_on_line(line)[1].options.line_hl + buffer:buffered_add_line_highlight( line - 1, - 0, - { line_hl_group = (sign or "NeogitDiffContext") .. "Highlight", priority = 10 } + (line_hl or "NeogitDiffContext") .. "Highlight", + { priority = 200, namespace = "ViewContext" } ) end end + + buffer:flush_line_highlight_buffer() end - buffer:set_decorations(decor_ns, { on_start = on_start, on_win = on_win, on_end = on_end }) + buffer:set_decorations("ViewDecor", { on_start = on_start, on_win = on_win, on_end = on_end }) end) end From 40c377af7238d1a29a1e734d5cdf014094521c5f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 21:02:57 +0100 Subject: [PATCH 072/443] Rewrite how signs lib works. Nearly all of it is now handled by extmarks --- lua/neogit/lib/signs.lua | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/lua/neogit/lib/signs.lua b/lua/neogit/lib/signs.lua index e665cef73..3d6aee182 100644 --- a/lua/neogit/lib/signs.lua +++ b/lua/neogit/lib/signs.lua @@ -1,35 +1,21 @@ local config = require("neogit.config") local M = {} -local signs = { - CommitViewDescription = { linehl = "NeogitHunkHeader" }, - CommitViewHeader = { linehl = "NeogitCommitViewHeader" }, - DiffAdd = { linehl = "NeogitDiffAdd" }, - DiffAddHighlight = { linehl = "NeogitDiffAddHighlight" }, - DiffContext = { linehl = "NeogitDiffContext" }, - DiffContextHighlight = { linehl = "NeogitDiffContextHighlight" }, - DiffDelete = { linehl = "NeogitDiffDelete" }, - DiffDeleteHighlight = { linehl = "NeogitDiffDeleteHighlight" }, - DiffHeader = { linehl = "NeogitDiffHeader" }, - HunkHeader = { linehl = "NeogitHunkHeader" }, - HunkHeaderHighlight = { linehl = "NeogitHunkHeaderHighlight" }, - LogViewCursorLine = { linehl = "NeogitCursorLine" }, - RebaseDone = { linehl = "NeogitRebaseDone" }, -} +local signs = {} + +function M.get(name) + return signs[name] +end function M.setup() if not config.values.disable_signs then for key, val in pairs(config.values.signs) do if key == "hunk" or key == "item" or key == "section" then - vim.fn.sign_define("NeogitClosed:" .. key, { text = val[1] }) - vim.fn.sign_define("NeogitOpen:" .. key, { text = val[2] }) + signs["NeogitClosed" .. key] = val[1] + signs["NeogitOpen" .. key] = val[2] end end end - - for key, val in pairs(signs) do - vim.fn.sign_define("Neogit" .. key, val) - end end return M From 3a51fd0f0e8beab18442f2e505912d1e5b5abfea Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 21:03:26 +0100 Subject: [PATCH 073/443] Remove sign getter --- lua/neogit/lib/ui/component.lua | 4 ---- lua/neogit/lib/ui/init.lua | 9 --------- 2 files changed, 13 deletions(-) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 4c7d15334..32bb5b8ad 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -101,10 +101,6 @@ function Component:get_tag() end end -function Component:get_sign() - return self.options.sign or (self.parent and self.parent:get_sign() or nil) -end - function Component:get_line_highlight() return self.options.line_hl or (self.parent and self.parent:get_line_highlight() or nil) end diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index fa2acee8c..64b0ad47b 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -278,7 +278,6 @@ function Ui:_render(first_line, first_col, parent, components, flags) c.position.col_start = 0 c.position.col_end = -1 - local sign = c:get_sign() local line_hl = c:get_line_highlight() local highlight = c:get_highlight() @@ -289,10 +288,6 @@ function Ui:_render(first_line, first_col, parent, components, flags) self.buf:buffered_add_highlight(curr_line - 1, c.position.col_start, c.position.col_end, highlight) end - if sign then - self.buf:buffered_place_sign(curr_line, sign, "hl") - end - if line_hl then self.buf:buffered_add_line_highlight(curr_line - 1, line_hl) end @@ -304,10 +299,6 @@ function Ui:_render(first_line, first_col, parent, components, flags) flags.in_row = true curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) - if sign then - self.buf:buffered_place_sign(curr_line - 1, sign, "hl") - end - if line_hl then self.buf:buffered_add_line_highlight(curr_line - 2, line_hl) end From c14af81e93f88169b292a4dd107136c5369a1f84 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 21:04:30 +0100 Subject: [PATCH 074/443] Use new line highlighting API --- lua/neogit/status.lua | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 528aee269..70da4aa0a 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -16,7 +16,6 @@ local util = require("neogit.lib.util") local watcher = require("neogit.watcher") local operation = require("neogit.operations") -local api = vim.api local fn = vim.fn local M = {} @@ -116,9 +115,9 @@ local max_len = #"Modified by us" local function draw_sign_for_item(item, name) if item.folded then - M.status_buffer:place_sign(item.first, "NeogitClosed:" .. name, "fold_markers") + M.status_buffer:place_sign(item.first, "NeogitClosed" .. name, { namespace = "fold_markers" }) else - M.status_buffer:place_sign(item.first, "NeogitOpen:" .. name, "fold_markers") + M.status_buffer:place_sign(item.first, "NeogitOpen" .. name, { namespace = "fold_markers" }) end end @@ -161,8 +160,7 @@ local function format_mode(mode) end local function draw_buffer() - M.status_buffer:clear_sign_group("hl") - M.status_buffer:clear_sign_group("fold_markers") + M.status_buffer:clear_namespace("fold_markers") local output = LineBuffer.new() if not config.values.disable_hint then @@ -312,7 +310,7 @@ local function draw_buffer() end if f.done then - M.status_buffer:place_sign(#output, "NeogitRebaseDone", "hl") + M.status_buffer:add_line_highlight(#output, "NeogitRebaseDone", { priority = 210 }) end local file = items_lookup[f.name] or { folded = true } @@ -1348,32 +1346,33 @@ end ---@param buffer Buffer ---@return nil local function set_decoration_provider(buffer) - local decor_ns = api.nvim_create_namespace("NeogitStatusDecor") - local context_ns = api.nvim_create_namespace("NeogitStatusContext") + buffer:create_namespace("View") + buffer:create_namespace("ViewDecor") + buffer:create_namespace("ViewContext") local function frame_key() - return table.concat { fn.line("w0"), fn.line("w$"), fn.line("."), buffer:get_changedtick() } + return table.concat { buffer.handle, fn.line("w0"), fn.line("w$"), fn.line("."), buffer:get_changedtick() } end local last_frame_key = frame_key() local function on_start() - return buffer:is_focused() and frame_key() ~= last_frame_key + return buffer:exists() and buffer:is_focused() and frame_key() ~= last_frame_key end local function on_end() - last_frame_key = frame_key() + last_frame_key = buffer:exists() and frame_key() or "NONE" end - local function on_win() - buffer:clear_namespace(decor_ns) - buffer:clear_namespace(context_ns) + local function on_win(_, _, _, top, bottom) + buffer:clear_namespace("ViewDecor") + buffer:clear_namespace("ViewContext") -- first and last lines of current context based on cursor position, if available local _, _, _, first, last = save_cursor_location() local cursor_line = vim.fn.line(".") - for line = fn.line("w0"), fn.line("w$") do + for line = top, bottom do local text = buffer:get_line(line)[1] if text then local highlight @@ -1393,7 +1392,7 @@ local function set_decoration_provider(buffer) end if highlight then - buffer:set_extmark(decor_ns, line - 1, 0, { line_hl_group = highlight, priority = 9 }) + buffer:add_line_highlight(line - 1, highlight, { priority = 190, namespace = "ViewDecor" }) end if @@ -1404,18 +1403,17 @@ local function set_decoration_provider(buffer) and line <= last and highlight ~= "NeogitCursorLine" then - buffer:set_extmark( - context_ns, + buffer:add_line_highlight( line - 1, - 0, - { line_hl_group = (highlight or "NeogitDiffContext") .. "Highlight", priority = 10 } + (highlight or "NeogitDiffContext") .. "Highlight", + { priority = 200, namespace = "ViewContext" } ) end end end end - buffer:set_decorations(decor_ns, { on_start = on_start, on_win = on_win, on_end = on_end }) + buffer:set_decorations("View", { on_start = on_start, on_win = on_win, on_end = on_end }) end --- Creates a new status buffer @@ -1480,6 +1478,7 @@ function M.create(kind, cwd) end end + buffer:create_namespace("fold_markers") set_decoration_provider(buffer) logger.debug("[STATUS BUFFER]: Dispatching initial render") From bb4218182c091679477b9886648d016f75cf18d4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 21:53:38 +0100 Subject: [PATCH 075/443] Use new options api --- lua/neogit/lib/buffer.lua | 46 ++++++++++++++++++++--------------- lua/neogit/lib/popup/init.lua | 6 ++--- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 2e52107a8..95506d6d7 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -7,6 +7,7 @@ local Ui = require("neogit.lib.ui") ---@class Buffer ---@field handle number +---@field win_handle number ---@field namespaces table ---@field mmanager MappingsManager ---@field ui Ui @@ -64,8 +65,8 @@ function Buffer:get_changedtick() end function Buffer:lock() - self:set_option("readonly", true) - self:set_option("modifiable", false) + self:set_buffer_option("readonly", true) + self:set_buffer_option("modifiable", false) end function Buffer:define_autocmd(events, script) @@ -297,6 +298,7 @@ function Buffer:show() require("ufo").detach() end + self.win_handle = win return win end @@ -322,16 +324,20 @@ function Buffer:set_fold_state(first, last, open) end function Buffer:unlock() - self:set_option("readonly", false) - self:set_option("modifiable", true) + self:set_buffer_option("readonly", false) + self:set_buffer_option("modifiable", true) end function Buffer:get_option(name) - return api.nvim_buf_get_option(self.handle, name) + return api.nvim_get_option_value(name, { buf = self.handle }) end -function Buffer:set_option(name, value) - api.nvim_buf_set_option(self.handle, name, value) +function Buffer:set_buffer_option(name, value) + api.nvim_set_option_value(name, value, { buf = self.handle }) +end + +function Buffer:set_window_option(name, value) + api.nvim_set_option_value(name, value, { win = self.win_handle }) end function Buffer:set_name(name) @@ -418,7 +424,7 @@ function Buffer:get_namespace_id(name) end function Buffer:set_filetype(ft) - api.nvim_buf_set_option(self.handle, "filetype", ft) + self:set_buffer_option("filetype", ft) end function Buffer:call(f) @@ -477,15 +483,15 @@ function Buffer.create(config) win = buffer:show() end - buffer:set_option("bufhidden", config.bufhidden or "wipe") - buffer:set_option("buftype", config.buftype or "nofile") - buffer:set_option("swapfile", false) + buffer:set_buffer_option("bufhidden", config.bufhidden or "wipe") + buffer:set_buffer_option("buftype", config.buftype or "nofile") + buffer:set_buffer_option("swapfile", false) if win then - vim.api.nvim_set_option_value("foldenable", true, { win = win }) - vim.api.nvim_set_option_value("foldlevel", 99, { win = win }) - vim.api.nvim_set_option_value("foldminlines", 0, { win = win }) - vim.api.nvim_set_option_value("foldtext", "v:lua.NeogitBufferFoldText()", { win = win }) + buffer:set_window_option("foldenable", true) + buffer:set_window_option("foldlevel", 99) + buffer:set_window_option("foldminlines", 0) + buffer:set_window_option("foldtext", "v:lua.NeogitBufferFoldText()") end if config.filetype then @@ -526,17 +532,17 @@ function Buffer.create(config) buffer.mmanager.register() if not config.modifiable then - buffer:set_option("modifiable", false) - buffer:set_option("modified", false) + buffer:set_buffer_option("modifiable", false) + buffer:set_buffer_option("modified", false) end if config.readonly == true then - buffer:set_option("readonly", true) + buffer:set_buffer_option("readonly", true) end if vim.fn.has("nvim-0.10") then - buffer:set_option("spell", false) - buffer:set_option("wrap", false) + buffer:set_window_option("spell", false) + buffer:set_window_option("wrap", false) end if config.after then diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 549b374a2..13ef7836c 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -636,9 +636,9 @@ function M:show() filetype = "NeogitPopup", kind = config.values.popup.kind, mappings = mappings, - after = function(buf, win) - vim.api.nvim_set_option_value("cursorline", false, { win = win }) - vim.api.nvim_set_option_value("list", false, { win = win }) + after = function(buf, _win) + buf:set_window_option("cursorline", false) + buf:set_window_option("list", false) if self.state.env.highlight then for i = 1, #self.state.env.highlight, 1 do From 64d8ec46332466d00cf5def0d5ba27c4a92378bb Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 22:01:03 +0100 Subject: [PATCH 076/443] Use plenary path to read files, like commit message --- lua/neogit/lib/buffer.lua | 6 +++--- lua/neogit/lib/uv.lua | 39 --------------------------------------- 2 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 lua/neogit/lib/uv.lua diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 95506d6d7..502a53249 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -5,6 +5,8 @@ local mappings_manager = require("neogit.lib.mappings_manager") local signs = require("neogit.lib.signs") local Ui = require("neogit.lib.ui") +local Path = require("plenary.path") + ---@class Buffer ---@field handle number ---@field win_handle number @@ -443,8 +445,6 @@ function Buffer:set_decorations(namespace, opts) return api.nvim_set_decoration_provider(self:get_namespace_id(namespace), opts) end -local uv_utils = require("neogit.lib.uv") - ---@class BufferConfig ---@field name string ---@field load boolean @@ -467,7 +467,7 @@ function Buffer.create(config) end if config.load then - local content = uv_utils.read_file_sync(config.name) + local content = Path:new(config.name):read() api.nvim_buf_set_lines(buffer, 0, -1, false, content) api.nvim_buf_call(buffer, function() vim.cmd("silent w!") diff --git a/lua/neogit/lib/uv.lua b/lua/neogit/lib/uv.lua deleted file mode 100644 index 2a8dcb2d0..000000000 --- a/lua/neogit/lib/uv.lua +++ /dev/null @@ -1,39 +0,0 @@ -local a = require("plenary.async") - -local M = {} - -function M.read_file(path) - local err, fd = a.uv.fs_open(path, "r", 438) - if err then - return err - end - - local err, stat = a.uv.fs_fstat(fd) - if err then - return err - end - - local err, data = a.uv.fs_read(fd, stat.size, 0) - if err then - return err - end - - local err = a.uv.fs_close(fd) - if err then - return err - end - - return nil, data -end - -M.read_file_sync = function(path) - local output = {} - - for line in io.lines(path) do - table.insert(output, line) - end - - return output -end - -return M From 7cd8426947759cdcd7a537f59066072f80d9947d Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 22:01:54 +0100 Subject: [PATCH 077/443] Layout[10] should just be the last one --- lua/neogit/buffers/commit_view/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 12f1c1c4c..818080919 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -136,8 +136,8 @@ function M:open(kind) end end -- The Diffs are in the 10th element of the layout. - -- TODO: Do better than assume that we care about layout[10] - find_diff_headers(self.buffer.ui.layout[10]) + -- TODO: Do better than assume that we care about the last item + find_diff_headers(self.buffer.ui.layout[#self.buffer.ui.layout]) -- Search for a match and jump if we find it for path, line_nr in pairs(diff_headers) do From 8d7d9163060bc3594a7a05f4c733b8a26a720a4d Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 22:11:15 +0100 Subject: [PATCH 078/443] Readlines, not read --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 502a53249..e11cd2932 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -467,7 +467,7 @@ function Buffer.create(config) end if config.load then - local content = Path:new(config.name):read() + local content = Path:new(config.name):readlines() api.nvim_buf_set_lines(buffer, 0, -1, false, content) api.nvim_buf_call(buffer, function() vim.cmd("silent w!") From 5ab11428671fbd1f1f183d789a2c4cac646c6f6d Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 23:18:44 +0100 Subject: [PATCH 079/443] Lint --- lua/neogit/buffers/commit_view/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index 4a9d277b6..98be97ed0 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -68,7 +68,7 @@ function M.CommitView(info, overview, signature_block, item_filter) end end) end - + return { M.CommitHeader(info), text(""), From 4304fb1752654259f66ebac51f56ae566dba97a2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 23:21:26 +0100 Subject: [PATCH 080/443] Use nightly nvim in test workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d5cada96..f045fb3ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - name: Install Neovim run: | - wget https://github.com/neovim/neovim/releases/download/stable/nvim-linux64.tar.gz + wget https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz tar -zxf nvim-linux64.tar.gz sudo ln -s $(pwd)/nvim-linux64/bin/nvim /usr/local/bin From 612483e8048143e54283e3d2fbefe0ee4a613ea1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 13 Dec 2023 23:25:41 +0100 Subject: [PATCH 081/443] Matrix --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f045fb3ad..b94485804 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,9 @@ jobs: test: name: Test runs-on: ubuntu-latest + strategy: + matrix: + release: [stable, nightly] steps: - uses: actions/checkout@v3 @@ -25,7 +28,7 @@ jobs: - name: Install Neovim run: | - wget https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz + wget https://github.com/neovim/neovim/releases/download/${{ matrix.release }}/nvim-linux64.tar.gz tar -zxf nvim-linux64.tar.gz sudo ln -s $(pwd)/nvim-linux64/bin/nvim /usr/local/bin From e3cfecff24f1bed8773776912f5999f99556a801 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Dec 2023 22:33:19 +0100 Subject: [PATCH 082/443] Add a debug mode for command history that shows calls otherwise hidden --- lua/neogit/buffers/git_command_history.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index 048900200..2f445dc4c 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -82,7 +82,7 @@ function M:show() local win_width = vim.fn.winwidth(0) return filter_map(self.state, function(item) - if item.hidden then + if item.hidden and not os.getenv("NEOGIT_DEBUG") then return end @@ -104,6 +104,9 @@ function M:show() return col({ row { + text.highlight("NeogitGraphAuthor")( + os.getenv("NEOGIT_DEBUG") and (item.hidden and "H" or " ") or "" + ), text.highlight(highlight_code)(code), text(" "), text(command), From 15594fe02ea9b1a8fe6edfda054f9c2fe8dc47ba Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Dec 2023 23:01:44 +0100 Subject: [PATCH 083/443] WIP - remote stuff --- lua/neogit/buffers/common.lua | 6 +++--- lua/neogit/buffers/log_view/init.lua | 4 +++- lua/neogit/buffers/refs_view/init.lua | 8 ++++++++ lua/neogit/lib/ui/init.lua | 5 +++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 0b6a51e0c..1a2f2f802 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -111,10 +111,10 @@ local highlight_for_signature = { M.CommitEntry = Component.new(function(commit, args) local ref = {} + local info = git.log.branch_info(commit.ref_name, git.remote.list()) + -- Parse out ref names if args.decorate and commit.ref_name ~= "" then - local info = git.log.branch_info(commit.ref_name, git.remote.list()) - -- Render local only branches first for name, _ in pairs(info.locals) do if info.remotes[name] == nil then @@ -222,7 +222,7 @@ M.CommitEntry = Component.new(function(commit, args) } ), details, - }, { oid = commit.oid, foldable = args.details == true, folded = true }) + }, { oid = commit.oid, foldable = args.details == true, folded = true, remote = info.remotes[1] }) end) M.CommitGraph = Component.new(function(commit, _) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 91ce77fdf..e31ac0663 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -105,7 +105,9 @@ function M:open() [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), - [popups.mapping_for("RemotePopup")] = popups.open("remote"), + [popups.mapping_for("RemotePopup")] = popups.open("remote", function(p) + p { remote = self.buffer.ui:get_item_options().remote } + end), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 5811214b2..4fc3caaa6 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -87,6 +87,10 @@ function M:open() [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), + [popups.mapping_for("RemotePopup")] = popups.open("remote", function(p) + p() + -- p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), @@ -123,6 +127,10 @@ function M:open() [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), + [popups.mapping_for("RemotePopup")] = popups.open("remote", function(p) + p() + -- p { commit = self.buffer.ui:get_commits_in_selection()[1] } + end), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 64b0ad47b..7443610ff 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -168,6 +168,11 @@ function Ui:get_commit_under_cursor() return stack[#stack].options.oid end +function Ui:get_item_options() + local stack = self:get_component_stack_under_cursor() + return stack[#stack].options or {} +end + function Ui.visualize_component(c, options) Ui._print_component(0, c, options or {}) if c.tag == "col" or c.tag == "row" then From f665f5013e6ff2a9eb4e7a690ffbec23ad5d1526 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Jan 2024 14:16:40 +0100 Subject: [PATCH 084/443] Fix: Rebase todo highlighting --- lua/neogit/status.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 97f0d8748..70d398517 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -310,7 +310,7 @@ local function draw_buffer() end if f.done then - M.status_buffer:add_line_highlight(#output, "NeogitRebaseDone", { priority = 210 }) + M.status_buffer:buffered_add_line_highlight(#output - 1, "NeogitRebaseDone", { priority = 210 }) end local file = items_lookup[f.name] or { folded = true } @@ -400,6 +400,7 @@ local function draw_buffer() render_section("Recent commits", "recent") M.status_buffer:replace_content_with(output) + M.status_buffer:flush_buffers() M.locations = new_locations end From 9451d94f787fc41aae1671b288b934f144e4e11e Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Jan 2024 19:11:08 +0100 Subject: [PATCH 085/443] Lint - remove unused variable --- lua/neogit/status.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 8fad22281..654fc81c7 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -16,8 +16,6 @@ local util = require("neogit.lib.util") local watcher = require("neogit.watcher") local operation = require("neogit.operations") -local fn = vim.fn - local M = {} M.disabled = false From 2b73c0730dec6cafd18bae2138637fc55910ded9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Jan 2024 22:38:32 +0100 Subject: [PATCH 086/443] Use json lib --- lua/neogit/lib/git/refs.lua | 47 ++++++++++--------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 8c91e4d11..8d8e9fd75 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -1,4 +1,5 @@ local cli = require("neogit.lib.git.cli") +local json = require("neogit.lib.json") local repo = require("neogit.lib.git.repository") local M = {} @@ -13,43 +14,19 @@ function M.list() return revisions end -local function json_format() - local template = { - [["head":"%(HEAD)"]], - [["oid":"%(objectname)"]], - [["ref":"%(refname)"]], - [["name":"%(refname:short)"]], - [["upstream_status":"%(upstream:trackshort)"]], - [["upstream_name":"%(upstream:short)"]], - [["subject":"%(subject)"]], - } - - return string.format("{%s},", table.concat(template, ",")) -end - -local json = json_format() +local json_template = json.encode { + head = "%(HEAD)", + oid = "%(objectname)", + ref = "%(refname)", + name = "%(refname:short)", + upstream_status = "%(upstream:trackshort)", + upstream_name = "%(upstream:short)", + subject = "%(subject)", +} function M.list_parsed() - local refs = cli["for-each-ref"].format(json).call_sync():trim().stdout - - -- Wrap list of refs in an Array - refs = "[" .. table.concat(refs, "\\n") .. "]" - - -- Remove trailing comma from last object in array - refs, _ = refs:gsub(",]", "]") - - -- Remove escaped newlines from in-between objects - refs, _ = refs:gsub("},\\n{", "},{") - - -- Escape any double-quote characters, or escape codes, in the subject - refs, _ = refs:gsub([[(,"subject":")(.-)("})]], function(before, subject, after) - return table.concat({ before, vim.fn.escape(subject, [[\"]]), after }, "") - end) - - local ok, result = pcall(vim.json.decode, refs, { luanil = { object = true, array = true } }) - if not ok then - assert(ok, "Failed to parse log json!: " .. result) - end + local refs = cli["for-each-ref"].format(json_template).call_sync():trim().stdout + local result = json.decode(refs, { escaped_fields = { "subject" } }) local output = { local_branch = {}, From 6dc089dbf1b3c1cdeaa7c5b113f9733c4fe97c3f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Jan 2024 22:47:54 +0100 Subject: [PATCH 087/443] Fix tests on CI --- tests/specs/neogit/popups/log_spec.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/specs/neogit/popups/log_spec.lua b/tests/specs/neogit/popups/log_spec.lua index b319b950a..9aef51a71 100644 --- a/tests/specs/neogit/popups/log_spec.lua +++ b/tests/specs/neogit/popups/log_spec.lua @@ -83,9 +83,9 @@ describe("log popup", function() local expected = { "e2c2a1c * master origin/second-branch b.txt", " * Author: Florian Proksch ", - " * AuthorDate: Tue Feb 9 20:33:33 2021 +0100", + " * AuthorDate: Tue, Feb 9 20:33:33 2021 +0100", " * Commit: Florian Proksch ", - " * CommitDate: Tue Feb 9 20:33:33 2021 +0100", + " * CommitDate: Tue, Feb 9 20:33:33 2021 +0100", " *", " * b.txt", " * ", @@ -120,9 +120,9 @@ describe("log popup", function() local expected = { "d86fa0e * a.txt", " * Author: Florian Proksch ", - " * AuthorDate: Sat Feb 6 08:08:32 2021 +0100", + " * AuthorDate: Sat, Feb 6 08:08:32 2021 +0100", " * Commit: Florian Proksch ", - " * CommitDate: Sat Feb 6 21:20:33 2021 +0100", + " * CommitDate: Sat, Feb 6 21:20:33 2021 +0100", " *", " * a.txt", " * ", @@ -141,9 +141,9 @@ describe("log popup", function() local expected = { "e2c2a1c * master origin/second-branch b.txt", " * Author: Florian Proksch ", - " * AuthorDate: Tue Feb 9 20:33:33 2021 +0100", + " * AuthorDate: Tue, Feb 9 20:33:33 2021 +0100", " * Commit: Florian Proksch ", - " * CommitDate: Tue Feb 9 20:33:33 2021 +0100", + " * CommitDate: Tue, Feb 9 20:33:33 2021 +0100", " *", " * b.txt", " * ", @@ -162,9 +162,9 @@ describe("log popup", function() local expected = { "d86fa0e * a.txt", " * Author: Florian Proksch ", - " * AuthorDate: Sat Feb 6 08:08:32 2021 +0100", + " * AuthorDate: Sat, Feb 6 08:08:32 2021 +0100", " * Commit: Florian Proksch ", - " * CommitDate: Sat Feb 6 21:20:33 2021 +0100", + " * CommitDate: Sat, Feb 6 21:20:33 2021 +0100", " *", " * a.txt", " * ", @@ -185,9 +185,9 @@ describe("log popup", function() " ...", "d86fa0e * a.txt", " * Author: Florian Proksch ", - " * AuthorDate: Sat Feb 6 08:08:32 2021 +0100", + " * AuthorDate: Sat, Feb 6 08:08:32 2021 +0100", " * Commit: Florian Proksch ", - " * CommitDate: Sat Feb 6 21:20:33 2021 +0100", + " * CommitDate: Sat, Feb 6 21:20:33 2021 +0100", " *", " * a.txt", " * ", @@ -207,9 +207,9 @@ describe("log popup", function() local expected = { "e2c2a1c * master origin/second-branch b.txt", " * Author: Florian Proksch ", - " * AuthorDate: Tue Feb 9 20:33:33 2021 +0100", + " * AuthorDate: Tue, Feb 9 20:33:33 2021 +0100", " * Commit: Florian Proksch ", - " * CommitDate: Tue Feb 9 20:33:33 2021 +0100", + " * CommitDate: Tue, Feb 9 20:33:33 2021 +0100", " *", " * b.txt", " * ", From 70a82533c1dd39703f5075660c7c12065b0bb46c Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Jan 2024 21:58:27 +0100 Subject: [PATCH 088/443] Pcall this - it would sometimes fail if the buffer isn't done rendering --- lua/neogit/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index a4976cd2f..cd216c659 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -1480,7 +1480,7 @@ function M.create(kind, cwd) return not M.is_refresh_locked() end) - restore_cursor_location(unpack(M.cursor_location)) + pcall(restore_cursor_location, unpack(M.cursor_location)) M.cursor_location = nil end end, From 35fb6aeee078350fa41e47db933976bfbf1044d4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 19 Jan 2024 23:37:52 +0100 Subject: [PATCH 089/443] Refactor: extract renderer class --- lua/neogit/lib/buffer.lua | 36 ++++-- lua/neogit/lib/ui/init.lua | 201 +++++------------------------- lua/neogit/lib/ui/renderer.lua | 220 +++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 181 deletions(-) create mode 100644 lua/neogit/lib/ui/renderer.lua diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 640edc293..e3c13e744 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -50,7 +50,7 @@ end function Buffer:focus() local windows = fn.win_findbuf(self.handle) - if #windows == 0 then + if not windows or not windows[1] then return nil end @@ -127,7 +127,7 @@ function Buffer:buffered_add_line_highlight(...) end function Buffer:resize(length) - api.nvim_buf_set_lines(self.handle, length, -1, false, {}) + api.nvim_buf_set_lines(self.handle, length or #self.line_buffer, -1, false, {}) end function Buffer:flush_line_buffer() @@ -138,32 +138,48 @@ function Buffer:flush_line_buffer() end function Buffer:flush_highlight_buffer() - for _, highlight in ipairs(self.hl_buffer) do + self:set_highlights(self.hl_buffer) + self.hl_buffer = {} +end + +function Buffer:set_highlights(highlights) + for _, highlight in ipairs(highlights) do self:add_highlight(unpack(highlight)) end - self.hl_buffer = {} end function Buffer:flush_extmark_buffer() - for _, ext in ipairs(self.ext_buffer) do + self:set_extmarks(self.ext_buffer) + self.ext_buffer = {} +end + +function Buffer:set_extmarks(extmarks) + for _, ext in ipairs(extmarks) do self:set_extmark(unpack(ext)) end - self.ext_buffer = {} end function Buffer:flush_line_highlight_buffer() - for _, hl in ipairs(self.line_hl_buffer) do + self:set_line_highlights(self.line_hl_buffer) + self.line_hl_buffer = {} +end + +function Buffer:set_line_highlights(highlights) + for _, hl in ipairs(highlights) do self:add_line_highlight(unpack(hl)) end - self.line_hl_buffer = {} end function Buffer:flush_fold_buffer() - for _, fold in ipairs(self.fold_buffer) do + self:set_folds(self.fold_buffer) + self.fold_buffer = {} +end + +function Buffer:set_folds(folds) + for _, fold in ipairs(folds) do self:create_fold(unpack(fold)) self:set_fold_state(unpack(fold)) end - self.fold_buffer = {} end function Buffer:flush_buffers() diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 7443610ff..2cf5c7e4c 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -1,5 +1,6 @@ local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") +local Renderer = require("neogit.lib.ui.renderer") local filter = util.filter @@ -12,14 +13,12 @@ local filter = util.filter ---@field buf number ---@field layout table local Ui = {} +Ui.__index = Ui +---@param buf Buffer +---@return Ui function Ui.new(buf) - local this = { - buf = buf, - layout = {}, - } - setmetatable(this, { __index = Ui }) - return this + return setmetatable({ buf = buf, layout = {} }, Ui) end function Ui._print_component(indent, c, _options) @@ -185,181 +184,36 @@ function Ui.visualize_tree(components, options) Ui._visualize_tree(1, components, options or {}) end -function Ui:_render(first_line, first_col, parent, components, flags) - local curr_line = first_line - - if flags.in_row then - local col_start = first_col - local col_end - local highlights = {} - local text = {} - - for i, c in ipairs(components) do - c.parent = parent - c.index = i - - c.position = {} - c.position.row_start = curr_line - first_line + 1 - - local highlight = c:get_highlight() - - if c.tag == "text" then - local padding_left = flags.in_nested_row and "" or c:get_padding_left(i == 1) - table.insert(text, 1, padding_left) - - col_start = col_start + #padding_left - col_end = col_start + c:get_width() - c.position.col_start = col_start - c.position.col_end = col_end - 1 - - if c.options.align_right then - table.insert(text, c.value) - table.insert(text, (" "):rep(c.options.align_right - #c.value)) - else - table.insert(text, c.value) - end - - if highlight then - table.insert(highlights, { - from = col_start, - to = col_end, - name = highlight, - }) - end - - col_start = col_end - elseif c.tag == "row" then - flags.in_nested_row = true - - local padding_left = flags.in_nested_row and "" or c:get_padding_left(i == 1) - local res = self:_render(curr_line, col_start, c, c.children, flags) - - flags.in_nested_row = false - - if c.position.col_end then - c.position.col_end = c.position.col_end + #padding_left - end - - table.insert(text, padding_left) - table.insert(text, res.text) - - for _, h in ipairs(res.highlights) do - h.to = h.to + #padding_left - table.insert(highlights, h) - end - - col_end = col_start + vim.fn.strdisplaywidth(res.text) - c.position.col_start = col_start - c.position.col_end = col_end - col_start = col_end - else - error("The row component does not support having a `" .. c.tag .. "` as child") - end - - c.position.row_end = c.position.row_start - end - - if flags.in_nested_row then - return { - text = table.concat(text), - highlights = highlights, - } - end - - self.buf:buffered_set_line(table.concat(text)) - - for _, h in ipairs(highlights) do - self.buf:buffered_add_highlight(curr_line - 1, h.from, h.to, h.name) - end - - curr_line = curr_line + 1 - else - for i, c in ipairs(components) do - c.parent = parent - c.index = i - - c.position = {} - c.position.row_start = curr_line - first_line + 1 - c.position.col_start = 0 - c.position.col_end = -1 - - local line_hl = c:get_line_highlight() - local highlight = c:get_highlight() - - if c.tag == "text" then - self.buf:buffered_set_line(table.concat { c:get_padding_left(), c.value }) - - if highlight then - self.buf:buffered_add_highlight(curr_line - 1, c.position.col_start, c.position.col_end, highlight) - end - - if line_hl then - self.buf:buffered_add_line_highlight(curr_line - 1, line_hl) - end - - curr_line = curr_line + 1 - elseif c.tag == "col" then - curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) - elseif c.tag == "row" then - flags.in_row = true - curr_line = curr_line + self:_render(curr_line, 0, c, c.children, flags) - - if line_hl then - self.buf:buffered_add_line_highlight(curr_line - 2, line_hl) - end - - if c.options.virtual_text then - local ns = self.buf:create_namespace("NeogitBufferVirtualText") - self.buf:buffered_set_extmark(ns, curr_line - 2, 0, { - hl_mode = "combine", - virt_text = c.options.virtual_text, - virt_text_pos = "right_align", - }) - end - - flags.in_row = false - end - - c.position.row_end = curr_line - first_line - - if c.options.foldable then - self.buf:buffered_create_fold( - #self.buf.line_buffer - (c.position.row_end - c.position.row_start), - #self.buf.line_buffer, - not c.options.folded - ) - end - end - end - - return curr_line - first_line -end - function Ui:render(...) self.layout = { ... } self.layout = filter(self.layout, function(x) return type(x) == "table" end) + self:update() end -- This shouldn't be called often as it completely rewrites the whole buffer function Ui:update() + local root = Component.new(function() + return { + tag = "_root", + children = self.layout, + } + end)() + + local ns = self.buf:create_namespace("VirtualText") + local buffer = Renderer:new(ns):render(root) + self.buf:unlock() - local lines_used = self:_render( - 1, - 0, - Component.new(function() - return { - tag = "_root", - children = self.layout, - } - end)(), - self.layout, - {} - ) - self.buf:resize(lines_used) - self.buf:flush_buffers() + self.buf:clear() + self.buf:clear_namespace("default") + self.buf:resize(#buffer.line) + self.buf:set_lines(0, -1, false, buffer.line) + self.buf:set_highlights(buffer.highlight) + self.buf:set_extmarks(buffer.extmark) + self.buf:set_line_highlights(buffer.line_highlight) + self.buf:set_folds(buffer.fold) self.buf:lock() end @@ -405,9 +259,14 @@ Ui.text = Component.new(function(value, options, ...) tag = "text", value = value or "", options = type(options) == "table" and options or nil, + __index = { + render = function(self) + return self.value + end + } } end) -Ui.Component = require("neogit.lib.ui.component") +Ui.Component = Component return Ui diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua new file mode 100644 index 000000000..53016e590 --- /dev/null +++ b/lua/neogit/lib/ui/renderer.lua @@ -0,0 +1,220 @@ +---@class RendererBuffer +---@field line string[] +---@field highlight table[] +---@field line_highlight table[] +---@field extmark table[] +---@field fold table[] + +---@class RendererFlags +---@field in_row boolean +---@field in_nested_row boolean + +---@class Renderer +---@field buffer RendererBuffer +---@field flags RendererFlags +---@field namespace integer +---@field current_line number +---@field current_column number +local Renderer = {} + +function Renderer:new(namespace) + local obj = { + namespace = namespace, + buffer = { + line = {}, + highlight = {}, + line_highlight = {}, + extmark = {}, + fold = {}, + }, + flags = { + in_row = false, + in_nested_row = false, + }, + curr_line = 1, + } + + setmetatable(obj, self) + self.__index = self + + return obj +end + +function Renderer:render(root) + self:_render(0, root, root.children) + + return self.buffer +end + +function Renderer:_render(first_col, parent, children) + local col_start = first_col + + if self.flags.in_row then + local col_end + local highlights = {} + local text = {} + + for i, c in ipairs(children) do + col_start = self:_render_in_row_child(c, parent, i, col_start, col_end, highlights, text) + end + + if self.flags.in_nested_row then + return { text = table.concat(text), highlights = highlights } + end + + table.insert(self.buffer.line, table.concat(text)) + + for _, h in ipairs(highlights) do + table.insert(self.buffer.highlight, { self.curr_line - 1, h.from, h.to, h.name }) + end + + self.curr_line = self.curr_line + 1 + else + for i, c in ipairs(children) do + self:_render_child(c, parent, i) + end + end +end + +function Renderer:_render_in_row_child(child, parent, i, col_start, col_end, highlights, text) + child.parent = parent + child.index = i + + child.position = {} + child.position.row_start = self.curr_line + + if child.tag == "text" then + col_start = self:_render_in_row_text(child, i, col_start, highlights, text) + elseif child.tag == "row" then + col_start = self:_render_in_row_row(child, highlights, text, col_start, col_end) + else + error("The row component does not support having a `" .. child.tag .. "` as a child") + end + + child.position.row_end = child.position.row_start + + return col_start +end + +function Renderer:_render_in_row_text(child, index, col_start, highlights, text) + local padding_left = self.flags.in_nested_row and "" or child:get_padding_left(index == 1) + + table.insert(text, 1, padding_left) + + col_start = col_start + #padding_left + local col_end = col_start + child:get_width() + + child.position.col_start = col_start + child.position.col_end = col_end - 1 + + if child.options.align_right then + table.insert(text, child.value) + table.insert(text, (" "):rep(child.options.align_right - #child.value)) + else + table.insert(text, child.value) + end + + local highlight = child:get_highlight() + if highlight then + table.insert(highlights, { + from = col_start, + to = col_end, + name = highlight, + }) + end + + return col_end +end + +function Renderer:_render_child_text(child) + table.insert(self.buffer.line, table.concat { child:get_padding_left(), child.value }) + + local highlight = child:get_highlight() + if highlight then + table.insert(self.buffer.highlight, { + self.curr_line - 1, + child.position.col_start, + child.position.col_end, + highlight, + }) + end + + local line_hl = child:get_line_highlight() + if line_hl then + table.insert(self.buffer.line_highlight, { self.curr_line - 1, line_hl }) + end + + self.curr_line = self.curr_line + 1 +end + +function Renderer:_render_child(child, parent, i) + child.parent = parent + child.index = i + + child.position = {} + child.position.row_start = self.curr_line + child.position.col_start = 0 + child.position.col_end = -1 + + if child.tag == "text" then + self:_render_child_text(child) + elseif child.tag == "col" then + self:_render(0, child, child.children) + elseif child.tag == "row" then + self:_render_child_row(child) + end + + child.position.row_end = self.curr_line - 1 + + if child.options.foldable then + table.insert(self.buffer.fold, { + #self.buffer.line - (child.position.row_end - child.position.row_start), + #self.buffer.line, + not child.options.folded, + }) + end +end + +function Renderer:_render_in_row_row(child, highlights, text, col_start, col_end) + self.flags.in_nested_row = true + local res = self:_render(col_start, child, child.children) + self.flags.in_nested_row = false + + table.insert(text, res.text) + + for _, h in ipairs(res.highlights) do + table.insert(highlights, h) + end + + col_end = col_start + vim.fn.strdisplaywidth(res.text) + child.position.col_start = col_start + child.position.col_end = col_end + + return col_end +end + +function Renderer:_render_child_row(child) + self.flags.in_row = true + self:_render(0, child, child.children) + self.flags.in_row = false + + local line_hl = child:get_line_highlight() + if line_hl then + table.insert(self.buffer.line_highlight, { self.curr_line - 2, line_hl }) + end + + if child.options.virtual_text then + table.insert(self.buffer.extmark, { + self.namespace, + self.curr_line - 2, + 0, + { + hl_mode = "combine", + virt_text = child.options.virtual_text, + virt_text_pos = "right_align", + }, + }) + end +end + +return Renderer From a4ca861cb4fddeac825679eea6a493f2fd7861f9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 22:00:24 +0100 Subject: [PATCH 090/443] Notes --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/buffers/refs_view/ui.lua | 2 ++ lua/neogit/lib/ui/component.lua | 14 +++++++++++++- lua/neogit/lib/ui/init.lua | 8 +++----- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index c9213c728..b66f26b2e 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -39,7 +39,7 @@ local M = { ---Creates a new CommitViewBuffer ---@param commit_id string the id of the commit/tag ----@param filter string[]? Filter diffs to filepaths in table +---@param filter? string[] Filter diffs to filepaths in table ---@return CommitViewBuffer function M.new(commit_id, filter) local commit_info = diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index b0baab802..79c021aa0 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -58,6 +58,8 @@ local function section(refs, heading, head) col.tag("Ref")({ Ref(ref) }, { oid = ref.oid, foldable = true, + ---@param this Component + ---@param ui Ui on_open = a.void(function(this, ui) vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.oid:sub(1, 7))) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 32bb5b8ad..592009970 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -12,15 +12,26 @@ local default_component_options = { ---@field col_end integer ---@class ComponentOptions +---@field line_hl string +---@field highlight string +---@field align_right integer|nil ---@field padding_left integer ---@field tag string ---@field foldable boolean ---@field folded boolean +---@field context boolean +---@field interactive boolean +---@field virtual_text string ---@class Component ---@field position ComponentPosition ---@field parent Component - +---@field children Component[] +---@field tag string|nil +---@field options ComponentOptions +---@field index number|nil +---@field value string|nil +---@field id string|nil local Component = {} function Component:row_range_abs() @@ -52,6 +63,7 @@ function Component:is_under_cursor(cursor) local row_ok = from <= row and row <= to local col_ok = self.position.col_end == -1 or (self.position.col_start <= col and col <= self.position.col_end) + return row_ok and col_ok end diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 2cf5c7e4c..806bb34b4 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -9,8 +9,10 @@ local filter = util.filter ---@field options table Component props or arguments ---@field children UiComponent[] +---@class FindOptions + ---@class Ui ----@field buf number +---@field buf Buffer ---@field layout table local Ui = {} Ui.__index = Ui @@ -80,10 +82,6 @@ function Ui._find_component(components, f, options) return nil end ----@class FindOptions - ---- Finds a ui component in the buffer ---- ---@param f fun(c: UiComponent): boolean ---@param options FindOptions|nil function Ui:find_component(f, options) From fcc1c18acca9a0d08ae0d9a17121bf8fc422422a Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 22:01:33 +0100 Subject: [PATCH 091/443] I shoulda commit more frequently. Basically, this is a big refactoring of how the rendering logic works, and some of the cleanup after. Adding an index of nodes makes for -way- faster lookups for things like context and interactive elements. --- lua/neogit/buffers/common.lua | 4 +- lua/neogit/buffers/log_view/init.lua | 4 +- lua/neogit/buffers/reflog_view/init.lua | 6 +- lua/neogit/lib/buffer.lua | 19 +- lua/neogit/lib/popup/init.lua | 36 ++-- lua/neogit/lib/ui/init.lua | 172 ++++++--------- lua/neogit/lib/ui/renderer.lua | 265 +++++++++++++++--------- 7 files changed, 266 insertions(+), 240 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index ab9da4cb6..e9ace18f7 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -37,7 +37,7 @@ M.Diff = Component.new(function(diff) col.tag("DiffInfo")(map(diff.info, text)), col.tag("HunkList")(map(hunk_props, M.Hunk)), }, - }, { foldable = true, folded = false }) + }, { foldable = true, folded = false, context = true }) end) local HunkLine = Component.new(function(line) @@ -58,7 +58,7 @@ M.Hunk = Component.new(function(props) return col.tag("Hunk")({ text.line_hl("NeogitHunkHeader")(props.header), col.tag("HunkContent")(map(props.content, HunkLine)), - }, { foldable = true, folded = false }) + }, { foldable = true, folded = false, context = true }) end) M.List = Component.new(function(props) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index e31ac0663..91ce77fdf 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -105,9 +105,7 @@ function M:open() [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), - [popups.mapping_for("RemotePopup")] = popups.open("remote", function(p) - p { remote = self.buffer.ui:get_item_options().remote } - end), + [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 737bf8a63..59306749c 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -115,8 +115,10 @@ function M:open(_) self:close() end, [""] = function() - local stack = self.buffer.ui:get_component_stack_under_cursor() - CommitViewBuffer.new(stack[#stack].options.oid):open() + local oid = self.buffer.ui:get_commit_under_cursor() + if oid then + CommitViewBuffer.new(oid):open() + end end, [popups.mapping_for("DiffPopup")] = function() if not config.check_integration("diffview") then diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e3c13e744..1e05c901e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -199,6 +199,10 @@ function Buffer:move_cursor(line) api.nvim_win_set_cursor(0, { line, 0 }) end +function Buffer:cursor_line() + return api.nvim_win_get_cursor(0)[1] +end + function Buffer:close(force) if force == nil then force = false @@ -590,19 +594,18 @@ function Buffer.create(config) local function on_win() buffer:clear_namespace("ViewContext") - -- TODO: this is WAY to slow to be called so frequently, especially in a large buffer - local stack = buffer.ui:get_component_stack_under_cursor() - if not stack then + local context = buffer.ui:get_cursor_context() + if not context then return end - local hovered_component = stack[2] or stack[1] - local first, last = hovered_component:row_range_abs() - local top_level = hovered_component.parent and not hovered_component.parent.parent + local first = context.position.row_start + local last = context.position.row_end for line = fn.line("w0"), fn.line("w$") do - if first and last and line >= first and line <= last and not top_level then - local line_hl = buffer.ui:get_component_stack_on_line(line)[1].options.line_hl + if line >= first and line <= last then + local line_hl = buffer.ui:get_line_highlight(line) + buffer:buffered_add_line_highlight( line - 1, (line_hl or "NeogitDiffContext") .. "Highlight", diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 740aa7bda..d8e693ed2 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -385,7 +385,7 @@ local Switch = Component.new(function(switch) .highlight(get_highlight_for_switch(switch)) { text(switch.cli_prefix), text(switch.cli) } end - return row.tag("Switch").value(switch) { + return row.tag("Switch").value(switch)({ row.highlight("NeogitPopupSwitchKey") { text(switch.key_prefix), text(switch.key), @@ -395,11 +395,11 @@ local Switch = Component.new(function(switch) text(" ("), value, text(")"), - } + }, { interactive = true }) end) local Option = Component.new(function(option) - return row.tag("Option").value(option) { + return row.tag("Option").value(option)({ row.highlight("NeogitPopupOptionKey") { text(option.key_prefix), text(option.key), @@ -414,7 +414,7 @@ local Option = Component.new(function(option) text(option.value or ""), }, text(")"), - } + }, { interactive = true }) end) local Section = Component.new(function(title, items) @@ -461,11 +461,11 @@ local Config = Component.new(function(props) key = config.key end - return row.tag("Config").value(config) { + return row.tag("Config").value(config)({ row.highlight("NeogitPopupConfigKey") { text(key) }, text(" " .. config.name .. " "), row.id(config.id) { unpack(value) }, - } + }, { interactive = true }) end)) ) @@ -521,19 +521,17 @@ function M:show() self:close() end, [""] = function() - local stack = self.buffer.ui:get_component_stack_under_cursor() - - for _, x in ipairs(stack) do - if x.options.tag == "Switch" then - self:toggle_switch(x.options.value) - break - elseif x.options.tag == "Config" then - self:set_config(x.options.value) - break - elseif x.options.tag == "Option" then - self:set_option(x.options.value) - break - end + local component = self.buffer.ui:get_interactive_component_under_cursor() + if not component then + return + end + + if component.options.tag == "Switch" then + self:toggle_switch(component.options.value) + elseif component.options.tag == "Config" then + self:set_config(component.options.value) + elseif component.options.tag == "Option" then + self:set_option(component.options.value) end end, }, diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 806bb34b4..0b9c5c24c 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -2,8 +2,6 @@ local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") local Renderer = require("neogit.lib.ui.renderer") -local filter = util.filter - ---@class UiComponent ---@field tag string ---@field options table Component props or arguments @@ -23,47 +21,6 @@ function Ui.new(buf) return setmetatable({ buf = buf, layout = {} }, Ui) end -function Ui._print_component(indent, c, _options) - local output = string.rep(" ", indent) - if c.position then - local text = "" - if c.position.row_start == c.position.row_end then - text = c.position.row_start - else - text = c.position.row_start .. " - " .. c.position.row_end - end - - if c.position.col_end ~= -1 then - text = text .. " | " .. c.position.col_start .. " - " .. c.position.col_end - end - - output = output .. "[" .. text .. "]" - end - - output = output .. " " .. c:get_tag() - - if c.tag == "text" then - output = output .. " '" .. c.value .. "'" - end - - for k, v in pairs(c.options) do - if k ~= "tag" then - output = output .. " " .. k .. "=" .. tostring(v) - end - end - - print(output) -end - -function Ui._visualize_tree(indent, components, options) - for _, c in ipairs(components) do - Ui._print_component(indent, c, options) - if c.tag == "col" or c.tag == "row" then - Ui._visualize_tree(indent + 1, c.children, options) - end - end -end - function Ui._find_component(components, f, options) for _, c in ipairs(components) do if c.tag == "col" or c.tag == "row" then @@ -106,85 +63,103 @@ function Ui:find_components(f, options) return result end +-- Check with node index function Ui:get_component_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) + ---@param c Component return self:find_component(function(c) return c:is_under_cursor(cursor) end) end +-- Check with node index function Ui:get_component_on_line(line) + ---@param c Component return self:find_component(function(c) return c:is_under_cursor { line, 0 } end) end -function Ui:get_component_stack_under_cursor() +---@param line integer +---@param f fun(c: Component): boolean +---@return Component|nil +function Ui:_find_component_by_index(line, f) + local node = self.node_index:find_by_line(line)[1] + while node do + if f(node) then + return node + end + + node = node.parent + end +end + +---@return Component|nil +function Ui:get_cursor_context() local cursor = vim.api.nvim_win_get_cursor(0) - return self:find_components(function(c) - return c:is_under_cursor(cursor) + return self:_find_component_by_index(cursor[1], function(node) + return node.options.context end) end -function Ui:get_fold_under_cursor() - local cursor = vim.api.nvim_win_get_cursor(0) - return self:find_component(function(c) - return c.options.foldable and c:is_under_cursor(cursor) +---@return string|nil +function Ui:get_line_highlight(line) + local component = self:_find_component_by_index(line, function(node) + return node.options.line_hl ~= nil end) + + return component and component.options.line_hl end -function Ui:get_component_stack_in_linewise_selection() - local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } - table.sort(range) - local start, stop = unpack(range) +---@return Component|nil +function Ui:get_interactive_component_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) - return self:find_components(function(c) - return c:is_in_linewise_range(start, stop) + return self:_find_component_by_index(cursor[1], function(node) + return node.options.interactive end) end -function Ui:get_component_stack_on_line(line) - return self:find_components(function(c) - return c:is_under_cursor { line, 0 } +function Ui:get_fold_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) + + return self:_find_component_by_index(cursor[1], function(node) + return node.options.foldable end) end function Ui:get_commits_in_selection() - local commits = util.filter_map(self:get_component_stack_in_linewise_selection(), function(c) - if c.options.oid then - return c.options.oid - end - end) + local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } + table.sort(range) + local start, stop = unpack(range) - -- Reversed so that the oldest commit is the first in the list - return util.reverse(commits) -end + local commits = {} + for i = start, stop do + local component = self:_find_component_by_index(i, function(node) + return node.options.oid + end) -function Ui:get_commit_under_cursor() - local stack = self:get_component_stack_under_cursor() - return stack[#stack].options.oid -end + if component then + table.insert(commits, 1, component.options.oid) + end + end -function Ui:get_item_options() - local stack = self:get_component_stack_under_cursor() - return stack[#stack].options or {} + return util.deduplicate(commits) end -function Ui.visualize_component(c, options) - Ui._print_component(0, c, options or {}) - if c.tag == "col" or c.tag == "row" then - Ui._visualize_tree(1, c.children, options or {}) - end -end +---@return string|nil +function Ui:get_commit_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) + local component = self:_find_component_by_index(cursor[1], function(node) + return node.options.oid + end) -function Ui.visualize_tree(components, options) - print("root") - Ui._visualize_tree(1, components, options or {}) + return component and component.options.oid end function Ui:render(...) self.layout = { ... } - self.layout = filter(self.layout, function(x) + self.layout = util.filter(self.layout, function(x) return type(x) == "table" end) @@ -193,15 +168,11 @@ end -- This shouldn't be called often as it completely rewrites the whole buffer function Ui:update() - local root = Component.new(function() - return { - tag = "_root", - children = self.layout, - } - end)() - local ns = self.buf:create_namespace("VirtualText") - local buffer = Renderer:new(ns):render(root) + local buffer, index = Renderer:new(ns):render(self.layout) + + self.node_index = index + local cursor_line = self.buf:cursor_line() self.buf:unlock() self.buf:clear() @@ -213,21 +184,14 @@ function Ui:update() self.buf:set_line_highlights(buffer.line_highlight) self.buf:set_folds(buffer.fold) self.buf:lock() -end ---- Will only work if something has been rendered -function Ui:print_layout_tree(options) - Ui.visualize_tree(self.layout, options) -end - -function Ui:debug(...) - Ui.visualize_tree({ ... }, {}) + self.buf:move_cursor(cursor_line) end Ui.col = Component.new(function(children, options) return { tag = "col", - children = filter(children, function(x) + children = util.filter(children, function(x) return type(x) == "table" end), options = options, @@ -237,7 +201,7 @@ end) Ui.row = Component.new(function(children, options) return { tag = "row", - children = filter(children, function(x) + children = util.filter(children, function(x) return type(x) == "table" end), options = options, @@ -260,8 +224,8 @@ Ui.text = Component.new(function(value, options, ...) __index = { render = function(self) return self.value - end - } + end, + }, } end) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 53016e590..51f1782ad 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -1,3 +1,44 @@ +local Component = require("neogit.lib.ui.component") + +---@class RendererIndex +---@field index table +local RendererIndex = {} +RendererIndex.__index = RendererIndex + +---@param line number +---@return Component[] +function RendererIndex:find_by_line(line) + return self.index[line] or {} +end + +---@param id string +---@return Component +function RendererIndex:find_by_id(id) + return self.index[id] +end + +---@param node Component +function RendererIndex:add(node) + if not self.index[node.position.row_start] then + self.index[node.position.row_start] = {} + end + + table.insert(self.index[node.position.row_start], node) +end + +---@param node Component +function RendererIndex:add_id(node) + if tonumber(node.id) then + error("Cannot use an integer ID for a component") + end + + self.index[node.id] = node +end + +function RendererIndex.new() + return setmetatable({ index = {} }, RendererIndex) +end + ---@class RendererBuffer ---@field line string[] ---@field highlight table[] @@ -13,8 +54,8 @@ ---@field buffer RendererBuffer ---@field flags RendererFlags ---@field namespace integer ----@field current_line number ---@field current_column number +---@field index table local Renderer = {} function Renderer:new(namespace) @@ -27,11 +68,11 @@ function Renderer:new(namespace) extmark = {}, fold = {}, }, + index = RendererIndex.new(), flags = { in_row = false, in_nested_row = false, }, - curr_line = 1, } setmetatable(obj, self) @@ -40,22 +81,51 @@ function Renderer:new(namespace) return obj end -function Renderer:render(root) - self:_render(0, root, root.children) +---@param layout table +---@return RendererBuffer, RendererIndex +function Renderer:render(layout) + local root = Component.new(function() + return { + tag = "_root", + children = layout, + } + end)() + + self:_render(root, root.children, 0) - return self.buffer + return self.buffer, self.index end -function Renderer:_render(first_col, parent, children) - local col_start = first_col +function Renderer:_build_child(child, parent, index) + if child.id then + self.index:add_id(child) + end + + child.parent = parent + + child.index = index + child.position = { + row_start = #self.buffer.line + 1, + row_end = self.flags.in_row and #self.buffer.line + 1 or -1, + col_start = 0, + col_end = -1, + } +end + +---@param parent Component +---@param children Component[] +---@param column integer +function Renderer:_render(parent, children, column) if self.flags.in_row then + local col_start = column local col_end local highlights = {} local text = {} - for i, c in ipairs(children) do - col_start = self:_render_in_row_child(c, parent, i, col_start, col_end, highlights, text) + for index, child in ipairs(children) do + self:_build_child(child, parent, index) + col_start = self:_render_child_in_row(child, index, col_start, col_end, highlights, text) end if self.flags.in_nested_row then @@ -65,119 +135,134 @@ function Renderer:_render(first_col, parent, children) table.insert(self.buffer.line, table.concat(text)) for _, h in ipairs(highlights) do - table.insert(self.buffer.highlight, { self.curr_line - 1, h.from, h.to, h.name }) + table.insert(self.buffer.highlight, { #self.buffer.line - 1, h.from, h.to, h.name }) end - - self.curr_line = self.curr_line + 1 else - for i, c in ipairs(children) do - self:_render_child(c, parent, i) + for index, child in ipairs(children) do + self:_build_child(child, parent, index) + self:_render_child(child) end end end -function Renderer:_render_in_row_child(child, parent, i, col_start, col_end, highlights, text) - child.parent = parent - child.index = i - - child.position = {} - child.position.row_start = self.curr_line - +---@param child Component +function Renderer:_render_child(child) if child.tag == "text" then - col_start = self:_render_in_row_text(child, i, col_start, highlights, text) + self:_render_text(child) + elseif child.tag == "col" then + self:_render_col(child) elseif child.tag == "row" then - col_start = self:_render_in_row_row(child, highlights, text, col_start, col_end) - else - error("The row component does not support having a `" .. child.tag .. "` as a child") + self:_render_row(child) end - child.position.row_end = child.position.row_start - - return col_start -end + child.position.row_end = #self.buffer.line -function Renderer:_render_in_row_text(child, index, col_start, highlights, text) - local padding_left = self.flags.in_nested_row and "" or child:get_padding_left(index == 1) - - table.insert(text, 1, padding_left) - - col_start = col_start + #padding_left - local col_end = col_start + child:get_width() - - child.position.col_start = col_start - child.position.col_end = col_end - 1 + local line_hl = child:get_line_highlight() + if line_hl then + table.insert(self.buffer.line_highlight, { #self.buffer.line - 1, line_hl }) + end - if child.options.align_right then - table.insert(text, child.value) - table.insert(text, (" "):rep(child.options.align_right - #child.value)) - else - table.insert(text, child.value) + if child.options.virtual_text then + table.insert(self.buffer.extmark, { + self.namespace, + #self.buffer.line - 1, + 0, + { + hl_mode = "combine", + virt_text = child.options.virtual_text, + virt_text_pos = "right_align", + }, + }) end - local highlight = child:get_highlight() - if highlight then - table.insert(highlights, { - from = col_start, - to = col_end, - name = highlight, + if child.options.foldable then + table.insert(self.buffer.fold, { + #self.buffer.line - (child.position.row_end - child.position.row_start), + #self.buffer.line, + not child.options.folded, }) end +end - return col_end +---@param child Component +function Renderer:_render_row(child) + self.flags.in_row = true + self:_render(child, child.children, 0) + self.flags.in_row = false end -function Renderer:_render_child_text(child) - table.insert(self.buffer.line, table.concat { child:get_padding_left(), child.value }) +---@param child Component +function Renderer:_render_col(child) + self:_render(child, child.children, 0) +end +---@param child Component +function Renderer:_render_text(child) local highlight = child:get_highlight() if highlight then table.insert(self.buffer.highlight, { - self.curr_line - 1, + #self.buffer.line, child.position.col_start, child.position.col_end, highlight, }) end - local line_hl = child:get_line_highlight() - if line_hl then - table.insert(self.buffer.line_highlight, { self.curr_line - 1, line_hl }) + local line_highlight = child:get_line_highlight() + if line_highlight then + table.insert(self.buffer.line_highlight, { #self.buffer.line, line_highlight }) end - self.curr_line = self.curr_line + 1 + table.insert(self.buffer.line, table.concat { child:get_padding_left(), child.value }) + self.index:add(child) end -function Renderer:_render_child(child, parent, i) - child.parent = parent - child.index = i - - child.position = {} - child.position.row_start = self.curr_line - child.position.col_start = 0 - child.position.col_end = -1 +-- TODO: This nested-row shit is lame. V +---@param child Component +---@param i integer index of child in parent.children +function Renderer:_render_child_in_row(child, i, col_start, col_end, highlights, text) if child.tag == "text" then - self:_render_child_text(child) - elseif child.tag == "col" then - self:_render(0, child, child.children) + return self:_render_in_row_text(child, i, col_start, highlights, text) elseif child.tag == "row" then - self:_render_child_row(child) + return self:_render_in_row_row(child, highlights, text, col_start, col_end) + else + error("The row component does not support having a `" .. child.tag .. "` as a child") end +end - child.position.row_end = self.curr_line - 1 +---@param child Component +---@param index integer index of child in parent.children +function Renderer:_render_in_row_text(child, index, col_start, highlights, text) + local padding_left = self.flags.in_nested_row and "" or child:get_padding_left(index == 1) + table.insert(text, 1, padding_left) - if child.options.foldable then - table.insert(self.buffer.fold, { - #self.buffer.line - (child.position.row_end - child.position.row_start), - #self.buffer.line, - not child.options.folded, - }) + col_start = col_start + #padding_left + local col_end = col_start + child:get_width() + + child.position.col_start = col_start + child.position.col_end = col_end - 1 + + if child.options.align_right then + table.insert(text, child.value) + table.insert(text, (" "):rep(child.options.align_right - #child.value)) + else + table.insert(text, child.value) + end + + local highlight = child:get_highlight() + if highlight then + table.insert(highlights, { from = col_start, to = col_end, name = highlight }) end + + self.index:add(child) + return col_end end +---@param child Component function Renderer:_render_in_row_row(child, highlights, text, col_start, col_end) self.flags.in_nested_row = true - local res = self:_render(col_start, child, child.children) + local res = self:_render(child, child.children, col_start) self.flags.in_nested_row = false table.insert(text, res.text) @@ -193,28 +278,4 @@ function Renderer:_render_in_row_row(child, highlights, text, col_start, col_end return col_end end -function Renderer:_render_child_row(child) - self.flags.in_row = true - self:_render(0, child, child.children) - self.flags.in_row = false - - local line_hl = child:get_line_highlight() - if line_hl then - table.insert(self.buffer.line_highlight, { self.curr_line - 2, line_hl }) - end - - if child.options.virtual_text then - table.insert(self.buffer.extmark, { - self.namespace, - self.curr_line - 2, - 0, - { - hl_mode = "combine", - virt_text = child.options.virtual_text, - virt_text_pos = "right_align", - }, - }) - end -end - return Renderer From dc0312f80b4c2063ff24dc4a79ab4de601256461 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:25:08 +0100 Subject: [PATCH 092/443] Use the ID from options for indexing nodes --- lua/neogit/lib/ui/renderer.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 51f1782ad..ae15f13ce 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -28,11 +28,11 @@ end ---@param node Component function RendererIndex:add_id(node) - if tonumber(node.id) then + if tonumber(node.options.id) then error("Cannot use an integer ID for a component") end - self.index[node.id] = node + self.index[node.options.id] = node end function RendererIndex.new() @@ -97,14 +97,12 @@ function Renderer:render(layout) end function Renderer:_build_child(child, parent, index) - if child.id then + if child.options.id then self.index:add_id(child) end child.parent = parent - child.index = index - child.position = { row_start = #self.buffer.line + 1, row_end = self.flags.in_row and #self.buffer.line + 1 or -1, From 58161ae3b6752b19c4ee469eb1209c157a8e05ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:25:32 +0100 Subject: [PATCH 093/443] Rewrite these to use the node index --- lua/neogit/lib/ui/init.lua | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 0b9c5c24c..78f619646 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -63,21 +63,18 @@ function Ui:find_components(f, options) return result end --- Check with node index -function Ui:get_component_under_cursor() - local cursor = vim.api.nvim_win_get_cursor(0) - ---@param c Component - return self:find_component(function(c) - return c:is_under_cursor(cursor) - end) +---@param fn fun(c: Component): boolean +---@return Component|nil +function Ui:get_component_under_cursor(fn) + local line = vim.api.nvim_win_get_cursor(0)[1] + return self:get_component_on_line(line, fn) end --- Check with node index -function Ui:get_component_on_line(line) - ---@param c Component - return self:find_component(function(c) - return c:is_under_cursor { line, 0 } - end) +---@param line integer +---@param fn fun(c: Component): boolean +---@return Component|nil +function Ui:get_component_on_line(line, fn) + return self:_find_component_by_index(line, fn) end ---@param line integer @@ -94,6 +91,11 @@ function Ui:_find_component_by_index(line, f) end end +---@return Component|nil +function Ui:find_by_id(id) + return self.node_index:find_by_id(id) +end + ---@return Component|nil function Ui:get_cursor_context() local cursor = vim.api.nvim_win_get_cursor(0) From faf29adaafdcab3d039ff33e98f8a9640038e1d1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:25:43 +0100 Subject: [PATCH 094/443] I don't see how this old code makes sense. The new renderer just gets the right position every time. --- lua/neogit/lib/ui/component.lua | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 592009970..74ed0bc7c 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -35,16 +35,7 @@ local default_component_options = { local Component = {} function Component:row_range_abs() - if self.position.row_end == nil then - return 0, 0 - end - local from = self.position.row_start - local len = self.position.row_end - from - if self.parent.tag ~= "_root" then - local p_from = self.parent:row_range_abs() - from = from + p_from - 1 - end - return from, from + len + return self.position.row_start, self.position.row_end end function Component:get_padding_left(recurse) From 087dcc0101b9267fa7f393d604d2f1e7e70afd86 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:26:15 +0100 Subject: [PATCH 095/443] Remove: unused --- lua/neogit/lib/ui/component.lua | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 74ed0bc7c..4e7cd0b0c 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -22,6 +22,7 @@ local default_component_options = { ---@field context boolean ---@field interactive boolean ---@field virtual_text string +---@field id string|nil ---@class Component ---@field position ComponentPosition @@ -47,22 +48,6 @@ function Component:get_padding_left(recurse) return padding_left_text .. (self.parent and self.parent:get_padding_left() or "") end -function Component:is_under_cursor(cursor) - local row = cursor[1] - local col = cursor[2] - local from, to = self:row_range_abs() - local row_ok = from <= row and row <= to - local col_ok = self.position.col_end == -1 - or (self.position.col_start <= col and col <= self.position.col_end) - - return row_ok and col_ok -end - -function Component:is_in_linewise_range(start, stop) - local from, to = self:row_range_abs() - return from >= start and from <= stop and to >= start and to <= stop -end - function Component:get_width() if self.tag == "text" then local width = string.len(self.value) From 9dabd69d2ab855871dc497fb057238195cb04142 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:26:27 +0100 Subject: [PATCH 096/443] Find by id! --- lua/neogit/lib/popup/init.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index d8e693ed2..02e9f59cd 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -22,6 +22,11 @@ local intersperse = util.intersperse local List = common.List local Grid = common.Grid +---@class PopupState + +---@class Popup +---@field state PopupState +---@field buffer Buffer local M = {} function M.builder() @@ -163,13 +168,11 @@ local function construct_config_options(config, prefix, suffix) end ---@param id integer ID of component to be updated ----@param highlight string New highlight group for value ----@param value string|table New value to display +---@param highlight? string New highlight group for value +---@param value? string|table New value to display ---@return nil function M:update_component(id, highlight, value) - local component = self.buffer.ui:find_component(function(c) - return c.options.id == id - end) + local component = self.buffer.ui:find_by_id(id) assert(component, "Component not found! Cannot update.") From cfc0d3ef45350d5d9a9564401f4c80518c74aceb Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:26:34 +0100 Subject: [PATCH 097/443] Rewrite to work with new component index --- lua/neogit/buffers/commit_view/init.lua | 113 ++++++++++++------------ 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index b66f26b2e..1afa748c4 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -102,61 +102,62 @@ function M:open(kind) mappings = { n = { [""] = function() - local c = self.buffer.ui:get_component_on_line(vim.fn.line(".")) - - local diff_headers - -- Check we are on top of a path on the OverviewFiles - if c.options.highlight == "NeogitFilePath" then - -- Some paths are padded for formatting purposes. We need to trim them - -- in order to use them as match patterns. - local selected_path = vim.fn.trim(c.value) - - diff_headers = {} - - -- Recursively navigate the layout until we hit NeogitDiffHeader leafs - -- Forward declaration required to avoid missing global error - local find_diff_headers - - function find_diff_headers(layout) - if layout.children then - -- One layout element may have multiple children so we need to loop - for _, val in pairs(layout.children) do - local v = find_diff_headers(val) - if v then - -- defensive trim - diff_headers[vim.fn.trim(v[1])] = v[2] - end - end - else - if layout.options.line_hl == "NeogitDiffHeader" then - return { layout.value, layout:row_range_abs() } + local c = self.buffer.ui:get_component_under_cursor(function(c) + return c.options.highlight == "NeogitFilePath" + end) + + if not c then + return + end + + -- Some paths are padded for formatting purposes. We need to trim them + -- in order to use them as match patterns. + local selected_path = vim.fn.trim(c.value) + + local diff_headers = {} + + -- Recursively navigate the layout until we hit NeogitDiffHeader leafs + -- Forward declaration required to avoid missing global error + local find_diff_headers + function find_diff_headers(layout) + if layout.children then + -- One layout element may have multiple children so we need to loop + for _, val in pairs(layout.children) do + local v = find_diff_headers(val) + if v then + -- defensive trim + diff_headers[vim.fn.trim(v[1])] = v[2] end end - end - -- The Diffs are in the 10th element of the layout. - -- TODO: Do better than assume that we care about the last item - find_diff_headers(self.buffer.ui.layout[#self.buffer.ui.layout]) - - -- Search for a match and jump if we find it - for path, line_nr in pairs(diff_headers) do - -- The gsub is to work around the fact that the OverviewFiles use - -- => in renames but the diff header uses -> - local match = string.match(path:gsub(" %-> ", " => "), selected_path) - if match then - local winid = vim.fn.win_getid() - vim.api.nvim_win_set_cursor(winid, { line_nr, 1 }) - break + else + if layout.options.line_hl == "NeogitDiffHeader" then + return { layout.value, layout:row_range_abs() } end end end + + -- The Diffs are in the 10th element of the layout. + -- TODO: Do better than assume that we care about the last item + find_diff_headers(self.buffer.ui.layout[#self.buffer.ui.layout]) + + -- Search for a match and jump if we find it + for path, line_nr in pairs(diff_headers) do + -- The gsub is to work around the fact that the OverviewFiles use + -- => in renames but the diff header uses -> + if path:gsub(" %-> ", " => "):match(selected_path) then + -- Save position in jumplist + vim.cmd("normal! m'") + + self.buffer:move_cursor(line_nr) + break + end + end end, ["{"] = function() -- Goto Previous local function previous_hunk_header(self, line) - local c = self.buffer.ui:get_component_on_line(line) - - while c and not vim.tbl_contains({ "Diff", "Hunk" }, c.options.tag) do - c = c.parent - end + local c = self.buffer.ui:get_component_on_line(line, function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" + end) if c then local first, _ = c:row_range_abs() @@ -175,21 +176,19 @@ function M:open(kind) end end, ["}"] = function() -- Goto next - local c = self.buffer.ui:get_component_under_cursor() - - while c and not vim.tbl_contains({ "Diff", "Hunk" }, c.options.tag) do - c = c.parent - end + local c = self.buffer.ui:get_component_under_cursor(function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" + end) if c then if c.options.tag == "Diff" then - api.nvim_win_set_cursor(0, { vim.fn.line(".") + 1, 0 }) + self.buffer:move_cursor(vim.fn.line(".") + 1) else local _, last = c:row_range_abs() if last == vim.fn.line("$") then - api.nvim_win_set_cursor(0, { last, 0 }) + self.buffer:move_cursor(last) else - api.nvim_win_set_cursor(0, { last + 1, 0 }) + self.buffer:move_cursor(last + 1) end end vim.cmd("normal! zt") @@ -236,6 +235,10 @@ function M:open(kind) [""] = function() pcall(vim.cmd, "normal! za") end, + [""] = function() + -- require("neogit.lib.ui.debug") + -- self.buffer.ui:debug_layout() + end, }, }, render = function() From f2a0d7090f886785b308ac855d32645cb9b4cb46 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:26:45 +0100 Subject: [PATCH 098/443] Add UI debugging as a module --- lua/neogit/lib/ui/debug.lua | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 lua/neogit/lib/ui/debug.lua diff --git a/lua/neogit/lib/ui/debug.lua b/lua/neogit/lib/ui/debug.lua new file mode 100644 index 000000000..a1c783273 --- /dev/null +++ b/lua/neogit/lib/ui/debug.lua @@ -0,0 +1,78 @@ +---@class Ui +local Ui = require("neogit.lib.ui") + +function Ui:debug(...) + Ui.visualize_tree({ ... }) +end + +--- Will only work if something has been rendered +function Ui:debug_layout() + Ui.visualize_tree(self.layout) +end + +function Ui.visualize_tree(components) + local tree = {} + Ui._visualize_tree(1, components, tree) + + vim.lsp.util.open_floating_preview( + tree, + "txt", + { + relative = "editor", + anchor = "NW", + wrap = false, + width = vim.o.columns - 2, + height = vim.o.lines - 2 + } + ) +end + +function Ui._visualize_tree(indent, components, tree) + for _, c in ipairs(components) do + table.insert(tree, Ui._draw_component(indent, c)) + + if c.tag == "col" or c.tag == "row" then + Ui._visualize_tree(indent + 1, c.children, tree) + end + end +end + +function Ui.visualize_component(c, options) + Ui._print_component(0, c, options or {}) + + if c.tag == "col" or c.tag == "row" then + Ui._visualize_tree(1, c.children, options or {}) + end +end + +function Ui._draw_component(indent, c, _) + local output = string.rep(" ", indent) + if c.position then + local text = "" + if c.position.row_start == c.position.row_end then + text = c.position.row_start + else + text = c.position.row_start .. " - " .. c.position.row_end + end + + if c.position.col_end ~= -1 then + text = text .. " | " .. c.position.col_start .. " - " .. c.position.col_end + end + + output = output .. "[" .. text .. "]" + end + + output = output .. " " .. c:get_tag() + + if c.tag == "text" then + output = output .. " '" .. c.value .. "'" + end + + for k, v in pairs(c.options) do + if k ~= "tag" then + output = output .. " " .. k .. "=" .. tostring(v) + end + end + + return output +end From aadb3bc343b41cf7fa821acf9f978d319879b5bb Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Jan 2024 23:40:02 +0100 Subject: [PATCH 099/443] Lint --- lua/neogit/lib/ui/debug.lua | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lua/neogit/lib/ui/debug.lua b/lua/neogit/lib/ui/debug.lua index a1c783273..4e5df63b9 100644 --- a/lua/neogit/lib/ui/debug.lua +++ b/lua/neogit/lib/ui/debug.lua @@ -2,7 +2,7 @@ local Ui = require("neogit.lib.ui") function Ui:debug(...) - Ui.visualize_tree({ ... }) + Ui.visualize_tree { ... } end --- Will only work if something has been rendered @@ -14,17 +14,13 @@ function Ui.visualize_tree(components) local tree = {} Ui._visualize_tree(1, components, tree) - vim.lsp.util.open_floating_preview( - tree, - "txt", - { - relative = "editor", - anchor = "NW", - wrap = false, - width = vim.o.columns - 2, - height = vim.o.lines - 2 - } - ) + vim.lsp.util.open_floating_preview(tree, "txt", { + relative = "editor", + anchor = "NW", + wrap = false, + width = vim.o.columns - 2, + height = vim.o.lines - 2, + }) end function Ui._visualize_tree(indent, components, tree) From 72b25f81928fed2e0a00c5c0c419d7ded4995069 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 23 Jan 2024 22:29:28 +0100 Subject: [PATCH 100/443] Nil check --- lua/neogit/buffers/log_view/init.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 91ce77fdf..84647e5a9 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -133,7 +133,10 @@ function M:open() self:close() end, [""] = function() - CommitViewBuffer.new(self.buffer.ui:get_commit_under_cursor(), self.files):open() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.new(commit, self.files):open() + end end, [""] = function() pcall(vim.cmd, "normal! zc") From effb704a5b941f5d7f59d1b96522e67f0c8a1662 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 23 Jan 2024 22:29:37 +0100 Subject: [PATCH 101/443] Proper docs --- lua/neogit/lib/rpc.lua | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lua/neogit/lib/rpc.lua b/lua/neogit/lib/rpc.lua index c85ea8896..47d431436 100644 --- a/lua/neogit/lib/rpc.lua +++ b/lua/neogit/lib/rpc.lua @@ -1,14 +1,11 @@ -local fn = vim.fn - +---@class RPC +---@field address string +---@field ch string local RPC = {} --- @class RPC --- @field address --- @field ch --- ---- Creates a new rpc channel --- @param address --- @return RPC +---Creates a new rpc channel +---@param address string +---@return RPC function RPC.new(address) local instance = { address = address, @@ -28,11 +25,11 @@ function RPC.create_connection(address) end function RPC:connect() - self.ch = fn.sockconnect("pipe", self.address, { rpc = true }) + self.ch = vim.fn.sockconnect("pipe", self.address, { rpc = true }) end function RPC:disconnect() - fn.chanclose(self.ch) + vim.fn.chanclose(self.ch) self.ch = nil end From 6ad26e5e991121ca65b7d66fb775ee1cb51a9294 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 24 Jan 2024 09:39:44 +0100 Subject: [PATCH 102/443] Implement Reset Worktree action, add git.index.with_temp_index --- lua/neogit/lib/git/cli.lua | 7 ++++ lua/neogit/lib/git/index.lua | 17 ++++++++ lua/neogit/popups/reset/actions.lua | 65 +++++++++++++++++------------ lua/neogit/popups/reset/init.lua | 2 +- 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index e47a51fe7..688ed16e4 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -32,6 +32,13 @@ local configurations = { init = config {}, + ["checkout-index"] = config { + flags = { + all = "--all", + force = "--force", + } + }, + worktree = config { flags = { add = "add", diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 1ff33d6d9..3bcb18d82 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -113,6 +113,23 @@ function M.reset(files) return cli.reset.files(unpack(files)).call() end +---Creates a temp index from a revision and calls the provided function with the index path +---@param revision string Revision to create a temp index from +---@param fn fun(index: string): nil +function M.with_temp_index(revision, fn) + assert(revision, "temp index requires a revision") + assert(fn, "Pass a function to call with temp index") + + local tmp_index = Path:new(vim.uv.os_tmpdir(), ("index.neogit.%s"):format(revision)) + cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call({ hidden = true }) + assert(tmp_index:exists(), "Failed to create temp index") + + fn(tmp_index:absolute()) + + tmp_index:rm() + assert(not tmp_index:exists(), "Failed to remove temp index") +end + -- Make sure the index is in sync as git-status skips it -- Do this manually since the `cli` add --no-optional-locks function M.update() diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index fa54fe94c..4db6198b3 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -1,74 +1,87 @@ -local a = require("plenary.async") local git = require("neogit.lib.git") local util = require("neogit.lib.util") - -local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") +local notification = require("neogit.lib.notification") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} -local function reset(type, popup) +---@param popup Popup +---@return string|nil +local function commit(popup, prompt) local commit if popup.state.env.commit then commit = popup.state.env.commit else - commit = CommitSelectViewBuffer.new(git.log.list()):open_async()[1] + local commits = util.merge( + { git.branch.current() }, + git.branch.get_all_branches(false), + git.tag.list(), + git.refs.heads() + ) + commit = FuzzyFinderBuffer.new(commits):open_async { prompt_prefix = prompt } if not commit then return end end - git.reset[type](commit) + return commit +end + +local function reset(type, popup, prompt) + local target = commit(popup, prompt) + if target then + git.reset[type](target) + end end function M.mixed(popup) - reset("mixed", popup) + reset("mixed", popup, ("Reset %s to"):format(git.branch.current())) end function M.soft(popup) - reset("soft", popup) + reset("soft", popup, ("Soft reset %s to"):format(git.branch.current())) end function M.hard(popup) - reset("hard", popup) + reset("hard", popup, ("Hard reset %s to"):format(git.branch.current())) end function M.keep(popup) - reset("keep", popup) + reset("keep", popup, ("Reset %s to"):format(git.branch.current())) end function M.index(popup) - reset("index", popup) + reset("index", popup, "Reset index to") end --- https://github.com/magit/magit/blob/main/lisp/magit-reset.el#L87 --- function M.worktree() --- end +function M.worktree(popup) + local target = commit(popup, "Reset worktree to") + if target then + git.index.with_temp_index(target, function(index) + git.cli["checkout-index"].all.force.env({ GIT_INDEX_FILE = index }).call() + notification.info(("Reset worktree to %s"):format(target)) + end) + end +end function M.a_file(popup) - local commit - if popup.state.env.commit then - commit = popup.state.env.commit - else - local commits = git.log.list(util.merge({ "--all" }, git.stash.list_refs())) - commit = CommitSelectViewBuffer.new(commits):open_async()[1] - if not commit then - return - end + local target = commit(popup, "Checkout from revision") + if not target then + return end - local files = util.deduplicate(util.merge(git.files.all(), git.files.diff(commit))) + local files = util.deduplicate(util.merge(git.files.all(), git.files.diff(target))) if not files[1] then + notification.info(("No files differ between HEAD and %s"):format(target)) return end - a.util.scheduler() local files = FuzzyFinderBuffer.new(files):open_async { allow_multi = true } if not files[1] then return end - git.reset.file(commit, files) + git.reset.file(target, files) end return M diff --git a/lua/neogit/popups/reset/init.lua b/lua/neogit/popups/reset/init.lua index b192138ed..7df38ede1 100644 --- a/lua/neogit/popups/reset/init.lua +++ b/lua/neogit/popups/reset/init.lua @@ -13,7 +13,7 @@ function M.create(env) :action("h", "hard (HEAD, index and files)", actions.hard) :action("k", "keep (HEAD and index, keeping uncommitted)", actions.keep) :action("i", "index (only)", actions.index) - :action("w", "worktree (only)") + :action("w", "worktree (only)", actions.worktree) :group_heading("") :action("f", "a file", actions.a_file) :env(env) From 84f1318542d0aabd08d447bf6b1da7ec746a61cd Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 24 Jan 2024 10:41:57 +0100 Subject: [PATCH 103/443] Update the default behaviour of "push --force-with-lease" to also pass "--force-if-includes". --- lua/neogit/popups/push/actions.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index 819d51746..f6495c6e0 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -14,6 +14,10 @@ local function push_to(args, remote, branch, opts) table.insert(args, "--set-upstream") end + if vim.tbl_contains(args, "--force-with-lease") then + table.insert(args, "--force-if-includes") + end + local name if branch then name = remote .. "/" .. branch From 00123346a0cbfbbbc519d979451bc4b16106beb7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 24 Jan 2024 23:00:41 +0100 Subject: [PATCH 104/443] Correctly display discard message --- lua/neogit/status.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index f95fb8f60..763c76760 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -947,8 +947,8 @@ local unstage = operation("unstage", function() }, "unstage_finish") end) -local function discard_message(files, hunk_count) - if vim.api.nvim_get_mode() == "V" then +local function discard_message(files, hunk_count, mode) + if vim.tbl_contains({ "V", "v", "" }, mode) then return "Discard selection?" elseif hunk_count > 0 then return string.format("Discard %d hunks?", hunk_count) @@ -1016,7 +1016,7 @@ local discard = operation("discard", function() if not input.get_confirmation( - discard_message(files, hunk_count), + discard_message(files, hunk_count, mode.mode), { values = { "&Yes", "&No" }, default = 2 } ) then From 50b45fd7c4c19053cc1304a93391244cc559bba0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Dec 2023 20:42:14 +0100 Subject: [PATCH 105/443] wip --- lua/neogit/buffers/status/ui.lua | 136 +++++++++++++------------------ 1 file changed, 56 insertions(+), 80 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 13d5c5180..ab0d79bed 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -10,6 +10,7 @@ local text = Ui.text local map = util.map local List = common.List +local Diff = common.Diff local M = {} @@ -24,7 +25,7 @@ local RemoteHeader = Component.new(function(props) end) local Section = Component.new(function(props) - return col { + return col({ row { text(props.title), text(" ("), @@ -32,7 +33,7 @@ local Section = Component.new(function(props) text(")"), }, col(props.items), - } + }, { foldable = true, folded = false }) end) function M.Status(state) @@ -52,92 +53,67 @@ function M.Status(state) msg = state.upstream.commit_message, }, }, - -- #state.untracked_files > 0 and Section { - -- title = "Untracked files", - -- items = map(state.untracked_files, Diff) - -- }, - -- #state.unstaged_changes > 0 and Section { - -- title = "Unstaged changes", - -- items = map(state.unstaged_changes, Diff) - -- }, - -- #state.staged_changes > 0 and Section { - -- title = "Staged changes", - -- items = map(state.staged_changes, Diff) - -- }, - #state.stashes > 0 - and Section { - title = "Stashes", - items = map(state.stashes, function(s) - return row { - text.highlight("Comment")("stash@{", s.idx, "}: "), - text(s.message), - } - end), - }, - -- #state.unpulled_changes > 0 and Section { + #state.untracked.items > 0 and Section { + title = "Untracked files", + items = map(state.untracked.items, Diff), + }, + #state.unstaged.items > 0 and Section { + title = "Unstaged changes", + items = map(state.unstaged.items, Diff), + }, + -- #state.staged.items > 0 and Section { + -- title = "Staged changes", + -- items = map(state.staged.items, Diff), + -- }, + -- #state.stashes.items > 0 and Section { + -- title = "Stashes", + -- items = map(state.stashes.items, function(s) + -- return row { + -- text.highlight("Comment")("stash@{" .. s.idx .. "}: "), + -- text(s.message), + -- } + -- end), + -- }, + -- #state.upstream.unpulled.items > 0 and Section { -- title = "Unpulled changes", - -- items = map(state.unpulled_changes, Diff) + -- items = map(state.upstream.unpulled.items, Diff), -- }, - -- #state.unmerged_changes > 0 and Section { + -- #state.upstream.unmerged.items > 0 and Section { -- title = "Unmerged changes", - -- items = map(state.unmerged_changes, Diff) + -- items = map(state.upstream.unmerged.items, Diff), -- }, }, }, } end --- function _load_diffs(repo) --- local cli = require("neogit.lib.git.cli") - --- local unstaged_jobs = map(repo.unstaged.items, function(f) --- return cli.diff.shortstat.patch.files(f.name).to_job() --- end) - --- local staged_jobs = map(repo.staged.items, function(f) --- return cli.diff.cached.shortstat.patch.files(f.name).to_job() --- end) - --- local jobs = {} - --- for _, x in ipairs { unstaged_jobs, staged_jobs } do --- for _, j in ipairs(x) do --- table.insert(jobs, j) --- end --- end - --- Job.start_all(jobs) --- Job.wait_all(jobs) - --- for i, j in ipairs(unstaged_jobs) do --- repo.unstaged.items[i].diff = difflib.parse(j.stdout, true) --- end - --- for i, j in ipairs(staged_jobs) do --- repo.staged.items[i].diff = difflib.parse(j.stdout, true) --- end --- end - -function M._TEST() - local repo = require("neogit.lib.git.repository").create() - - require("neogit.buffers.status") - .new({ - head = repo.head, - upstream = repo.upstream, - untracked_files = repo.untracked.items, - unstaged_changes = map(repo.unstaged.items, function(f) - return f.diff - end), - staged_changes = map(repo.staged.items, function(f) - return f.diff - end), - stashes = repo.stashes.items, - unpulled_changes = repo.upstream.unpulled.items, - unmerged_changes = repo.upstream.unmerged.items, - recent_changes = repo.recent.items, - }) - :open() -end +local a = require("plenary.async") + +M._TEST = a.void(function() + local git = require("neogit.lib.git") + + local render_status = function() + local git = require("neogit.lib.git") + require("neogit.buffers.status").new(git.repo):open() + -- .new({ + -- head = git.repo.head, + -- upstream = git.repo.upstream, + -- untracked_files = git.repo.untracked.items, + -- unstaged_changes = map(git.repo.unstaged.items, function(f) + -- return f.diff + -- end), + -- staged_changes = map(git.repo.staged.items, function(f) + -- return f.diff + -- end), + -- stashes = git.repo.stashes.items, + -- unpulled_changes = git.repo.upstream.unpulled.items, + -- unmerged_changes = git.repo.upstream.unmerged.items, + -- recent_changes = git.repo.recent.items, + -- }) + -- :open() + end + + git.repo:refresh { source = "status_test", callback = render_status } +end) return M From 370827beccefb451f49b51ec26509bfa5093884c Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 1 Feb 2024 12:57:00 +0100 Subject: [PATCH 106/443] Progress! --- lua/neogit/buffers/common.lua | 18 ++-- lua/neogit/buffers/status/init.lua | 26 +++++- lua/neogit/buffers/status/ui.lua | 135 ++++++++++++++++++++++------- lua/neogit/lib/git/branch.lua | 9 +- lua/neogit/lib/git/repository.lua | 5 ++ lua/neogit/lib/git/status.lua | 2 +- lua/neogit/popups/commit/init.lua | 4 + 7 files changed, 160 insertions(+), 39 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index e9ace18f7..03f0cf8df 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -18,6 +18,13 @@ local diff_add_start = "+" local diff_delete_start = "-" M.Diff = Component.new(function(diff) + return col.tag("Diff")({ + text(string.format("%s %s", diff.kind, diff.file), { line_hl = "NeogitDiffHeader" }), + M.DiffHunks(diff), + }, { foldable = true, folded = false, context = true }) +end) + +M.DiffHunks = Component.new(function(diff) local hunk_props = map(diff.hunks, function(hunk) local header = diff.lines[hunk.diff_from] @@ -31,13 +38,10 @@ M.Diff = Component.new(function(diff) } end) - return col.tag("Diff")({ - text(string.format("%s %s", diff.kind, diff.file), { line_hl = "NeogitDiffHeader" }), - col.tag("DiffContent") { - col.tag("DiffInfo")(map(diff.info, text)), - col.tag("HunkList")(map(hunk_props, M.Hunk)), - }, - }, { foldable = true, folded = false, context = true }) + return col.tag("DiffContent") { + col.tag("DiffInfo")(map(diff.info, text)), + col.tag("HunkList")(map(hunk_props, M.Hunk)), + } end) local HunkLine = Component.new(function(line) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 08bc4d8b5..ae414a9bc 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,3 +1,4 @@ +local config = require("neogit.config") local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.status.ui") @@ -21,12 +22,31 @@ function M.new(state) end function M:open(kind) - kind = kind or "tab" + kind = kind or config.values.kind self.buffer = Buffer.create { name = "NeogitStatusNew", filetype = "NeogitStatusNew", + context_highlight = true, kind = kind, + disable_line_numbers = config.values.disable_line_numbers, + mappings = { + n = { + [""] = function() + local fold = self.buffer.ui:get_fold_under_cursor() + if fold then + if fold.options.on_open then + fold.options.on_open(fold, self.buffer.ui) + else + local ok, _ = pcall(vim.cmd, "normal! za") + if ok then + fold.options.folded = not fold.options.folded + end + end + end + end, + }, + }, initialize = function() self.prev_autochdir = vim.o.autochdir @@ -35,6 +55,10 @@ function M:open(kind) render = function() return ui.Status(self.state) end, + after = function() + vim.cmd([[setlocal nowrap]]) + -- M.watcher = watcher.new(git.repo:git_path():absolute()) + end } end diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index ab0d79bed..00e7b1a3d 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -1,7 +1,12 @@ +-- TODO +-- - When a section is collapsed, there should not be an empty line between it and the next section +-- - Get fold markers to work +-- local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") local common = require("neogit.buffers.common") +local a = require("plenary.async") local col = Ui.col local row = Ui.row @@ -10,70 +15,135 @@ local text = Ui.text local map = util.map local List = common.List -local Diff = common.Diff +local DiffHunks = common.DiffHunks +local EmptyLine = col({ row { text("") } }) local M = {} -local RemoteHeader = Component.new(function(props) +local HEAD = Component.new(function(props) + local highlight = props.remote and "NeogitRemote" or "NeogitBranch" + local ref = props.remote and ("%s/%s"):format(props.remote, props.branch) or props.branch + return row { - text(props.name), - text(": "), - text(props.branch), + text(util.pad_right(props.name .. ":", 10)), + text.highlight(highlight)(ref), text(" "), text(props.msg or "(no commits)"), } end) +local Tag = Component.new(function(props) + return row { + text(util.pad_right("Tag:", 10)), + text.highlight("NeogitTagName")(props.name), + text(" ("), + text.highlight("NeogitTagDistance")(props.distance), + text(")"), + } +end) + local Section = Component.new(function(props) - return col({ + return col.tag("Section")({ row { - text(props.title), + text.highlight("NeogitSectionHeader")(props.title), text(" ("), text(#props.items), text(")"), }, col(props.items), - }, { foldable = true, folded = false }) + EmptyLine, + }, { foldable = true, folded = false, fold_adjustment = 1 }) +end) + +local load_diff = function(item) + return a.void(function(this, ui) + this.options.on_open = nil + this.options.folded = false + -- vim.cmd("norm! zE") -- Eliminate all existing folds + this:append(DiffHunks(item.diff)) + ui:update() + end) +end + +local SectionItemFile = Component.new(function(item) + local mode_to_text = { + M = "Modified ", + N = "New File ", + A = "Added ", + D = "Deleted ", + C = "Copied ", + U = "Updated ", + UU = "Both Modified ", + R = "Renamed ", + ["?"] = "", -- Untracked + } + + return col.tag("SectionItemFile")({ + row { + text.highlight(("NeogitChange%s"):format(mode_to_text[item.mode]:gsub(" ", "")))(mode_to_text[item.mode]), + text.highlight("")(item.name) + } + }, { foldable = true, folded = true, on_open = load_diff(item), context = true }) +end) + +local SectionItemStash = Component.new(function(item) + return row { + text.highlight("Comment")(("stash@{%s}: "):format(item.idx)), + text(item.message), + } +end) + +local SectionItemCommit = Component.new(function(item) + return row { + text.highlight("Comment")(item.commit.abbreviated_commit), + text(" "), + text(item.commit.subject), + } end) function M.Status(state) return { List { - separator = " ", items = { col { - RemoteHeader { + HEAD { name = "Head", branch = state.head.branch, msg = state.head.commit_message, }, - state.upstream.ref and RemoteHeader { - name = "Upstream", - branch = state.upstream.ref, + state.upstream.ref and HEAD { + name = "Merge", + branch = state.upstream.branch, + remote = state.upstream.remote, msg = state.upstream.commit_message, }, + state.pushRemote.ref and HEAD { + name = "Push", + branch = state.pushRemote.branch, + remote = state.pushRemote.remote, + msg = state.pushRemote.commit_message, + }, + state.head.tag.name and Tag { + name = state.head.tag.name, + distance = state.head.tag.distance, + }, }, + EmptyLine, + -- Rebase + -- Sequencer + -- Merge #state.untracked.items > 0 and Section { title = "Untracked files", - items = map(state.untracked.items, Diff), + items = map(state.untracked.items, SectionItemFile), }, #state.unstaged.items > 0 and Section { title = "Unstaged changes", - items = map(state.unstaged.items, Diff), + items = map(state.unstaged.items, SectionItemFile), + }, + #state.staged.items > 0 and Section { + title = "Staged changes", + items = map(state.staged.items, SectionItemFile), }, - -- #state.staged.items > 0 and Section { - -- title = "Staged changes", - -- items = map(state.staged.items, Diff), - -- }, - -- #state.stashes.items > 0 and Section { - -- title = "Stashes", - -- items = map(state.stashes.items, function(s) - -- return row { - -- text.highlight("Comment")("stash@{" .. s.idx .. "}: "), - -- text(s.message), - -- } - -- end), - -- }, -- #state.upstream.unpulled.items > 0 and Section { -- title = "Unpulled changes", -- items = map(state.upstream.unpulled.items, Diff), @@ -82,6 +152,14 @@ function M.Status(state) -- title = "Unmerged changes", -- items = map(state.upstream.unmerged.items, Diff), -- }, + #state.stashes.items > 0 and Section { + title = "Stashes", + items = map(state.stashes.items, SectionItemStash), + }, + #state.recent.items > 0 and Section { + title = "Recent Commits", + items = map(state.recent.items, SectionItemCommit), + }, }, }, } @@ -93,7 +171,6 @@ M._TEST = a.void(function() local git = require("neogit.lib.git") local render_status = function() - local git = require("neogit.lib.git") require("neogit.buffers.status").new(git.repo):open() -- .new({ -- head = git.repo.head, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index ec406ff42..16a8fca1a 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -284,15 +284,22 @@ local function update_branch_information(state) local commit = git.log.list({ state.upstream.ref, "--max-count=1" }, nil, {}, true)[1] -- May be done earlier by `update_status`, but this function can be called separately if commit then + state.upstream.oid = commit.oid state.upstream.commit_message = commit.subject state.upstream.abbrev = git.rev_parse.abbreviate_commit(commit.oid) end end - local pushRemote = require("neogit.lib.git").branch.pushRemote_ref() + local pushRemote = git.branch.pushRemote_ref() if pushRemote and not git.branch.is_detached() then + local remote, branch = unpack(vim.split(pushRemote, "/")) + state.pushRemote.ref = pushRemote + state.pushRemote.remote = remote + state.pushRemote.branch = branch + local commit = git.log.list({ pushRemote, "--max-count=1" }, nil, {}, true)[1] if commit then + state.pushRemote.oid = commit.oid state.pushRemote.commit_message = commit.subject state.pushRemote.abbrev = git.rev_parse.abbreviate_commit(commit.oid) end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 80cdc797a..93f665970 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -19,11 +19,16 @@ local function empty_state() commit_message = nil, remote = nil, ref = nil, + oid = nil, unmerged = { items = {} }, unpulled = { items = {} }, }, pushRemote = { + branch = nil, commit_message = nil, + remote = nil, + ref = nil, + oid = nil, unmerged = { items = {} }, unpulled = { items = {} }, }, diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index e1c395032..c375a06a8 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -102,7 +102,7 @@ local function update_status(state) table.insert(unstaged_files, update_file(cwd, old_files_hash.unstaged_files[name], mode, name)) elseif kind == "?" then - table.insert(untracked_files, update_file(cwd, old_files_hash.untracked_files[rest], nil, rest)) + table.insert(untracked_files, update_file(cwd, old_files_hash.untracked_files[rest], "?", rest)) elseif kind == "1" then local mode_staged, mode_unstaged, _, _, _, _, _, _, name = rest:match(match_1) diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index 3262e9fdd..a3b8a654d 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -19,6 +19,10 @@ function M.create(env) :option("C", "reuse-message", "", "Reuse commit message") :group_heading("Create") :action("c", "Commit", actions.commit) + :action_if(vim.fn.has("git-absorb") == 0, "b", "Absorb", function() + -- TODO: https://github.com/SuperBo/fugit2.nvim/blob/dev/lua/fugit2/git_absorb.lua + vim.system({ "git", "absorb", "--and-rebase" }, { env = { GIT_SEQUENCE_EDITOR = ":" } }):wait() + end) :new_action_group("Edit HEAD") :action("e", "Extend", actions.extend) :action("w", "Reword", actions.reword) From 2625c52e2afc704aa1a3cfe84fb96649e99f9317 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 2 Feb 2024 17:32:30 +0100 Subject: [PATCH 107/443] More stuff working on new buffer --- lua/neogit/buffers/common.lua | 5 +- lua/neogit/buffers/log_view/init.lua | 2 + lua/neogit/buffers/status/init.lua | 274 +++++++++++++++++++++++++-- lua/neogit/buffers/status/ui.lua | 226 +++++++++++++--------- lua/neogit/lib/git/repository.lua | 3 + lua/neogit/lib/git/status.lua | 6 +- lua/neogit/lib/ui/init.lua | 43 +++++ 7 files changed, 452 insertions(+), 107 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 03f0cf8df..60c367886 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -32,9 +32,12 @@ M.DiffHunks = Component.new(function(diff) return diff.lines[i] end) + hunk.content = content + return { header = header, content = content, + hunk = hunk, } end) @@ -62,7 +65,7 @@ M.Hunk = Component.new(function(props) return col.tag("Hunk")({ text.line_hl("NeogitHunkHeader")(props.header), col.tag("HunkContent")(map(props.content, HunkLine)), - }, { foldable = true, folded = false, context = true }) + }, { foldable = true, folded = false, context = true, hunk = props.hunk }) end) M.List = Component.new(function(props) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 84647e5a9..5e537ce6d 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -76,6 +76,7 @@ function M:open() end), [popups.mapping_for("PullPopup")] = popups.open("pull"), ["d"] = function() + -- TODO: Use diff popup if not config.check_integration("diffview") then notification.error("Diffview integration must be enabled for log diff") return @@ -172,6 +173,7 @@ function M:open() pcall(vim.cmd, "normal! za") end, ["d"] = function() + -- TODO: Use diff popup if not config.check_integration("diffview") then notification.error("Diffview integration must be enabled for log diff") return diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ae414a9bc..3d466af30 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,28 +1,86 @@ local config = require("neogit.config") local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.status.ui") +local popups = require("neogit.popups") +local git = require("neogit.lib.git") +local watcher = require("neogit.watcher") +local a = require("plenary.async") -local M = {} +local api = vim.api --- @class StatusBuffer --- @field is_open whether the buffer is currently visible --- @field buffer buffer instance --- @field state StatusState --- @see Buffer --- @see StatusState +---@class StatusBuffer +---@field is_open boolean whether the buffer is currently visible +---@field buffer Buffer instance +---@field state NeogitRepo +---@field config NeogitConfig +local M = {} +M.__index = M -function M.new(state) - local x = { +---@param state NeogitRepo +---@param config NeogitConfig +---@return StatusBuffer +function M.new(state, config) + local instance = { is_open = false, state = state, + config = config, buffer = nil, } - setmetatable(x, { __index = M }) - return x + + setmetatable(instance, M) + + return instance end +function M:close() + self.is_open = false + self.buffer:close() + self.buffer = nil +end + +function M:refresh() + + -- Update repo state + -- rerender UI + -- diff with current UI + -- only update deltas +end + +-- TODO +-- Save/restore cursor location +-- Redrawing w/ lock, that doesn't discard opened/closed diffs, keeps cursor location +-- on-close hook to teardown stuff +-- +-- Actions: +-- Staging / Unstaging / Discarding +-- +-- Contexts: +-- - Normal +-- - Section +-- - File +-- - Hunk +-- - Visual +-- - Files in selection +-- - Hunks in selection +-- - Lines in selection +-- +-- Mappings: +-- Ensure it will work when passing multiple mappings to the same function +-- function M:open(kind) + if M.instance and M.instance.is_open then + M.instance:close() + end + + M.instance = self + + if self.is_open then + return + end + self.is_open = true + kind = kind or config.values.kind + local mappings = config.get_reversed_status_maps() self.buffer = Buffer.create { name = "NeogitStatusNew", @@ -30,9 +88,150 @@ function M:open(kind) context_highlight = true, kind = kind, disable_line_numbers = config.values.disable_line_numbers, + autocmds = { + ["BufUnload"] = function() + M.watcher:stop() + M.instance.is_open = false + end, + }, mappings = { + v = {}, n = { - [""] = function() + [mappings["Close"]] = function() + self:close() + end, + [mappings["CommandHistory"]] = function() + require("neogit.buffers.git_command_history"):new():show() + end, + [mappings["Console"]] = function() + require("neogit.process").show_console() + end, + [mappings["Depth1"]] = function() + -- TODO + end, + [mappings["Depth2"]] = function() + -- TODO + end, + [mappings["Depth3"]] = function() + -- TODO + end, + [mappings["Depth4"]] = function() + -- TODO + end, + [mappings["Discard"]] = function() + -- TODO + end, + [mappings["GoToFile"]] = function() + -- TODO + end, + [mappings["GoToNextHunkHeader"]] = function() + -- TODO: Doesn't go across file boundaries + local c = self.buffer.ui:get_component_under_cursor(function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" + end) + + if c then + if c.options.tag == "Diff" then + self.buffer:move_cursor(vim.fn.line(".") + 1) + else + local _, last = c:row_range_abs() + if last == vim.fn.line("$") then + self.buffer:move_cursor(last) + else + self.buffer:move_cursor(last + 1) + end + end + vim.cmd("normal! zt") + end + end, + [mappings["GoToPreviousHunkHeader"]] = function() + -- TODO: Doesn't go across file boundaries + local function previous_hunk_header(self, line) + local c = self.buffer.ui:get_component_on_line(line, function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" + end) + + if c then + local first, _ = c:row_range_abs() + if vim.fn.line(".") == first then + first = previous_hunk_header(self, line - 1) + end + + return first + end + end + + local previous_header = previous_hunk_header(self, vim.fn.line(".")) + if previous_header then + api.nvim_win_set_cursor(0, { previous_header, 0 }) + vim.cmd("normal! zt") + end + end, + [mappings["InitRepo"]] = function() + git.init.init_repo() + end, + [mappings["RefreshBuffer"]] = function() + -- TODO + end, + [mappings["ShowRefs"]] = function() + require("neogit.buffers.refs_view").new(git.refs.list_parsed()):open() + end, + [mappings["SplitOpen"]] = function() + -- TODO + end, + [mappings["Stage"]] = a.void(function() + local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + + if stagable then + if stagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + + git.index.apply(patch, { cached = true }) + elseif stagable.filename then + local section = self.buffer.ui:get_current_section() + + if section == "unstaged" then + git.status.stage { stagable.filename } + elseif section == "untracked" then + git.index.add { stagable.filename } + end + end + end + end), + [mappings["StageAll"]] = function() + -- TODO + end, + [mappings["StageUnstaged"]] = function() + -- TODO + end, + [mappings["TabOpen"]] = function() + -- TODO + end, + [mappings["Unstage"]] = function() + -- TODO + end, + [mappings["UnstageStaged"]] = function() + -- TODO + end, + [mappings["VSplitOpen"]] = function() + -- TODO + end, + [mappings["YankSelected"]] = function() + local yank = self.buffer.ui:get_yankable_under_cursor() + if yank then + if yank:match("^stash@{%d+}") then + yank = git.rev_parse.oid(yank:match("^(stash@{%d+})")) + end + + yank = string.format("'%s'", yank) + vim.cmd.let("@+=" .. yank) + vim.cmd.echo(yank) + else + vim.cmd("echo ''") + end + end, + [mappings["Toggle"]] = function() local fold = self.buffer.ui:get_fold_under_cursor() if fold then if fold.options.on_open then @@ -45,6 +244,51 @@ function M:open(kind) end end end, + [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), + [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), + [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("MergePopup")] = popups.open("merge", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("PushPopup")] = popups.open("push", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), + [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("StashPopup")] = popups.open("stash", function(p) + -- TODO: Pass in stash name if its under the cursor + p { name = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + -- TODO use current section/item + p { section = {}, item = {} } + end), + [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) + -- TODO use current absolute paths in selection + p { paths = {}, git_root = git.repo.git_root } + end), + [popups.mapping_for("RemotePopup")] = popups.open("remote"), + [popups.mapping_for("FetchPopup")] = popups.open("fetch"), + [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("HelpPopup")] = popups.open("help"), + [popups.mapping_for("LogPopup")] = popups.open("log"), + [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), }, }, initialize = function() @@ -53,12 +297,12 @@ function M:open(kind) vim.o.autochdir = false end, render = function() - return ui.Status(self.state) + return ui.Status(self.state, self.config) end, after = function() vim.cmd([[setlocal nowrap]]) - -- M.watcher = watcher.new(git.repo:git_path():absolute()) - end + M.watcher = watcher.new(git.repo:git_path():absolute()) + end, } end diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 00e7b1a3d..0c8eb5e62 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -2,6 +2,9 @@ -- - When a section is collapsed, there should not be an empty line between it and the next section -- - Get fold markers to work -- +-- +-- Rule! No external state! +-- local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") @@ -24,35 +27,47 @@ local HEAD = Component.new(function(props) local highlight = props.remote and "NeogitRemote" or "NeogitBranch" local ref = props.remote and ("%s/%s"):format(props.remote, props.branch) or props.branch - return row { + return row({ text(util.pad_right(props.name .. ":", 10)), text.highlight(highlight)(ref), text(" "), text(props.msg or "(no commits)"), - } + }, { yankable = props.yankable }) end) local Tag = Component.new(function(props) - return row { + return row({ text(util.pad_right("Tag:", 10)), text.highlight("NeogitTagName")(props.name), text(" ("), text.highlight("NeogitTagDistance")(props.distance), text(")"), + }, { yankable = props.yankable }) +end) + +local SectionTitle = Component.new(function(props) + return { text.highlight("NeogitSectionHeader")(props.title) } +end) + +local SectionTitleRemote = Component.new(function(props) + return { + text.highlight("NeogitSectionHeader")(props.title), + text(" "), + text.highlight("NeogitRemote")(props.ref) } end) local Section = Component.new(function(props) return col.tag("Section")({ - row { - text.highlight("NeogitSectionHeader")(props.title), - text(" ("), - text(#props.items), - text(")"), - }, - col(props.items), + row( + util.merge( + props.title, + { text(" ("), text(#props.items), text(")"), } + ) + ), + col(map(props.items, props.render)), EmptyLine, - }, { foldable = true, folded = false, fold_adjustment = 1 }) + }, { foldable = true, folded = props.folded, section = props.name }) end) local load_diff = function(item) @@ -78,119 +93,154 @@ local SectionItemFile = Component.new(function(item) ["?"] = "", -- Untracked } + local conflict = false + local mode = mode_to_text[item.mode] + if mode == nil then + conflict = true + mode = mode_to_text[item.mode:sub(1, 1)] + end + + local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) + return col.tag("SectionItemFile")({ row { - text.highlight(("NeogitChange%s"):format(mode_to_text[item.mode]:gsub(" ", "")))(mode_to_text[item.mode]), - text.highlight("")(item.name) + text.highlight(highlight)(conflict and ("%s by us"):format(mode) or mode), + text(item.name) } - }, { foldable = true, folded = true, on_open = load_diff(item), context = true }) + }, { + foldable = true, + folded = true, + on_open = load_diff(item), + context = true, + yankable = item.name, + filename = item.name, + item = item, + }) end) local SectionItemStash = Component.new(function(item) - return row { - text.highlight("Comment")(("stash@{%s}: "):format(item.idx)), + local name = ("stash@{%s}"):format(item.idx) + return row({ + text.highlight("Comment")(name), + text.highlight("Comment")(": "), text(item.message), - } + }, { yankable = name }) end) local SectionItemCommit = Component.new(function(item) - return row { + return row({ text.highlight("Comment")(item.commit.abbreviated_commit), text(" "), text(item.commit.subject), - } + }, { yankable = item.commit.oid }) end) -function M.Status(state) +-- TODO: Hint at top of buffer! +function M.Status(state, config) return { List { items = { - col { - HEAD { - name = "Head", - branch = state.head.branch, - msg = state.head.commit_message, - }, - state.upstream.ref and HEAD { - name = "Merge", - branch = state.upstream.branch, - remote = state.upstream.remote, - msg = state.upstream.commit_message, - }, - state.pushRemote.ref and HEAD { - name = "Push", - branch = state.pushRemote.branch, - remote = state.pushRemote.remote, - msg = state.pushRemote.commit_message, - }, - state.head.tag.name and Tag { - name = state.head.tag.name, - distance = state.head.tag.distance, - }, + HEAD { + name = "Head", + branch = state.head.branch, + msg = state.head.commit_message, + yankable = state.head.oid, + }, + state.upstream.ref and HEAD { -- Do not render if HEAD is detached + name = "Merge", + branch = state.upstream.branch, + remote = state.upstream.remote, + msg = state.upstream.commit_message, + yankable = state.upstream.oid, + }, + state.pushRemote.ref and HEAD { -- Do not render if HEAD is detached + name = "Push", + branch = state.pushRemote.branch, + remote = state.pushRemote.remote, + msg = state.pushRemote.commit_message, + yankable = state.pushRemote.oid, + }, + state.head.tag.name and Tag { + name = state.head.tag.name, + distance = state.head.tag.distance, + yankable = state.head.tag.oid, }, EmptyLine, - -- Rebase - -- Sequencer - -- Merge - #state.untracked.items > 0 and Section { - title = "Untracked files", - items = map(state.untracked.items, SectionItemFile), + -- TODO Rebasing (rebase) + -- TODO Reverting (sequencer - revert_head) + -- TODO Picking (sequencer - cherry_pick_head) + -- TODO Respect if user has section hidden + #state.untracked.items > 0 and Section { -- TODO: Group by directory and create a fold + title = SectionTitle({ title = "Untracked files" }), + render = SectionItemFile, + items = state.untracked.items, + folded = config.sections.untracked.folded, + name = "untracked", }, #state.unstaged.items > 0 and Section { - title = "Unstaged changes", - items = map(state.unstaged.items, SectionItemFile), + title = SectionTitle({ title = "Unstaged changes" }), + render = SectionItemFile, + items = state.unstaged.items, + folded = config.sections.unstaged.folded, + name = "unstaged", }, #state.staged.items > 0 and Section { - title = "Staged changes", - items = map(state.staged.items, SectionItemFile), + title = SectionTitle({ title = "Staged changes" }), + render = SectionItemFile, + items = state.staged.items, + folded = config.sections.staged.folded, + name = "staged", + }, + #state.upstream.unpulled.items > 0 and Section { + title = SectionTitleRemote({ title = "Unpulled from", ref = state.upstream.ref }), + render = SectionItemCommit, + items = state.upstream.unpulled.items, + folded = config.sections.unpulled_upstream.folded, + }, + (#state.pushRemote.unpulled.items > 0 and state.pushRemote.ref ~= state.upstream.ref) and Section { + title = SectionTitleRemote({ title = "Unpulled from", ref = state.pushRemote.ref }), + render = SectionItemCommit, + items = state.pushRemote.unpulled.items, + folded = config.sections.unpulled_pushRemote.folded, + }, + #state.upstream.unmerged.items > 0 and Section { + title = SectionTitleRemote({ title = "Unmerged into", ref = state.upstream.ref }), + render = SectionItemCommit, + items = state.upstream.unmerged.items, + folded = config.sections.unmerged_upstream.folded, + }, + (#state.pushRemote.unmerged.items > 0 and state.pushRemote.ref ~= state.upstream.ref) and Section { + title = SectionTitleRemote({ title = "Unpushed to", ref = state.pushRemote.ref }), + render = SectionItemCommit, + items = state.pushRemote.unmerged.items, + folded = config.sections.unmerged_pushRemote.folded, }, - -- #state.upstream.unpulled.items > 0 and Section { - -- title = "Unpulled changes", - -- items = map(state.upstream.unpulled.items, Diff), - -- }, - -- #state.upstream.unmerged.items > 0 and Section { - -- title = "Unmerged changes", - -- items = map(state.upstream.unmerged.items, Diff), - -- }, #state.stashes.items > 0 and Section { - title = "Stashes", - items = map(state.stashes.items, SectionItemStash), + title = SectionTitle({ title = "Stashes" }), + render = SectionItemStash, + items = state.stashes.items, + folded = config.sections.stashes.folded, }, #state.recent.items > 0 and Section { - title = "Recent Commits", - items = map(state.recent.items, SectionItemCommit), + title = SectionTitle({ title = "Recent Commits" }), + render = SectionItemCommit, + items = state.recent.items, + folded = config.sections.recent.folded, }, }, }, } end -local a = require("plenary.async") - M._TEST = a.void(function() local git = require("neogit.lib.git") - - local render_status = function() - require("neogit.buffers.status").new(git.repo):open() - -- .new({ - -- head = git.repo.head, - -- upstream = git.repo.upstream, - -- untracked_files = git.repo.untracked.items, - -- unstaged_changes = map(git.repo.unstaged.items, function(f) - -- return f.diff - -- end), - -- staged_changes = map(git.repo.staged.items, function(f) - -- return f.diff - -- end), - -- stashes = git.repo.stashes.items, - -- unpulled_changes = git.repo.upstream.unpulled.items, - -- unmerged_changes = git.repo.upstream.unmerged.items, - -- recent_changes = git.repo.recent.items, - -- }) - -- :open() - end - - git.repo:refresh { source = "status_test", callback = render_status } + local config = require("neogit.config") + git.repo:refresh { + source = "status_test", + callback = function() + require("neogit.buffers.status").new(git.repo, config.values):open() + end + } end) return M diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 93f665970..68858973a 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -4,13 +4,16 @@ local Path = require("plenary.path") local cli = require("neogit.lib.git.cli") local function empty_state() + ---@class NeogitRepo return { git_root = cli.git_root_of_cwd(), head = { branch = nil, + oid = nil, commit_message = nil, tag = { name = nil, + oid = nil, distance = nil, }, }, diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index c375a06a8..1b557287b 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -158,12 +158,12 @@ local function update_status(state) if #tag == 1 then local tag, distance = tostring(tag[1]):match(tag_pattern) if tag and distance then - head.tag = { name = tag, distance = tonumber(distance) } + head.tag = { name = tag, distance = tonumber(distance), oid = git.rev_parse.oid(tag) } else - head.tag = { name = nil, distance = nil } + head.tag = { name = nil, distance = nil, oid = nil } end else - head.tag = { name = nil, distance = nil } + head.tag = { name = nil, distance = nil, oid = nil } end state.head = head diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 78f619646..e4871062f 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -159,6 +159,49 @@ function Ui:get_commit_under_cursor() return component and component.options.oid end +---@return string|nil +function Ui:get_yankable_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) + local component = self:_find_component_by_index(cursor[1], function(node) + return node.options.yankable + end) + + return component and component.options.yankable +end + +---@return string|nil +function Ui:get_current_section() + local cursor = vim.api.nvim_win_get_cursor(0) + local component = self:_find_component_by_index(cursor[1], function(node) + return node.options.section + end) + + return component and component.options.section +end + +---@return table|nil +function Ui:get_hunk_or_filename_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) + local component = self:_find_component_by_index(cursor[1], function(node) + return node.options.hunk or node.options.filename + end) + + return component and { + hunk = component.options.hunk, + filename = component.options.filename + } +end + +---@return table|nil +function Ui:get_item_under_cursor() + local cursor = vim.api.nvim_win_get_cursor(0) + local component = self:_find_component_by_index(cursor[1], function(node) + return node.options.item + end) + + return component and component.options.item +end + function Ui:render(...) self.layout = { ... } self.layout = util.filter(self.layout, function(x) From 1414c91e688fff9f96873b3cf99b5aee26a68012 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 2 Feb 2024 17:41:09 +0100 Subject: [PATCH 108/443] I did this one, also lint --- lua/neogit/buffers/status/ui.lua | 73 +++++++++++++++----------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 0c8eb5e62..ff25d042a 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -1,5 +1,4 @@ -- TODO --- - When a section is collapsed, there should not be an empty line between it and the next section -- - Get fold markers to work -- -- @@ -19,7 +18,7 @@ local map = util.map local List = common.List local DiffHunks = common.DiffHunks -local EmptyLine = col({ row { text("") } }) +local EmptyLine = col { row { text("") } } local M = {} @@ -53,18 +52,13 @@ local SectionTitleRemote = Component.new(function(props) return { text.highlight("NeogitSectionHeader")(props.title), text(" "), - text.highlight("NeogitRemote")(props.ref) + text.highlight("NeogitRemote")(props.ref), } end) local Section = Component.new(function(props) return col.tag("Section")({ - row( - util.merge( - props.title, - { text(" ("), text(#props.items), text(")"), } - ) - ), + row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), col(map(props.items, props.render)), EmptyLine, }, { foldable = true, folded = props.folded, section = props.name }) @@ -82,14 +76,14 @@ end local SectionItemFile = Component.new(function(item) local mode_to_text = { - M = "Modified ", - N = "New File ", - A = "Added ", - D = "Deleted ", - C = "Copied ", - U = "Updated ", + M = "Modified ", + N = "New File ", + A = "Added ", + D = "Deleted ", + C = "Copied ", + U = "Updated ", UU = "Both Modified ", - R = "Renamed ", + R = "Renamed ", ["?"] = "", -- Untracked } @@ -105,8 +99,8 @@ local SectionItemFile = Component.new(function(item) return col.tag("SectionItemFile")({ row { text.highlight(highlight)(conflict and ("%s by us"):format(mode) or mode), - text(item.name) - } + text(item.name), + }, }, { foldable = true, folded = true, @@ -153,7 +147,7 @@ function M.Status(state, config) msg = state.upstream.commit_message, yankable = state.upstream.oid, }, - state.pushRemote.ref and HEAD { -- Do not render if HEAD is detached + state.pushRemote.ref and HEAD { -- Do not render if HEAD is detached name = "Push", branch = state.pushRemote.branch, remote = state.pushRemote.remote, @@ -166,63 +160,64 @@ function M.Status(state, config) yankable = state.head.tag.oid, }, EmptyLine, - -- TODO Rebasing (rebase) - -- TODO Reverting (sequencer - revert_head) - -- TODO Picking (sequencer - cherry_pick_head) - -- TODO Respect if user has section hidden - #state.untracked.items > 0 and Section { -- TODO: Group by directory and create a fold - title = SectionTitle({ title = "Untracked files" }), - render = SectionItemFile, - items = state.untracked.items, - folded = config.sections.untracked.folded, - name = "untracked", - }, + -- TODO Rebasing (rebase) + -- TODO Reverting (sequencer - revert_head) + -- TODO Picking (sequencer - cherry_pick_head) + -- TODO Respect if user has section hidden + #state.untracked.items > 0 + and Section { -- TODO: Group by directory and create a fold + title = SectionTitle { title = "Untracked files" }, + render = SectionItemFile, + items = state.untracked.items, + folded = config.sections.untracked.folded, + name = "untracked", + }, #state.unstaged.items > 0 and Section { - title = SectionTitle({ title = "Unstaged changes" }), + title = SectionTitle { title = "Unstaged changes" }, render = SectionItemFile, items = state.unstaged.items, folded = config.sections.unstaged.folded, name = "unstaged", }, #state.staged.items > 0 and Section { - title = SectionTitle({ title = "Staged changes" }), + title = SectionTitle { title = "Staged changes" }, render = SectionItemFile, items = state.staged.items, folded = config.sections.staged.folded, name = "staged", }, #state.upstream.unpulled.items > 0 and Section { - title = SectionTitleRemote({ title = "Unpulled from", ref = state.upstream.ref }), + title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref }, render = SectionItemCommit, items = state.upstream.unpulled.items, folded = config.sections.unpulled_upstream.folded, }, (#state.pushRemote.unpulled.items > 0 and state.pushRemote.ref ~= state.upstream.ref) and Section { - title = SectionTitleRemote({ title = "Unpulled from", ref = state.pushRemote.ref }), + title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, render = SectionItemCommit, items = state.pushRemote.unpulled.items, folded = config.sections.unpulled_pushRemote.folded, }, #state.upstream.unmerged.items > 0 and Section { - title = SectionTitleRemote({ title = "Unmerged into", ref = state.upstream.ref }), + title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref }, render = SectionItemCommit, items = state.upstream.unmerged.items, folded = config.sections.unmerged_upstream.folded, }, (#state.pushRemote.unmerged.items > 0 and state.pushRemote.ref ~= state.upstream.ref) and Section { - title = SectionTitleRemote({ title = "Unpushed to", ref = state.pushRemote.ref }), + title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref }, render = SectionItemCommit, items = state.pushRemote.unmerged.items, folded = config.sections.unmerged_pushRemote.folded, }, #state.stashes.items > 0 and Section { - title = SectionTitle({ title = "Stashes" }), + title = SectionTitle { title = "Stashes" }, render = SectionItemStash, items = state.stashes.items, folded = config.sections.stashes.folded, }, #state.recent.items > 0 and Section { - title = SectionTitle({ title = "Recent Commits" }), + title = SectionTitle { title = "Recent Commits" }, render = SectionItemCommit, items = state.recent.items, folded = config.sections.recent.folded, @@ -239,7 +234,7 @@ M._TEST = a.void(function() source = "status_test", callback = function() require("neogit.buffers.status").new(git.repo, config.values):open() - end + end, } end) From 70e782f5f33d15205cee5b190c4387c2031e0cb6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 2 Feb 2024 17:41:45 +0100 Subject: [PATCH 109/443] lint --- lua/neogit/buffers/status/init.lua | 3 ++- lua/neogit/lib/git/cli.lua | 2 +- lua/neogit/lib/git/index.lua | 2 +- lua/neogit/lib/ui/init.lua | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 3d466af30..daf2dcaab 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -185,7 +185,8 @@ function M:open(kind) if stagable then if stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() - local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + local patch = + git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) elseif stagable.filename then diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 050f553d5..6ea84dec3 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -37,7 +37,7 @@ local configurations = { flags = { all = "--all", force = "--force", - } + }, }, worktree = config { diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 3bcb18d82..b7187309a 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -121,7 +121,7 @@ function M.with_temp_index(revision, fn) assert(fn, "Pass a function to call with temp index") local tmp_index = Path:new(vim.uv.os_tmpdir(), ("index.neogit.%s"):format(revision)) - cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call({ hidden = true }) + cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call { hidden = true } assert(tmp_index:exists(), "Failed to create temp index") fn(tmp_index:absolute()) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index e4871062f..9377c3016 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -188,7 +188,7 @@ function Ui:get_hunk_or_filename_under_cursor() return component and { hunk = component.options.hunk, - filename = component.options.filename + filename = component.options.filename, } end From bb5273099166d96d24f76d779ab07932c5e9a2cf Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 Feb 2024 22:18:17 +0100 Subject: [PATCH 110/443] Add GotoFile support --- lua/neogit/buffers/status/init.lua | 46 +++++++++++++++++++++++++++++- lua/neogit/buffers/status/ui.lua | 14 +++++++-- lua/neogit/lib/git/diff.lua | 9 ++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index daf2dcaab..b2f60a48e 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -122,7 +122,51 @@ function M:open(kind) -- TODO end, [mappings["GoToFile"]] = function() - -- TODO + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.absolute_path then + local cursor + if rawget(item, "diff") then + -- If the cursor is located within a hunk, we need to turn that back into + -- a line number in the file. + local line = self.buffer:cursor_line() + for _, hunk in ipairs(item.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + local offset = line - hunk.first + local row = hunk.disk_from + offset - 1 + + for i = 1, offset do + -- If the line is a deletion, we need to adjust the row + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + + cursor = { row, 0 } + break + end + end + end + + self:close() + + -- Copied from original - not sure what it's for, entirely + if not vim.o.hidden and vim.bo.buftype == "" and not vim.bo.readonly and vim.fn.bufname() ~= "" then + vim.cmd("update") + end + + vim.cmd.edit(vim.fn.fnameescape(vim.fn.fnamemodify(item.absolute_path, ":~:."))) + if cursor then + vim.api.nvim_win_set_cursor(0, cursor) + end + + return + end + + local ref = self.buffer.ui:get_yankable_under_cursor() + if ref then + require("neogit.buffers.commit_view").new(ref):open() + end end, [mappings["GoToNextHunkHeader"]] = function() -- TODO: Doesn't go across file boundaries diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index ff25d042a..38edb1fb2 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -68,8 +68,18 @@ local load_diff = function(item) return a.void(function(this, ui) this.options.on_open = nil this.options.folded = false - -- vim.cmd("norm! zE") -- Eliminate all existing folds - this:append(DiffHunks(item.diff)) + + local row, _ = this:row_range_abs() + row = row + 1 -- Filename row + + local diff = item.diff + for _, hunk in ipairs(diff.hunks) do + hunk.first = row + hunk.last = row + hunk.length + row = hunk.last + 1 + end + + this:append(DiffHunks(diff)) ui:update() end) end diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index bcc0d3bf4..18519011a 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -159,6 +159,15 @@ local function build_hunks(lines) insert(hunks, hunk) end + for _, hunk in ipairs(hunks) do + hunk.lines = {} + for i = hunk.diff_from + 1, hunk.diff_to do + table.insert(hunk.lines, lines[i]) + end + + hunk.length = hunk.diff_to - hunk.diff_from + end + return hunks end From d3b07bcca59a4cd643317a35b1f75fb71a2b0ec8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 Feb 2024 22:18:48 +0100 Subject: [PATCH 111/443] Add unstage, and status refresh --- lua/neogit/buffers/status/init.lua | 56 ++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index b2f60a48e..5c6fd40ad 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -39,9 +39,12 @@ function M:close() end function M:refresh() - - -- Update repo state - -- rerender UI + git.repo:refresh { + source = "status", + callback = function() + self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) + end, + } -- diff with current UI -- only update deltas end @@ -242,23 +245,46 @@ function M:open(kind) git.index.add { stagable.filename } end end + + self:refresh() end end), - [mappings["StageAll"]] = function() - -- TODO - end, - [mappings["StageUnstaged"]] = function() - -- TODO - end, + [mappings["StageAll"]] = a.void(function() + git.status.stage_all() + self:refresh() + end), + [mappings["StageUnstaged"]] = a.void(function() + git.status.stage_modified() + self:refresh() + end), [mappings["TabOpen"]] = function() -- TODO end, - [mappings["Unstage"]] = function() - -- TODO - end, - [mappings["UnstageStaged"]] = function() - -- TODO - end, + [mappings["Unstage"]] = a.void(function() + local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + + if stagable then + if stagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + local patch = + git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to, true) + + git.index.apply(patch, { cached = true, reverse = true }) + elseif stagable.filename then + local section = self.buffer.ui:get_current_section() + + if section == "staged" then + git.status.unstage { stagable.filename } + end + end + + self:refresh() + end + end), + [mappings["UnstageStaged"]] = a.void(function() + git.status.unstage_all() + self:refresh() + end), [mappings["VSplitOpen"]] = function() -- TODO end, From 547c800deb9d68514a5fd485b4224a4aabcafe4f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 3 Feb 2024 22:19:43 +0100 Subject: [PATCH 112/443] notes --- lua/neogit/buffers/status/ui.lua | 1 + lua/neogit/popups/init.lua | 1 + lua/neogit/watcher.lua | 1 + 3 files changed, 3 insertions(+) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 38edb1fb2..ac8e83b17 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -84,6 +84,7 @@ local load_diff = function(item) end) end +-- TODO: Handle renames local SectionItemFile = Component.new(function(item) local mode_to_text = { M = "Modified ", diff --git a/lua/neogit/popups/init.lua b/lua/neogit/popups/init.lua index 93938a64f..9e68e676d 100644 --- a/lua/neogit/popups/init.lua +++ b/lua/neogit/popups/init.lua @@ -54,6 +54,7 @@ function M.mappings_table() end, commits) end + -- TODO: These can either be removed, or modified return { { "HelpPopup", "Help", M.open("help") }, { diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index d34093d7b..07e3c0278 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -34,6 +34,7 @@ local fs_event_handler = function(err, filename, events) end logger.debug(info) + -- TODO: Dispatch to new buffer require("neogit.status").dispatch_refresh(nil, "watcher") end From 24400f1a2b77ee275334edbecf2d9c54c9fc0517 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 12:21:00 +0100 Subject: [PATCH 113/443] Reorganize order of functions and implement all depth functions. This time around we can scope them to the current section only. --- lua/neogit/buffers/status/init.lua | 324 +++++++++++++++++++++-------- lua/neogit/lib/git/status.lua | 2 + lua/neogit/lib/ui/component.lua | 86 +++++++- lua/neogit/lib/ui/init.lua | 10 +- 4 files changed, 319 insertions(+), 103 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 5c6fd40ad..e9f3ae8b3 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -45,8 +45,6 @@ function M:refresh() self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) end, } - -- diff with current UI - -- only update deltas end -- TODO @@ -100,77 +98,119 @@ function M:open(kind) mappings = { v = {}, n = { + [mappings["Toggle"]] = function() + local fold = self.buffer.ui:get_fold_under_cursor() + if fold then + if fold.options.on_open then + fold.options.on_open(fold, self.buffer.ui) + else + local ok, _ = pcall(vim.cmd, "normal! za") + if ok then + fold.options.folded = not fold.options.folded + end + end + end + end, [mappings["Close"]] = function() self:close() end, - [mappings["CommandHistory"]] = function() - require("neogit.buffers.git_command_history"):new():show() - end, - [mappings["Console"]] = function() - require("neogit.process").show_console() + [mappings["RefreshBuffer"]] = function() + self:refresh() end, [mappings["Depth1"]] = function() - -- TODO + local section = self.buffer.ui:get_current_section() + if section then + local start, _ = section:row_range_abs() + self.buffer:move_cursor(start) + section:close_all_folds(self.buffer.ui) + + self.buffer.ui:update() + end end, [mappings["Depth2"]] = function() - -- TODO + local section = self.buffer.ui:get_current_section() + local row = self.buffer.ui:get_component_under_cursor() + + if section then + local start, _ = section:row_range_abs() + self.buffer:move_cursor(start) + + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 1) + + self.buffer.ui:update() + + if row then + local start, _ = row:row_range_abs() + self.buffer:move_cursor(start) + end + end end, [mappings["Depth3"]] = function() - -- TODO - end, - [mappings["Depth4"]] = function() - -- TODO - end, - [mappings["Discard"]] = function() - -- TODO - end, - [mappings["GoToFile"]] = function() - local item = self.buffer.ui:get_item_under_cursor() + local section = self.buffer.ui:get_current_section() + local context = self.buffer.ui:get_cursor_context() - if item and item.absolute_path then - local cursor - if rawget(item, "diff") then - -- If the cursor is located within a hunk, we need to turn that back into - -- a line number in the file. - local line = self.buffer:cursor_line() - for _, hunk in ipairs(item.diff.hunks) do - if line >= hunk.first and line <= hunk.last then - local offset = line - hunk.first - local row = hunk.disk_from + offset - 1 + if section then + local start, _ = section:row_range_abs() + self.buffer:move_cursor(start) - for i = 1, offset do - -- If the line is a deletion, we need to adjust the row - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 2) + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 2) - cursor = { row, 0 } - break - end - end + self.buffer.ui:update() + + if context then + local start, _ = context:row_range_abs() + self.buffer:move_cursor(start) end + end + end, + [mappings["Depth4"]] = function() + local section = self.buffer.ui:get_current_section() + local context = self.buffer.ui:get_cursor_context() - self:close() + if section then + local start, _ = section:row_range_abs() + self.buffer:move_cursor(start) + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 3) - -- Copied from original - not sure what it's for, entirely - if not vim.o.hidden and vim.bo.buftype == "" and not vim.bo.readonly and vim.fn.bufname() ~= "" then - vim.cmd("update") - end + self.buffer.ui:update() - vim.cmd.edit(vim.fn.fnameescape(vim.fn.fnamemodify(item.absolute_path, ":~:."))) - if cursor then - vim.api.nvim_win_set_cursor(0, cursor) + if context then + local start, _ = context:row_range_abs() + self.buffer:move_cursor(start) end - - return end + end, + [mappings["CommandHistory"]] = function() + require("neogit.buffers.git_command_history"):new():show() + end, + [mappings["Console"]] = function() + require("neogit.process").show_console() + end, + [mappings["ShowRefs"]] = function() + require("neogit.buffers.refs_view").new(git.refs.list_parsed()):open() + end, + [mappings["YankSelected"]] = function() + local yank = self.buffer.ui:get_yankable_under_cursor() + if yank then + if yank:match("^stash@{%d+}") then + yank = git.rev_parse.oid(yank:match("^(stash@{%d+})")) + end - local ref = self.buffer.ui:get_yankable_under_cursor() - if ref then - require("neogit.buffers.commit_view").new(ref):open() + yank = string.format("'%s'", yank) + vim.cmd.let("@+=" .. yank) + vim.cmd.echo(yank) + else + vim.cmd("echo ''") end end, + [mappings["Discard"]] = function() + -- TODO + end, [mappings["GoToNextHunkHeader"]] = function() -- TODO: Doesn't go across file boundaries local c = self.buffer.ui:get_component_under_cursor(function(c) @@ -217,15 +257,6 @@ function M:open(kind) [mappings["InitRepo"]] = function() git.init.init_repo() end, - [mappings["RefreshBuffer"]] = function() - -- TODO - end, - [mappings["ShowRefs"]] = function() - require("neogit.buffers.refs_view").new(git.refs.list_parsed()):open() - end, - [mappings["SplitOpen"]] = function() - -- TODO - end, [mappings["Stage"]] = a.void(function() local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -238,11 +269,12 @@ function M:open(kind) git.index.apply(patch, { cached = true }) elseif stagable.filename then local section = self.buffer.ui:get_current_section() - - if section == "unstaged" then - git.status.stage { stagable.filename } - elseif section == "untracked" then - git.index.add { stagable.filename } + if section then + if section.options.section == "unstaged" then + git.status.stage { stagable.filename } + elseif section.options.section == "untracked" then + git.index.add { stagable.filename } + end end end @@ -257,9 +289,6 @@ function M:open(kind) git.status.stage_modified() self:refresh() end), - [mappings["TabOpen"]] = function() - -- TODO - end, [mappings["Unstage"]] = a.void(function() local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -273,7 +302,7 @@ function M:open(kind) elseif stagable.filename then local section = self.buffer.ui:get_current_section() - if section == "staged" then + if section and section.options.section == "staged" then git.status.unstage { stagable.filename } end end @@ -285,34 +314,147 @@ function M:open(kind) git.status.unstage_all() self:refresh() end), - [mappings["VSplitOpen"]] = function() - -- TODO + [mappings["GoToFile"]] = function() + local item = self.buffer.ui:get_item_under_cursor() + + -- Goto FILE + if item and item.escaped_path then + local cursor + -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. + if rawget(item, "diff") then + local line = self.buffer:cursor_line() + + for _, hunk in ipairs(item.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + local offset = line - hunk.first + local row = hunk.disk_from + offset - 1 + + for i = 1, offset do + -- If the line is a deletion, we need to adjust the row + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + + cursor = { row, 0 } + break + end + end + end + + self:close() + + vim.cmd.edit(item.escaped_path) + if cursor then + vim.api.nvim_win_set_cursor(0, cursor) + end + + return + end + + -- Goto COMMIT + local ref = self.buffer.ui:get_yankable_under_cursor() + if ref then + require("neogit.buffers.commit_view").new(ref):open() + end end, - [mappings["YankSelected"]] = function() - local yank = self.buffer.ui:get_yankable_under_cursor() - if yank then - if yank:match("^stash@{%d+}") then - yank = git.rev_parse.oid(yank:match("^(stash@{%d+})")) + [mappings["TabOpen"]] = function() + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.escaped_path then + local cursor + -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. + if rawget(item, "diff") then + local line = self.buffer:cursor_line() + + for _, hunk in ipairs(item.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + local offset = line - hunk.first + local row = hunk.disk_from + offset - 1 + + for i = 1, offset do + -- If the line is a deletion, we need to adjust the row + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + + cursor = { row, 0 } + break + end + end end - yank = string.format("'%s'", yank) - vim.cmd.let("@+=" .. yank) - vim.cmd.echo(yank) - else - vim.cmd("echo ''") + vim.cmd.tabedit(item.escaped_path) + if cursor then + vim.api.nvim_win_set_cursor(0, cursor) + end end end, - [mappings["Toggle"]] = function() - local fold = self.buffer.ui:get_fold_under_cursor() - if fold then - if fold.options.on_open then - fold.options.on_open(fold, self.buffer.ui) - else - local ok, _ = pcall(vim.cmd, "normal! za") - if ok then - fold.options.folded = not fold.options.folded + [mappings["SplitOpen"]] = function() + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.escaped_path then + local cursor + -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. + if rawget(item, "diff") then + local line = self.buffer:cursor_line() + + for _, hunk in ipairs(item.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + local offset = line - hunk.first + local row = hunk.disk_from + offset - 1 + + for i = 1, offset do + -- If the line is a deletion, we need to adjust the row + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + + cursor = { row, 0 } + break + end + end + end + + vim.cmd.split(item.escaped_path) + if cursor then + vim.api.nvim_win_set_cursor(0, cursor) + end + end + end, + [mappings["VSplitOpen"]] = function() + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.escaped_path then + local cursor + -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. + if rawget(item, "diff") then + local line = self.buffer:cursor_line() + + for _, hunk in ipairs(item.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + local offset = line - hunk.first + local row = hunk.disk_from + offset - 1 + + for i = 1, offset do + -- If the line is a deletion, we need to adjust the row + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + + cursor = { row, 0 } + break + end end end + + vim.cmd.vsplit(item.escaped_path) + if cursor then + vim.api.nvim_win_set_cursor(0, cursor) + end end end, [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 1b557287b..858d5c947 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -12,6 +12,7 @@ local function update_file(cwd, file, mode, name, original_name) local mt, diff, has_diff local absolute_path = Path:new(cwd, name):absolute() + local escaped_path = vim.fn.fnameescape(vim.fn.fnamemodify(absolute_path, ":~:.")) if file then mt = getmetatable(file) @@ -29,6 +30,7 @@ local function update_file(cwd, file, mode, name, original_name) has_diff = has_diff, diff = diff, absolute_path = absolute_path, + escaped_path = escaped_path, }, mt or {}) end diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 4e7cd0b0c..fe4710ee7 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -102,16 +102,81 @@ function Component:append(c) return self end +---@param ui Ui +---@param depth integer +function Component:open_all_folds(ui, depth) + assert(ui, "Pass in self.buffer.ui") + + if self.options.foldable then + if self.options.on_open then + self.options.on_open(self, ui) + end + + self.options.folded = false + depth = depth - 1 + end + + if self.children and depth > 0 then + for _, child in ipairs(self.children) do + child:open_all_folds(ui, depth) + end + end +end + +---@param ui Ui +function Component:close_all_folds(ui) + assert(ui, "Pass in self.buffer.ui") + + if self.options.foldable then + self.options.folded = true + end + + if self.children then + for _, child in ipairs(self.children) do + child:close_all_folds(ui) + end + end +end + function Component.new(f) - local x = {} - setmetatable(x, { + local instance = {} + + local mt = { __call = function(tbl, ...) - local x = f(...) - local options = vim.tbl_extend("force", default_component_options, tbl, x.options or {}) - x.options = options - setmetatable(x, { __index = Component }) - return x + local this = f(...) + + local options = vim.tbl_extend( + "force", + default_component_options, + tbl, + this.options or {} + ) + this.options = options + + setmetatable(this, { __index = Component }) + + return this + end, + + __eq = function(this, other) + local same_tag = this.options.tag == other.options.tag + local same_index = this.index == other.index + local same_id = this.id == other.id + local have_children = this.children and other.children + + if not have_children then + local same_value = this.value == other.value + + return same_tag and same_index and same_id and same_value + else + local same_fold_state = this.options.folded == other.options.folded + local same_load_state = this.options.on_open == nil and other.options.on_open == nil + local same_child_count = #this.children == #other.children + + return same_tag and same_index and same_id and same_child_count and same_fold_state and same_load_state + end end, + __index = function(tbl, name) local value = rawget(Component, name) @@ -125,8 +190,11 @@ function Component.new(f) return value end, - }) - return x + } + + setmetatable(instance, mt) + + return instance end return Component diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 9377c3016..73f41e739 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -63,9 +63,13 @@ function Ui:find_components(f, options) return result end ----@param fn fun(c: Component): boolean +---@param fn? fun(c: Component): boolean ---@return Component|nil function Ui:get_component_under_cursor(fn) + fn = fn or function() + return true + end + local line = vim.api.nvim_win_get_cursor(0)[1] return self:get_component_on_line(line, fn) end @@ -169,14 +173,14 @@ function Ui:get_yankable_under_cursor() return component and component.options.yankable end ----@return string|nil +---@return Component|nil function Ui:get_current_section() local cursor = vim.api.nvim_win_get_cursor(0) local component = self:_find_component_by_index(cursor[1], function(node) return node.options.section end) - return component and component.options.section + return component end ---@return table|nil From 1f95550ead837a1bb0641fa73fc51356450167f2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 12:21:40 +0100 Subject: [PATCH 114/443] WIP tree diffing stuff. not there yet. --- lua/neogit/lib/ui/init.lua | 56 ++++++++++++++++++++++++++++------ lua/neogit/lib/ui/renderer.lua | 10 +----- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 73f41e739..79ea17294 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -206,11 +206,52 @@ function Ui:get_item_under_cursor() return component and component.options.item end -function Ui:render(...) - self.layout = { ... } - self.layout = util.filter(self.layout, function(x) +local function filter_layout(layout) + return util.filter(layout, function(x) return type(x) == "table" end) +end + +local function compare_trees(old, new) + if old == nil or new == nil then + return false + end + + if old == new then + return true + end + + if old.children and new.children then + for i = 1, #old.children do + if not compare_trees(old.children[i], new.children[i]) then + if old.children[i] and new.children[i] then + -- P({ old = old.children[i].options, new = new.children[i].options }) + + if not old.children[i].tag == "Section" and not new.children[i].tag == "Section" then + old.children[i] = new.children[i] + end + end + + return false + end + end + end + + return true +end + +function Ui:render(...) + local layout = filter_layout { ... } + local root = Component.new(function() + return { tag = "_root", children = layout } + end)() + + if vim.tbl_isempty(self.layout) then + self.layout = root + else + -- This is hard. + compare_trees(self.layout, root) + end self:update() end @@ -233,16 +274,13 @@ function Ui:update() self.buf:set_line_highlights(buffer.line_highlight) self.buf:set_folds(buffer.fold) self.buf:lock() - self.buf:move_cursor(cursor_line) end Ui.col = Component.new(function(children, options) return { tag = "col", - children = util.filter(children, function(x) - return type(x) == "table" - end), + children = filter_layout(children), options = options, } end) @@ -250,9 +288,7 @@ end) Ui.row = Component.new(function(children, options) return { tag = "row", - children = util.filter(children, function(x) - return type(x) == "table" - end), + children = filter_layout(children), options = options, } end) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index ae15f13ce..50e2a6f4d 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -84,15 +84,7 @@ end ---@param layout table ---@return RendererBuffer, RendererIndex function Renderer:render(layout) - local root = Component.new(function() - return { - tag = "_root", - children = layout, - } - end)() - - self:_render(root, root.children, 0) - + self:_render(layout, layout.children, 0) return self.buffer, self.index end From 1fdd472bfd5cd0e1b745d6959682dbf81dce3992 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 12:32:44 +0100 Subject: [PATCH 115/443] Refactor renderer class so it's responsible for drawing buffer, instead of the caller needing to know about it's internals. --- lua/neogit/lib/ui/init.lua | 26 +++++++-------------- lua/neogit/lib/ui/renderer.lua | 41 ++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 79ea17294..5763063b5 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -126,6 +126,7 @@ function Ui:get_interactive_component_under_cursor() end) end +---@return Component|nil function Ui:get_fold_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) @@ -134,6 +135,7 @@ function Ui:get_fold_under_cursor() end) end +---@return string[] function Ui:get_commits_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } table.sort(range) @@ -206,12 +208,16 @@ function Ui:get_item_under_cursor() return component and component.options.item end +---@param layout table +---@return table[] local function filter_layout(layout) return util.filter(layout, function(x) return type(x) == "table" end) end +---@param old table +---@param new table local function compare_trees(old, new) if old == nil or new == nil then return false @@ -256,25 +262,9 @@ function Ui:render(...) self:update() end --- This shouldn't be called often as it completely rewrites the whole buffer function Ui:update() - local ns = self.buf:create_namespace("VirtualText") - local buffer, index = Renderer:new(ns):render(self.layout) - - self.node_index = index - local cursor_line = self.buf:cursor_line() - - self.buf:unlock() - self.buf:clear() - self.buf:clear_namespace("default") - self.buf:resize(#buffer.line) - self.buf:set_lines(0, -1, false, buffer.line) - self.buf:set_highlights(buffer.highlight) - self.buf:set_extmarks(buffer.extmark) - self.buf:set_line_highlights(buffer.line_highlight) - self.buf:set_folds(buffer.fold) - self.buf:lock() - self.buf:move_cursor(cursor_line) + local renderer = Renderer:new(self.layout, self.buf):render() + self.node_index = renderer:node_index() end Ui.col = Component.new(function(children, options) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 50e2a6f4d..461fba7b5 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -1,5 +1,3 @@ -local Component = require("neogit.lib.ui.component") - ---@class RendererIndex ---@field index table local RendererIndex = {} @@ -52,15 +50,22 @@ end ---@class Renderer ---@field buffer RendererBuffer +---@field ui_buffer Buffer ---@field flags RendererFlags ---@field namespace integer +---@field layout table ---@field current_column number ---@field index table local Renderer = {} -function Renderer:new(namespace) +---@param layout table +---@param buffer Buffer +---@return Renderer +function Renderer:new(layout, buffer) local obj = { - namespace = namespace, + namespace = buffer:create_namespace("VirtualText"), + layout = layout, + ui_buffer = buffer, buffer = { line = {}, highlight = {}, @@ -81,11 +86,29 @@ function Renderer:new(namespace) return obj end ----@param layout table ----@return RendererBuffer, RendererIndex -function Renderer:render(layout) - self:_render(layout, layout.children, 0) - return self.buffer, self.index +---@return Renderer +function Renderer:render() + self:_render(self.layout, self.layout.children, 0) + + local cursor_line = self.ui_buffer:cursor_line() + self.ui_buffer:unlock() + self.ui_buffer:clear() + self.ui_buffer:clear_namespace("default") + self.ui_buffer:resize(#self.buffer.line) + self.ui_buffer:set_lines(0, -1, false, self.buffer.line) + self.ui_buffer:set_highlights(self.buffer.highlight) + self.ui_buffer:set_extmarks(self.buffer.extmark) + self.ui_buffer:set_line_highlights(self.buffer.line_highlight) + self.ui_buffer:set_folds(self.buffer.fold) + self.ui_buffer:lock() + self.ui_buffer:move_cursor(cursor_line) + + return self +end + +---@return RendererIndex +function Renderer:node_index() + return self.index end function Renderer:_build_child(child, parent, index) From 04242ae5748743b59d3e10e0c89ced768b3b3e2c Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 12:41:16 +0100 Subject: [PATCH 116/443] Set __index the same as we do elsewhere --- lua/neogit/lib/ui/renderer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 461fba7b5..afae3a4a3 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -57,6 +57,7 @@ end ---@field current_column number ---@field index table local Renderer = {} +Renderer.__index = Renderer ---@param layout table ---@param buffer Buffer @@ -81,7 +82,6 @@ function Renderer:new(layout, buffer) } setmetatable(obj, self) - self.__index = self return obj end From 399bdf022dfc56908b48476f93d406cbf2877b1d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 12:41:38 +0100 Subject: [PATCH 117/443] Fix: __root now encapsulates the tree, so we now need to care about it's children --- lua/neogit/buffers/commit_view/init.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 1afa748c4..11b5006ed 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -114,12 +114,10 @@ function M:open(kind) -- in order to use them as match patterns. local selected_path = vim.fn.trim(c.value) - local diff_headers = {} - -- Recursively navigate the layout until we hit NeogitDiffHeader leafs -- Forward declaration required to avoid missing global error - local find_diff_headers - function find_diff_headers(layout) + local diff_headers = {} + local function find_diff_headers(layout) if layout.children then -- One layout element may have multiple children so we need to loop for _, val in pairs(layout.children) do @@ -136,9 +134,7 @@ function M:open(kind) end end - -- The Diffs are in the 10th element of the layout. - -- TODO: Do better than assume that we care about the last item - find_diff_headers(self.buffer.ui.layout[#self.buffer.ui.layout]) + find_diff_headers(self.buffer.ui.layout) -- Search for a match and jump if we find it for path, line_nr in pairs(diff_headers) do From 533aec9ae4229ba9e8a74d32aca384840d3557de Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 14:53:52 +0100 Subject: [PATCH 118/443] Add "get permission" to provide a negative alternative to "get confirmation" --- lua/neogit/lib/input.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index e15ab0b2e..187ee22c3 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -12,6 +12,18 @@ function M.get_confirmation(msg, options) return vim.fn.confirm(msg, table.concat(options.values, "\n"), options.default) == 1 end +--- Provides the user with a confirmation. Like get_confirmation, but defaults to false +---@param msg string Prompt to use for confirmation +---@param options table|nil +---@return boolean Confirmation (Yes/No) +function M.get_permission(msg, options) + options = options or {} + options.values = options.values or { "&Yes", "&No" } + options.default = options.default or 2 + + return vim.fn.confirm(msg, table.concat(options.values, "\n"), options.default) == 1 +end + ---@class UserChoiceOptions ---@field values table List of choices prefixed with '&' ---@field default integer Default choice to select From b6edbdfe3d833a3d6693c389dcdfa94ab5e14891 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 14:54:56 +0100 Subject: [PATCH 119/443] Add discard fn for normal mode --- lua/neogit/buffers/status/init.lua | 67 +++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index e9f3ae8b3..40099ce52 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -5,6 +5,9 @@ local popups = require("neogit.popups") local git = require("neogit.lib.git") local watcher = require("neogit.watcher") local a = require("plenary.async") +local input = require("neogit.lib.input") +local logger = require("neogit.logger") -- TODO: Add logging +local notification = require("neogit.lib.notification") -- TODO local api = vim.api @@ -118,6 +121,7 @@ function M:open(kind) self:refresh() end, [mappings["Depth1"]] = function() + -- TODO: Need to work with stashes/recent local section = self.buffer.ui:get_current_section() if section then local start, _ = section:row_range_abs() @@ -128,6 +132,7 @@ function M:open(kind) end end, [mappings["Depth2"]] = function() + -- TODO: Need to work with stashes/recent local section = self.buffer.ui:get_current_section() local row = self.buffer.ui:get_component_under_cursor() @@ -147,6 +152,7 @@ function M:open(kind) end end, [mappings["Depth3"]] = function() + -- TODO: Need to work with stashes/recent, but same as depth2 local section = self.buffer.ui:get_current_section() local context = self.buffer.ui:get_cursor_context() @@ -168,6 +174,7 @@ function M:open(kind) end end, [mappings["Depth4"]] = function() + -- TODO: Need to work with stashes/recent, but same as depth2 local section = self.buffer.ui:get_current_section() local context = self.buffer.ui:get_cursor_context() @@ -208,9 +215,46 @@ function M:open(kind) vim.cmd("echo ''") end end, - [mappings["Discard"]] = function() - -- TODO - end, + [mappings["Discard"]] = a.void(function() + git.index.update() -- Check if needed + + local discardable = self.buffer.ui:get_hunk_or_filename_under_cursor() + + if discardable then + local section = self.buffer.ui:get_current_section() + if not section then + return + end + + if discardable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + local hunk = discardable.hunk + local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + + if input.get_permission("Discard hunk?") then + if section.options.section == "staged" then + git.index.apply(patch, { index = true, reverse = true }) + else + git.index.apply(patch, { reverse = true }) + end + end + elseif discardable.filename then + if input.get_permission(("Discard %q?"):format(discardable.filename)) then + if section.options.section == "staged" then + git.index.reset { discardable.filename } + git.index.checkout { discardable.filename } + elseif section.options.section == "unstaged" then + git.index.checkout { discardable.filename } + elseif section.options.section == "untracked" then + a.util.scheduler() + vim.fn.delete(vim.fn.fnameescape(discardable.filename)) + end + end + end + + self:refresh() + end + end), [mappings["GoToNextHunkHeader"]] = function() -- TODO: Doesn't go across file boundaries local c = self.buffer.ui:get_component_under_cursor(function(c) @@ -290,20 +334,20 @@ function M:open(kind) self:refresh() end), [mappings["Unstage"]] = a.void(function() - local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() - if stagable then - if stagable.hunk then + if unstagable then + if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() local patch = - git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to, true) + git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) git.index.apply(patch, { cached = true, reverse = true }) - elseif stagable.filename then + elseif unstagable.filename then local section = self.buffer.ui:get_current_section() if section and section.options.section == "staged" then - git.status.unstage { stagable.filename } + git.status.unstage { unstagable.filename } end end @@ -506,15 +550,16 @@ function M:open(kind) }, initialize = function() self.prev_autochdir = vim.o.autochdir - vim.o.autochdir = false end, render = function() + -- TODO: Figure out a way to remove the very last empty line from the last visible section. + -- it's created by the newline spacer between sections. return ui.Status(self.state, self.config) end, after = function() vim.cmd([[setlocal nowrap]]) - M.watcher = watcher.new(git.repo:git_path():absolute()) + M.watcher = watcher.new(git.repo:git_path():absolute()) -- TODO: pass self in so refresh can be sent end, } end From 70be799560926c549f57f295de48643ae029bc8d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 4 Feb 2024 15:05:18 +0100 Subject: [PATCH 120/443] Get discard working for Added mode files (new files that are staged) --- lua/neogit/buffers/status/init.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 40099ce52..5b1d0235d 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -222,12 +222,13 @@ function M:open(kind) if discardable then local section = self.buffer.ui:get_current_section() - if not section then + local item = self.buffer.ui:get_item_under_cursor() + + if not section or not item then return end if discardable.hunk then - local item = self.buffer.ui:get_item_under_cursor() local hunk = discardable.hunk local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) @@ -240,12 +241,18 @@ function M:open(kind) end elseif discardable.filename then if input.get_permission(("Discard %q?"):format(discardable.filename)) then - if section.options.section == "staged" then + if section.options.section == "staged" and item.mode == "M" then -- Modified git.index.reset { discardable.filename } git.index.checkout { discardable.filename } + elseif section.options.section == "staged" and item.mode == "A" then -- Added + -- TODO: Close any open buffers with this file + git.index.reset { discardable.filename } + a.util.scheduler() + vim.fn.delete(vim.fn.fnameescape(discardable.filename)) elseif section.options.section == "unstaged" then git.index.checkout { discardable.filename } elseif section.options.section == "untracked" then + -- TODO: Close any open buffers with this file a.util.scheduler() vim.fn.delete(vim.fn.fnameescape(discardable.filename)) end From 94d4048326e803b8f1226352662fbd55a1d70651 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 5 Feb 2024 09:32:40 +0100 Subject: [PATCH 121/443] Use new get permission function, and correct hunk messsage --- lua/neogit/lib/git/branch.lua | 7 +------ lua/neogit/lib/git/init.lua | 7 +------ lua/neogit/popups/branch/actions.lua | 11 ++--------- lua/neogit/popups/commit/actions.lua | 12 +++--------- lua/neogit/popups/merge/actions.lua | 12 ++++-------- lua/neogit/popups/rebase/actions.lua | 2 +- lua/neogit/popups/remote/actions.lua | 5 +---- lua/neogit/status.lua | 13 ++++++------- 8 files changed, 19 insertions(+), 50 deletions(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 16a8fca1a..af3eb119c 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -141,12 +141,7 @@ function M.delete(name) local result if M.is_unmerged(name) then - if - input.get_confirmation( - string.format("'%s' contains unmerged commits! Are you sure you want to delete it?", name), - { values = { "&Yes", "&No" }, default = 2 } - ) - then + if input.get_permission(("'%s' contains unmerged commits! Are you sure you want to delete it?"):format(name)) then result = cli.branch.delete.force.name(name).call_sync() end else diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 01808959f..6c1a16f35 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -34,12 +34,7 @@ M.init_repo = function() vim.cmd.lcd(directory) if cli.is_inside_worktree() then - if - not input.get_confirmation( - string.format("Reinitialize existing repository %s?", directory), - { values = { "&Yes", "&No" }, default = 2 } - ) - then + if not input.get_permission(("Reinitialize existing repository %s?"):format(directory)) then return end end diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index efa2e1bb3..926857639 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -173,11 +173,7 @@ end) M.reset_branch = operation("reset_branch", function(popup) if git.status.is_dirty() then - local confirmation = input.get_confirmation( - "Uncommitted changes will be lost. Proceed?", - { values = { "&Yes", "&No" }, default = 2 } - ) - if not confirmation then + if not input.get_permission("Uncommitted changes will be lost. Proceed?") then return end end @@ -226,10 +222,7 @@ M.delete_branch = operation("delete_branch", function() if remote and branch_name - and input.get_confirmation( - string.format("Delete remote branch '%s/%s'?", remote, branch_name), - { values = { "&Yes", "&No" }, default = 2 } - ) + and input.get_permission(("Delete remote branch '%s/%s'?"):format(remote, branch_name)) then success = git.cli.push.remote(remote).delete.to(branch_name).call_sync().code == 0 elseif not remote and branch_name == git.branch.current() then diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 0b4280fed..64a5399db 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -11,12 +11,11 @@ local function confirm_modifications() if git.branch.upstream() and #git.repo.upstream.unmerged.items < 1 - and not input.get_confirmation( + and not input.get_permission( string.format( "This commit has already been published to %s, do you really want to modify it?", git.branch.upstream() - ), - { values = { "&Yes", "&No" }, default = 2 } + ) ) then return false @@ -37,12 +36,7 @@ end local function commit_special(popup, method, opts) if not git.status.anything_staged() then if git.status.anything_unstaged() then - local stage_all = input.get_confirmation( - "Nothing is staged. Commit all uncommitted changed?", - { values = { "&Yes", "&No" }, default = 2 } - ) - - if stage_all then + if input.get_permission("Nothing is staged. Commit all uncommitted changed?") then opts.all = true else return diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index d8452018d..bf4e6c39d 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -14,20 +14,16 @@ function M.commit() end function M.abort() - if not input.get_confirmation("Abort merge?", { values = { "&Yes", "&No" }, default = 2 }) then - return + if input.get_permission("Abort merge?") then + git.merge.abort() end - - git.merge.abort() end function M.merge(popup) local branch = FuzzyFinderBuffer.new(git.branch.get_all_branches()):open_async() - if not branch then - return + if branch then + git.merge.merge(branch, popup:get_arguments()) end - - git.merge.merge(branch, popup:get_arguments()) end return M diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index d75fb82ab..dff624319 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -151,7 +151,7 @@ end -- TODO: Extract to rebase lib? function M.abort() - if input.get_confirmation("Abort rebase?", { values = { "&Yes", "&No" }, default = 2 }) then + if input.get_permission("Abort rebase?") then git.cli.rebase.abort.call_sync() end end diff --git a/lua/neogit/popups/remote/actions.lua b/lua/neogit/popups/remote/actions.lua index 8717f29cc..7186e6ceb 100644 --- a/lua/neogit/popups/remote/actions.lua +++ b/lua/neogit/popups/remote/actions.lua @@ -46,10 +46,7 @@ M.add = operation("add_remote", function(popup) local success = git.remote.add(name, remote_url, popup:get_arguments()) if success then local set_default = ask_to_set_pushDefault() - and input.get_confirmation( - [[Set 'remote.pushDefault' to "]] .. name .. [["?]], - { values = { "&Yes", "&No" }, default = 2 } - ) + and input.get_permission([[Set 'remote.pushDefault' to "]] .. name .. [["?]]) if set_default then git.config.set("remote.pushDefault", name) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua index 763c76760..30a750921 100644 --- a/lua/neogit/status.lua +++ b/lua/neogit/status.lua @@ -951,7 +951,11 @@ local function discard_message(files, hunk_count, mode) if vim.tbl_contains({ "V", "v", "" }, mode) then return "Discard selection?" elseif hunk_count > 0 then - return string.format("Discard %d hunks?", hunk_count) + if hunk_count == 1 then + return "Discard hunk?" + else + return string.format("Discard %d hunks?", hunk_count) + end elseif #files > 1 then return string.format("Discard %d files?", #files) else @@ -1014,12 +1018,7 @@ local discard = operation("discard", function() end end - if - not input.get_confirmation( - discard_message(files, hunk_count, mode.mode), - { values = { "&Yes", "&No" }, default = 2 } - ) - then + if not input.get_permission(discard_message(files, hunk_count, mode.mode)) then return end From 68eda0fb2b39abf6aa91f59dbda243f11bd52c07 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 5 Feb 2024 09:40:32 +0100 Subject: [PATCH 122/443] Note --- lua/neogit/buffers/status/init.lua | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 5b1d0235d..3dc839e6b 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -216,7 +216,7 @@ function M:open(kind) end end, [mappings["Discard"]] = a.void(function() - git.index.update() -- Check if needed + git.index.update() local discardable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -245,15 +245,26 @@ function M:open(kind) git.index.reset { discardable.filename } git.index.checkout { discardable.filename } elseif section.options.section == "staged" and item.mode == "A" then -- Added - -- TODO: Close any open buffers with this file git.index.reset { discardable.filename } + a.util.scheduler() + + local bufnr = vim.fn.bufexists(discardable.filename) + if bufnr and bufnr > 0 then + vim.api.nvim_buf_delete(bufnr, { force = true }) + end + vim.fn.delete(vim.fn.fnameescape(discardable.filename)) elseif section.options.section == "unstaged" then git.index.checkout { discardable.filename } elseif section.options.section == "untracked" then - -- TODO: Close any open buffers with this file a.util.scheduler() + + local bufnr = vim.fn.bufexists(discardable.filename) + if bufnr and bufnr > 0 then + vim.api.nvim_buf_delete(bufnr, { force = true }) + end + vim.fn.delete(vim.fn.fnameescape(discardable.filename)) end end @@ -328,9 +339,11 @@ function M:open(kind) end end end - - self:refresh() + else + -- TODO check if section header, act on entire section end + + self:refresh() end), [mappings["StageAll"]] = a.void(function() git.status.stage_all() From a21dd8ebf1320e4e5b6ee26ebb8e94bffba36d6d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 5 Feb 2024 15:32:36 +0100 Subject: [PATCH 123/443] wip --- lua/neogit/lib/ui/init.lua | 79 ++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 5763063b5..17b77bbcb 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -216,34 +216,64 @@ local function filter_layout(layout) end) end ----@param old table ----@param new table -local function compare_trees(old, new) - if old == nil or new == nil then - return false - end +local function eq(a, b) + local same_tag = a.options.tag == b.options.tag + local same_id = a.id == b.id + local have_children = a.children and b.children - if old == new then - return true - end + if not have_children then + local same_value = a.value == b.value + + -- P({ + -- same_id = same_id, + -- same_tag = same_tag, + -- same_value = same_value + -- }) - if old.children and new.children then - for i = 1, #old.children do - if not compare_trees(old.children[i], new.children[i]) then - if old.children[i] and new.children[i] then - -- P({ old = old.children[i].options, new = new.children[i].options }) + return same_tag and same_id and same_value + else + local same_fold_state = a.options.folded == b.options.folded + local same_load_state = a.options.on_open == nil and b.options.on_open == nil + local same_child_count = #a.children == #b.children + + -- P({ + -- same_tag = same_tag, + -- same_id = same_id, + -- same_child_count = same_child_count, + -- same_fold_state = same_fold_state, + -- same_load_state = same_load_state + -- } + -- ) + + return same_tag + and same_id + and same_child_count + and same_fold_state + and same_load_state + end +end - if not old.children[i].tag == "Section" and not new.children[i].tag == "Section" then - old.children[i] = new.children[i] - end - end +---@param old table +---@param new table +local function compare_trees(old, new, deltas) + if not new.children or not old.children then + return + end - return false - end + for i = 1, #new.children do + if eq(new.children[i], old.children[i]) then + compare_trees(old.children[i], new.children[i], deltas) + else + table.insert(deltas, { old = old.children[i], new = new.children[i] }) + -- if new.children[i].options.highlight == "NeogitSectionHeader" + -- and old.children[i].options.highlight == "NeogitSectionHeader" + -- then + -- old.children[i] = new.children[i] + -- else + -- P { old = old.children[i].options, new = new.children[i].options } + -- end end end - - return true end function Ui:render(...) @@ -255,8 +285,9 @@ function Ui:render(...) if vim.tbl_isempty(self.layout) then self.layout = root else - -- This is hard. - compare_trees(self.layout, root) + local deltas = {} + compare_trees(self.layout, root, deltas) + P(deltas) end self:update() From b720eb25af9b88f3b5177d5d00aa3dd34b20bd57 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 5 Feb 2024 15:33:05 +0100 Subject: [PATCH 124/443] Remove eqality check --- lua/neogit/lib/ui/component.lua | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index fe4710ee7..900f360a4 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -145,38 +145,13 @@ function Component.new(f) __call = function(tbl, ...) local this = f(...) - local options = vim.tbl_extend( - "force", - default_component_options, - tbl, - this.options or {} - ) + local options = vim.tbl_extend("force", default_component_options, tbl, this.options or {}) this.options = options setmetatable(this, { __index = Component }) return this end, - - __eq = function(this, other) - local same_tag = this.options.tag == other.options.tag - local same_index = this.index == other.index - local same_id = this.id == other.id - local have_children = this.children and other.children - - if not have_children then - local same_value = this.value == other.value - - return same_tag and same_index and same_id and same_value - else - local same_fold_state = this.options.folded == other.options.folded - local same_load_state = this.options.on_open == nil and other.options.on_open == nil - local same_child_count = #this.children == #other.children - - return same_tag and same_index and same_id and same_child_count and same_fold_state and same_load_state - end - end, - __index = function(tbl, name) local value = rawget(Component, name) From d6b80dc9766b32beea79c7100f551041fda63f47 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 5 Feb 2024 21:42:24 +0100 Subject: [PATCH 125/443] Update last callers to use_permission so they default to false for destructive actions --- lua/neogit/popups/tag/actions.lua | 4 ++-- lua/neogit/popups/worktree/actions.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/neogit/popups/tag/actions.lua b/lua/neogit/popups/tag/actions.lua index 319cb9fdc..363a8c374 100644 --- a/lua/neogit/popups/tag/actions.lua +++ b/lua/neogit/popups/tag/actions.lua @@ -112,7 +112,7 @@ function M.prune(_) elseif choice == "r" then l_tags = utils.filter(l_tags, function(tag) vim.cmd.redraw() - return input.get_confirmation("Delete local tag: " .. tag) + return input.get_permission("Delete local tag: " .. tag) end) else l_tags = {} @@ -131,7 +131,7 @@ function M.prune(_) elseif choice == "r" then r_tags = utils.filter(r_tags, function(tag) vim.cmd.redraw() - return input.get_confirmation("Delete remote tag: " .. tag) + return input.get_permission("Delete remote tag: " .. tag) end) else r_tags = {} diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 623103dae..7f64c872d 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -126,7 +126,7 @@ M.delete = operations("delete_worktree", function() local change_dir = selected == vim.fn.getcwd() local success = false - if input.get_confirmation("Remove worktree?") then + if input.get_permission("Remove worktree?") then if change_dir then status.chdir(git.worktree.main().path) end @@ -135,7 +135,7 @@ M.delete = operations("delete_worktree", function() if git.worktree.remove(selected) then success = true else - if input.get_confirmation("Worktree has untracked or modified files. Remove anyways?") then + if input.get_permission("Worktree has untracked or modified files. Remove anyways?") then if git.worktree.remove(selected, { "--force" }) then success = true end From 93773498c667d9071556927470719a4a961a519e Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 7 Feb 2024 22:20:54 +0100 Subject: [PATCH 126/443] Some notes: - Dropping support for multiple instances of a status buffer per neovim instance. It's too much of a PITA to track, so if you want to use different repos, just us different instances of neovim to do so. - Rewrite Watcher to expose an .instance attribute which can be used to get the current instance of a watcher - Rewrite Status to expose an .instance attribute which can be used to get the current status buffer instance - Implement the refresh locking/dispatching for the new status buffer --- lua/neogit.lua | 146 ++++++++++++++-------- lua/neogit/buffers/status/init.lua | 190 ++++++++++++++++++++++------- lua/neogit/buffers/status/ui.lua | 15 ++- lua/neogit/lib/finder.lua | 8 +- lua/neogit/lib/git/cli.lua | 4 +- lua/neogit/lib/git/init.lua | 1 + lua/neogit/lib/git/repository.lua | 1 + lua/neogit/lib/popup/builder.lua | 9 +- lua/neogit/lib/ui/init.lua | 20 +-- lua/neogit/lib/ui/renderer.lua | 5 + lua/neogit/watcher.lua | 146 ++++++++++++---------- 11 files changed, 355 insertions(+), 190 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 820bfa0e8..7a6059f47 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -21,14 +21,47 @@ function M.setup(opts) M.autocmd_group = vim.api.nvim_create_augroup("Neogit", { clear = false }) - M.status = require("neogit.status") - M.dispatch_reset = M.status.dispatch_reset - M.refresh = M.status.refresh - M.reset = M.status.reset - M.refresh_manually = M.status.refresh_manually - M.dispatch_refresh = M.status.dispatch_refresh - M.refresh_viml_compat = M.status.refresh_viml_compat - M.close = M.status.close + M.status = require("neogit.buffers.status") + + M.dispatch_reset = function() + local instance = M.status.instance + if instance then + instance:dispatch_reset() + end + end + + M.refresh = function() + local instance = M.status.instance + if instance then + instance:refresh() + end + end + + M.reset = function() + local instance = M.status.instance + if instance then + instance:reset() + end + end + + M.dispatch_refresh = function() + local instance = M.status.instance + if instance then + instance:dispatch_refresh() + end + end + + M.close = function() + local instance = M.status.instance + if instance then + instance:close() + end + end + + -- TODO ? + -- M.refresh_viml_compat = M.status.refresh_viml_compat + -- M.refresh_manually = M.status.refresh_manually + M.lib = require("neogit.lib") M.cli = M.lib.git.cli @@ -44,6 +77,53 @@ function M.setup(opts) folds.setup() end +local function construct_opts(opts) + opts = opts or {} + + if opts.cwd and not opts.no_expand then + opts.cwd = vim.fn.expand(opts.cwd) + end + + if not opts.cwd then + local git = require("neogit.lib.git") + opts.cwd = git.cli.git_root_of_cwd() + + if opts.cwd == "" then + opts.cwd = vim.fn.getcwd() + end + end + + return opts +end + +local function open_popup(name) + local has_pop, popup = pcall(require, "neogit.popups." .. name) + + if not has_pop then + vim.api.nvim_err_writeln("Invalid popup '" .. name .. "'") + else + popup.create {} + end +end + +local function open_status_buffer(opts) + local a = require("plenary.async") + local status = require("neogit.buffers.status") + local config = require("neogit.config") + local git = require("neogit.lib.git") + + vim.cmd.lcd(opts.cwd) + + a.run(function() + git.repo:refresh { + source = "initialize", + callback = function() + status.new(git.repo, config.values):open(opts.kind) + end, + } + end) +end + ---@alias Popup "cherry_pick" | "commit" | "branch" | "diff" | "fetch" | "log" | "merge" | "remote" | "pull" | "push" | "rebase" | "revert" | "reset" | "stash" --- ---@class OpenOpts @@ -54,38 +134,20 @@ end ---@param opts OpenOpts|nil function M.open(opts) - local a = require("plenary.async") - local lib = require("neogit.lib") - local status = require("neogit.status") - local input = require("neogit.lib.input") - local cli = require("neogit.lib.git.cli") - local logger = require("neogit.logger") local notification = require("neogit.lib.notification") - opts = opts or {} - - if opts.cwd and not opts.no_expand then - opts.cwd = vim.fn.expand(opts.cwd) - end - - if not opts.cwd then - opts.cwd = require("neogit.lib.git.cli").git_root_of_cwd() - end - if not did_setup then notification.error("Neogit has not been setup!") - logger.error("Neogit not setup!") return end - if not cli.is_inside_worktree(opts.cwd) then - if - input.get_confirmation( - string.format("Initialize repository in %s?", opts.cwd or vim.fn.getcwd()), - { values = { "&Yes", "&No" }, default = 2 } - ) - then - lib.git.init.create(opts.cwd or vim.fn.getcwd(), true) + opts = construct_opts(opts) + + local git = require("neogit.lib.git") + if not git.cli.is_inside_worktree(opts.cwd) then + local input = require("neogit.lib.input") + if input.get_permission(("Initialize repository in %s?"):format(opts.cwd)) then + git.init.create(opts.cwd, true) else notification.error("The current working directory is not a git repository") return @@ -93,23 +155,9 @@ function M.open(opts) end if opts[1] ~= nil then - local popup_name = opts[1] - local has_pop, popup = pcall(require, "neogit.popups." .. popup_name) - - if not has_pop then - vim.api.nvim_err_writeln("Invalid popup '" .. popup_name .. "'") - else - popup.create {} - end + open_popup(opts[1]) else - a.run(function() - if status.status_buffer then - vim.cmd.lcd(opts.cwd) - status.refresh(nil, "open") - else - status.create(opts.kind, opts.cwd) - end - end) + open_status_buffer(opts) end end diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 3dc839e6b..197f934d3 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -6,16 +6,24 @@ local git = require("neogit.lib.git") local watcher = require("neogit.watcher") local a = require("plenary.async") local input = require("neogit.lib.input") + local logger = require("neogit.logger") -- TODO: Add logging local notification = require("neogit.lib.notification") -- TODO local api = vim.api +local fn = vim.fn + +---@class Semaphore +---@field permits number +---@field acquire function ---@class StatusBuffer ---@field is_open boolean whether the buffer is currently visible ---@field buffer Buffer instance ---@field state NeogitRepo ---@field config NeogitConfig +---@field frozen boolean +---@field refresh_lock Semaphore local M = {} M.__index = M @@ -25,9 +33,12 @@ M.__index = M function M.new(state, config) local instance = { is_open = false, + -- frozen = false, state = state, config = config, buffer = nil, + watcher = nil, + refresh_lock = a.control.Semaphore.new(1), } setmetatable(instance, M) @@ -35,21 +46,6 @@ function M.new(state, config) return instance end -function M:close() - self.is_open = false - self.buffer:close() - self.buffer = nil -end - -function M:refresh() - git.repo:refresh { - source = "status", - callback = function() - self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - end, - } -end - -- TODO -- Save/restore cursor location -- Redrawing w/ lock, that doesn't discard opened/closed diffs, keeps cursor location @@ -67,15 +63,11 @@ end -- - Files in selection -- - Hunks in selection -- - Lines in selection --- --- Mappings: --- Ensure it will work when passing multiple mappings to the same function --- + function M:open(kind) if M.instance and M.instance.is_open then M.instance:close() end - M.instance = self if self.is_open then @@ -94,8 +86,9 @@ function M:open(kind) disable_line_numbers = config.values.disable_line_numbers, autocmds = { ["BufUnload"] = function() - M.watcher:stop() - M.instance.is_open = false + watcher.instance:stop() + self.is_open = false + vim.o.autochdir = self.prev_autochdir end, }, mappings = { @@ -117,9 +110,9 @@ function M:open(kind) [mappings["Close"]] = function() self:close() end, - [mappings["RefreshBuffer"]] = function() + [mappings["RefreshBuffer"]] = a.void(function() self:refresh() - end, + end), [mappings["Depth1"]] = function() -- TODO: Need to work with stashes/recent local section = self.buffer.ui:get_current_section() @@ -228,6 +221,9 @@ function M:open(kind) return end + -- TODO: Discard Commit? + -- TODO: Discard Stash? + -- TODO: Discard Section? if discardable.hunk then local hunk = discardable.hunk local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) @@ -249,23 +245,23 @@ function M:open(kind) a.util.scheduler() - local bufnr = vim.fn.bufexists(discardable.filename) + local bufnr = fn.bufexists(discardable.filename) if bufnr and bufnr > 0 then - vim.api.nvim_buf_delete(bufnr, { force = true }) + api.nvim_buf_delete(bufnr, { force = true }) end - vim.fn.delete(vim.fn.fnameescape(discardable.filename)) + fn.delete(fn.fnameescape(discardable.filename)) elseif section.options.section == "unstaged" then git.index.checkout { discardable.filename } elseif section.options.section == "untracked" then a.util.scheduler() - local bufnr = vim.fn.bufexists(discardable.filename) + local bufnr = fn.bufexists(discardable.filename) if bufnr and bufnr > 0 then - vim.api.nvim_buf_delete(bufnr, { force = true }) + api.nvim_buf_delete(bufnr, { force = true }) end - vim.fn.delete(vim.fn.fnameescape(discardable.filename)) + fn.delete(fn.fnameescape(discardable.filename)) end end end @@ -281,10 +277,10 @@ function M:open(kind) if c then if c.options.tag == "Diff" then - self.buffer:move_cursor(vim.fn.line(".") + 1) + self.buffer:move_cursor(fn.line(".") + 1) else local _, last = c:row_range_abs() - if last == vim.fn.line("$") then + if last == fn.line("$") then self.buffer:move_cursor(last) else self.buffer:move_cursor(last + 1) @@ -302,7 +298,7 @@ function M:open(kind) if c then local first, _ = c:row_range_abs() - if vim.fn.line(".") == first then + if fn.line(".") == first then first = previous_hunk_header(self, line - 1) end @@ -310,7 +306,7 @@ function M:open(kind) end end - local previous_header = previous_hunk_header(self, vim.fn.line(".")) + local previous_header = previous_hunk_header(self, fn.line(".")) if previous_header then api.nvim_win_set_cursor(0, { previous_header, 0 }) vim.cmd("normal! zt") @@ -320,8 +316,10 @@ function M:open(kind) git.init.init_repo() end, [mappings["Stage"]] = a.void(function() + -- TODO: Cursor Placement local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local cursor if stagable then if stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() @@ -329,7 +327,10 @@ function M:open(kind) git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) + cursor = stagable.hunk.first elseif stagable.filename then + cursor = self.buffer:cursor_line() + local section = self.buffer.ui:get_current_section() if section then if section.options.section == "unstaged" then @@ -343,24 +344,36 @@ function M:open(kind) -- TODO check if section header, act on entire section end + if cursor then + self.buffer:move_cursor(cursor) + end + self:refresh() end), [mappings["StageAll"]] = a.void(function() + -- TODO: Cursor Placement git.status.stage_all() self:refresh() end), [mappings["StageUnstaged"]] = a.void(function() + -- TODO: Cursor Placement git.status.stage_modified() self:refresh() end), [mappings["Unstage"]] = a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + -- TODO: Cursor Placement if unstagable then if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() - local patch = - git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) + local patch = git.index.generate_patch( + item, + unstagable.hunk, + unstagable.hunk.from, + unstagable.hunk.to, + true + ) git.index.apply(patch, { cached = true, reverse = true }) elseif unstagable.filename then @@ -375,6 +388,7 @@ function M:open(kind) end end), [mappings["UnstageStaged"]] = a.void(function() + -- TODO: Cursor Placement git.status.unstage_all() self:refresh() end), @@ -410,7 +424,7 @@ function M:open(kind) vim.cmd.edit(item.escaped_path) if cursor then - vim.api.nvim_win_set_cursor(0, cursor) + api.nvim_win_set_cursor(0, cursor) end return @@ -451,7 +465,7 @@ function M:open(kind) vim.cmd.tabedit(item.escaped_path) if cursor then - vim.api.nvim_win_set_cursor(0, cursor) + api.nvim_win_set_cursor(0, cursor) end end end, @@ -484,7 +498,7 @@ function M:open(kind) vim.cmd.split(item.escaped_path) if cursor then - vim.api.nvim_win_set_cursor(0, cursor) + api.nvim_win_set_cursor(0, cursor) end end end, @@ -517,7 +531,7 @@ function M:open(kind) vim.cmd.vsplit(item.escaped_path) if cursor then - vim.api.nvim_win_set_cursor(0, cursor) + api.nvim_win_set_cursor(0, cursor) end end end, @@ -579,9 +593,103 @@ function M:open(kind) end, after = function() vim.cmd([[setlocal nowrap]]) - M.watcher = watcher.new(git.repo:git_path():absolute()) -- TODO: pass self in so refresh can be sent + + if config.values.filewatcher.enabled then + watcher.new(git.repo:git_path():absolute()):start() + end end, } end +function M:close() + vim.o.autochdir = self.prev_autochdir + + watcher.instance:stop() + self.is_open = false + self.buffer:close() + self.buffer = nil +end + +function M:focus() + if self.buffer then + self.buffer:focus() + end +end + +-- TODO: When launching the fuzzy finder, any refresh attempted will raise an exception because the set_folds() function +-- cannot be called when the buffer is not focused, as it's not a proper API. We could implement some kind of freeze +-- mechanism to prevent the buffer from refreshing while the fuzzy finder is open. +-- function M:freeze() +-- self.frozen = true +-- end +-- +-- function M:unfreeze() +-- self.frozen = false +-- end + +function M:refresh(partial, reason) + -- if self.frozen then + -- return + -- end + + local permit = self:_get_refresh_lock(reason) + + git.repo:refresh { + source = "status", + partial = partial, + callback = function() + self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) + + api.nvim_exec_autocmds( + "User", + { pattern = "NeogitStatusRefreshed", modeline = false } + ) + + permit:forget() + logger.info("[STATUS BUFFER]: Refresh lock is now free") + end, + } +end + +function M:dispatch_refresh(partial, reason) + a.run(function() + if self:_is_refresh_locked() then + logger.debug("[STATUS] Refresh lock is active. Skipping refresh from " .. reason) + else + self:refresh(partial, reason) + end + end) +end + +function M:reset() + git.repo:reset() + self:refresh(nil, "reset") +end + +function M:dispatch_reset() + a.run(function() + self:reset() + end) +end + +function M:_is_refresh_locked() + return self.refresh_lock.permits == 0 +end + +function M:_get_refresh_lock(reason) + local permit = self.refresh_lock:acquire() + logger.debug(("[STATUS BUFFER]: Acquired refresh lock:"):format(reason or "unknown")) + + vim.defer_fn(function() + if self:_is_refresh_locked() then + permit:forget() + logger.debug( + ("[STATUS BUFFER]: Refresh lock for %s expired after 10 seconds"):format(reason or "unknown") + ) + end + end, 10000) + + return permit +end + return M diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index ac8e83b17..151f0c642 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -175,14 +175,13 @@ function M.Status(state, config) -- TODO Reverting (sequencer - revert_head) -- TODO Picking (sequencer - cherry_pick_head) -- TODO Respect if user has section hidden - #state.untracked.items > 0 - and Section { -- TODO: Group by directory and create a fold - title = SectionTitle { title = "Untracked files" }, - render = SectionItemFile, - items = state.untracked.items, - folded = config.sections.untracked.folded, - name = "untracked", - }, + #state.untracked.items > 0 and Section { -- TODO: Group by directory and create a fold + title = SectionTitle { title = "Untracked files" }, + render = SectionItemFile, + items = state.untracked.items, + folded = config.sections.untracked.folded, + name = "untracked", + }, #state.unstaged.items > 0 and Section { title = SectionTitle { title = "Unstaged changes" }, render = SectionItemFile, diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 33af90f7f..77ff7a0cf 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -2,10 +2,10 @@ local config = require("neogit.config") local a = require("plenary.async") local function refocus_status_buffer() - local status = require("neogit.status") - if status.status_buffer then - status.status_buffer:focus() - status.dispatch_refresh(nil, "finder.refocus") + local status = require("neogit.buffers.status") + if status.instance then + status.instance:focus() + status.instance:dispatch_refresh(nil, "finder.refocus") end end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 6ea84dec3..e80281db0 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -569,6 +569,7 @@ local configurations = { -- repository.git_root is used by all other library functions, so it's most likely the one you want to use. -- git_root_of_cwd() returns the git repo of the cwd, which can change anytime -- after git_root_of_cwd() has been called. +---@return string local function git_root_of_cwd() local process = process .new({ @@ -580,8 +581,7 @@ local function git_root_of_cwd() :spawn_blocking() if process ~= nil and process.code == 0 then - local out = process.stdout[1] - return Path:new(out):absolute() + return Path:new(process.stdout[1]):absolute() else return "" end diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 6c1a16f35..647498004 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -15,6 +15,7 @@ M.create = function(directory, sync) end -- TODO Use path input +-- TODO: Don't call the status buffer directly here M.init_repo = function() local directory = input.get_user_input("Create repository in", { completion = "dir" }) if not directory then diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 68858973a..e374ca633 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -3,6 +3,7 @@ local logger = require("neogit.logger") local Path = require("plenary.path") local cli = require("neogit.lib.git.cli") +---@return NeogitRepo local function empty_state() ---@class NeogitRepo return { diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 3ec67252c..4a7c913f9 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -374,14 +374,15 @@ function M:action(keys, description, callback) local permit = action_lock:acquire() logger.debug(string.format("[ACTION] Running action from %s", self.state.name)) - watcher.pause() - callback(...) - watcher.resume() + local args = { ... } + watcher.suspend(function() + callback(unpack(args)) + end) permit:forget() logger.debug("[ACTION] Dispatching Refresh") - require("neogit.status").dispatch_refresh(nil, "action") + require("neogit.buffers.status").instance:dispatch_refresh(nil, "action") end) end diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 17b77bbcb..f328204a5 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -245,11 +245,7 @@ local function eq(a, b) -- } -- ) - return same_tag - and same_id - and same_child_count - and same_fold_state - and same_load_state + return same_tag and same_id and same_child_count and same_fold_state and same_load_state end end @@ -265,13 +261,6 @@ local function compare_trees(old, new, deltas) compare_trees(old.children[i], new.children[i], deltas) else table.insert(deltas, { old = old.children[i], new = new.children[i] }) - -- if new.children[i].options.highlight == "NeogitSectionHeader" - -- and old.children[i].options.highlight == "NeogitSectionHeader" - -- then - -- old.children[i] = new.children[i] - -- else - -- P { old = old.children[i].options, new = new.children[i].options } - -- end end end end @@ -285,9 +274,10 @@ function Ui:render(...) if vim.tbl_isempty(self.layout) then self.layout = root else - local deltas = {} - compare_trees(self.layout, root, deltas) - P(deltas) + -- local deltas = {} + -- compare_trees(self.layout, root, deltas) + -- P(deltas) + self.layout = root end self:update() diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index afae3a4a3..33c5530b1 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -88,6 +88,11 @@ end ---@return Renderer function Renderer:render() + -- If the buffer is not focused, trying to set folds will raise an error because it's not a proper API. + if not self.ui_buffer:is_focused() then + return self + end + self:_render(self.layout, self.layout.children, 0) local cursor_line = self.ui_buffer:cursor_line() diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 07e3c0278..14e1e870c 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -1,90 +1,102 @@ -local uv = vim.loop +-- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/watcher.lua#L103 -local config = require("neogit.config") +local uv = vim.loop local logger = require("neogit.logger") -local paused = false - -local fs_event_handler = function(err, filename, events) - if paused then - return - end - - if err then - logger.error(string.format("[WATCHER] Git dir update error: %s", err)) - return - end +---@class Watcher +---@field gitdir string +---@field paused boolean +---@field started boolean +---@field status_buffer StatusBuffer +---@field fs_event_handler uv_fs_event_t +local Watcher = {} +Watcher.__index = Watcher - local info = string.format( - "[WATCHER] Git dir update: '%s' %s", - filename, - vim.inspect(events, { indent = "", newline = " " }) - ) - - -- stylua: ignore - if - filename == nil or - filename:match("%.lock$") or - filename:match("COMMIT_EDITMSG") or - filename:match("~$") or - filename:match("%d%d%d%d") - then - logger.debug(string.format("%s (ignoring)", info)) - return +function Watcher:fs_event_callback() + local status = require("neogit.buffers.status") + + return function(err, filename, events) + if self.paused then + return + end + + if err then + logger.error(string.format("[WATCHER] Git dir update error: %s", err)) + return + end + + local info = string.format( + "[WATCHER] Git dir update: '%s' %s", + filename, + vim.inspect(events, { indent = "", newline = " " }) + ) + + -- stylua: ignore + if + filename == nil or + filename:match("%.lock$") or + filename:match("COMMIT_EDITMSG") or + filename:match("~$") or + filename:match("%d%d%d%d") + then + logger.debug(string.format("%s (ignoring)", info)) + return + end + + logger.debug(info) + status.instance:dispatch_refresh(nil, "watcher") end - - logger.debug(info) - -- TODO: Dispatch to new buffer - require("neogit.status").dispatch_refresh(nil, "watcher") end --- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/watcher.lua#L103 ---- @param gitdir string ---- @return uv_fs_event_t -local function start(gitdir) - local w = assert(uv.new_fs_event()) - w:start(gitdir, {}, fs_event_handler) +function Watcher:pause() + logger.debug("[WATCHER] Paused") + self.paused = true +end - return w +function Watcher:resume() + logger.debug("[WATCHER] Resumed") + self.paused = false end ----@class Watcher ----@field gitdir string ----@field fs_event_handler uv_fs_event_t|nil -local Watcher = {} -Watcher.__index = Watcher +function Watcher:start() + logger.debug("[WATCHER] Watching git dir: " .. self.gitdir) + self.paused = false + self.fs_event_handler:start(self.gitdir, {}, self:fs_event_callback()) +end function Watcher:stop() - if self.fs_event_handler then - logger.debug("[WATCHER] Stopped watching git dir: " .. self.gitdir) - self.fs_event_handler:stop() - end + logger.debug("[WATCHER] Stopped watching git dir: " .. self.gitdir) + self.paused = true + self.fs_event_handler:stop() end -function Watcher:create(gitdir) - self.gitdir = gitdir - self.paused = false - - if config.values.filewatcher.enabled then - logger.debug("[WATCHER] Watching git dir: " .. gitdir) - self.fs_event_handler = start(gitdir) +function Watcher.new(gitdir) + if Watcher.instance then + Watcher.instance:stop() + Watcher.instance = nil end - return self -end + local instance = { + gitdir = gitdir, + paused = true, + fs_event_handler = assert(uv.new_fs_event()), + } -function Watcher.new(...) - return Watcher:create(...) -end + setmetatable(instance, Watcher) -function Watcher.pause() - logger.debug("[WATCHER] Paused") - paused = true + Watcher.instance = instance + return instance end -function Watcher.resume() - logger.debug("[WATCHER] Resumed") - paused = false +function Watcher.suspend(callback) + local watcher = Watcher.instance + if watcher then + watcher:pause() + callback() + watcher:resume() + else + callback() + end end return Watcher From 01a07c317473207250e60e23997bee6da6572116 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 7 Feb 2024 23:45:46 +0100 Subject: [PATCH 127/443] lint --- lua/neogit.lua | 1 - lua/neogit/buffers/status/init.lua | 5 +---- lua/neogit/buffers/status/ui.lua | 11 ++++++----- lua/neogit/lib/git/branch.lua | 3 ++- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 7a6059f47..9e28c7e25 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -62,7 +62,6 @@ function M.setup(opts) -- M.refresh_viml_compat = M.status.refresh_viml_compat -- M.refresh_manually = M.status.refresh_manually - M.lib = require("neogit.lib") M.cli = M.lib.git.cli M.popups = require("neogit.popups") diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 197f934d3..3a065e4aa 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -640,10 +640,7 @@ function M:refresh(partial, reason) callback = function() self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - api.nvim_exec_autocmds( - "User", - { pattern = "NeogitStatusRefreshed", modeline = false } - ) + api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) permit:forget() logger.info("[STATUS BUFFER]: Refresh lock is now free") diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 151f0c642..258b491a5 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -171,11 +171,12 @@ function M.Status(state, config) yankable = state.head.tag.oid, }, EmptyLine, - -- TODO Rebasing (rebase) - -- TODO Reverting (sequencer - revert_head) - -- TODO Picking (sequencer - cherry_pick_head) - -- TODO Respect if user has section hidden - #state.untracked.items > 0 and Section { -- TODO: Group by directory and create a fold + -- TODO Rebasing (rebase) + -- TODO Reverting (sequencer - revert_head) + -- TODO Picking (sequencer - cherry_pick_head) + -- TODO Respect if user has section hidden + -- TODO: Group untracked by directory and create a fold + #state.untracked.items > 0 and Section { title = SectionTitle { title = "Untracked files" }, render = SectionItemFile, items = state.untracked.items, diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index af3eb119c..7ddeff392 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -141,7 +141,8 @@ function M.delete(name) local result if M.is_unmerged(name) then - if input.get_permission(("'%s' contains unmerged commits! Are you sure you want to delete it?"):format(name)) then + local message = ("'%s' contains unmerged commits! Are you sure you want to delete it?"):format(name) + if input.get_permission(message) then result = cli.branch.delete.force.name(name).call_sync() end else From 28a2fdfd783a2d6c20e16461dc71822ed7b1de23 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 7 Feb 2024 23:47:12 +0100 Subject: [PATCH 128/443] Cleaner watcher suspend API --- lua/neogit/lib/popup/builder.lua | 6 +----- lua/neogit/watcher.lua | 10 ++++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 4a7c913f9..1c31501da 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -374,11 +374,7 @@ function M:action(keys, description, callback) local permit = action_lock:acquire() logger.debug(string.format("[ACTION] Running action from %s", self.state.name)) - local args = { ... } - watcher.suspend(function() - callback(unpack(args)) - end) - + watcher.suspend(callback, { ... }) permit:forget() logger.debug("[ACTION] Dispatching Refresh") diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 14e1e870c..b80f74cb8 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -88,14 +88,16 @@ function Watcher.new(gitdir) return instance end -function Watcher.suspend(callback) +function Watcher.suspend(callback, args) local watcher = Watcher.instance if watcher then watcher:pause() - callback() + end + + callback(unpack(args)) + + if watcher then watcher:resume() - else - callback() end end From e088bc9509da86d263b1b0953e420ca153cfb25f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 10 Feb 2024 23:22:51 +0100 Subject: [PATCH 129/443] Updates --- lua/neogit/buffers/status/init.lua | 58 +++++++++++++++--------------- lua/neogit/watcher.lua | 19 ++++++---- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 3a065e4aa..4bffa95ec 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,3 +1,32 @@ +-- TODO +-- Save/restore cursor location +-- Redrawing w/ lock, that doesn't discard opened/closed diffs, keeps cursor location +-- on-close hook to teardown stuff +-- +-- Actions: +-- Staging / Unstaging / Discarding +-- +-- Contexts: +-- - Normal +-- - Section +-- - File +-- - Hunk +-- - Visual +-- - Files in selection +-- - Hunks in selection +-- - Lines in selection + +-- TODO: When launching the fuzzy finder, any refresh attempted will raise an exception because the set_folds() function +-- cannot be called when the buffer is not focused, as it's not a proper API. We could implement some kind of freeze +-- mechanism to prevent the buffer from refreshing while the fuzzy finder is open. +-- function M:freeze() +-- self.frozen = true +-- end +-- +-- function M:unfreeze() +-- self.frozen = false +-- end + local config = require("neogit.config") local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.status.ui") @@ -46,24 +75,6 @@ function M.new(state, config) return instance end --- TODO --- Save/restore cursor location --- Redrawing w/ lock, that doesn't discard opened/closed diffs, keeps cursor location --- on-close hook to teardown stuff --- --- Actions: --- Staging / Unstaging / Discarding --- --- Contexts: --- - Normal --- - Section --- - File --- - Hunk --- - Visual --- - Files in selection --- - Hunks in selection --- - Lines in selection - function M:open(kind) if M.instance and M.instance.is_open then M.instance:close() @@ -616,17 +627,6 @@ function M:focus() end end --- TODO: When launching the fuzzy finder, any refresh attempted will raise an exception because the set_folds() function --- cannot be called when the buffer is not focused, as it's not a proper API. We could implement some kind of freeze --- mechanism to prevent the buffer from refreshing while the fuzzy finder is open. --- function M:freeze() --- self.frozen = true --- end --- --- function M:unfreeze() --- self.frozen = false --- end - function M:refresh(partial, reason) -- if self.frozen then -- return diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index b80f74cb8..b20802839 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -59,15 +59,19 @@ function Watcher:resume() end function Watcher:start() - logger.debug("[WATCHER] Watching git dir: " .. self.gitdir) - self.paused = false - self.fs_event_handler:start(self.gitdir, {}, self:fs_event_callback()) + if not self.running then + logger.debug("[WATCHER] Watching git dir: " .. self.gitdir) + self.running = true + self.fs_event_handler:start(self.gitdir, {}, self:fs_event_callback()) + end end function Watcher:stop() - logger.debug("[WATCHER] Stopped watching git dir: " .. self.gitdir) - self.paused = true - self.fs_event_handler:stop() + if self.running then + logger.debug("[WATCHER] Stopped watching git dir: " .. self.gitdir) + self.running = false + self.fs_event_handler:stop() + end end function Watcher.new(gitdir) @@ -78,7 +82,8 @@ function Watcher.new(gitdir) local instance = { gitdir = gitdir, - paused = true, + paused = false, + running = false, fs_event_handler = assert(uv.new_fs_event()), } From 3971830a926563e394aecc2516e4359e0e1fce61 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 Feb 2024 21:44:49 +0100 Subject: [PATCH 130/443] Working tree diffing stuff --- lua/neogit/lib/ui/init.lua | 118 +++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index f328204a5..5a3e527af 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -216,51 +216,80 @@ local function filter_layout(layout) end) end -local function eq(a, b) - local same_tag = a.options.tag == b.options.tag - local same_id = a.id == b.id - local have_children = a.children and b.children +local function gather_nodes(node, node_table, prefix) + if not node_table then + node_table = {} + end - if not have_children then - local same_value = a.value == b.value + prefix = prefix or "" - -- P({ - -- same_id = same_id, - -- same_tag = same_tag, - -- same_value = same_value - -- }) + if node.options.section then + node_table[node.options.section] = { + folded = node.options.folded, + } - return same_tag and same_id and same_value + if node.children then + for _, child in ipairs(node.children) do + gather_nodes(child, node_table, node.options.section) + end + end else - local same_fold_state = a.options.folded == b.options.folded - local same_load_state = a.options.on_open == nil and b.options.on_open == nil - local same_child_count = #a.children == #b.children - - -- P({ - -- same_tag = same_tag, - -- same_id = same_id, - -- same_child_count = same_child_count, - -- same_fold_state = same_fold_state, - -- same_load_state = same_load_state - -- } - -- ) - - return same_tag and same_id and same_child_count and same_fold_state and same_load_state + if node.options.filename then + local key = ("%s--%s"):format(prefix, node.options.filename) + node_table[key] = { + folded = node.options.folded, + } + + for _, child in ipairs(node.children) do + gather_nodes(child, node_table, key) + end + elseif node.options.hunk then + local key = ("%s--%s"):format(prefix, node.options.hunk.hash) + node_table[key] = { folded = node.options.folded } + elseif node.children then + for _, child in ipairs(node.children) do + gather_nodes(child, node_table, prefix) + end + end end + + return node_table end ----@param old table ----@param new table -local function compare_trees(old, new, deltas) - if not new.children or not old.children then - return - end +function Ui:_update_attributes(node, attributes, prefix) + prefix = prefix or "" - for i = 1, #new.children do - if eq(new.children[i], old.children[i]) then - compare_trees(old.children[i], new.children[i], deltas) - else - table.insert(deltas, { old = old.children[i], new = new.children[i] }) + if node.options.section then + if attributes[node.options.section] then + node.options.folded = attributes[node.options.section].folded + end + + if node.children then + for _, child in ipairs(node.children) do + self:_update_attributes(child, attributes, node.options.section) + end + end + else + if node.options.filename then + local key = ("%s--%s"):format(prefix, node.options.filename) + if attributes[key] and not attributes[key].folded then + if node.options.on_open then + node.options.on_open(node, self) + end + end + + for _, child in ipairs(node.children) do + self:_update_attributes(child, attributes, key) + end + elseif node.options.hunk then + local key = ("%s--%s"):format(prefix, node.options.hunk.hash) + if attributes[key] then + node.options.folded = attributes[key].folded + end + elseif node.children then + for _, child in ipairs(node.children) do + self:_update_attributes(child, attributes, prefix) + end end end end @@ -271,21 +300,22 @@ function Ui:render(...) return { tag = "_root", children = layout } end)() - if vim.tbl_isempty(self.layout) then - self.layout = root - else - -- local deltas = {} - -- compare_trees(self.layout, root, deltas) - -- P(deltas) - self.layout = root + if not vim.tbl_isempty(self.layout) then + self._old_node_attributes = gather_nodes(self.layout) end + self.layout = root self:update() end function Ui:update() local renderer = Renderer:new(self.layout, self.buf):render() self.node_index = renderer:node_index() + + if self._old_node_attributes then + self:_update_attributes(self.layout, self._old_node_attributes) + self._old_node_attributes = nil + end end Ui.col = Component.new(function(children, options) From 2c3ffebfb05a24dc5bdf83bcfd3c182114ca6453 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 Feb 2024 21:52:00 +0100 Subject: [PATCH 131/443] Add stage untracked --- lua/neogit/buffers/status/init.lua | 33 +++++++++++++++++------------- lua/neogit/lib/git/status.lua | 8 ++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 4bffa95ec..0ccbd3cfc 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -232,7 +232,6 @@ function M:open(kind) return end - -- TODO: Discard Commit? -- TODO: Discard Stash? -- TODO: Discard Section? if discardable.hunk then @@ -329,9 +328,10 @@ function M:open(kind) [mappings["Stage"]] = a.void(function() -- TODO: Cursor Placement local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local section = self.buffer.ui:get_current_section() - local cursor - if stagable then + local cursor = self.buffer:cursor_line() + if stagable and section then if stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() local patch = @@ -340,19 +340,18 @@ function M:open(kind) git.index.apply(patch, { cached = true }) cursor = stagable.hunk.first elseif stagable.filename then - cursor = self.buffer:cursor_line() - - local section = self.buffer.ui:get_current_section() - if section then - if section.options.section == "unstaged" then - git.status.stage { stagable.filename } - elseif section.options.section == "untracked" then - git.index.add { stagable.filename } - end + if section.options.section == "unstaged" then + git.status.stage { stagable.filename } + elseif section.options.section == "untracked" then + git.index.add { stagable.filename } end end - else - -- TODO check if section header, act on entire section + elseif section then + if section.options.section == "untracked" then + git.status.stage_untracked() + elseif section.options.section == "unstaged" then + git.status.stage_modified() + end end if cursor then @@ -627,6 +626,12 @@ function M:focus() end end +-- TODO: Allow passing some kind of cursor identifier into this, which can be injected into the renderer to +-- find the location of a new named element to set the cursor to upon update. +-- +-- For example, when staging all items in untracked section via `s`, cursor should be updated to go to header of +-- staged section +-- function M:refresh(partial, reason) -- if self.frozen then -- return diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 858d5c947..dac4f1e4b 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -183,6 +183,14 @@ local status = { stage_modified = function() git.cli.add.update.call() end, + stage_untracked = function() + local repo = require("neogit.lib.git.repository") + local paths = util.map(repo.untracked.items, function(item) + return item.escaped_path + end) + + git.cli.add.files(unpack(paths)).call() + end, stage_all = function() git.cli.add.all.call() end, From 3d9f3463e1f87f6cfa7fe9a138cc83ed89de8754 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 Feb 2024 23:23:40 +0100 Subject: [PATCH 132/443] Extract some responsibilities from renderer, clear namespaces because we were getting issues with highlights not going away after updating, do some hacky shit to let folded hunks stay folded when updating buffer. --- lua/neogit/buffers/common.lua | 3 +- lua/neogit/buffers/status/ui.lua | 34 +++++---- lua/neogit/lib/buffer.lua | 46 +++++++----- lua/neogit/lib/ui/init.lua | 123 +++++++++++++++++-------------- lua/neogit/lib/ui/renderer.lua | 20 ----- 5 files changed, 118 insertions(+), 108 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 60c367886..4248fd217 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -38,6 +38,7 @@ M.DiffHunks = Component.new(function(diff) header = header, content = content, hunk = hunk, + folded = hunk._folded, } end) @@ -65,7 +66,7 @@ M.Hunk = Component.new(function(props) return col.tag("Hunk")({ text.line_hl("NeogitHunkHeader")(props.header), col.tag("HunkContent")(map(props.content, HunkLine)), - }, { foldable = true, folded = false, context = true, hunk = props.hunk }) + }, { foldable = true, folded = props.folded or false, context = true, hunk = props.hunk }) end) M.List = Component.new(function(props) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 258b491a5..409111770 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -65,7 +65,7 @@ local Section = Component.new(function(props) end) local load_diff = function(item) - return a.void(function(this, ui) + return a.void(function(this, ui, prefix) this.options.on_open = nil this.options.folded = false @@ -77,6 +77,13 @@ local load_diff = function(item) hunk.first = row hunk.last = row + hunk.length row = hunk.last + 1 + + if prefix then + local key = ("%s--%s"):format(prefix, hunk.hash) + if ui._old_node_attributes and ui._old_node_attributes[key] then + hunk._folded = ui._old_node_attributes[key].folded + end + end end this:append(DiffHunks(diff)) @@ -171,18 +178,19 @@ function M.Status(state, config) yankable = state.head.tag.oid, }, EmptyLine, - -- TODO Rebasing (rebase) - -- TODO Reverting (sequencer - revert_head) - -- TODO Picking (sequencer - cherry_pick_head) - -- TODO Respect if user has section hidden - -- TODO: Group untracked by directory and create a fold - #state.untracked.items > 0 and Section { - title = SectionTitle { title = "Untracked files" }, - render = SectionItemFile, - items = state.untracked.items, - folded = config.sections.untracked.folded, - name = "untracked", - }, + -- TODO Rebasing (rebase) + -- TODO Reverting (sequencer - revert_head) + -- TODO Picking (sequencer - cherry_pick_head) + -- TODO Respect if user has section hidden + -- TODO: Group untracked by directory and create a fold + #state.untracked.items > 0 + and Section { + title = SectionTitle { title = "Untracked files" }, + render = SectionItemFile, + items = state.untracked.items, + folded = config.sections.untracked.folded, + name = "untracked", + }, #state.unstaged.items > 0 and Section { title = SectionTitle { title = "Unstaged changes" }, render = SectionItemFile, diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 399c5aecf..33cfa2ba6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -385,31 +385,34 @@ function Buffer:open_fold(line, reset_pos) end function Buffer:add_highlight(line, col_start, col_end, name, namespace) - api.nvim_buf_add_highlight(self.handle, self:get_namespace_id(namespace), name, line, col_start, col_end) + local ns_id = self:get_namespace_id(namespace) + if ns_id then + api.nvim_buf_add_highlight(self.handle, ns_id, name, line, col_start, col_end) + end end function Buffer:place_sign(line, name, opts) opts = opts or {} - api.nvim_buf_set_extmark( - self.handle, - self:get_namespace_id(opts.namespace), - line - 1, - 0, - { sign_text = signs.get(name) } - ) + local ns_id = self:get_namespace_id(opts.namespace) + if ns_id then + api.nvim_buf_set_extmark(self.handle, ns_id, line - 1, 0, { sign_text = signs.get(name) }) + end end function Buffer:add_line_highlight(line, hl_group, opts) opts = opts or {} - api.nvim_buf_set_extmark( - self.handle, - self:get_namespace_id(opts.namespace), - line, - 0, - { line_hl_group = hl_group, priority = opts.priority or 190 } - ) + local ns_id = self:get_namespace_id(opts.namespace) + if ns_id then + api.nvim_buf_set_extmark( + self.handle, + ns_id, + line, + 0, + { line_hl_group = hl_group, priority = opts.priority or 190 } + ) + end end function Buffer:clear_namespace(name) @@ -419,7 +422,10 @@ function Buffer:clear_namespace(name) return end - api.nvim_buf_clear_namespace(self.handle, self:get_namespace_id(name), 0, -1) + local ns_id = self:get_namespace_id(name) + if ns_id then + api.nvim_buf_clear_namespace(self.handle, ns_id, 0, -1) + end end function Buffer:create_namespace(name) @@ -433,11 +439,12 @@ function Buffer:create_namespace(name) return self.namespaces[namespace] end +---@param name string +---@return number|nil function Buffer:get_namespace_id(name) local ns_id if name and name ~= "default" then ns_id = self.namespaces["neogit-buffer-" .. self.handle .. "-" .. name] - assert(ns_id, "Namespace ID should never be nil! Create '" .. name .. "' namespace before using it") else ns_id = self.namespaces.default end @@ -462,7 +469,10 @@ function Buffer:set_extmark(...) end function Buffer:set_decorations(namespace, opts) - return api.nvim_set_decoration_provider(self:get_namespace_id(namespace), opts) + local ns_id = self:get_namespace_id(namespace) + if ns_id then + return api.nvim_set_decoration_provider(ns_id, opts) + end end ---@class BufferConfig diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 5a3e527af..79da5965d 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -101,9 +101,9 @@ function Ui:find_by_id(id) end ---@return Component|nil -function Ui:get_cursor_context() - local cursor = vim.api.nvim_win_get_cursor(0) - return self:_find_component_by_index(cursor[1], function(node) +function Ui:get_cursor_context(line) + local cursor = line or vim.api.nvim_win_get_cursor(0)[1] + return self:_find_component_by_index(cursor, function(node) return node.options.context end) end @@ -216,6 +216,25 @@ local function filter_layout(layout) end) end +local function node_prefix(node, prefix) + local base = false + local key + if node.options.section then + key = node.options.section + elseif node.options.filename then + key = node.options.filename + elseif node.options.hunk then + base = true + key = node.options.hunk.hash + end + + if key then + return ("%s--%s"):format(prefix, key), base + else + return nil, base + end +end + local function gather_nodes(node, node_table, prefix) if not node_table then node_table = {} @@ -223,33 +242,15 @@ local function gather_nodes(node, node_table, prefix) prefix = prefix or "" - if node.options.section then - node_table[node.options.section] = { - folded = node.options.folded, - } + local key, base = node_prefix(node, prefix) + if key then + prefix = key + node_table[prefix] = { folded = node.options.folded } + end - if node.children then - for _, child in ipairs(node.children) do - gather_nodes(child, node_table, node.options.section) - end - end - else - if node.options.filename then - local key = ("%s--%s"):format(prefix, node.options.filename) - node_table[key] = { - folded = node.options.folded, - } - - for _, child in ipairs(node.children) do - gather_nodes(child, node_table, key) - end - elseif node.options.hunk then - local key = ("%s--%s"):format(prefix, node.options.hunk.hash) - node_table[key] = { folded = node.options.folded } - elseif node.children then - for _, child in ipairs(node.children) do - gather_nodes(child, node_table, prefix) - end + if node.children and not base then + for _, child in ipairs(node.children) do + gather_nodes(child, node_table, prefix) end end @@ -259,37 +260,23 @@ end function Ui:_update_attributes(node, attributes, prefix) prefix = prefix or "" - if node.options.section then - if attributes[node.options.section] then - node.options.folded = attributes[node.options.section].folded - end + local key, base = node_prefix(node, prefix) + if key then + prefix = key - if node.children then - for _, child in ipairs(node.children) do - self:_update_attributes(child, attributes, node.options.section) + -- TODO: If a hunk is closed, it will be re-opened on update because the on_open callback runs async :\ + if attributes[prefix] then + if node.options.on_open and not attributes[prefix].folded then + node.options.on_open(node, self, prefix) end + + node.options.folded = attributes[prefix].folded end - else - if node.options.filename then - local key = ("%s--%s"):format(prefix, node.options.filename) - if attributes[key] and not attributes[key].folded then - if node.options.on_open then - node.options.on_open(node, self) - end - end + end - for _, child in ipairs(node.children) do - self:_update_attributes(child, attributes, key) - end - elseif node.options.hunk then - local key = ("%s--%s"):format(prefix, node.options.hunk.hash) - if attributes[key] then - node.options.folded = attributes[key].folded - end - elseif node.children then - for _, child in ipairs(node.children) do - self:_update_attributes(child, attributes, prefix) - end + if node.children and not base then + for _, child in ipairs(node.children) do + self:_update_attributes(child, attributes, prefix) end end end @@ -309,9 +296,33 @@ function Ui:render(...) end function Ui:update() + -- If the buffer is not focused, trying to set folds will raise an error because it's not a proper API. + if not self.buf:is_focused() then + return + end + local renderer = Renderer:new(self.layout, self.buf):render() self.node_index = renderer:node_index() + local cursor_line = self.buf:cursor_line() + + self.buf:unlock() + self.buf:clear() + self.buf:clear_namespace("default") + self.buf:clear_namespace("ViewContext") + self.buf:clear_namespace("ViewDecor") + self.buf:resize(#renderer.buffer.line) + self.buf:set_lines(0, -1, false, renderer.buffer.line) + self.buf:set_highlights(renderer.buffer.highlight) + self.buf:set_extmarks(renderer.buffer.extmark) + self.buf:set_line_highlights(renderer.buffer.line_highlight) + self.buf:set_folds(renderer.buffer.fold) + self.buf:lock() + + -- P(self:get_cursor_context(math.min(cursor_line, #renderer.buffer.line)):row_range_abs()) + + self.buf:move_cursor(math.min(cursor_line, #renderer.buffer.line)) + if self._old_node_attributes then self:_update_attributes(self.layout, self._old_node_attributes) self._old_node_attributes = nil diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 33c5530b1..31bbcf041 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -50,7 +50,6 @@ end ---@class Renderer ---@field buffer RendererBuffer ----@field ui_buffer Buffer ---@field flags RendererFlags ---@field namespace integer ---@field layout table @@ -66,7 +65,6 @@ function Renderer:new(layout, buffer) local obj = { namespace = buffer:create_namespace("VirtualText"), layout = layout, - ui_buffer = buffer, buffer = { line = {}, highlight = {}, @@ -88,26 +86,8 @@ end ---@return Renderer function Renderer:render() - -- If the buffer is not focused, trying to set folds will raise an error because it's not a proper API. - if not self.ui_buffer:is_focused() then - return self - end - self:_render(self.layout, self.layout.children, 0) - local cursor_line = self.ui_buffer:cursor_line() - self.ui_buffer:unlock() - self.ui_buffer:clear() - self.ui_buffer:clear_namespace("default") - self.ui_buffer:resize(#self.buffer.line) - self.ui_buffer:set_lines(0, -1, false, self.buffer.line) - self.ui_buffer:set_highlights(self.buffer.highlight) - self.ui_buffer:set_extmarks(self.buffer.extmark) - self.ui_buffer:set_line_highlights(self.buffer.line_highlight) - self.ui_buffer:set_folds(self.buffer.fold) - self.ui_buffer:lock() - self.ui_buffer:move_cursor(cursor_line) - return self end From 6284fdf7a6200a36a448b44cb2a4a8b19451e312 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 Feb 2024 23:38:56 +0100 Subject: [PATCH 133/443] Don't allow staging/unstaging within a section where that doesn't apply --- lua/neogit/buffers/status/init.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 0ccbd3cfc..04f4d5536 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -332,6 +332,10 @@ function M:open(kind) local cursor = self.buffer:cursor_line() if stagable and section then + if section.options.section == "staged" then + return + end + if stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() local patch = @@ -373,6 +377,11 @@ function M:open(kind) [mappings["Unstage"]] = a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local section = self.buffer.ui:get_current_section() + if section and section.options.section ~= "staged" then + return + end + -- TODO: Cursor Placement if unstagable then if unstagable.hunk then From 600ce14430541777f5fa50067f96816b570f3d28 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 13:23:13 +0100 Subject: [PATCH 134/443] Fix: With detached head, parse status correctly --- lua/neogit/lib/git/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index dac4f1e4b..cf49d88f2 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -61,7 +61,7 @@ local function update_status(state) return end - if line ~= "" and (line:match("^[12u]%s[MTADRC%s%.%?!][MTDRC%s%.%?!]%s") or line:match("^[%?!#]%s")) then + if line ~= "" and (line:match("^[12u]%s[%u%s%.%?!][%u%s%.%?!]%s") or line:match("^[%?!#]%s")) then table.insert(collection, line) else collection[#collection] = ("%s\t%s"):format(collection[#collection], line) From ce8e9b0f8a339af8cca92719c9cfacdc92a14435 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 13:23:30 +0100 Subject: [PATCH 135/443] Make padding correct --- lua/neogit/buffers/status/ui.lua | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 409111770..c65bf8860 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -94,14 +94,14 @@ end -- TODO: Handle renames local SectionItemFile = Component.new(function(item) local mode_to_text = { - M = "Modified ", - N = "New File ", - A = "Added ", - D = "Deleted ", - C = "Copied ", - U = "Updated ", - UU = "Both Modified ", - R = "Renamed ", + M = "Modified", + N = "New File", + A = "Added", + D = "Deleted", + C = "Copied", + U = "Updated", + UU = "Both Modified", + R = "Renamed", ["?"] = "", -- Untracked } @@ -114,9 +114,18 @@ local SectionItemFile = Component.new(function(item) local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) + local mode_text + if mode == "" then + mode_text = "" + elseif conflict then + mode_text = util.pad_right(("%s by us"):format(mode), 15) + else + mode_text = util.pad_right(mode, 15) + end + return col.tag("SectionItemFile")({ row { - text.highlight(highlight)(conflict and ("%s by us"):format(mode) or mode), + text.highlight(highlight)(mode_text), text(item.name), }, }, { From e533b1c6d2f2dcf3581b96ddb75953ddc4a0fc3c Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 22:53:07 +0100 Subject: [PATCH 136/443] Add rebase section to new status --- lua/neogit/buffers/status/ui.lua | 45 +++++++++++++++++++++++++++++++- lua/neogit/lib/git/cli.lua | 11 ++++++++ lua/neogit/lib/git/rebase.lua | 37 ++++++++++++++++---------- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index c65bf8860..c0e95fbe4 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -56,6 +56,16 @@ local SectionTitleRemote = Component.new(function(props) } end) +local SectionTitleRebase = Component.new(function(props) + return { + text.highlight("NeogitSectionHeader")(props.title), + text(" "), + text.highlight("NeogitBranch")(props.head), + text.highlight("NeogitSectionHeader")(" onto "), + text.highlight("NeogitBranch")(props.onto), + } +end) + local Section = Component.new(function(props) return col.tag("Section")({ row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), @@ -156,6 +166,29 @@ local SectionItemCommit = Component.new(function(item) }, { yankable = item.commit.oid }) end) +local SectionItemRebase = Component.new(function(item) + if item.oid then + local action_hl = item.done and "NeogitRebaseDone" or "NeogitGraphOrange" + local oid_hl = "NeogitRebaseDone" + local subject_hl = item.done and "NeogitRebaseDone" or "Normal" + + return row({ + text(item.stopped and "> " or " "), + text.highlight(action_hl)(item.action), + text(" "), + text.highlight(oid_hl)(item.oid:sub(1, 7)), + text(" "), + text.highlight(subject_hl)(item.subject), + }, { yankable = item.oid }) + else + return row({ + text.highlight("NeogitGraphOrange")(item.action), + text(" "), + text(item.subject), + }) + end +end) + -- TODO: Hint at top of buffer! function M.Status(state, config) return { @@ -187,7 +220,17 @@ function M.Status(state, config) yankable = state.head.tag.oid, }, EmptyLine, - -- TODO Rebasing (rebase) + #state.rebase.items > 0 and Section { + title = SectionTitleRebase { + title = "Rebasing", + head = state.rebase.head, + onto = state.rebase.onto + }, + render = SectionItemRebase, + items = state.rebase.items, + folded = config.sections.rebase.folded, + name = "rebase", + }, -- TODO Reverting (sequencer - revert_head) -- TODO Picking (sequencer - cherry_pick_head) -- TODO Respect if user has section hidden diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index e80281db0..4d17c8f2e 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -31,6 +31,17 @@ local configurations = { }, }, + ["name-rev"] = config { + flags = { + name_only = "--name-only", + no_undefined = "--no-undefined", + }, + options = { + refs = "--refs", + exclude = "--exclude", + }, + }, + init = config {}, ["checkout-index"] = config { diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index e9f7b45a4..a20143f26 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -104,17 +104,6 @@ function M.edit() return rebase_command(cli.rebase.edit_todo) end -local function line_oid(line) - return vim.split(line, " ")[2] -end - -local function format_line(line) - local sections = vim.split(line, " ") - sections[2] = sections[2]:sub(1, 7) - - return table.concat(sections, " ") -end - function M.update_rebase_status(state) local repo = require("neogit.lib.git.repository") if repo.git_root == "" then @@ -136,17 +125,32 @@ function M.update_rebase_status(state) if rebase_file then local head = rebase_file:joinpath("head-name") if not head:exists() then - logger.error("Failed to read rebase-merge head") + logger.error("Failed to read rebase-merge head-name") return end state.rebase.head = head:read():match("refs/heads/([^\r\n]+)") + local onto = rebase_file:joinpath("onto") + if onto:exists() then + state.rebase.onto = cli["name-rev"].name_only.no_undefined + .refs("refs/heads/*") + .exclude("*/HEAD") + .exclude("*/refs/heads/*") + .args(vim.trim(onto:read())) + .call({ hidden = true }).stdout[1] + end + local done = rebase_file:joinpath("done") if done:exists() then for line in done:iter() do if line:match("^[^#]") and line ~= "" then - table.insert(state.rebase.items, { name = format_line(line), oid = line_oid(line), done = true }) + table.insert(state.rebase.items, { + action = line:match("^(%w+) "), + oid = line:match("^%w+ (%x+)"), + subject = line:match("^%w+ %x+ (.+)$"), + done = true, + }) end end end @@ -162,7 +166,12 @@ function M.update_rebase_status(state) if todo:exists() then for line in todo:iter() do if line:match("^[^#]") and line ~= "" then - table.insert(state.rebase.items, { name = format_line(line), oid = line_oid(line) }) + table.insert(state.rebase.items, { + done = false, + action = line:match("^(%w+) "), + oid = line:match("^%w+ (%x+)"), + subject = line:match("^%w+ %x+ (.+)$"), + }) end end end From ea5f1cc91f5499dbb9cf1f367174f8561f508960 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 23:13:23 +0100 Subject: [PATCH 137/443] Handle rendering renamed files --- lua/neogit/buffers/status/ui.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index c0e95fbe4..237054569 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -101,7 +101,6 @@ local load_diff = function(item) end) end --- TODO: Handle renames local SectionItemFile = Component.new(function(item) local mode_to_text = { M = "Modified", @@ -136,7 +135,7 @@ local SectionItemFile = Component.new(function(item) return col.tag("SectionItemFile")({ row { text.highlight(highlight)(mode_text), - text(item.name), + text(item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name), }, }, { foldable = true, From 654cf2cf249c184c0f9f9e9b24561f78f8f18491 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 23:13:43 +0100 Subject: [PATCH 138/443] Notes --- lua/neogit/buffers/status/init.lua | 3 ++- lua/neogit/buffers/status/ui.lua | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 04f4d5536..8f5ad6ffa 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -220,6 +220,7 @@ function M:open(kind) end end, [mappings["Discard"]] = a.void(function() + -- TODO: Discarding a RENAME should set the filename back to the original git.index.update() local discardable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -636,7 +637,7 @@ function M:focus() end -- TODO: Allow passing some kind of cursor identifier into this, which can be injected into the renderer to --- find the location of a new named element to set the cursor to upon update. +-- find the location of a new named element to set the cursor to upon update. -- -- For example, when staging all items in untracked section via `s`, cursor should be updated to go to header of -- staged section diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 237054569..31b7ff5d7 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -56,6 +56,7 @@ local SectionTitleRemote = Component.new(function(props) } end) +-- TODO: Determine if 'onto' is local or remote local SectionTitleRebase = Component.new(function(props) return { text.highlight("NeogitSectionHeader")(props.title), @@ -75,6 +76,9 @@ local Section = Component.new(function(props) end) local load_diff = function(item) + ---@param this Component + ---@param ui Ui + ---@param prefix string|nil return a.void(function(this, ui, prefix) this.options.on_open = nil this.options.folded = false @@ -88,6 +92,7 @@ local load_diff = function(item) hunk.last = row + hunk.length row = hunk.last + 1 + -- Set fold state when called from ui:update() if prefix then local key = ("%s--%s"):format(prefix, hunk.hash) if ui._old_node_attributes and ui._old_node_attributes[key] then From edcc41d7b389fa2c834e3a1e38ba4485a77197b6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 23:35:01 +0100 Subject: [PATCH 139/443] Add cursor line --- lua/neogit/lib/buffer.lua | 18 ++++++++++++------ lua/neogit/lib/hl.lua | 4 ++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 33cfa2ba6..15202f8f0 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -594,14 +594,13 @@ function Buffer.create(config) if config.context_highlight then buffer:create_namespace("ViewContext") - buffer:create_namespace("ViewDecor") buffer:call(function() local function on_start() return buffer:exists() and buffer:is_focused() end - local function on_win() + local function on_win(_, _, _, top, bottom) buffer:clear_namespace("ViewContext") local context = buffer.ui:get_cursor_context() @@ -611,15 +610,19 @@ function Buffer.create(config) local first = context.position.row_start local last = context.position.row_end + local cursor = vim.fn.line(".") - for line = fn.line("w0"), fn.line("w$") do + for line = top, bottom do if line >= first and line <= last then local line_hl = buffer.ui:get_line_highlight(line) buffer:buffered_add_line_highlight( line - 1, - (line_hl or "NeogitDiffContext") .. "Highlight", - { priority = 200, namespace = "ViewContext" } + ("%s%s"):format(line_hl or "NeogitDiffContext", line == cursor and "Cursor" or "Highlight"), + { + priority = 200, + namespace = "ViewContext" + } ) end end @@ -627,7 +630,10 @@ function Buffer.create(config) buffer:flush_line_highlight_buffer() end - buffer:set_decorations("ViewDecor", { on_start = on_start, on_win = on_win }) + buffer:set_decorations( + "ViewContext", + { on_start = on_start, on_win = on_win } + ) end) end diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index fc45c29c9..653aab2d0 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -142,12 +142,16 @@ function M.setup() NeogitSignatureGoodRevokedKey = { link = "NeogitGraphRed" }, NeogitHunkHeader = { fg = palette.bg0, bg = palette.grey, bold = palette.bold }, NeogitHunkHeaderHighlight = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, + NeogitHunkHeaderCursor = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, NeogitDiffContext = { bg = palette.bg1 }, NeogitDiffContextHighlight = { bg = palette.bg2 }, + NeogitDiffContextCursor = { bg = palette.bg1 }, NeogitDiffAdd = { bg = palette.line_green, fg = palette.bg_green }, NeogitDiffAddHighlight = { bg = palette.line_green, fg = palette.green }, + NeogitDiffAddCursor = { bg = palette.bg1, fg = palette.green }, NeogitDiffDelete = { bg = palette.line_red, fg = palette.bg_red }, NeogitDiffDeleteHighlight = { bg = palette.line_red, fg = palette.red }, + NeogitDiffDeleteCursor = { bg = palette.bg1, fg = palette.red }, NeogitPopupSectionTitle = { link = "Function" }, NeogitPopupBranchName = { link = "String" }, NeogitPopupBold = { bold = palette.bold }, From 2e40a0ca930d3553447a1eb6cbb4a550294f54bd Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 14 Feb 2024 23:46:05 +0100 Subject: [PATCH 140/443] cleanup --- lua/neogit/lib/buffer.lua | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 15202f8f0..0414a147a 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -595,12 +595,11 @@ function Buffer.create(config) if config.context_highlight then buffer:create_namespace("ViewContext") - buffer:call(function() - local function on_start() + buffer:set_decorations("ViewContext", { + on_start = function() return buffer:exists() and buffer:is_focused() - end - - local function on_win(_, _, _, top, bottom) + end, + on_win = function(_, _, _, top, bottom) buffer:clear_namespace("ViewContext") local context = buffer.ui:get_cursor_context() @@ -614,27 +613,21 @@ function Buffer.create(config) for line = top, bottom do if line >= first and line <= last then - local line_hl = buffer.ui:get_line_highlight(line) - - buffer:buffered_add_line_highlight( - line - 1, - ("%s%s"):format(line_hl or "NeogitDiffContext", line == cursor and "Cursor" or "Highlight"), - { - priority = 200, - namespace = "ViewContext" - } + local line_hl = ("%s%s"):format( + buffer.ui:get_line_highlight(line) or "NeogitDiffContext", + line == cursor and "Cursor" or "Highlight" ) + + buffer:buffered_add_line_highlight(line - 1, line_hl, { + priority = 200, + namespace = "ViewContext", + }) end end buffer:flush_line_highlight_buffer() - end - - buffer:set_decorations( - "ViewContext", - { on_start = on_start, on_win = on_win } - ) - end) + end, + }) end return buffer From b3ed407c5eb20c2e9f33d2f5bac138c10c427a9d Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 15 Feb 2024 09:54:28 +0100 Subject: [PATCH 141/443] Add hints --- lua/neogit/buffers/status/ui.lua | 229 ++++++++++++++++++++++--------- 1 file changed, 164 insertions(+), 65 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 31b7ff5d7..f0cd83ff3 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -22,6 +22,60 @@ local EmptyLine = col { row { text("") } } local M = {} +local HINT = Component.new(function(props) + ---@return table + local function reversed_lookup(tbl) + local result = {} + for k, v in pairs(tbl) do + if v then + local current = result[v] + if current then + table.insert(current, k) + else + result[v] = { k } + end + end + end + + return result + end + + local reversed_status_map = reversed_lookup(props.config.mappings.status) + local reversed_popup_map = reversed_lookup(props.config.mappings.popup) + + local entry = function(name, hint) + local keys = reversed_status_map[name] or reversed_popup_map[name] + local key_hint + + if keys and #keys > 0 then + key_hint = table.concat(keys, " ") + else + key_hint = "" + end + + return row { + text.highlight("NeogitPopupActionKey")(key_hint), + text(" "), + text(hint), + } + end + + return row { + text.highlight("Comment")("Hint: "), + entry("Toggle", "toggle"), + text.highlight("Comment")(" | "), + entry("Stage", "stage"), + text.highlight("Comment")(" | "), + entry("Unstage", "unstage"), + text.highlight("Comment")(" | "), + entry("Discard", "discard"), + text.highlight("Comment")(" | "), + entry("CommitPopup", "commit"), + text.highlight("Comment")(" | "), + entry("HelpPopup", "help"), + } +end) + local HEAD = Component.new(function(props) local highlight = props.remote and "NeogitRemote" or "NeogitBranch" local ref = props.remote and ("%s/%s"):format(props.remote, props.branch) or props.branch @@ -75,38 +129,38 @@ local Section = Component.new(function(props) }, { foldable = true, folded = props.folded, section = props.name }) end) -local load_diff = function(item) - ---@param this Component - ---@param ui Ui - ---@param prefix string|nil - return a.void(function(this, ui, prefix) - this.options.on_open = nil - this.options.folded = false - - local row, _ = this:row_range_abs() - row = row + 1 -- Filename row - - local diff = item.diff - for _, hunk in ipairs(diff.hunks) do - hunk.first = row - hunk.last = row + hunk.length - row = hunk.last + 1 - - -- Set fold state when called from ui:update() - if prefix then - local key = ("%s--%s"):format(prefix, hunk.hash) - if ui._old_node_attributes and ui._old_node_attributes[key] then - hunk._folded = ui._old_node_attributes[key].folded +local SectionItemFile = Component.new(function(item) + local load_diff = function(item) + ---@param this Component + ---@param ui Ui + ---@param prefix string|nil + return a.void(function(this, ui, prefix) + this.options.on_open = nil + this.options.folded = false + + local row, _ = this:row_range_abs() + row = row + 1 -- Filename row + + local diff = item.diff + for _, hunk in ipairs(diff.hunks) do + hunk.first = row + hunk.last = row + hunk.length + row = hunk.last + 1 + + -- Set fold state when called from ui:update() + if prefix then + local key = ("%s--%s"):format(prefix, hunk.hash) + if ui._old_node_attributes and ui._old_node_attributes[key] then + hunk._folded = ui._old_node_attributes[key].folded + end end end - end - this:append(DiffHunks(diff)) - ui:update() - end) -end + this:append(DiffHunks(diff)) + ui:update() + end) + end -local SectionItemFile = Component.new(function(item) local mode_to_text = { M = "Modified", N = "New File", @@ -185,113 +239,169 @@ local SectionItemRebase = Component.new(function(item) text.highlight(subject_hl)(item.subject), }, { yankable = item.oid }) else - return row({ + return row { text.highlight("NeogitGraphOrange")(item.action), text(" "), text(item.subject), - }) + } end end) --- TODO: Hint at top of buffer! function M.Status(state, config) + -- stylua: ignore start + local show_hint = not config.disable_hint + + local show_upstream = state.upstream.ref + and state.head.branch ~= "(detached)" + + local show_pushRemote = state.pushRemote.ref + and state.head.branch ~= "(detached)" + + local show_tag = state.head.tag.name + and state.head.branch ~= "(detached)" + + local show_rebase = #state.rebase.items > 0 + and not config.sections.rebase.hidden + + local show_cherry_pick = #state.sequencer.items > 0 + and state.sequencer.cherry_pick + and not config.sections.sequencer.hidden + + local show_revert = #state.sequencer.items > 0 + and state.sequencer.revert + and not config.sections.sequencer.hidden + + local show_untracked = #state.untracked.items > 0 + and not config.sections.untracked.hidden + + local show_unstaged = #state.unstaged.items > 0 + and not config.sections.unstaged.hidden + + local show_staged = #state.staged.items > 0 + and not config.sections.staged.hidden + + local show_upstream_unpulled = #state.upstream.unpulled.items > 0 + and not config.sections.unpulled_upstream.hidden + + local show_pushRemote_unpulled = #state.pushRemote.unpulled.items > 0 + and state.pushRemote.ref ~= state.upstream.ref + and not config.sections.unpulled_pushRemote.hidden + + local show_upstream_unmerged = #state.upstream.unmerged.items > 0 + and not config.sections.unmerged_upstream.hidden + + local show_pushRemote_unmerged = #state.pushRemote.unmerged.items > 0 + and state.pushRemote.ref ~= state.upstream.ref + and not config.sections.unmerged_pushRemote.hidden + + local show_stashes = #state.stashes.items > 0 + and not config.sections.stashes.hidden + + local show_recent = #state.recent.items > 0 + and not config.sections.recent.hidden + -- stylua: ignore end + return { List { items = { + show_hint and HINT { config = config }, + show_hint and EmptyLine, HEAD { name = "Head", branch = state.head.branch, msg = state.head.commit_message, yankable = state.head.oid, }, - state.upstream.ref and HEAD { -- Do not render if HEAD is detached + show_upstream and HEAD { name = "Merge", branch = state.upstream.branch, remote = state.upstream.remote, msg = state.upstream.commit_message, yankable = state.upstream.oid, }, - state.pushRemote.ref and HEAD { -- Do not render if HEAD is detached + show_pushRemote and HEAD { name = "Push", branch = state.pushRemote.branch, remote = state.pushRemote.remote, msg = state.pushRemote.commit_message, yankable = state.pushRemote.oid, }, - state.head.tag.name and Tag { + show_tag and Tag { name = state.head.tag.name, distance = state.head.tag.distance, yankable = state.head.tag.oid, }, EmptyLine, - #state.rebase.items > 0 and Section { + show_rebase and Section { title = SectionTitleRebase { title = "Rebasing", head = state.rebase.head, - onto = state.rebase.onto + onto = state.rebase.onto, }, render = SectionItemRebase, items = state.rebase.items, folded = config.sections.rebase.folded, name = "rebase", }, - -- TODO Reverting (sequencer - revert_head) + show_cherry_pick and Section { -- TODO Picking (sequencer - cherry_pick_head) - -- TODO Respect if user has section hidden + }, + show_revert and Section { + -- TODO Reverting (sequencer - revert_head) + }, + show_untracked and Section { -- TODO: Group untracked by directory and create a fold - #state.untracked.items > 0 - and Section { - title = SectionTitle { title = "Untracked files" }, - render = SectionItemFile, - items = state.untracked.items, - folded = config.sections.untracked.folded, - name = "untracked", - }, - #state.unstaged.items > 0 and Section { + title = SectionTitle { title = "Untracked files" }, + render = SectionItemFile, + items = state.untracked.items, + folded = config.sections.untracked.folded, + name = "untracked", + }, + show_unstaged and Section { title = SectionTitle { title = "Unstaged changes" }, render = SectionItemFile, items = state.unstaged.items, folded = config.sections.unstaged.folded, name = "unstaged", }, - #state.staged.items > 0 and Section { + show_staged and Section { title = SectionTitle { title = "Staged changes" }, render = SectionItemFile, items = state.staged.items, folded = config.sections.staged.folded, name = "staged", }, - #state.upstream.unpulled.items > 0 and Section { + show_upstream_unpulled and Section { title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref }, render = SectionItemCommit, items = state.upstream.unpulled.items, folded = config.sections.unpulled_upstream.folded, }, - (#state.pushRemote.unpulled.items > 0 and state.pushRemote.ref ~= state.upstream.ref) and Section { + show_pushRemote_unpulled and Section { title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, render = SectionItemCommit, items = state.pushRemote.unpulled.items, folded = config.sections.unpulled_pushRemote.folded, }, - #state.upstream.unmerged.items > 0 and Section { + show_upstream_unmerged and Section { title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref }, render = SectionItemCommit, items = state.upstream.unmerged.items, folded = config.sections.unmerged_upstream.folded, }, - (#state.pushRemote.unmerged.items > 0 and state.pushRemote.ref ~= state.upstream.ref) and Section { + show_pushRemote_unmerged and Section { title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref }, render = SectionItemCommit, items = state.pushRemote.unmerged.items, folded = config.sections.unmerged_pushRemote.folded, }, - #state.stashes.items > 0 and Section { + show_stashes and Section { title = SectionTitle { title = "Stashes" }, render = SectionItemStash, items = state.stashes.items, folded = config.sections.stashes.folded, }, - #state.recent.items > 0 and Section { + show_recent and Section { title = SectionTitle { title = "Recent Commits" }, render = SectionItemCommit, items = state.recent.items, @@ -302,15 +412,4 @@ function M.Status(state, config) } end -M._TEST = a.void(function() - local git = require("neogit.lib.git") - local config = require("neogit.config") - git.repo:refresh { - source = "status_test", - callback = function() - require("neogit.buffers.status").new(git.repo, config.values):open() - end, - } -end) - return M From b08da9aab3ee1926c7c474857e6f1ba231b2010f Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 15 Feb 2024 09:54:36 +0100 Subject: [PATCH 142/443] Don't need this namespace --- lua/neogit/lib/ui/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 79da5965d..f295bf54d 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -310,7 +310,6 @@ function Ui:update() self.buf:clear() self.buf:clear_namespace("default") self.buf:clear_namespace("ViewContext") - self.buf:clear_namespace("ViewDecor") self.buf:resize(#renderer.buffer.line) self.buf:set_lines(0, -1, false, renderer.buffer.line) self.buf:set_highlights(renderer.buffer.highlight) From dabbad1ea0221bdcc118b347dc750fd5f09aa917 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 15 Feb 2024 21:16:07 +0100 Subject: [PATCH 143/443] add "onto" into rebase sequencer --- lua/neogit/buffers/status/ui.lua | 9 ++++++--- lua/neogit/lib/git/rebase.lua | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index f0cd83ff3..8de845dc8 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -226,13 +226,16 @@ end) local SectionItemRebase = Component.new(function(item) if item.oid then - local action_hl = item.done and "NeogitRebaseDone" or "NeogitGraphOrange" + local action_hl = (item.done and "NeogitRebaseDone") + or (item.action == "onto" and "NeogitGraphBlue") + or "NeogitGraphOrange" + local oid_hl = "NeogitRebaseDone" local subject_hl = item.done and "NeogitRebaseDone" or "Normal" return row({ text(item.stopped and "> " or " "), - text.highlight(action_hl)(item.action), + text.highlight(action_hl)(util.pad_right(item.action, 6)), text(" "), text.highlight(oid_hl)(item.oid:sub(1, 7)), text(" "), @@ -336,7 +339,7 @@ function M.Status(state, config) title = SectionTitleRebase { title = "Rebasing", head = state.rebase.head, - onto = state.rebase.onto, + onto = state.rebase.onto.ref, }, render = SectionItemRebase, items = state.rebase.items, diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index a20143f26..c57bea220 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -106,11 +106,12 @@ end function M.update_rebase_status(state) local repo = require("neogit.lib.git.repository") + local log = require("neogit.lib.git.log") if repo.git_root == "" then return end - state.rebase = { items = {}, head = nil, current = nil } + state.rebase = { items = {}, onto = {}, head = nil, current = nil } local rebase_file local rebase_merge = repo:git_path("rebase-merge") @@ -133,11 +134,13 @@ function M.update_rebase_status(state) local onto = rebase_file:joinpath("onto") if onto:exists() then - state.rebase.onto = cli["name-rev"].name_only.no_undefined + state.rebase.onto.oid = vim.trim(onto:read()) + state.rebase.onto.subject = log.message(state.rebase.onto.oid) + state.rebase.onto.ref = cli["name-rev"].name_only.no_undefined .refs("refs/heads/*") .exclude("*/HEAD") .exclude("*/refs/heads/*") - .args(vim.trim(onto:read())) + .args(state.rebase.onto.oid) .call({ hidden = true }).stdout[1] end @@ -175,6 +178,15 @@ function M.update_rebase_status(state) end end end + + if onto:exists() then + table.insert(state.rebase.items, { + done = false, + action = "onto", + oid = state.rebase.onto.oid, + subject = state.rebase.onto.subject, + }) + end end end From 8d3c2e4774bff4e53479d74a49c3411a174de7bc Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 16 Feb 2024 22:12:26 +0100 Subject: [PATCH 144/443] Add reverting (more todo) --- lua/neogit/buffers/status/ui.lua | 31 ++++++++++++------ lua/neogit/lib/git/rebase.lua | 12 +++---- lua/neogit/lib/git/sequencer.lua | 55 ++++++++++++++++++++++---------- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 8de845dc8..3121d637e 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -1,9 +1,6 @@ -- TODO -- - Get fold markers to work -- --- --- Rule! No external state! --- local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") @@ -230,16 +227,13 @@ local SectionItemRebase = Component.new(function(item) or (item.action == "onto" and "NeogitGraphBlue") or "NeogitGraphOrange" - local oid_hl = "NeogitRebaseDone" - local subject_hl = item.done and "NeogitRebaseDone" or "Normal" - return row({ text(item.stopped and "> " or " "), text.highlight(action_hl)(util.pad_right(item.action, 6)), text(" "), - text.highlight(oid_hl)(item.oid:sub(1, 7)), + text.highlight("NeogitRebaseDone")(item.oid:sub(1, 7)), text(" "), - text.highlight(subject_hl)(item.subject), + text.highlight(item.done and "NeogitRebaseDone")(item.subject), }, { yankable = item.oid }) else return row { @@ -250,6 +244,21 @@ local SectionItemRebase = Component.new(function(item) end end) +-- TODO: "gone", "work", "onto" highlighting +local SectionItemRevert = Component.new(function(item) + local action_hl = (item.action == "work" and "NeogitGraphRed") + or (item.action == "onto" and "NeogitGraphBlue") + or "NeogitGraphOrange" + + return row({ + text.highlight(action_hl)(util.pad_right(item.action, 6)), + text(" "), + text.highlight("Comment")(item.oid:sub(1, 7)), + text(" "), + text(item.subject), + }, { yankable = item.oid }) +end) + function M.Status(state, config) -- stylua: ignore start local show_hint = not config.disable_hint @@ -350,7 +359,11 @@ function M.Status(state, config) -- TODO Picking (sequencer - cherry_pick_head) }, show_revert and Section { - -- TODO Reverting (sequencer - revert_head) + title = SectionTitle { title = "Reverting" }, + render = SectionItemRevert, + items = state.sequencer.items, + folded = config.sections.sequencer.folded, + name = "revert" }, show_untracked and Section { -- TODO: Group untracked by directory and create a fold diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index c57bea220..36a00cff3 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -1,6 +1,7 @@ local logger = require("neogit.logger") local client = require("neogit.client") local notification = require("neogit.lib.notification") +local util = require("neogit.lib.util") local cli = require("neogit.lib.git.cli") local M = {} @@ -105,17 +106,16 @@ function M.edit() end function M.update_rebase_status(state) - local repo = require("neogit.lib.git.repository") - local log = require("neogit.lib.git.log") - if repo.git_root == "" then + local git = require("neogit.lib.git") + if git.repo.git_root == "" then return end state.rebase = { items = {}, onto = {}, head = nil, current = nil } local rebase_file - local rebase_merge = repo:git_path("rebase-merge") - local rebase_apply = repo:git_path("rebase-apply") + local rebase_merge = git.repo:git_path("rebase-merge") + local rebase_apply = git.repo:git_path("rebase-apply") if rebase_merge:exists() then rebase_file = rebase_merge @@ -135,7 +135,7 @@ function M.update_rebase_status(state) local onto = rebase_file:joinpath("onto") if onto:exists() then state.rebase.onto.oid = vim.trim(onto:read()) - state.rebase.onto.subject = log.message(state.rebase.onto.oid) + state.rebase.onto.subject = git.log.message(state.rebase.onto.oid) state.rebase.onto.ref = cli["name-rev"].name_only.no_undefined .refs("refs/heads/*") .exclude("*/HEAD") diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 6cb1764b2..8eb789cea 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -1,3 +1,4 @@ +local util = require("neogit.lib.util") local M = {} -- .git/sequencer/todo does not exist when there is only one commit left. @@ -10,7 +11,7 @@ function M.pick_or_revert_in_progress() local pick_or_revert_todo = false for _, item in ipairs(git.repo.sequencer.items) do - if item.name:match("^pick") or item.name:match("^revert") then + if item.action == "pick" or item.action == "revert" then pick_or_revert_todo = true break end @@ -20,36 +21,58 @@ function M.pick_or_revert_in_progress() end function M.update_sequencer_status(state) - local repo = require("neogit.lib.git.repository") + local git = require("neogit.lib.git") state.sequencer = { items = {}, head = nil, head_oid = nil } - local revert_head = repo:git_path("REVERT_HEAD") - local cherry_head = repo:git_path("CHERRY_PICK_HEAD") + local revert_head = git.repo:git_path("REVERT_HEAD") + local cherry_head = git.repo:git_path("CHERRY_PICK_HEAD") if cherry_head:exists() then state.sequencer.head = "CHERRY_PICK_HEAD" - state.sequencer.head_oid = repo:git_path("CHERRY_PICK_HEAD"):read() + state.sequencer.head_oid = vim.trim(git.repo:git_path("CHERRY_PICK_HEAD"):read()) state.sequencer.cherry_pick = true elseif revert_head:exists() then state.sequencer.head = "REVERT_HEAD" - state.sequencer.head_oid = repo:git_path("REVERT_HEAD"):read() + state.sequencer.head_oid = vim.trim(git.repo:git_path("REVERT_HEAD"):read()) state.sequencer.revert = true end - local todo = repo:git_path("sequencer/todo") - local orig = repo:git_path("ORIG_HEAD") + local todo = git.repo:git_path("sequencer/todo") if todo:exists() then for line in todo:iter() do - if not line:match("^#") then - table.insert(state.sequencer.items, { name = line }) + if line:match("^[^#]") and line ~= "" then + table.insert(state.sequencer.items, { + action = line:match("^(%w+) "), + oid = line:match("^%w+ (%x+)"), + subject = line:match("^%w+ %x+ (.+)$"), + }) end end - elseif state.sequencer.head_oid and orig:exists() then - local head = state.sequencer.head_oid:sub(1, 7) - orig = orig:read():sub(1, 7) - local git = require("neogit.lib.git") - table.insert(state.sequencer.items, { name = string.format("work %s %s", orig, git.log.message(orig)) }) - table.insert(state.sequencer.items, { name = string.format("onto %s %s", head, git.log.message(head)) }) + end + + state.sequencer.items = util.reverse(state.sequencer.items) + + -- TODO: Figure out the logic behind onto/gone/work + local orig = git.repo:git_path("ORIG_HEAD") + if state.sequencer.head_oid and orig:exists() then + local orig_head = vim.trim(orig:read()) + table.insert( + state.sequencer.items, + { + action = "work", + oid = orig_head, + subject = git.log.message(orig_head) + } + ) + + table.insert( + state.sequencer.items, + { + action = "onto", + oid = state.sequencer.head_oid, + subject = git.log.message(state.sequencer.head_oid) + } + ) end end From bd28fcdde8e9fb542bd4ffc46f4583027c4ec75f Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 16 Feb 2024 23:37:07 +0100 Subject: [PATCH 145/443] wip: progress on cursor positioning after update() is called --- lua/neogit/buffers/status/ui.lua | 158 ++++++++++++++++--------------- lua/neogit/lib/ui/init.lua | 45 ++++++++- lua/neogit/lib/ui/renderer.lua | 14 ++- 3 files changed, 136 insertions(+), 81 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 3121d637e..50f9852e8 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -123,86 +123,90 @@ local Section = Component.new(function(props) row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), col(map(props.items, props.render)), EmptyLine, - }, { foldable = true, folded = props.folded, section = props.name }) + }, { foldable = true, folded = props.folded, section = props.name, id = props.name }) end) -local SectionItemFile = Component.new(function(item) - local load_diff = function(item) - ---@param this Component - ---@param ui Ui - ---@param prefix string|nil - return a.void(function(this, ui, prefix) - this.options.on_open = nil - this.options.folded = false - - local row, _ = this:row_range_abs() - row = row + 1 -- Filename row - - local diff = item.diff - for _, hunk in ipairs(diff.hunks) do - hunk.first = row - hunk.last = row + hunk.length - row = hunk.last + 1 - - -- Set fold state when called from ui:update() - if prefix then - local key = ("%s--%s"):format(prefix, hunk.hash) - if ui._old_node_attributes and ui._old_node_attributes[key] then - hunk._folded = ui._old_node_attributes[key].folded +local SectionItemFile = function(section) + return Component.new(function(item) + local load_diff = function(item) + ---@param this Component + ---@param ui Ui + ---@param prefix string|nil + return a.void(function(this, ui, prefix) + this.options.on_open = nil + this.options.folded = false + + local row, _ = this:row_range_abs() + row = row + 1 -- Filename row + + local diff = item.diff + for _, hunk in ipairs(diff.hunks) do + hunk.first = row + hunk.last = row + hunk.length + row = hunk.last + 1 + + -- Set fold state when called from ui:update() + if prefix then + local key = ("%s--%s"):format(prefix, hunk.hash) + if ui._old_node_attributes and ui._old_node_attributes[key] then + hunk._folded = ui._old_node_attributes[key].folded + end end end - end - this:append(DiffHunks(diff)) - ui:update() - end) - end + this:append(DiffHunks(diff)) + ui:update() + end) + end - local mode_to_text = { - M = "Modified", - N = "New File", - A = "Added", - D = "Deleted", - C = "Copied", - U = "Updated", - UU = "Both Modified", - R = "Renamed", - ["?"] = "", -- Untracked - } + local mode_to_text = { + M = "Modified", + N = "New File", + A = "Added", + D = "Deleted", + C = "Copied", + U = "Updated", + UU = "Both Modified", + R = "Renamed", + ["?"] = "", -- Untracked + } - local conflict = false - local mode = mode_to_text[item.mode] - if mode == nil then - conflict = true - mode = mode_to_text[item.mode:sub(1, 1)] - end + local conflict = false + local mode = mode_to_text[item.mode] + if mode == nil then + conflict = true + mode = mode_to_text[item.mode:sub(1, 1)] + end - local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) + local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) - local mode_text - if mode == "" then - mode_text = "" - elseif conflict then - mode_text = util.pad_right(("%s by us"):format(mode), 15) - else - mode_text = util.pad_right(mode, 15) - end + local mode_text + if mode == "" then + mode_text = "" + elseif conflict then + mode_text = util.pad_right(("%s by us"):format(mode), 15) + else + mode_text = util.pad_right(mode, 15) + end - return col.tag("SectionItemFile")({ - row { - text.highlight(highlight)(mode_text), - text(item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name), - }, - }, { - foldable = true, - folded = true, - on_open = load_diff(item), - context = true, - yankable = item.name, - filename = item.name, - item = item, - }) -end) + return col({ + row { + text.highlight(highlight)(mode_text), + text(item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name), + }, + }, { + tag = "File", + foldable = true, + folded = true, + on_open = load_diff(item), + context = true, + id = ("%s--%s"):format(section, item.name), + yankable = item.name, + filename = item.name, + item = item, + }) + end) +end local SectionItemStash = Component.new(function(item) local name = ("stash@{%s}"):format(item.idx) @@ -363,26 +367,26 @@ function M.Status(state, config) render = SectionItemRevert, items = state.sequencer.items, folded = config.sections.sequencer.folded, - name = "revert" + name = "revert", }, show_untracked and Section { -- TODO: Group untracked by directory and create a fold title = SectionTitle { title = "Untracked files" }, - render = SectionItemFile, + render = SectionItemFile("untracked"), items = state.untracked.items, folded = config.sections.untracked.folded, name = "untracked", }, show_unstaged and Section { title = SectionTitle { title = "Unstaged changes" }, - render = SectionItemFile, + render = SectionItemFile("unstaged"), items = state.unstaged.items, folded = config.sections.unstaged.folded, name = "unstaged", }, show_staged and Section { title = SectionTitle { title = "Staged changes" }, - render = SectionItemFile, + render = SectionItemFile("staged"), items = state.staged.items, folded = config.sections.staged.folded, name = "staged", @@ -392,36 +396,42 @@ function M.Status(state, config) render = SectionItemCommit, items = state.upstream.unpulled.items, folded = config.sections.unpulled_upstream.folded, + name = "upstream_unpulled", }, show_pushRemote_unpulled and Section { title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, render = SectionItemCommit, items = state.pushRemote.unpulled.items, folded = config.sections.unpulled_pushRemote.folded, + name = "pushRemote_unpulled", }, show_upstream_unmerged and Section { title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref }, render = SectionItemCommit, items = state.upstream.unmerged.items, folded = config.sections.unmerged_upstream.folded, + name = "upstream_unmerged", }, show_pushRemote_unmerged and Section { title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref }, render = SectionItemCommit, items = state.pushRemote.unmerged.items, folded = config.sections.unmerged_pushRemote.folded, + name = "pushRemote_unmerged", }, show_stashes and Section { title = SectionTitle { title = "Stashes" }, render = SectionItemStash, items = state.stashes.items, folded = config.sections.stashes.folded, + name = "stashes", }, show_recent and Section { title = SectionTitle { title = "Recent Commits" }, render = SectionItemCommit, items = state.recent.items, folded = config.sections.recent.folded, + name = "recent", }, }, }, diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index f295bf54d..b19e3b4ae 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -289,6 +289,34 @@ function Ui:render(...) if not vim.tbl_isempty(self.layout) then self._old_node_attributes = gather_nodes(self.layout) + + local context = self:get_cursor_context() + if context then + if context.options.tag == "Hunk" then + if context.index == 1 then + if #context.parent.children > 1 then + self._cursor_context_start = ({ context:row_range_abs() })[1] + else + self._cursor_context_start = ({ context:row_range_abs() })[1] - 1 + end + else + self._cursor_context_start = ({ context.parent.children[context.index - 1]:row_range_abs() })[1] + end + elseif context.options.tag == "File" then + if context.index == 1 then + if #context.parent.children > 1 then + -- id is scoped by section. Advance to next file. + self._cursor_goto = context.parent.children[2].options.id + else + -- Yankable lets us jump from one section to the other. Go to same file in new section. + self._cursor_goto = context.options.yankable + end + else + self._cursor_goto = context.parent.children[context.index - 1].options.id + end + else + end + end end self.layout = root @@ -318,14 +346,23 @@ function Ui:update() self.buf:set_folds(renderer.buffer.fold) self.buf:lock() - -- P(self:get_cursor_context(math.min(cursor_line, #renderer.buffer.line)):row_range_abs()) - - self.buf:move_cursor(math.min(cursor_line, #renderer.buffer.line)) - if self._old_node_attributes then self:_update_attributes(self.layout, self._old_node_attributes) self._old_node_attributes = nil end + + if self._cursor_context_start then + self.buf:move_cursor(self._cursor_context_start) + self._cursor_context_start = nil + elseif self._cursor_goto then + if self.node_index:find_by_id(self._cursor_goto) then + self.buf:move_cursor(self.node_index:find_by_id(self._cursor_goto):row_range_abs()) + end + + self._cursor_goto = nil + else + self.buf:move_cursor(cursor_line) + end end Ui.col = Component.new(function(children, options) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 31bbcf041..b1683d493 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -25,12 +25,16 @@ function RendererIndex:add(node) end ---@param node Component -function RendererIndex:add_id(node) - if tonumber(node.options.id) then +---@param id? string +function RendererIndex:add_id(node, id) + id = id or node.options.id + assert(id, "id cannot be nil") + + if tonumber(id) then error("Cannot use an integer ID for a component") end - self.index[node.options.id] = node + self.index[id] = node end function RendererIndex.new() @@ -101,6 +105,10 @@ function Renderer:_build_child(child, parent, index) self.index:add_id(child) end + if child.options.yankable then + self.index:add_id(child, child.options.yankable) + end + child.parent = parent child.index = index child.position = { From fc1c48ab21c180601fb473de93e8477051c76065 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 16 Feb 2024 23:52:57 +0100 Subject: [PATCH 146/443] diff/stash popup wiring --- lua/neogit/buffers/status/init.lua | 10 ++++++---- lua/neogit/buffers/status/ui.lua | 6 +++--- lua/neogit/lib/ui/init.lua | 2 ++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8f5ad6ffa..dc0c6a9be 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -583,12 +583,13 @@ function M:open(kind) p { commit = self.buffer.ui:get_commit_under_cursor() } end), [popups.mapping_for("StashPopup")] = popups.open("stash", function(p) - -- TODO: Pass in stash name if its under the cursor - p { name = self.buffer.ui:get_commit_under_cursor() } + local stash = self.buffer.ui:get_yankable_under_cursor() + p { name = stash and stash:match("^stash@{%d+}") } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - -- TODO use current section/item - p { section = {}, item = {} } + local section = self.buffer.ui:get_current_section().options.section + local item = self.buffer.ui:get_yankable_under_cursor() + p { section = { name = section }, item = { name = item } } end), [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) -- TODO use current absolute paths in selection @@ -653,6 +654,7 @@ function M:refresh(partial, reason) source = "status", partial = partial, callback = function() + -- TODO: move cursor restoration logic here? self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 50f9852e8..db2104fc9 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -222,7 +222,7 @@ local SectionItemCommit = Component.new(function(item) text.highlight("Comment")(item.commit.abbreviated_commit), text(" "), text(item.commit.subject), - }, { yankable = item.commit.oid }) + }, { oid = item.commit.oid, yankable = item.commit.oid }) end) local SectionItemRebase = Component.new(function(item) @@ -238,7 +238,7 @@ local SectionItemRebase = Component.new(function(item) text.highlight("NeogitRebaseDone")(item.oid:sub(1, 7)), text(" "), text.highlight(item.done and "NeogitRebaseDone")(item.subject), - }, { yankable = item.oid }) + }, { yankable = item.oid, oid = item.oid }) else return row { text.highlight("NeogitGraphOrange")(item.action), @@ -260,7 +260,7 @@ local SectionItemRevert = Component.new(function(item) text.highlight("Comment")(item.oid:sub(1, 7)), text(" "), text(item.subject), - }, { yankable = item.oid }) + }, { yankable = item.oid, oid = item.oid }) end) function M.Status(state, config) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index b19e3b4ae..200eba126 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -290,6 +290,8 @@ function Ui:render(...) if not vim.tbl_isempty(self.layout) then self._old_node_attributes = gather_nodes(self.layout) + -- Restoring cursor location for status buffer on update. Might need to move this, as it doesn't really make sense + -- here. local context = self:get_cursor_context() if context then if context.options.tag == "Hunk" then From 14ea90b3bd2cd9619ca9474fef17ed1f6d3fcbaf Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 17 Feb 2024 23:35:45 +0100 Subject: [PATCH 147/443] Update status buffer BothModified to say Unmerged --- lua/neogit/buffers/status/ui.lua | 21 +++++++++------------ lua/neogit/lib/hl.lua | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index db2104fc9..3dd8928da 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -166,32 +166,29 @@ local SectionItemFile = function(section) D = "Deleted", C = "Copied", U = "Updated", - UU = "Both Modified", R = "Renamed", + DD = "Unmerged", + AU = "Unmerged", + UD = "Unmerged", + UA = "Unmerged", + DU = "Unmerged", + AA = "Unmerged", + UU = "Unmerged", ["?"] = "", -- Untracked } - local conflict = false local mode = mode_to_text[item.mode] - if mode == nil then - conflict = true - mode = mode_to_text[item.mode:sub(1, 1)] - end - - local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) local mode_text if mode == "" then mode_text = "" - elseif conflict then - mode_text = util.pad_right(("%s by us"):format(mode), 15) else - mode_text = util.pad_right(mode, 15) + mode_text = util.pad_right(mode, 11) end return col({ row { - text.highlight(highlight)(mode_text), + text.highlight(("NeogitChange%s"):format(mode:gsub(" ", "")))(mode_text), text(item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name), }, }, { diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 653aab2d0..522d39b5a 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -192,7 +192,7 @@ function M.setup() NeogitChangeRenamed = { fg = palette.bg_purple, bold = palette.bold, italic = palette.italic }, NeogitChangeUpdated = { fg = palette.bg_orange, bold = palette.bold, italic = palette.italic }, NeogitChangeCopied = { fg = palette.bg_cyan, bold = palette.bold, italic = palette.italic }, - NeogitChangeBothModified = { fg = palette.bg_yellow, bold = palette.bold, italic = palette.italic }, + NeogitChangeUnmerged = { fg = palette.bg_yellow, bold = palette.bold, italic = palette.italic }, NeogitChangeNewFile = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, NeogitSectionHeader = { fg = palette.bg_purple, bold = palette.bold }, NeogitUntrackedfiles = { link = "NeogitSectionHeader" }, From 51830ad52d73ea7c5e24b74592c7a6e2ad0f549c Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 17 Feb 2024 23:36:13 +0100 Subject: [PATCH 148/443] Display "New File" appropriately. --- lua/neogit/lib/git/status.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index cf49d88f2..d7928b29d 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -106,9 +106,13 @@ local function update_status(state) elseif kind == "?" then table.insert(untracked_files, update_file(cwd, old_files_hash.untracked_files[rest], "?", rest)) elseif kind == "1" then - local mode_staged, mode_unstaged, _, _, _, _, _, _, name = rest:match(match_1) + local mode_staged, mode_unstaged, _, _, _, _, hH, _, name = rest:match(match_1) if mode_staged ~= "." then + if hH:match("^0+$") then + mode_staged = "N" + end + table.insert(staged_files, update_file(cwd, old_files_hash.staged_files[name], mode_staged, name)) end From d5c4352e1aad8547cf54e38aaf023623d64e13b7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 17 Feb 2024 23:36:33 +0100 Subject: [PATCH 149/443] Make the sequencer display a bit better --- lua/neogit/buffers/status/ui.lua | 42 +++++++++++++++++++++++--------- lua/neogit/lib/git/sequencer.lua | 40 +++++++++++------------------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 3dd8928da..853125ed3 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -123,7 +123,25 @@ local Section = Component.new(function(props) row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), col(map(props.items, props.render)), EmptyLine, - }, { foldable = true, folded = props.folded, section = props.name, id = props.name }) + }, { + foldable = true, + folded = props.folded, + section = props.name, + id = props.name, + }) +end) + +local SequencerSection = Component.new(function(props) + return col.tag("Section")({ + row(util.merge(props.title)), + col(map(props.items, props.render)), + EmptyLine, + }, { + foldable = true, + folded = props.folded, + section = props.name, + id = props.name, + }) end) local SectionItemFile = function(section) @@ -246,7 +264,7 @@ local SectionItemRebase = Component.new(function(item) end) -- TODO: "gone", "work", "onto" highlighting -local SectionItemRevert = Component.new(function(item) +local SectionItemSequencer = Component.new(function(item) local action_hl = (item.action == "work" and "NeogitGraphRed") or (item.action == "onto" and "NeogitGraphBlue") or "NeogitGraphOrange" @@ -276,12 +294,10 @@ function M.Status(state, config) local show_rebase = #state.rebase.items > 0 and not config.sections.rebase.hidden - local show_cherry_pick = #state.sequencer.items > 0 - and state.sequencer.cherry_pick + local show_cherry_pick = state.sequencer.cherry_pick and not config.sections.sequencer.hidden - local show_revert = #state.sequencer.items > 0 - and state.sequencer.revert + local show_revert = state.sequencer.revert and not config.sections.sequencer.hidden local show_untracked = #state.untracked.items > 0 @@ -356,13 +372,17 @@ function M.Status(state, config) folded = config.sections.rebase.folded, name = "rebase", }, - show_cherry_pick and Section { - -- TODO Picking (sequencer - cherry_pick_head) + show_cherry_pick and SequencerSection { + title = SectionTitle { title = "Cherry Picking" }, + render = SectionItemSequencer, + items = util.reverse(state.sequencer.items), + folded = config.sections.sequencer.folded, + name = "cherry_pick", }, - show_revert and Section { + show_revert and SequencerSection { title = SectionTitle { title = "Reverting" }, - render = SectionItemRevert, - items = state.sequencer.items, + render = SectionItemSequencer, + items = util.reverse(state.sequencer.items), folded = config.sections.sequencer.folded, name = "revert", }, diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 8eb789cea..eafd53a6b 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -22,7 +22,7 @@ end function M.update_sequencer_status(state) local git = require("neogit.lib.git") - state.sequencer = { items = {}, head = nil, head_oid = nil } + state.sequencer = { items = {}, head = nil, head_oid = nil, revert = false, cherry_pick = false } local revert_head = git.repo:git_path("REVERT_HEAD") local cherry_head = git.repo:git_path("CHERRY_PICK_HEAD") @@ -37,6 +37,13 @@ function M.update_sequencer_status(state) state.sequencer.revert = true end + local HEAD_oid = git.rev_parse.oid("HEAD") + table.insert(state.sequencer.items, { + action = "onto", + oid = HEAD_oid, + subject = git.log.message(HEAD_oid), + }) + local todo = git.repo:git_path("sequencer/todo") if todo:exists() then for line in todo:iter() do @@ -48,31 +55,12 @@ function M.update_sequencer_status(state) }) end end - end - - state.sequencer.items = util.reverse(state.sequencer.items) - - -- TODO: Figure out the logic behind onto/gone/work - local orig = git.repo:git_path("ORIG_HEAD") - if state.sequencer.head_oid and orig:exists() then - local orig_head = vim.trim(orig:read()) - table.insert( - state.sequencer.items, - { - action = "work", - oid = orig_head, - subject = git.log.message(orig_head) - } - ) - - table.insert( - state.sequencer.items, - { - action = "onto", - oid = state.sequencer.head_oid, - subject = git.log.message(state.sequencer.head_oid) - } - ) + elseif state.sequencer.cherry_pick or state.sequencer.revert then + table.insert(state.sequencer.items, { + action = "join", + oid = state.sequencer.head_oid, + subject = git.log.message(state.sequencer.head_oid), + }) end end From af70b0700662376fd951884318d1f7d19c07f4a5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 18 Feb 2024 00:00:53 +0100 Subject: [PATCH 150/443] Show when rebase onto target is remote, and show counter of actions done/remaining --- lua/neogit/buffers/status/ui.lua | 52 ++++++++++++++++++++++++++------ lua/neogit/lib/git/rebase.lua | 1 + 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 853125ed3..2262f0ee5 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -107,15 +107,22 @@ local SectionTitleRemote = Component.new(function(props) } end) --- TODO: Determine if 'onto' is local or remote local SectionTitleRebase = Component.new(function(props) - return { - text.highlight("NeogitSectionHeader")(props.title), - text(" "), - text.highlight("NeogitBranch")(props.head), - text.highlight("NeogitSectionHeader")(" onto "), - text.highlight("NeogitBranch")(props.onto), - } + if props.onto then + return { + text.highlight("NeogitSectionHeader")(props.title), + text(" "), + text.highlight("NeogitBranch")(props.head), + text.highlight("NeogitSectionHeader")(" onto "), + text.highlight(props.is_remote_ref and "NeogitRemote" or "NeogitBranch")(props.onto), + } + else + return { + text.highlight("NeogitSectionHeader")(props.title), + text(" "), + text.highlight("NeogitBranch")(props.head), + } + end end) local Section = Component.new(function(props) @@ -144,6 +151,30 @@ local SequencerSection = Component.new(function(props) }) end) +local RebaseSection = Component.new(function(props) + return col.tag("Section")({ + row( + util.merge( + props.title, + { + text(" ("), + text(props.current), + text("/"), + text(#props.items - 1), + text(")") + } + ) + ), + col(map(props.items, props.render)), + EmptyLine, + }, { + foldable = true, + folded = props.folded, + section = props.name, + id = props.name, + }) +end) + local SectionItemFile = function(section) return Component.new(function(item) local load_diff = function(item) @@ -361,13 +392,16 @@ function M.Status(state, config) yankable = state.head.tag.oid, }, EmptyLine, - show_rebase and Section { + show_rebase and RebaseSection { title = SectionTitleRebase { title = "Rebasing", head = state.rebase.head, onto = state.rebase.onto.ref, + oid = state.rebase.onto.oid, + is_remote_ref = state.rebase.onto.is_remote, }, render = SectionItemRebase, + current = state.rebase.current, items = state.rebase.items, folded = config.sections.rebase.folded, name = "rebase", diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 36a00cff3..4fe9b71a3 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -142,6 +142,7 @@ function M.update_rebase_status(state) .exclude("*/refs/heads/*") .args(state.rebase.onto.oid) .call({ hidden = true }).stdout[1] + state.rebase.onto.is_remote = not git.branch.exists(state.rebase.onto.ref) end local done = rebase_file:joinpath("done") From 0254449b847b0289e3732224e3a9cc78bd770369 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 18 Feb 2024 00:01:23 +0100 Subject: [PATCH 151/443] Join label --- lua/neogit/buffers/status/ui.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 2262f0ee5..aec13ba15 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -294,9 +294,8 @@ local SectionItemRebase = Component.new(function(item) end end) --- TODO: "gone", "work", "onto" highlighting local SectionItemSequencer = Component.new(function(item) - local action_hl = (item.action == "work" and "NeogitGraphRed") + local action_hl = (item.action == "join" and "NeogitGraphRed") or (item.action == "onto" and "NeogitGraphBlue") or "NeogitGraphOrange" @@ -421,7 +420,6 @@ function M.Status(state, config) name = "revert", }, show_untracked and Section { - -- TODO: Group untracked by directory and create a fold title = SectionTitle { title = "Untracked files" }, render = SectionItemFile("untracked"), items = state.untracked.items, From 5ab8ba7f1a604425c8c95cb3a61a936cd13e3402 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 18 Feb 2024 00:04:48 +0100 Subject: [PATCH 152/443] When rebasing, prevent from showing up on list of branches --- lua/neogit/lib/git/branch.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 7ddeff392..c2f62c838 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -15,13 +15,20 @@ local function parse_branches(branches, include_current) local head = "^(.*)/HEAD" local ref = " %-> " local detached = "^%(HEAD detached at %x%x%x%x%x%x%x" + local no_branch = "^%(no branch," local pattern = include_current and "^[* ] (.+)" or "^ (.+)" for _, b in ipairs(branches) do local branch_name = b:match(pattern) if branch_name then local name = branch_name:match(remotes) or branch_name - if name and not name:match(ref) and not name:match(head) and not name:match(detached) then + if + name + and not name:match(ref) + and not name:match(head) + and not name:match(detached) + and not name:match(no_branch) + then table.insert(other_branches, name) end end From cfb2b16b527bc680be5c1ca54580b4968543bdb7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 20 Feb 2024 22:51:08 +0100 Subject: [PATCH 153/443] Empty string will just render the line when folded.. so.. empty string! --- lua/neogit.lua | 2 -- lua/neogit/lib/buffer.lua | 2 +- lua/neogit/lib/folds.lua | 50 --------------------------------------- 3 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 lua/neogit/lib/folds.lua diff --git a/lua/neogit.lua b/lua/neogit.lua index 9e28c7e25..9417b31ca 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -11,7 +11,6 @@ function M.setup(opts) local hl = require("neogit.lib.hl") local state = require("neogit.lib.state") local logger = require("neogit.logger") - local folds = require("neogit.lib.folds") if did_setup then logger.debug("Already did setup!") @@ -73,7 +72,6 @@ function M.setup(opts) signs.setup() state.setup() autocmds.setup() - folds.setup() end local function construct_opts(opts) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 0414a147a..b376fe1a3 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -521,7 +521,7 @@ function Buffer.create(config) buffer:set_window_option("foldenable", true) buffer:set_window_option("foldlevel", 99) buffer:set_window_option("foldminlines", 0) - buffer:set_window_option("foldtext", "v:lua.NeogitBufferFoldText()") + buffer:set_window_option("foldtext", "") end if config.filetype then diff --git a/lua/neogit/lib/folds.lua b/lua/neogit/lib/folds.lua deleted file mode 100644 index 1ed494027..000000000 --- a/lua/neogit/lib/folds.lua +++ /dev/null @@ -1,50 +0,0 @@ -local M = {} - -function M.fold_text() - local text = vim.fn.getline(vim.v.foldstart) - - -- TODO: Remove this once nvim-0.10 is released - if vim.fn.has("nvim-0.10") == 0 then - return text - end - - local bufnr = vim.fn.bufnr() - local ns = vim.api.nvim_get_namespaces()["neogit-buffer-" .. bufnr] - - local lnum = vim.v.foldstart - 1 - local start_range = { lnum, 0 } - local end_range = { lnum, -1 } - - local last_col_end = 0 - local res = {} - - local marks = vim.api.nvim_buf_get_extmarks(bufnr, ns, start_range, end_range, { details = true }) - - for _, m in ipairs(marks) do - local start_col, details = m[3], m[4] - local end_col = details.end_col or (start_col + 1) - local hl_group = details.hl_group - - if hl_group then - if start_col > last_col_end then - table.insert(res, { text:sub(last_col_end + 1, start_col), "NeogitGraphWhite" }) - end - - last_col_end = end_col - table.insert(res, { text:sub(start_col + 1, end_col), hl_group }) - end - end - - if #text > last_col_end then - table.insert(res, { text:sub(last_col_end + 1, -1), "NeogitGraphWhite" }) - end - - return res -end - -function M.setup() - -- selene: allow(global_usage) - _G.NeogitBufferFoldText = M.fold_text -end - -return M From 92bb4d78cd8d0cd204581bed29632d4a3cb4bbd3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 20 Feb 2024 23:22:55 +0100 Subject: [PATCH 154/443] Remove old status buffer and a bunch of libs no longer needed. --- lua/neogit/autocmds.lua | 17 +- lua/neogit/bootstrap.lua | 18 - lua/neogit/lib/fs.lua | 14 - lua/neogit/lib/functional.lua | 33 - lua/neogit/lib/git/files.lua | 9 + lua/neogit/lib/line_buffer.lua | 25 - lua/neogit/status.lua | 1526 -------------------------------- syntax/NeogitCommitView.vim | 8 - syntax/NeogitStatus.vim | 65 -- 9 files changed, 19 insertions(+), 1696 deletions(-) delete mode 100644 lua/neogit/bootstrap.lua delete mode 100644 lua/neogit/lib/fs.lua delete mode 100644 lua/neogit/lib/functional.lua delete mode 100644 lua/neogit/lib/line_buffer.lua delete mode 100644 lua/neogit/status.lua delete mode 100644 syntax/NeogitCommitView.vim delete mode 100644 syntax/NeogitStatus.vim diff --git a/lua/neogit/autocmds.lua b/lua/neogit/autocmds.lua index f22b721f8..5cd89b0f1 100644 --- a/lua/neogit/autocmds.lua +++ b/lua/neogit/autocmds.lua @@ -2,8 +2,8 @@ local M = {} local api = vim.api local a = require("plenary.async") -local status = require("neogit.status") -local fs = require("neogit.lib.fs") +local status_buffer = require("neogit.buffers.status") +local git = require("neogit.lib.git") local group = require("neogit").autocmd_group function M.setup() @@ -14,22 +14,25 @@ function M.setup() api.nvim_create_autocmd({ "BufWritePost", "ShellCmdPost", "VimResume" }, { callback = function(o) - -- Skip update if the buffer is not open - if not status.status_buffer then + if not status_buffer.instance then return end -- Do not trigger on neogit buffers such as commit - if api.nvim_buf_get_option(o.buf, "filetype"):find("Neogit") then + if api.nvim_get_option_value("filetype", { buf = o.buf }):find("Neogit") then return end a.run(function() - local path = fs.relpath_from_repository(o.file) + local path = git.files.relpath_from_repository(o.file) if not path then return end - status.refresh({ update_diffs = { "*:" .. path } }, string.format("%s:%s", o.event, o.file)) + + status_buffer.instance:dispatch_refresh( + { update_diffs = { "*:" .. path } }, + string.format("%s:%s", o.event, o.file) + ) end, function() end) end, group = group, diff --git a/lua/neogit/bootstrap.lua b/lua/neogit/bootstrap.lua deleted file mode 100644 index d066bd4b0..000000000 --- a/lua/neogit/bootstrap.lua +++ /dev/null @@ -1,18 +0,0 @@ --- This module does all necessary dependency checks and takes care of --- initializing global values and configuration. --- It MUST NOT error, as this function is called from viml and errors won't --- be caught. --- --- The module returns true if everything went well, or false if any part of --- the initialization failed. -local res, err = pcall(require, "plenary") -if not res then - print("WARNING: Neogit depends on `nvim-lua/plenary.nvim` to work, but loading the plugin failed!") - print( - "Make sure you add `nvim-lua/plenary.nvim` to your plugin manager BEFORE neogit for everything to work" - ) - print(err) -- TODO: find out how to print the error without raising it AND properly print tabs - return false -end - -return true diff --git a/lua/neogit/lib/fs.lua b/lua/neogit/lib/fs.lua deleted file mode 100644 index c05f5a678..000000000 --- a/lua/neogit/lib/fs.lua +++ /dev/null @@ -1,14 +0,0 @@ -local cli = require("neogit.lib.git.cli") - -local M = {} - -function M.relpath_from_repository(path) - local result = cli["ls-files"].others.cached.modified.deleted.full_name - .args(path) - .show_popup(false) - .call { hidden = true } - - return result.stdout[1] -end - -return M diff --git a/lua/neogit/lib/functional.lua b/lua/neogit/lib/functional.lua deleted file mode 100644 index c248daeeb..000000000 --- a/lua/neogit/lib/functional.lua +++ /dev/null @@ -1,33 +0,0 @@ -local util = require("neogit.lib.util") -local collect = require("neogit.lib.collection") -local M = {} - -function M.dot(chain) - local parts = collect(util.split(chain, "%.")) - return function(tbl) - parts:each(function(p) - if tbl then - tbl = tbl[p] - end - end) - return tbl - end -end - -function M.compose(...) - local funcs = collect { ... } - return function(...) - return funcs:reduce(function(cur, ...) - return cur(...) - end, ...) - end -end -M.C = M.compose - -function M.eq(a) - return function(b) - return a == b - end -end - -return M diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index 5fe7762e3..d6756f71f 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -18,4 +18,13 @@ function M.diff(commit) return cli.diff.name_only.args(commit .. "...").call_sync({ hidden = true }).stdout end +function M.relpath_from_repository(path) + local result = cli["ls-files"].others.cached.modified.deleted.full_name + .args(path) + .show_popup(false) + .call { hidden = true } + + return result.stdout[1] +end + return M diff --git a/lua/neogit/lib/line_buffer.lua b/lua/neogit/lib/line_buffer.lua deleted file mode 100644 index 524eee45d..000000000 --- a/lua/neogit/lib/line_buffer.lua +++ /dev/null @@ -1,25 +0,0 @@ -local M = {} - -function M.new(initial_value) - initial_value = initial_value or {} - if type(initial_value) ~= "table" then - error("Initial value must be a table", 2) - end - - return setmetatable(initial_value, { __index = M }) -end - -function M.append(tbl, data) - if type(data) == "string" then - table.insert(tbl, data) - elseif type(data) == "table" then - for _, r in ipairs(data) do - table.insert(tbl, r) - end - else - error("invalid data type: " .. type(data), 2) - end - return tbl -end - -return M diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua deleted file mode 100644 index 30a750921..000000000 --- a/lua/neogit/status.lua +++ /dev/null @@ -1,1526 +0,0 @@ -local Buffer = require("neogit.lib.buffer") -local GitCommandHistory = require("neogit.buffers.git_command_history") -local RefsViewBuffer = require("neogit.buffers.refs_view") -local CommitView = require("neogit.buffers.commit_view") -local git = require("neogit.lib.git") -local notification = require("neogit.lib.notification") -local config = require("neogit.config") -local a = require("plenary.async") -local logger = require("neogit.logger") -local Collection = require("neogit.lib.collection") -local F = require("neogit.lib.functional") -local LineBuffer = require("neogit.lib.line_buffer") -local fs = require("neogit.lib.fs") -local input = require("neogit.lib.input") -local util = require("neogit.lib.util") -local watcher = require("neogit.watcher") -local operation = require("neogit.operations") - -local M = {} - -M.disabled = false - -M.prev_autochdir = nil -M.status_buffer = nil -M.commit_view = nil -M.cursor_location = nil - ----@class Section ----@field first number ----@field last number ----@field items StatusItem[] ----@field name string ----@field ignore_sign boolean If true will skip drawing the section icons ----@field folded boolean|nil - ----@type Section[] ----Sections in order by first lines -M.locations = {} - -M.outdated = {} - ----@class StatusItem ----@field name string ----@field first number ----@field last number ----@field oid string|nil optional object id ----@field commit CommitLogEntry|nil optional object id ----@field folded boolean|nil ----@field hunks Hunk[]|nil - -local head_start = "@" -local add_start = "+" -local del_start = "-" - -local function get_section_idx_for_line(linenr) - for i, l in pairs(M.locations) do - if l.first <= linenr and linenr <= l.last then - return i - end - end - return nil -end - -local function get_section_item_idx_for_line(linenr) - local section_idx = get_section_idx_for_line(linenr) - local section = M.locations[section_idx] - - if section == nil then - return nil, nil - end - - for i, item in pairs(section.items) do - if item.first <= linenr and linenr <= item.last then - return section_idx, i - end - end - - return section_idx, nil -end - ----@return Section|nil, StatusItem|nil -local function get_section_item_for_line(linenr) - local section_idx, item_idx = get_section_item_idx_for_line(linenr) - local section = M.locations[section_idx] - - if section == nil then - return nil, nil - end - if item_idx == nil then - return section, nil - end - - return section, section.items[item_idx] -end - ----@return Section|nil, StatusItem|nil -local function get_current_section_item() - return get_section_item_for_line(vim.fn.line(".")) -end - -local mode_to_text = { - M = "Modified", - N = "New file", - A = "Added", - D = "Deleted", - C = "Copied", - U = "Updated", - UU = "Both Modified", - R = "Renamed", -} - -local max_len = #"Modified by us" - -local function draw_sign_for_item(item, name) - if item.folded then - M.status_buffer:place_sign(item.first, "NeogitClosed" .. name, { namespace = "fold_markers" }) - else - M.status_buffer:place_sign(item.first, "NeogitOpen" .. name, { namespace = "fold_markers" }) - end -end - -local function draw_signs() - if config.values.disable_signs then - return - end - for _, l in ipairs(M.locations) do - if not l.ignore_sign then - draw_sign_for_item(l, "section") - if not l.folded then - Collection.new(l.items):filter(F.dot("hunks")):each(function(f) - draw_sign_for_item(f, "item") - if not f.folded then - Collection.new(f.hunks):each(function(h) - draw_sign_for_item(h, "hunk") - end) - end - end) - end - end - end -end - -local function format_mode(mode) - if not mode then - return "" - end - local res = mode_to_text[mode] - if res then - return res - end - - local res = mode_to_text[mode:sub(1, 1)] - if res then - return res .. " by us" - end - - return mode -end - -local function draw_buffer() - M.status_buffer:clear_namespace("fold_markers") - - local output = LineBuffer.new() - if not config.values.disable_hint then - local reversed_status_map = config.get_reversed_status_maps() - local reversed_popup_map = config.get_reversed_popup_maps() - - local function hint_label(map_name, hint) - local keys = reversed_status_map[map_name] or reversed_popup_map[map_name] - if keys and #keys > 0 then - return string.format("[%s] %s", table.concat(keys, " "), hint) - else - return string.format("[] %s", hint) - end - end - - local hints = { - hint_label("Toggle", "toggle diff"), - hint_label("Stage", "stage"), - hint_label("Unstage", "unstage"), - hint_label("Discard", "discard"), - hint_label("CommitPopup", "commit"), - hint_label("HelpPopup", "help"), - } - - output:append("Hint: " .. table.concat(hints, " | ")) - output:append("") - end - - local new_locations = {} - local locations_lookup = Collection.new(M.locations):key_by("name") - - output:append( - string.format( - "Head: %s%s %s", - (git.repo.head.abbrev and git.repo.head.abbrev .. " ") or "", - git.repo.head.branch, - git.repo.head.commit_message or "(no commits)" - ) - ) - - table.insert(new_locations, { - name = "head_branch_header", - first = #output, - last = #output, - items = {}, - ignore_sign = true, - commit = { oid = git.repo.head.oid }, - }) - - if not git.branch.is_detached() then - if git.repo.upstream.ref then - output:append( - string.format( - "Merge: %s%s %s", - (git.repo.upstream.abbrev and git.repo.upstream.abbrev .. " ") or "", - git.repo.upstream.ref, - git.repo.upstream.commit_message or "(no commits)" - ) - ) - - table.insert(new_locations, { - name = "upstream_header", - first = #output, - last = #output, - items = {}, - ignore_sign = true, - commit = { oid = git.repo.upstream.oid }, - }) - end - - if git.branch.pushRemote_ref() and git.repo.pushRemote.abbrev then - output:append( - string.format( - "Push: %s%s %s", - (git.repo.pushRemote.abbrev and git.repo.pushRemote.abbrev .. " ") or "", - git.branch.pushRemote_ref(), - git.repo.pushRemote.commit_message or "(does not exist)" - ) - ) - - table.insert(new_locations, { - name = "push_branch_header", - first = #output, - last = #output, - items = {}, - ignore_sign = true, - ref = git.branch.pushRemote_ref(), - }) - end - end - - if git.repo.head.tag.name then - output:append(string.format("Tag: %s (%s)", git.repo.head.tag.name, git.repo.head.tag.distance)) - table.insert(new_locations, { - name = "tag_header", - first = #output, - last = #output, - items = {}, - ignore_sign = true, - commit = { oid = git.rev_parse.oid(git.repo.head.tag.name) }, - }) - end - - output:append("") - - local function render_section(header, key, data) - local section_config = config.values.sections[key] - if section_config.hidden then - return - end - - data = data or git.repo[key] - if #data.items == 0 then - return - end - - if data.current then - output:append(string.format("%s (%d/%d)", header, data.current, #data.items)) - else - output:append(string.format("%s (%d)", header, #data.items)) - end - - local location = locations_lookup[key] - or { - name = key, - folded = section_config.folded, - items = {}, - } - location.first = #output - - if not location.folded then - local items_lookup = Collection.new(location.items):key_by("name") - location.items = {} - - for _, f in ipairs(data.items) do - local label = util.pad_right(format_mode(f.mode), max_len) - if label and vim.o.columns < 120 then - label = vim.trim(label) - end - - if f.mode and f.original_name then - output:append(string.format("%s %s -> %s", label, f.original_name, f.name)) - elseif f.mode then - output:append(string.format("%s %s", label, f.name)) - else - output:append(f.name) - end - - if f.done then - M.status_buffer:buffered_add_line_highlight(#output - 1, "NeogitRebaseDone", { priority = 210 }) - end - - local file = items_lookup[f.name] or { folded = true } - file.first = #output - - if not file.folded and f.has_diff then - local hunks_lookup = Collection.new(file.hunks or {}):key_by("hash") - - local hunks = {} - for _, h in ipairs(f.diff.hunks) do - local current_hunk = hunks_lookup[h.hash] or { folded = false } - - output:append(f.diff.lines[h.diff_from]) - current_hunk.first = #output - - if not current_hunk.folded then - for i = h.diff_from + 1, h.diff_to do - output:append(f.diff.lines[i]) - end - end - - current_hunk.last = #output - table.insert(hunks, setmetatable(current_hunk, { __index = h })) - end - - file.hunks = hunks - elseif f.has_diff then - file.hunks = file.hunks or {} - end - - file.last = #output - table.insert(location.items, setmetatable(file, { __index = f })) - end - end - - location.last = #output - - if not location.folded then - output:append("") - end - - table.insert(new_locations, location) - end - - if git.repo.rebase.head then - render_section("Rebasing: " .. git.repo.rebase.head, "rebase") - elseif git.repo.sequencer.head == "REVERT_HEAD" then - render_section("Reverting", "sequencer") - elseif git.repo.sequencer.head == "CHERRY_PICK_HEAD" then - render_section("Picking", "sequencer") - end - - render_section("Untracked files", "untracked") - render_section("Unstaged changes", "unstaged") - render_section("Staged changes", "staged") - render_section("Stashes", "stashes") - - local pushRemote = git.branch.pushRemote_ref() - local upstream = git.branch.upstream() - - if pushRemote and upstream ~= pushRemote then - render_section( - string.format("Unpulled from %s", pushRemote), - "unpulled_pushRemote", - git.repo.pushRemote.unpulled - ) - render_section( - string.format("Unpushed to %s", pushRemote), - "unmerged_pushRemote", - git.repo.pushRemote.unmerged - ) - end - - if upstream then - render_section( - string.format("Unpulled from %s", upstream), - "unpulled_upstream", - git.repo.upstream.unpulled - ) - render_section( - string.format("Unmerged into %s", upstream), - "unmerged_upstream", - git.repo.upstream.unmerged - ) - end - - render_section("Recent commits", "recent") - - M.status_buffer:replace_content_with(output) - M.status_buffer:flush_buffers() - M.locations = new_locations -end - ---- Find the smallest section the cursor is contained within. --- --- The first 3 values are tables in the shape of {number, string}, where the number is --- the relative offset of the found item and the string is it's identifier. --- The remaining 2 numbers are the first and last line of the found section. ----@param linenr number|nil ----@return table, table, table, number, number -local function save_cursor_location(linenr) - local line = linenr or vim.api.nvim_win_get_cursor(0)[1] - local section_loc, file_loc, hunk_loc, first, last - - for li, loc in ipairs(M.locations) do - if line == loc.first then - section_loc = { li, loc.name } - first, last = loc.first, loc.last - - break - elseif line >= loc.first and line <= loc.last then - section_loc = { li, loc.name } - - for fi, file in ipairs(loc.items) do - if line == file.first then - file_loc = { fi, file.name } - first, last = file.first, file.last - - break - elseif line >= file.first and line <= file.last then - file_loc = { fi, file.name } - - for hi, hunk in ipairs(file.hunks) do - if line >= hunk.first and line <= hunk.last then - hunk_loc = { hi, hunk.hash } - first, last = hunk.first, hunk.last - - break - end - end - - break - end - end - - break - end - end - - return section_loc, file_loc, hunk_loc, first, last -end - -local function restore_cursor_location(section_loc, file_loc, hunk_loc) - if #M.locations == 0 then - return vim.api.nvim_win_set_cursor(0, { 1, 0 }) - end - - if not section_loc then - -- Skip the headers and put the cursor on the first foldable region - local idx = 1 - for i, location in ipairs(M.locations) do - if not location.ignore_sign then - idx = i - break - end - end - section_loc = { idx, "" } - end - - local section = Collection.new(M.locations):find(function(s) - return s.name == section_loc[2] - end) - - if not section then - file_loc, hunk_loc = nil, nil - section = M.locations[section_loc[1]] or M.locations[#M.locations] - end - - if not file_loc or not section.items or #section.items == 0 then - return vim.api.nvim_win_set_cursor(0, { section.first, 0 }) - end - - local file = Collection.new(section.items):find(function(f) - return f.name == file_loc[2] - end) - - if not file then - hunk_loc = nil - file = section.items[file_loc[1]] or section.items[#section.items] - end - - if not hunk_loc or not file.hunks or #file.hunks == 0 then - return vim.api.nvim_win_set_cursor(0, { file.first, 0 }) - end - - local hunk = Collection.new(file.hunks):find(function(h) - return h.hash == hunk_loc[2] - end) or file.hunks[hunk_loc[1]] or file.hunks[#file.hunks] - - return vim.api.nvim_win_set_cursor(0, { hunk.first, 0 }) -end - -local function refresh_status_buffer() - if M.status_buffer == nil then - return - end - - M.status_buffer:unlock() - - logger.debug("[STATUS BUFFER]: Redrawing") - - draw_buffer() - draw_signs() - - logger.debug("[STATUS BUFFER]: Finished Redrawing") - - M.status_buffer:lock() - - vim.cmd("redraw") -end - -local refresh_lock = a.control.Semaphore.new(1) - -function M.is_refresh_locked() - return refresh_lock.permits == 0 -end - -local function get_refresh_lock(reason) - local permit = refresh_lock:acquire() - logger.debug(("[STATUS BUFFER]: Acquired refresh lock:"):format(reason or "unknown")) - - vim.defer_fn(function() - if M.is_refresh_locked() then - permit:forget() - logger.debug( - ("[STATUS BUFFER]: Refresh lock for %s expired after 10 seconds"):format(reason or "unknown") - ) - end - end, 10000) - - return permit -end - -local function refresh(partial, reason) - local permit = get_refresh_lock(reason) - local callback = function() - local s, f, h = save_cursor_location() - refresh_status_buffer() - - if M.status_buffer ~= nil and M.status_buffer:is_focused() then - pcall(restore_cursor_location, s, f, h) - end - - vim.api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) - - permit:forget() - logger.info("[STATUS BUFFER]: Refresh lock is now free") - end - - git.repo:refresh { source = reason, callback = callback, partial = partial } -end - -local dispatch_refresh = a.void(function(partial, reason) - reason = reason or "unknown" - if M.is_refresh_locked() then - logger.debug("[STATUS] Refresh lock is active. Skipping refresh from " .. reason) - else - refresh(partial, reason) - end -end) - -local refresh_manually = a.void(function(fname) - if not fname or fname == "" then - return - end - - local path = fs.relpath_from_repository(fname) - if not path then - return - end - if refresh_lock.permits > 0 then - refresh({ update_diffs = { "*:" .. path } }, "manually") - end -end) - ---- Compatibility endpoint to refresh data from an autocommand. --- `fname` should be `` in this case. This function will take care of --- resolving the file name to the path relative to the repository root and --- refresh that file's cache data. -local function refresh_viml_compat(fname) - logger.info("[STATUS BUFFER]: refresh_viml_compat") - if not config.values.auto_refresh then - return - end - if #vim.fs.find(".git/", { upward = true }) == 0 then -- not a git repository - return - end - - refresh_manually(fname) -end - -local function current_line_is_hunk() - local _, _, h = save_cursor_location() - return h ~= nil -end - -local function toggle() - local selection = M.get_selection() - if selection.section == nil then - return - end - - local item = selection.item - - local hunks = item and M.get_item_hunks(item, selection.first_line, selection.last_line, false) - if item and hunks and #hunks > 0 then - for _, hunk in ipairs(hunks) do - hunk.hunk.folded = not hunk.hunk.folded - end - - vim.api.nvim_win_set_cursor(0, { hunks[1].first, 0 }) - elseif item then - item.folded = not item.folded - elseif selection.section ~= nil then - selection.section.folded = not selection.section.folded - end - - refresh_status_buffer() -end - -local reset = function() - git.repo:reset() - M.locations = {} - if not config.values.auto_refresh then - return - end - refresh(nil, "reset") -end - -local dispatch_reset = a.void(reset) - -local closing = false -local function close(skip_close) - if closing then - return - end - closing = true - - if skip_close == nil then - skip_close = false - end - - M.cursor_location = { save_cursor_location() } - - if not skip_close then - M.status_buffer:close() - end - - if M.watcher then - M.watcher:stop() - end - notification.delete_all() - M.status_buffer = nil - vim.o.autochdir = M.prev_autochdir - if M.old_cwd then - vim.cmd.lcd(M.old_cwd) - end - - closing = false -end - ----@class Selection ----@field sections SectionSelection[] ----@field first_line number ----@field last_line number ----Current items under the cursor ----@field section Section|nil ----@field item StatusItem|nil ----@field commit CommitLogEntry|nil ---- ----@field commits CommitLogEntry[] ----@field items StatusItem[] -local Selection = {} -Selection.__index = Selection - ----@class SectionSelection: Section ----@field section Section ----@field name string ----@field items StatusItem[] - ----@return string[], string[] - -function Selection:format() - local lines = {} - - table.insert(lines, string.format("%d,%d:", self.first_line, self.last_line)) - - for _, sec in ipairs(self.sections) do - table.insert(lines, string.format("%s:", sec.name)) - for _, item in ipairs(sec.items) do - table.insert(lines, string.format(" %s%s:", item == self.item and "*" or "", item.name)) - for _, hunk in ipairs(M.get_item_hunks(item, self.first_line, self.last_line, true)) do - table.insert(lines, string.format(" %d,%d:", hunk.from, hunk.to)) - for _, line in ipairs(hunk.lines) do - table.insert(lines, string.format(" %s", line)) - end - end - end - end - - return table.concat(lines, "\n") -end - ----@class SelectedHunk: Hunk ----@field from number start offset from the first line of the hunk ----@field to number end offset from the first line of the hunk ----@field lines string[] - ----@param item StatusItem ----@param first_line number ----@param last_line number ----@param partial boolean ----@return SelectedHunk[] -function M.get_item_hunks(item, first_line, last_line, partial) - local hunks = {} - - if not item.folded and item.hunks then - for _, h in ipairs(item.hunks) do - if h.first <= last_line and h.last >= first_line then - local from, to - - if partial then - local cursor_offset = first_line - h.first - local length = last_line - first_line - - from = h.diff_from + cursor_offset - to = from + length - else - from = h.diff_from + 1 - to = h.diff_to - end - - local hunk_lines = {} - for i = from, to do - table.insert(hunk_lines, item.diff.lines[i]) - end - - local o = { - from = from, - to = to, - __index = h, - hunk = h, - lines = hunk_lines, - } - - setmetatable(o, o) - - table.insert(hunks, o) - end - end - end - - return hunks -end - ----@param selection Selection -function M.selection_hunks(selection) - local res = {} - for _, item in ipairs(selection.items) do - local lines = {} - local hunks = {} - - for _, h in ipairs(selection.item.hunks) do - if h.first <= selection.last_line and h.last >= selection.first_line then - table.insert(hunks, h) - for i = h.diff_from, h.diff_to do - table.insert(lines, item.diff.lines[i]) - end - break - end - end - - table.insert(res, { - item = item, - hunks = hunks, - lines = lines, - }) - end - - return res -end - ----Returns the selected items grouped by spanned sections ----@return Selection -function M.get_selection() - local visual_pos = vim.fn.getpos("v")[2] - local cursor_pos = vim.fn.getpos(".")[2] - - local first_line = math.min(visual_pos, cursor_pos) - local last_line = math.max(visual_pos, cursor_pos) - - local res = { - sections = {}, - first_line = first_line, - last_line = last_line, - item = nil, - commit = nil, - commits = {}, - items = {}, - } - - for _, section in ipairs(M.locations) do - local items = {} - - if section.first > last_line then - break - end - - if section.last >= first_line then - if section.first <= first_line and section.last >= last_line then - res.section = section - end - - local entire_section = section.first == first_line and first_line == last_line - - for _, item in pairs(section.items) do - if entire_section or item.first <= last_line and item.last >= first_line then - if not res.item and item.first <= first_line and item.last >= last_line then - res.item = item - - res.commit = item.commit - end - - if item.commit then - table.insert(res.commits, item.commit) - end - - table.insert(res.items, item) - table.insert(items, item) - end - end - - local section = { - section = section, - items = items, - __index = section, - } - - setmetatable(section, section) - table.insert(res.sections, section) - end - end - - return setmetatable(res, Selection) -end - -local stage = operation("stage", function() - local selection = M.get_selection() - local mode = vim.api.nvim_get_mode() - - local files = {} - - for _, section in ipairs(selection.sections) do - for _, item in ipairs(section.items) do - local hunks = M.get_item_hunks(item, selection.first_line, selection.last_line, mode.mode == "V") - - if section.name == "unstaged" then - if #hunks > 0 then - for _, hunk in ipairs(hunks) do - -- Apply works for both tracked and untracked - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to) - git.index.apply(patch, { cached = true }) - end - else - git.status.stage { item.name } - end - elseif section.name == "untracked" then - if #hunks > 0 then - for _, hunk in ipairs(hunks) do - -- Apply works for both tracked and untracked - git.index.apply(git.index.generate_patch(item, hunk, hunk.from, hunk.to), { cached = true }) - end - else - table.insert(files, item.name) - end - else - logger.fmt_debug("[STATUS]: Not staging item in %s", section.name) - end - end - end - - --- Add all collected files - if #files > 0 then - git.index.add(files) - end - - refresh({ - update_diffs = vim.tbl_map(function(v) - return "*:" .. v.name - end, selection.items), - }, "stage_finish") -end) - -local unstage = operation("unstage", function() - local selection = M.get_selection() - local mode = vim.api.nvim_get_mode() - - local files = {} - - for _, section in ipairs(selection.sections) do - for _, item in ipairs(section.items) do - if section.name == "staged" then - local hunks = M.get_item_hunks(item, selection.first_line, selection.last_line, mode.mode == "V") - - if #hunks > 0 then - for _, hunk in ipairs(hunks) do - logger.fmt_debug( - "[STATUS]: Unstaging hunk %d %d of %d %d, index_from %d", - hunk.from, - hunk.to, - hunk.diff_from, - hunk.diff_to, - hunk.index_from - ) - -- Apply works for both tracked and untracked - git.index.apply( - git.index.generate_patch(item, hunk, hunk.from, hunk.to, true), - { cached = true, reverse = true } - ) - end - else - table.insert(files, item.name) - end - end - end - end - - if #files > 0 then - git.status.unstage(files) - end - - refresh({ - update_diffs = vim.tbl_map(function(v) - return "*:" .. v.name - end, selection.items), - }, "unstage_finish") -end) - -local function discard_message(files, hunk_count, mode) - if vim.tbl_contains({ "V", "v", "" }, mode) then - return "Discard selection?" - elseif hunk_count > 0 then - if hunk_count == 1 then - return "Discard hunk?" - else - return string.format("Discard %d hunks?", hunk_count) - end - elseif #files > 1 then - return string.format("Discard %d files?", #files) - else - return string.format("Discard %q?", files[1]) - end -end - -local discard = operation("discard", function() - local selection = M.get_selection() - local mode = vim.api.nvim_get_mode() - - git.index.update() - - local t = {} - - local hunk_count = 0 - local file_count = 0 - local files = {} - - for _, section in ipairs(selection.sections) do - local section_name = section.name - - file_count = file_count + #section.items - for _, item in ipairs(section.items) do - table.insert(files, item.name) - local hunks = M.get_item_hunks(item, selection.first_line, selection.last_line, mode.mode == "V") - - if #hunks > 0 then - logger.fmt_debug("Discarding %d hunks from %q", #hunks, item.name) - - hunk_count = hunk_count + #hunks - - for _, hunk in ipairs(hunks) do - table.insert(t, function() - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) - logger.fmt_debug("Patch: %s", patch) - - if section_name == "staged" then - --- Apply both to the worktree and the staging area - git.index.apply(patch, { index = true, reverse = true }) - else - git.index.apply(patch, { reverse = true }) - end - end) - end - else - logger.fmt_debug("Discarding in section %s %s", section_name, item.name) - table.insert(t, function() - if section_name == "untracked" then - a.util.scheduler() - vim.fn.delete(vim.fn.fnameescape(item.absolute_path)) - elseif section_name == "unstaged" then - git.index.checkout { item.name } - elseif section_name == "staged" then - git.index.reset { item.name } - git.index.checkout { item.name } - end - end) - end - end - end - - if not input.get_permission(discard_message(files, hunk_count, mode.mode)) then - return - end - - for i, v in ipairs(t) do - logger.fmt_debug("Discard job %d", i) - v() - end - - refresh(nil, "discard") - - a.util.scheduler() - vim.cmd("checktime") -end) - -local set_folds = function(to) - Collection.new(M.locations):each(function(l) - l.folded = to[1] - Collection.new(l.items):each(function(f) - f.folded = to[2] - if f.hunks then - Collection.new(f.hunks):each(function(h) - h.folded = to[3] - end) - end - end) - end) - refresh(nil, "set_folds") -end - ---- Handles the GoToFile action on sections that contain a hunk ----@param item File ----@see section_has_hunks -local function handle_section_item(item) - if not item.absolute_path then - notification.error("Cannot open file. No path found.") - return - end - - local row, col - local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) - local hunk = M.get_item_hunks(item, cursor_row, cursor_row, false)[1] - if hunk then - local line_offset = cursor_row - hunk.first - row = hunk.disk_from + line_offset - 1 - for i = 1, line_offset do - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - -- adjust for diff sign column - col = math.max(0, cursor_col - 1) - end - - notification.delete_all() - M.status_buffer:close() - - if not vim.o.hidden and vim.bo.buftype == "" and not vim.bo.readonly and vim.fn.bufname() ~= "" then - vim.cmd("update") - end - - local path = vim.fn.fnameescape(vim.fn.fnamemodify(item.absolute_path, ":~:.")) - vim.cmd(string.format("edit %s", path)) - - if row and col then - vim.api.nvim_win_set_cursor(0, { row, col }) - end -end - ---- Returns the section header ref the user selected ----@param section Section ----@return string|nil -local function get_header_ref(section) - if section.name == "head_branch_header" then - return git.repo.head.branch - end - if section.name == "upstream_header" and git.repo.upstream.branch then - return git.repo.upstream.branch - end - if section.name == "tag_header" and git.repo.head.tag.name then - return git.repo.head.tag.name - end - if section.name == "push_branch_header" and git.repo.pushRemote.abbrev then - return git.repo.pushRemote.abbrev - end - return nil -end - ---- Determines if a given section is a status header section ----@param section Section ----@return boolean -local function is_section_header(section) - return vim.tbl_contains( - { "head_branch_header", "upstream_header", "tag_header", "push_branch_header" }, - section.name - ) -end - ---- Determines if a given section contains hunks/diffs ----@param section Section ----@return boolean -local function section_has_hunks(section) - return vim.tbl_contains({ "unstaged", "staged", "untracked" }, section.name) -end - ---- Determines if a given section has a list of commits under it ----@param section Section ----@return boolean -local function section_has_commits(section) - return vim.tbl_contains({ - "unmerged_pushRemote", - "unpulled_pushRemote", - "unmerged_upstream", - "unpulled_upstream", - "recent", - "stashes", - }, section.name) -end - ---- These needs to be a function to avoid a circular dependency ---- between this module and the popup modules -local cmd_func_map = function() - local mappings = { - ["Close"] = M.close, - ["InitRepo"] = a.void(git.init.init_repo), - ["Depth1"] = a.void(function() - set_folds { true, true, false } - end), - ["Depth2"] = a.void(function() - set_folds { false, true, false } - end), - ["Depth3"] = a.void(function() - set_folds { false, false, true } - end), - ["Depth4"] = a.void(function() - set_folds { false, false, false } - end), - ["Toggle"] = toggle, - ["Discard"] = { "nv", a.void(discard) }, - ["Stage"] = { "nv", a.void(stage) }, - ["StageUnstaged"] = a.void(function() - git.status.stage_modified() - refresh({ update_diffs = true }, "StageUnstaged") - end), - ["StageAll"] = a.void(function() - git.status.stage_all() - refresh { update_diffs = true } - end), - ["Unstage"] = { "nv", a.void(unstage) }, - ["UnstageStaged"] = a.void(function() - git.status.unstage_all() - refresh({ update_diffs = true }, "UnstageStaged") - end), - ["CommandHistory"] = function() - GitCommandHistory:new():show() - end, - ["ShowRefs"] = a.void(function() - RefsViewBuffer.new(git.refs.list_parsed()):open() - end), - ["Console"] = function() - local process = require("neogit.process") - process.show_console() - end, - ["TabOpen"] = function() - local _, item = get_current_section_item() - if item then - vim.cmd("tabedit " .. item.name) - end - end, - ["VSplitOpen"] = function() - local _, item = get_current_section_item() - if item then - vim.cmd("vsplit " .. item.name) - end - end, - ["SplitOpen"] = function() - local _, item = get_current_section_item() - if item then - vim.cmd("split " .. item.name) - end - end, - ["YankSelected"] = function() - local yank - - local selection = require("neogit.status").get_selection() - if selection.item then - yank = selection.item.oid or selection.item.name - elseif selection.commit then - yank = selection.commit.oid - elseif selection.section and selection.section.ref then - yank = selection.section.ref - elseif selection.section and selection.section.commit then - yank = selection.section.commit.oid - end - - if yank then - if yank:match("^stash@{%d+}") then - yank = git.rev_parse.oid(yank:match("^(stash@{%d+})")) - end - - yank = string.format("'%s'", yank) - vim.cmd.let("@+=" .. yank) - vim.cmd.echo(yank) - else - vim.cmd("echo ''") - end - end, - ["GoToPreviousHunkHeader"] = function() - local section, item = get_current_section_item() - if not section then - return - end - - local selection = M.get_selection() - local on_hunk = item and current_line_is_hunk() - - if item and not on_hunk then - local _, prev_item = get_section_item_for_line(vim.fn.line(".") - 1) - if prev_item then - vim.api.nvim_win_set_cursor(0, { prev_item.hunks[#prev_item.hunks].first, 0 }) - end - elseif on_hunk then - local hunks = M.get_item_hunks(selection.item, 0, selection.first_line - 1, false) - local hunk = hunks[#hunks] - - if hunk then - vim.api.nvim_win_set_cursor(0, { hunk.first, 0 }) - vim.cmd("normal! zt") - else - local _, prev_item = get_section_item_for_line(vim.fn.line(".") - 2) - if prev_item then - vim.api.nvim_win_set_cursor(0, { prev_item.hunks[#prev_item.hunks].first, 0 }) - end - end - end - end, - ["GoToNextHunkHeader"] = function() - local section, item = get_current_section_item() - if not section then - return - end - - local on_hunk = item and current_line_is_hunk() - - if item and not on_hunk then - vim.api.nvim_win_set_cursor(0, { vim.fn.line(".") + 1, 0 }) - elseif on_hunk then - local selection = M.get_selection() - local hunks = - M.get_item_hunks(selection.item, selection.last_line + 1, selection.last_line + 1, false) - - local hunk = hunks[1] - - assert(hunk, "Hunk is nil") - assert(item, "Item is nil") - - if hunk.last == item.last then - local _, next_item = get_section_item_for_line(hunk.last + 1) - if next_item then - vim.api.nvim_win_set_cursor(0, { next_item.first + 1, 0 }) - end - else - vim.api.nvim_win_set_cursor(0, { hunk.last + 1, 0 }) - end - vim.cmd("normal! zt") - end - end, - ["GoToFile"] = a.void(function() - a.util.scheduler() - local section, item = get_current_section_item() - if not section then - return - end - if item then - if section_has_hunks(section) then - handle_section_item(item) - else - if section_has_commits(section) then - if M.commit_view and M.commit_view.is_open then - M.commit_view:close() - end - M.commit_view = CommitView.new(item.name:match("(.-):? ")) - M.commit_view:open() - end - end - else - if is_section_header(section) then - local ref = get_header_ref(section) - if not ref then - return - end - if M.commit_view and M.commit_view.is_open then - M.commit_view:close() - end - M.commit_view = CommitView.new(ref) - M.commit_view:open() - end - end - end), - - ["RefreshBuffer"] = function() - notification.info("Refreshing Status") - dispatch_refresh(nil, "manual") - end, - } - - local popups = require("neogit.popups") - --- Load the popups from the centralized popup file - for _, v in ipairs(popups.mappings_table()) do - --- { name, display_name, mapping } - if mappings[v[1]] then - error("Neogit: Mapping '" .. v[1] .. "' is already in use!") - end - - mappings[v[1]] = v[3] - end - - return mappings -end - --- Sets decoration provider for buffer ----@param buffer Buffer ----@return nil -local function set_decoration_provider(buffer) - buffer:create_namespace("View") - buffer:create_namespace("ViewDecor") - buffer:create_namespace("ViewContext") - - local function on_start() - return buffer:exists() and buffer:is_focused() - end - - local function on_win(_, _, _, top, bottom) - buffer:clear_namespace("ViewDecor") - buffer:clear_namespace("ViewContext") - - -- first and last lines of current context based on cursor position, if available - local _, _, _, first, last = save_cursor_location() - local cursor_line = vim.fn.line(".") - - for line = top, bottom do - local text = buffer:get_line(line)[1] - if text then - local highlight - local start = string.sub(text, 1, 1) - local _, _, hunk, _, _ = save_cursor_location(line) - - if start == head_start then - highlight = "NeogitHunkHeader" - elseif line == cursor_line then - highlight = "NeogitCursorLine" - elseif start == add_start then - highlight = "NeogitDiffAdd" - elseif start == del_start then - highlight = "NeogitDiffDelete" - elseif hunk then - highlight = "NeogitDiffContext" - end - - if highlight then - buffer:add_line_highlight(line - 1, highlight, { priority = 190, namespace = "ViewDecor" }) - end - - if - not config.values.disable_context_highlighting - and first - and last - and line >= first - and line <= last - and highlight ~= "NeogitCursorLine" - then - buffer:add_line_highlight( - line - 1, - (highlight or "NeogitDiffContext") .. "Highlight", - { priority = 200, namespace = "ViewContext" } - ) - end - end - end - end - - buffer:set_decorations("View", { on_start = on_start, on_win = on_win }) -end - ---- Creates a new status buffer -function M.create(kind, cwd) - kind = kind or config.values.kind - - if M.status_buffer then - logger.debug("Status buffer already exists. Focusing the existing one") - M.status_buffer:focus() - return - end - - logger.debug("[STATUS BUFFER]: Creating...") - - Buffer.create { - name = "NeogitStatus", - filetype = "NeogitStatus", - kind = kind, - disable_line_numbers = config.values.disable_line_numbers, - ---@param buffer Buffer - initialize = function(buffer, win) - logger.debug("[STATUS BUFFER]: Initializing...") - - M.status_buffer = buffer - - M.prev_autochdir = vim.o.autochdir - - -- Breaks when initializing a new repo in CWD - if cwd and win then - M.old_cwd = vim.fn.getcwd(win) - - vim.api.nvim_win_call(win, function() - vim.cmd.lcd(cwd) - end) - end - - vim.o.autochdir = false - - local mappings = buffer.mmanager.mappings - local func_map = cmd_func_map() - local keys = vim.tbl_extend("error", config.values.mappings.status, config.values.mappings.popup) - - for key, val in pairs(keys) do - if val and val ~= "" then - local func = func_map[val] - - if func ~= nil then - if type(func) == "function" then - mappings.n[key] = func - elseif type(func) == "table" then - for _, mode in pairs(vim.split(func[1], "")) do - mappings[mode][key] = func[2] - end - end - elseif type(val) == "function" then -- For user mappings that are either function values... - mappings.n[key] = val - elseif type(val) == "string" then -- ...or VIM command strings - mappings.n[key] = function() - vim.cmd(val) - end - end - end - end - - buffer:create_namespace("fold_markers") - set_decoration_provider(buffer) - - logger.debug("[STATUS BUFFER]: Dispatching initial render") - refresh(nil, "Buffer.create") - end, - after = function() - vim.cmd([[setlocal nowrap]]) - M.watcher = watcher.new(git.repo:git_path():absolute()) - - if M.cursor_location then - vim.wait(2000, function() - return not M.is_refresh_locked() - end) - - local ok, _ = pcall(restore_cursor_location, unpack(M.cursor_location)) - if ok then - M.cursor_location = nil - end - end - end, - } -end - -M.toggle = toggle -M.reset = reset -M.dispatch_reset = dispatch_reset -M.refresh = refresh -M.dispatch_refresh = dispatch_refresh -M.refresh_viml_compat = refresh_viml_compat -M.refresh_manually = refresh_manually -M.get_current_section_item = get_current_section_item -M.close = close - -function M.enable() - M.disabled = false -end - -function M.disable() - M.disabled = true -end - -function M.get_status() - return M.status -end - -function M.chdir(dir) - local destination = require("plenary.path").new(dir) - vim.wait(5000, function() - return destination:exists() - end) - - logger.debug("[STATUS] Changing Dir: " .. dir) - M.old_cwd = dir - vim.cmd.cd(dir) - vim.loop.chdir(dir) - reset() -end - -return M diff --git a/syntax/NeogitCommitView.vim b/syntax/NeogitCommitView.vim deleted file mode 100644 index bf962335b..000000000 --- a/syntax/NeogitCommitView.vim +++ /dev/null @@ -1,8 +0,0 @@ -if exists("b:current_syntax") - finish -endif - -syn match NeogitDiffAdd /.*/ contained -syn match NeogitDiffDelete /.*/ contained - -let b:current_syntax = 1 diff --git a/syntax/NeogitStatus.vim b/syntax/NeogitStatus.vim deleted file mode 100644 index d14279313..000000000 --- a/syntax/NeogitStatus.vim +++ /dev/null @@ -1,65 +0,0 @@ -if exists("b:current_syntax") - finish -endif - -" Support the rebase todo highlights -source $VIMRUNTIME/syntax/gitrebase.vim - -" Added for Reverting section when sequencer/todo doesn't exist -syn match gitrebasePick "\v^work=>" nextgroup=gitrebaseCommit skipwhite -syn match gitrebaseBreak "\v^onto=>" nextgroup=gitrebaseCommit skipwhite - -" Labels to the left of files -syn match NeogitChangeModified /\v^Modified( by us|)/ -syn match NeogitChangeAdded /\v^Added( by us|)/ -syn match NeogitChangeDeleted /\v^Deleted( by us|)/ -syn match NeogitChangeRenamed /\v^Renamed( by us|)/ -syn match NeogitChangeUpdated /\v^Updated( by us|)/ -syn match NeogitChangeCopied /\v^Copied( by us|)/ -syn match NeogitChangeBothModified /^Both Modified/ -syn match NeogitChangeNewFile /^New file/ - -syn match NeogitCommitMessage /.*/ contained -syn match NeogitBranch /\S\+/ contained nextgroup=NeogitObjectId,NeogitCommitMessage -syn match NeogitRemote /\S\+/ contained nextgroup=NeogitObjectId,NeogitCommitMessage -syn match NeogitDiffAdd /.*/ contained -syn match NeogitDiffDelete /.*/ contained -syn match NeogitUnmergedInto /Unmerged into/ contained -syn match NeogitUnpushedTo /Unpushed to/ contained -syn match NeogitUnpulledFrom /Unpulled from/ contained -syn match NeogitTagName /\S\+ / contained nextgroup=NeogitTagDistance -syn match NeogitTagDistance /[0-9]/ contained - -syn match NeogitStash /stash@{[0-9]*}\ze/ -syn match NeogitObjectId "\v<\x{7,}>" contains=@NoSpell - -let b:sections = [ - \ "Untracked files", - \ "Unstaged changes", - \ "Unmerged changes", - \ "Unpulled changes", - \ "Recent commits", - \ "Staged changes", - \ "Stashes", - \ "Rebasing", - \ "Reverting", - \ "Picking" - \ ] - -for section in b:sections - let id = join(split(section, " "), "") - execute 'syn match Neogit' . id . ' /^' . section . '/ contained' - execute 'syn region Neogit' . id . 'Region start=/^' . section . '\ze.*/ end=/./ contains=Neogit' . id -endfor - -syn region NeogitHeadRegion start=/^Head: \zs/ end=/$/ contains=NeogitObjectId,NeogitBranch -syn region NeogitPushRegion start=/^Push: \zs/ end=/$/ contains=NeogitObjectId,NeogitRemote -syn region NeogitMergeRegion start=/^Merge: \zs/ end=/$/ contains=NeogitObjectId,NeogitRemote -syn region NeogitUnmergedIntoRegion start=/^Unmerged into .*/ end=/$/ contains=NeogitRemote,NeogitUnmergedInto -syn region NeogitUnpushedToRegion start=/^Unpushed to .*/ end=/$/ contains=NeogitRemote,NeogitUnpushedTo -syn region NeogitUnpulledFromRegion start=/^Unpulled from .*/ end=/$/ contains=NeogitRemote,NeogitUnpulledFrom -syn region NeogitDiffAddRegion start=/^+.*$/ end=/$/ contains=NeogitDiffAdd -syn region NeogitDiffDeleteRegion start=/^-.*$/ end=/$/ contains=NeogitDiffDelete -syn region NeogitTagRegion start=/^Tag: \zs/ end=/$/ contains=NeogitTagName,NeogitTagDistance - -let b:current_syntax = 1 From 9cda5c72a20ea8791585cc320b708957e25487a4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 20 Feb 2024 23:23:33 +0100 Subject: [PATCH 155/443] Add some V mode mappings. still a WIP. --- lua/neogit/buffers/status/init.lua | 296 ++++++++++++++++++++++++++++- 1 file changed, 291 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index dc0c6a9be..54b67f5e5 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,6 +1,4 @@ -- TODO --- Save/restore cursor location --- Redrawing w/ lock, that doesn't discard opened/closed diffs, keeps cursor location -- on-close hook to teardown stuff -- -- Actions: @@ -35,6 +33,7 @@ local git = require("neogit.lib.git") local watcher = require("neogit.watcher") local a = require("plenary.async") local input = require("neogit.lib.input") +local util = require("neogit.lib.util") local logger = require("neogit.logger") -- TODO: Add logging local notification = require("neogit.lib.notification") -- TODO @@ -90,8 +89,8 @@ function M:open(kind) local mappings = config.get_reversed_status_maps() self.buffer = Buffer.create { - name = "NeogitStatusNew", - filetype = "NeogitStatusNew", + name = "NeogitStatus", + filetype = "NeogitStatus", context_highlight = true, kind = kind, disable_line_numbers = config.values.disable_line_numbers, @@ -103,7 +102,294 @@ function M:open(kind) end, }, mappings = { - v = {}, + v = { + [mappings["Discard"]] = a.void(function() + -- TODO: Discard Stash? + -- TODO: Discard Section? + local discardable = self.buffer.ui:get_hunks_and_filenames_in_selection() + + local total_files = #discardable.files.staged + + #discardable.files.unstaged + + #discardable.files.untracked + + local total_hunks = #discardable.hunks.staged + + #discardable.hunks.unstaged + + #discardable.hunks.untracked + + if total_files > 0 then + if input.get_permission(("Discard %s files?"):format(total_files)) then + if #discardable.files.staged > 0 then + local new_files = {} + local modified_files = {} + + for _, file in ipairs(discardable.files.staged) do + if file.mode == "A" then + table.insert(new_files, file.escaped_path) + else + table.insert(modified_files, file.escaped_path) + end + end + + if #modified_files > 0 then + git.index.reset(modified_files) + git.index.checkout(modified_files) + end + + if #new_files > 0 then + git.index.reset(new_files) + + a.util.scheduler() + + for _, file in ipairs(new_files) do + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(file.escaped_path) + end + end + end + + if #discardable.files.unstaged > 0 then + git.index.checkout(util.map(discardable.files.unstaged, function(f) + return f.escaped_path + end)) + end + + if #discardable.files.untracked > 0 then + a.util.scheduler() + + for _, file in ipairs(discardable.files.untracked) do + + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(file.escaped_path) + end + end + end + end + + if total_hunks > 0 then + if input.get_permission(("Discard %s hunks?"):format(total_hunks)) then + if #discardable.files.staged > 0 then + local new_files = {} + local modified_files = {} + + for _, file in ipairs(discardable.files.staged) do + if file.mode == "A" then + table.insert(new_files, file.escaped_path) + else + table.insert(modified_files, file.escaped_path) + end + end + + if #modified_files > 0 then + git.index.reset(modified_files) + git.index.checkout(modified_files) + end + + if #new_files > 0 then + git.index.reset(new_files) + + a.util.scheduler() + + for _, file in ipairs(new_files) do + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(file.escaped_path) + end + end + end + + if #discardable.files.unstaged > 0 then + git.index.checkout(util.map(discardable.files.unstaged, function(f) + return f.escaped_path + end)) + end + + if #discardable.files.untracked > 0 then + a.util.scheduler() + + for _, file in ipairs(discardable.files.untracked) do + + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(file.escaped_path) + end + end + end + end + + -- if discardable then + -- local section = self.buffer.ui:get_current_section() + -- local item = self.buffer.ui:get_item_under_cursor() + -- + -- if not section or not item then + -- return + -- end + -- + -- if discardable.hunk then + -- local hunk = discardable.hunk + -- local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + -- + -- if input.get_permission("Discard hunk?") then + -- if section.options.section == "staged" then + -- git.index.apply(patch, { index = true, reverse = true }) + -- else + -- git.index.apply(patch, { reverse = true }) + -- end + -- end + + self:refresh() + end), + [mappings["Stage"]] = a.void(function() + local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local section = self.buffer.ui:get_current_section() + + local cursor = self.buffer:cursor_line() + if stagable and section then + if section.options.section == "staged" then + return + end + + if stagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + local patch = + git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + + git.index.apply(patch, { cached = true }) + cursor = stagable.hunk.first + elseif stagable.filename then + if section.options.section == "unstaged" then + git.status.stage { stagable.filename } + elseif section.options.section == "untracked" then + git.index.add { stagable.filename } + end + end + elseif section then + if section.options.section == "untracked" then + git.status.stage_untracked() + elseif section.options.section == "unstaged" then + git.status.stage_modified() + end + end + + if cursor then + self.buffer:move_cursor(cursor) + end + + self:refresh() + end), + [mappings["Unstage"]] = a.void(function() + local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + + local section = self.buffer.ui:get_current_section() + if section and section.options.section ~= "staged" then + return + end + + -- TODO: Cursor Placement + if unstagable then + if unstagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + local patch = git.index.generate_patch( + item, + unstagable.hunk, + unstagable.hunk.from, + unstagable.hunk.to, + true + ) + + git.index.apply(patch, { cached = true, reverse = true }) + elseif unstagable.filename then + local section = self.buffer.ui:get_current_section() + + if section and section.options.section == "staged" then + git.status.unstage { unstagable.filename } + end + end + + self:refresh() + end + end), + [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) + p { commits = self.buffer.ui:get_commit_under_cursor() } + end), + [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end), + [popups.mapping_for("MergePopup")] = popups.open("merge", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end), + [popups.mapping_for("PushPopup")] = popups.open("push", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end), + [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end), + [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), + [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end), + [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end), + [popups.mapping_for("StashPopup")] = popups.open("stash", function(p) + -- TODO: Verify + local stash = self.buffer.ui:get_yankable_under_cursor() + p { name = stash and stash:match("^stash@{%d+}") } + end), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + -- TODO: Verify + local section = self.buffer.ui:get_current_section().options.section + local item = self.buffer.ui:get_yankable_under_cursor() + p { section = { name = section }, item = { name = item } } + end), + [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) + -- TODO use current absolute paths in selection + p { paths = {}, git_root = git.repo.git_root } + end), + [popups.mapping_for("RemotePopup")] = popups.open("remote"), + [popups.mapping_for("FetchPopup")] = popups.open("fetch"), + [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("HelpPopup")] = popups.open("help"), + [popups.mapping_for("LogPopup")] = popups.open("log"), + [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), + + }, n = { [mappings["Toggle"]] = function() local fold = self.buffer.ui:get_fold_under_cursor() From 528b5c04891f777aeda4dbf6532e6e2b939a57ef Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 20 Feb 2024 23:23:55 +0100 Subject: [PATCH 156/443] Add more helper functions to ui. --- lua/neogit/lib/ui/init.lua | 46 +++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 200eba126..b7cfb4d59 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -135,6 +135,46 @@ function Ui:get_fold_under_cursor() end) end +---@return table +function Ui:get_hunks_and_filenames_in_selection() + local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } + table.sort(range) + local start, stop = unpack(range) + + local items = { + hunks = { + untracked = {}, + unstaged = {}, + staged = {} + }, + files = { + untracked = {}, + unstaged = {}, + staged = {} + }, + } + + for i = start, stop do + local section = self:get_current_section(i) + + local component = self:_find_component_by_index(i, function(node) + return node.options.hunk or node.options.filename + end) + + if component and section then + section = section.options.section + + if component.options.hunk then + table.insert(items.hunks[section], component.options.hunk) + elseif component.options.filename then + table.insert(items.files[section], component.options.item) + end + end + end + + return items +end + ---@return string[] function Ui:get_commits_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } @@ -176,9 +216,9 @@ function Ui:get_yankable_under_cursor() end ---@return Component|nil -function Ui:get_current_section() - local cursor = vim.api.nvim_win_get_cursor(0) - local component = self:_find_component_by_index(cursor[1], function(node) +function Ui:get_current_section(line) + line = line or vim.api.nvim_win_get_cursor(0)[1] + local component = self:_find_component_by_index(line, function(node) return node.options.section end) From 445abb202011120b5c26da5fbaff444b7730a36a Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 20 Feb 2024 23:38:34 +0100 Subject: [PATCH 157/443] Add chdir stuff for worktrees --- lua/neogit/buffers/status/init.lua | 14 +++++++++++--- lua/neogit/popups/help/actions.lua | 2 +- lua/neogit/popups/worktree/actions.lua | 12 ++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 54b67f5e5..752642cfb 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -161,7 +161,6 @@ function M:open(kind) a.util.scheduler() for _, file in ipairs(discardable.files.untracked) do - local bufnr = fn.bufexists(file.name) if bufnr and bufnr > 0 then api.nvim_buf_delete(bufnr, { force = true }) @@ -218,7 +217,6 @@ function M:open(kind) a.util.scheduler() for _, file in ipairs(discardable.files.untracked) do - local bufnr = fn.bufexists(file.name) if bufnr and bufnr > 0 then api.nvim_buf_delete(bufnr, { force = true }) @@ -388,7 +386,6 @@ function M:open(kind) [popups.mapping_for("HelpPopup")] = popups.open("help"), [popups.mapping_for("LogPopup")] = popups.open("log"), [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), - }, n = { [mappings["Toggle"]] = function() @@ -917,6 +914,17 @@ function M:close() self.buffer = nil end +function M:chdir(dir) + local destination = require("plenary.path").new(dir) + vim.wait(5000, function() + return destination:exists() + end) + + logger.debug("[STATUS] Changing Dir: " .. dir) + vim.api.nvim_set_current_dir(dir) + self:dispatch_reset() +end + function M:focus() if self.buffer then self.buffer:focus() diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 3e00a5426..7ee93e61b 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -67,7 +67,7 @@ M.essential = function() "RefreshBuffer", "Refresh", function() - require("neogit.status").refresh(nil, "user_refresh") + require("neogit.buffers.status").instance:dispatch_refresh(nil, "user_refresh") end, }, { "GoToFile", "Go to file", NONE }, diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 7f64c872d..6698ef77b 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -3,7 +3,7 @@ local M = {} local git = require("neogit.lib.git") local input = require("neogit.lib.input") local util = require("neogit.lib.util") -local status = require("neogit.status") +local status = require("neogit.buffers.status") local notification = require("neogit.lib.notification") local operations = require("neogit.operations") @@ -49,7 +49,7 @@ M.checkout_worktree = operations("checkout_worktree", function() if git.worktree.add(selected, path) then notification.info("Added worktree") - status.chdir(path) + status.instance:chdir(path) end end) @@ -73,7 +73,7 @@ M.create_worktree = operations("create_worktree", function() if git.worktree.add(selected, path, { "-b", name }) then notification.info("Added worktree") - status.chdir(path) + status.instance:chdir(path) end end) @@ -103,7 +103,7 @@ M.move = operations("move_worktree", function() notification.info(("Moved worktree to %s"):format(path)) if change_dir then - status.chdir(path) + status.instance:chdir(path) end end end) @@ -128,7 +128,7 @@ M.delete = operations("delete_worktree", function() if input.get_permission("Remove worktree?") then if change_dir then - status.chdir(git.worktree.main().path) + status.instance:chdir(git.worktree.main().path) end -- This might produce some error messages that need to get suppressed @@ -160,7 +160,7 @@ M.visit = operations("visit_worktree", function() local selected = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "visit worktree" } if selected then - status.chdir(selected) + status.instance:chdir(selected) end end) From 10bf268468610ec854fc341a8afb7851b8527199 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 Feb 2024 21:36:26 +0100 Subject: [PATCH 158/443] Remove calls to old status buffer --- lua/neogit/buffers/commit_select_view/init.lua | 14 -------------- lua/neogit/buffers/log_view/init.lua | 12 ------------ lua/neogit/buffers/status/init.lua | 5 +++++ lua/neogit/integrations/diffview.lua | 6 +++--- lua/neogit/lib/git/init.lua | 10 +++------- lua/neogit/popups/help/actions.lua | 1 - tests/specs/neogit/lib/git/branch_spec.lua | 12 ++++++------ tests/specs/neogit/lib/git/log_spec.lua | 4 ++-- tests/specs/neogit/lib/git/status_spec.lua | 12 ++++++------ tests/specs/neogit/popups/branch_spec.lua | 10 +++++----- tests/specs/neogit/popups/log_spec.lua | 1 - tests/specs/neogit/popups/remote_spec.lua | 4 ++-- tests/specs/neogit/status_spec.lua | 10 +++++----- tests/util/git_harness.lua | 10 ++++++---- 14 files changed, 43 insertions(+), 68 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 256bc60d5..d8fa7fa40 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -31,9 +31,6 @@ end ---@param action fun(commit: CommitLogEntry[]) function M:open(action) - -- TODO: Pass this in as a param instead of reading state from object - local _, item = require("neogit.status").get_current_section_item() - ---@type fun(commit: CommitLogEntry[])|nil local action = action @@ -96,17 +93,6 @@ function M:open(action) end end, }, - after = function(buffer, win) - if win and item and item.commit then - local found = buffer.ui:find_component(function(c) - return c.options.oid == item.commit.oid - end) - - if found then - vim.api.nvim_win_set_cursor(win, { found.position.row_start, 0 }) - end - end - end, render = function() return ui.View(self.commits) end, diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 5e537ce6d..ac5056006 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -37,7 +37,6 @@ function M:close() end function M:open() - local _, item = require("neogit.status").get_current_section_item() self.buffer = Buffer.create { name = "NeogitLogView", filetype = "NeogitLogView", @@ -184,17 +183,6 @@ function M:open() end, }, }, - after = function(buffer, win) - if win and item and item.commit then - local found = buffer.ui:find_component(function(c) - return c.options.oid == item.commit.oid - end) - - if found then - vim.api.nvim_win_set_cursor(win, { found.position.row_start, 0 }) - end - end - end, render = function() return ui.View(self.commits, self.internal_args) end, diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 752642cfb..4ee1236a7 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -948,7 +948,12 @@ function M:refresh(partial, reason) source = "status", partial = partial, callback = function() + if not self.buffer then + return + end + -- TODO: move cursor restoration logic here? + self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index d3db8752c..ec148b449 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -10,7 +10,7 @@ local dv_utils = require("diffview.utils") local neogit = require("neogit") local repo = require("neogit.lib.git.repository") -local status = require("neogit.status") +local status = require("neogit.buffers.status") local a = require("plenary.async") local old_config @@ -18,7 +18,7 @@ local old_config M.diffview_mappings = { close = function() vim.cmd("tabclose") - neogit.dispatch_refresh(nil, "diffview_close") + neogit.dispatch_refresh() dv.setup(old_config) end, } @@ -101,7 +101,7 @@ local function get_local_diff_view(section_name, item_name, opts) } view:on_files_staged(a.void(function(_) - status.refresh({ update_diffs = true }, "on_files_staged") + status.instance:dispatch_refresh({ update_diffs = true }, "on_files_staged") view:update_files() end)) diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 647498004..0445c2157 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -15,7 +15,6 @@ M.create = function(directory, sync) end -- TODO Use path input --- TODO: Don't call the status buffer directly here M.init_repo = function() local directory = input.get_user_input("Create repository in", { completion = "dir" }) if not directory then @@ -29,10 +28,8 @@ M.init_repo = function() notification.error("Invalid Directory") return end - - local status = require("neogit.status") - status.cwd_changed = true - vim.cmd.lcd(directory) + local status = require("neogit.buffers.status") + status.instance:chdir(directory) if cli.is_inside_worktree() then if not input.get_permission(("Reinitialize existing repository %s?"):format(directory)) then @@ -41,8 +38,7 @@ M.init_repo = function() end M.create(directory) - - status.refresh(nil, "InitRepo") + status.instance:dispatch_refresh(nil, "InitRepo") end return M diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 7ee93e61b..5ea30ccef 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -37,7 +37,6 @@ M.popups = function() local items = vim.list_extend({ { - "CommandHistory", "History", function() diff --git a/tests/specs/neogit/lib/git/branch_spec.lua b/tests/specs/neogit/lib/git/branch_spec.lua index f0a79aa5a..1f9166e9f 100644 --- a/tests/specs/neogit/lib/git/branch_spec.lua +++ b/tests/specs/neogit/lib/git/branch_spec.lua @@ -1,5 +1,5 @@ local gb = require("neogit.lib.git.branch") -local status = require("neogit.status") +local neogit = require("neogit") local plenary_async = require("plenary.async") local git_harness = require("tests.util.git_harness") local neogit_util = require("neogit.lib.util") @@ -10,7 +10,7 @@ describe("lib.git.branch", function() describe("#exists", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) end) it("returns true when branch exists", function() @@ -25,7 +25,7 @@ describe("lib.git.branch", function() describe("#is_unmerged", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) end) it("returns true when feature branch has commits base branch doesn't", function() @@ -73,7 +73,7 @@ describe("lib.git.branch", function() describe("#delete", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) end) describe("when branch is unmerged", function() @@ -117,7 +117,7 @@ describe("lib.git.branch", function() describe("recent branches", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) end) it( @@ -173,7 +173,7 @@ describe("lib.git.branch", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) setup_local_git_branches() end) diff --git a/tests/specs/neogit/lib/git/log_spec.lua b/tests/specs/neogit/lib/git/log_spec.lua index 08892cf92..40de3975e 100644 --- a/tests/specs/neogit/lib/git/log_spec.lua +++ b/tests/specs/neogit/lib/git/log_spec.lua @@ -1,4 +1,4 @@ -local status = require("neogit.status") +local neogit = require("neogit") local plenary_async = require("plenary.async") local git_harness = require("tests.util.git_harness") local util = require("tests.util.util") @@ -9,7 +9,7 @@ local subject = require("neogit.lib.git.log") describe("lib.git.log", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) end) describe("#is_ancestor", function() diff --git a/tests/specs/neogit/lib/git/status_spec.lua b/tests/specs/neogit/lib/git/status_spec.lua index 9953caeac..0b155903d 100644 --- a/tests/specs/neogit/lib/git/status_spec.lua +++ b/tests/specs/neogit/lib/git/status_spec.lua @@ -1,4 +1,4 @@ -local status = require("neogit.status") +local neogit = require("neogit") local plenary_async = require("plenary.async") local git_harness = require("tests.util.git_harness") local util = require("tests.util.util") @@ -8,20 +8,20 @@ local subject = require("neogit.lib.git.status") describe("lib.git.status", function() before_each(function() git_harness.prepare_repository() - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) end) describe("#anything_staged", function() it("returns true when there are staged items", function() util.system("git add --all") - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) assert.True(subject.anything_staged()) end) it("returns false when there are no staged items", function() util.system("git reset") - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) assert.False(subject.anything_staged()) end) @@ -30,14 +30,14 @@ describe("lib.git.status", function() describe("#anything_unstaged", function() it("returns true when there are unstaged items", function() util.system("git reset") - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) assert.True(subject.anything_unstaged()) end) it("returns false when there are no unstaged items", function() util.system("git add --all") - plenary_async.util.block_on(status.reset) + plenary_async.util.block_on(neogit.reset) assert.False(subject.anything_unstaged()) end) diff --git a/tests/specs/neogit/popups/branch_spec.lua b/tests/specs/neogit/popups/branch_spec.lua index 89b3e23b3..475a96478 100644 --- a/tests/specs/neogit/popups/branch_spec.lua +++ b/tests/specs/neogit/popups/branch_spec.lua @@ -10,7 +10,7 @@ local get_git_rev = harness.get_git_rev local util = require("tests.util.util") local FuzzyFinderBuffer = require("tests.mocks.fuzzy_finder") -local status = require("neogit.status") +local neogit = require("neogit") local input = require("tests.mocks.input") local function act(normal_cmd) @@ -189,7 +189,7 @@ describe("branch popup", function() input.confirmed = true local remote = harness.prepare_repository() - async.util.block_on(status.reset) + async.util.block_on(neogit.reset) util.system("git remote add upstream " .. remote) util.system([[ git stash --include-untracked @@ -268,7 +268,7 @@ describe("branch popup", function() git add . git commit -m 'some feature' ]]) - async.util.block_on(status.reset) + async.util.block_on(neogit.reset) local input_branch = "spin-out-branch" input.values = { input_branch } @@ -300,7 +300,7 @@ describe("branch popup", function() touch wip.js git add . ]]) - async.util.block_on(status.reset) + async.util.block_on(neogit.reset) local input_branch = "spin-out-branch" input.values = { input_branch } @@ -332,7 +332,7 @@ describe("branch popup", function() git add . git commit -m 'some feature' ]]) - async.util.block_on(status.reset) + async.util.block_on(neogit.reset) local input_branch = "spin-off-branch" input.values = { input_branch } diff --git a/tests/specs/neogit/popups/log_spec.lua b/tests/specs/neogit/popups/log_spec.lua index 9aef51a71..81d6f94f2 100644 --- a/tests/specs/neogit/popups/log_spec.lua +++ b/tests/specs/neogit/popups/log_spec.lua @@ -5,7 +5,6 @@ local harness = require("tests.util.git_harness") local util = require("tests.util.util") local in_prepared_repo = harness.in_prepared_repo -local status = require("neogit.status") local state = require("neogit.lib.state") local input = require("tests.mocks.input") diff --git a/tests/specs/neogit/popups/remote_spec.lua b/tests/specs/neogit/popups/remote_spec.lua index 9c36972a0..c9fcac0fa 100644 --- a/tests/specs/neogit/popups/remote_spec.lua +++ b/tests/specs/neogit/popups/remote_spec.lua @@ -5,7 +5,7 @@ local operations = require("neogit.operations") local harness = require("tests.util.git_harness") local in_prepared_repo = harness.in_prepared_repo -local status = require("neogit.status") +local neogit = require("neogit") local input = require("tests.mocks.input") local lib = require("neogit.lib") @@ -20,7 +20,7 @@ describe("remote popup", function() in_prepared_repo(function() local remote_a = harness.prepare_repository() local remote_b = harness.prepare_repository() - async.util.block_on(status.reset) + async.util.block_on(neogit.reset) input.values = { "foo", remote_a } act("Ma") diff --git a/tests/specs/neogit/status_spec.lua b/tests/specs/neogit/status_spec.lua index 969aaed7f..b78c4ffef 100644 --- a/tests/specs/neogit/status_spec.lua +++ b/tests/specs/neogit/status_spec.lua @@ -1,6 +1,6 @@ local a = require("plenary.async") local eq = assert.are.same -local status = require("neogit.status") +local neogit = require("neogit") local operations = require("neogit.operations") local harness = require("tests.util.git_harness") local _ = require("tests.mocks.input") @@ -35,8 +35,8 @@ describe("status buffer", function() harness.exec { "git", "add", "testfile" } harness.exec { "git", "add", "renamed-testfile" } - a.util.block_on(status.reset) - a.util.block_on(status.refresh) + a.util.block_on(neogit.reset) + a.util.block_on(neogit.refresh) local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true) assert.True(vim.tbl_contains(lines, "Renamed testfile -> renamed-testfile")) @@ -49,8 +49,8 @@ describe("status buffer", function() "Handles non-english filenames correctly", in_prepared_repo(function() harness.exec { "touch", "你好.md" } - a.util.block_on(status.reset) - a.util.block_on(status.refresh) + a.util.block_on(neogit.reset) + a.util.block_on(neogit.refresh) find("你好%.md") act("s") diff --git a/tests/util/git_harness.lua b/tests/util/git_harness.lua index 4554b9f7d..e7da40314 100644 --- a/tests/util/git_harness.lua +++ b/tests/util/git_harness.lua @@ -1,4 +1,4 @@ -local status = require("neogit.status") +local neogit = require("neogit") local a = require("plenary.async") local M = {} local util = require("tests.util.util") @@ -57,10 +57,10 @@ function M.in_prepared_repo(cb) require("neogit").setup() vim.cmd("Neogit") - a.util.block_on(status.reset) + a.util.block_on(neogit.reset) vim.wait(1000, function() - return not status.is_refresh_locked() + return not neogit.status.instance:_is_refresh_locked() end, 100) a.util.block_on(function() @@ -69,7 +69,9 @@ function M.in_prepared_repo(cb) error(err) end - a.util.block_on(status.close) + a.util.block_on(function() + neogit.status.instance:close() + end) end) end end From 11f821bad2d5162b410a18cccb259fec599bbefa Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 Feb 2024 21:37:16 +0100 Subject: [PATCH 159/443] provide a defaule empty environment to prevent null issues downstream --- lua/neogit/lib/popup/builder.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 1c31501da..48f3ec495 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -92,7 +92,7 @@ function M:name(x) end function M:env(x) - self.state.env = x + self.state.env = x or {} return self end From b68987c6465651907dd7db2973ec1f03d46246ed Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 Feb 2024 21:38:07 +0100 Subject: [PATCH 160/443] Clean up popups mappings. these are now only accessible via the api - all the buffers have their own mappings now --- lua/neogit/popups/init.lua | 125 ++++--------------------------------- 1 file changed, 11 insertions(+), 114 deletions(-) diff --git a/lua/neogit/popups/init.lua b/lua/neogit/popups/init.lua index 9e68e676d..8fd82a208 100644 --- a/lua/neogit/popups/init.lua +++ b/lua/neogit/popups/init.lua @@ -40,128 +40,25 @@ end --- Returns an array useful for creating mappings for the available popups ---@return table function M.mappings_table() - ---@param commit CommitLogEntry|nil - ---@return string|nil - local function commit_oid(commit) - return commit and commit.oid - end - - ---@param commits CommitLogEntry[] - ---@return string[] - local function map_commits(commits) - return vim.tbl_map(function(v) - return v.oid - end, commits) - end - - -- TODO: These can either be removed, or modified return { { "HelpPopup", "Help", M.open("help") }, - { - "DiffPopup", - "Diff", - M.open("diff", function(f) - local section, item = require("neogit.status").get_current_section_item() - - f { section = section, item = item } - end), - }, + { "DiffPopup", "Diff", M.open("diff") }, { "PullPopup", "Pull", M.open("pull") }, - { - "RebasePopup", - "Rebase", - M.open("rebase", function(f) - f { commit = commit_oid(require("neogit.status").get_selection().commit) } - end), - }, + { "RebasePopup", "Rebase", M.open("rebase") }, { "MergePopup", "Merge", M.open("merge") }, - { - "PushPopup", - "Push", - M.open("push", function(f) - f { commit = commit_oid(require("neogit.status").get_selection().commit) } - end), - }, - { - "CommitPopup", - "Commit", - M.open("commit", function(f) - f { commit = commit_oid(require("neogit.status").get_selection().commit) } - end), - }, - { - "IgnorePopup", - "Ignore", - { - "nv", - M.open("ignore", function(f) - f { - paths = util.filter_map(require("neogit.status").get_selection().items, function(v) - return v.absolute_path - end), - git_root = git.repo.git_root, - } - end), - }, - }, - { - "TagPopup", - "Tag", - M.open("tag", function(f) - f { commit = commit_oid(require("neogit.status").get_selection().commit) } - end), - }, + { "PushPopup", "Push", M.open("push") }, + { "CommitPopup", "Commit", M.open("commit") }, + { "IgnorePopup", "Ignore", M.open("ignore") }, + { "TagPopup", "Tag", M.open("tag") }, { "LogPopup", "Log", M.open("log") }, - { - "CherryPickPopup", - "Cherry Pick", - { - "nv", - M.open("cherry_pick", function(f) - f { commits = util.reverse(map_commits(require("neogit.status").get_selection().commits)) } - end), - }, - }, - { - "BranchPopup", - "Branch", - { - "nv", - M.open("branch", function(f) - f { commits = map_commits(require("neogit.status").get_selection().commits) } - end), - }, - }, + { "CherryPickPopup", "Cherry Pick", M.open("cherry_pick") }, + { "BranchPopup", "Branch", M.open("branch") }, { "FetchPopup", "Fetch", M.open("fetch") }, - { - "ResetPopup", - "Reset", - { - "nv", - M.open("reset", function(f) - f { commit = commit_oid(require("neogit.status").get_selection().commit) } - end), - }, - }, - { - "RevertPopup", - "Revert", - { - "nv", - M.open("revert", function(f) - f { commits = util.reverse(map_commits(require("neogit.status").get_selection().commits)) } - end), - }, - }, + { "ResetPopup", "Reset", M.open("reset") }, + { "RevertPopup", "Revert", M.open("revert") }, { "RemotePopup", "Remote", M.open("remote") }, { "WorktreePopup", "Worktree", M.open("worktree") }, - { - "StashPopup", - "Stash", - M.open("stash", function(f) - f { name = require("neogit.status").status_buffer:get_current_line()[1]:match("^(stash@{%d+})") } - end), - }, + { "StashPopup", "Stash", M.open("stash") }, } end From 4c299565e4afc5e781865d3628e519cdc7f0190c Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 Feb 2024 22:52:48 +0100 Subject: [PATCH 161/443] refactor: move cursor logic to status buffer class, since no other ui needs to refresh and maintain position using this logic --- lua/neogit/buffers/status/init.lua | 57 +++++++++++++++++++++++------- lua/neogit/lib/ui/init.lua | 42 ---------------------- lua/neogit/lib/ui/renderer.lua | 2 -- 3 files changed, 44 insertions(+), 57 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 4ee1236a7..d1162d3c1 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -610,11 +610,9 @@ function M:open(kind) git.init.init_repo() end, [mappings["Stage"]] = a.void(function() - -- TODO: Cursor Placement local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() local section = self.buffer.ui:get_current_section() - local cursor = self.buffer:cursor_line() if stagable and section then if section.options.section == "staged" then return @@ -626,7 +624,6 @@ function M:open(kind) git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) - cursor = stagable.hunk.first elseif stagable.filename then if section.options.section == "unstaged" then git.status.stage { stagable.filename } @@ -642,19 +639,13 @@ function M:open(kind) end end - if cursor then - self.buffer:move_cursor(cursor) - end - self:refresh() end), [mappings["StageAll"]] = a.void(function() - -- TODO: Cursor Placement git.status.stage_all() self:refresh() end), [mappings["StageUnstaged"]] = a.void(function() - -- TODO: Cursor Placement git.status.stage_modified() self:refresh() end), @@ -666,7 +657,6 @@ function M:open(kind) return end - -- TODO: Cursor Placement if unstagable then if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() @@ -875,8 +865,11 @@ function M:open(kind) p { section = { name = section }, item = { name = item } } end), [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) - -- TODO use current absolute paths in selection - p { paths = {}, git_root = git.repo.git_root } + local path = self.buffer.ui:get_hunk_or_filename_under_cursor() + p { + paths = { path and path.escaped_path }, + git_root = git.repo.git_root + } end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("FetchPopup")] = popups.open("fetch"), @@ -952,10 +945,48 @@ function M:refresh(partial, reason) return end - -- TODO: move cursor restoration logic here? + local cursor_line = self.buffer:cursor_line() + local cursor_context_start, cursor_goto + local context = self.buffer.ui:get_cursor_context() + if context then + if context.options.tag == "Hunk" then + if context.index == 1 then + if #context.parent.children > 1 then + cursor_context_start = ({ context:row_range_abs() })[1] + else + cursor_context_start = ({ context:row_range_abs() })[1] - 1 + end + else + cursor_context_start = ({ context.parent.children[context.index - 1]:row_range_abs() })[1] + end + elseif context.options.tag == "File" then + if context.index == 1 then + if #context.parent.children > 1 then + -- id is scoped by section. Advance to next file. + cursor_goto = context.parent.children[2].options.id + else + -- Yankable lets us jump from one section to the other. Go to same file in new section. + cursor_goto = context.options.yankable + end + else + cursor_goto = context.parent.children[context.index - 1].options.id + end + else + -- TODO: profit? + end + end self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) + if cursor_context_start then + self.buffer:move_cursor(cursor_context_start) + elseif cursor_goto then + local line, _ = self.buffer.ui.node_index:find_by_id(cursor_goto):row_range_abs() + self.buffer:move_cursor(line) + else + self.buffer:move_cursor(cursor_line) + end + api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) permit:forget() diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index b7cfb4d59..33801cbf8 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -332,33 +332,6 @@ function Ui:render(...) -- Restoring cursor location for status buffer on update. Might need to move this, as it doesn't really make sense -- here. - local context = self:get_cursor_context() - if context then - if context.options.tag == "Hunk" then - if context.index == 1 then - if #context.parent.children > 1 then - self._cursor_context_start = ({ context:row_range_abs() })[1] - else - self._cursor_context_start = ({ context:row_range_abs() })[1] - 1 - end - else - self._cursor_context_start = ({ context.parent.children[context.index - 1]:row_range_abs() })[1] - end - elseif context.options.tag == "File" then - if context.index == 1 then - if #context.parent.children > 1 then - -- id is scoped by section. Advance to next file. - self._cursor_goto = context.parent.children[2].options.id - else - -- Yankable lets us jump from one section to the other. Go to same file in new section. - self._cursor_goto = context.options.yankable - end - else - self._cursor_goto = context.parent.children[context.index - 1].options.id - end - else - end - end end self.layout = root @@ -374,8 +347,6 @@ function Ui:update() local renderer = Renderer:new(self.layout, self.buf):render() self.node_index = renderer:node_index() - local cursor_line = self.buf:cursor_line() - self.buf:unlock() self.buf:clear() self.buf:clear_namespace("default") @@ -392,19 +363,6 @@ function Ui:update() self:_update_attributes(self.layout, self._old_node_attributes) self._old_node_attributes = nil end - - if self._cursor_context_start then - self.buf:move_cursor(self._cursor_context_start) - self._cursor_context_start = nil - elseif self._cursor_goto then - if self.node_index:find_by_id(self._cursor_goto) then - self.buf:move_cursor(self.node_index:find_by_id(self._cursor_goto):row_range_abs()) - end - - self._cursor_goto = nil - else - self.buf:move_cursor(cursor_line) - end end Ui.col = Component.new(function(children, options) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index b1683d493..a3f22dd42 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -223,8 +223,6 @@ function Renderer:_render_text(child) self.index:add(child) end --- TODO: This nested-row shit is lame. V - ---@param child Component ---@param i integer index of child in parent.children function Renderer:_render_child_in_row(child, i, col_start, col_end, highlights, text) From 5a7d91aca01359f2f01d1d01e363962b1c9fefd1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 Feb 2024 23:15:41 +0100 Subject: [PATCH 162/443] Add cursor placement back to update - this is needed for the load_diff callback. Fortunatly, the logic is very simple - keep the current line. --- lua/neogit/lib/ui/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 33801cbf8..57460ad4e 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -347,6 +347,7 @@ function Ui:update() local renderer = Renderer:new(self.layout, self.buf):render() self.node_index = renderer:node_index() + local cursor_line = self.buf:cursor_line() self.buf:unlock() self.buf:clear() self.buf:clear_namespace("default") @@ -358,6 +359,7 @@ function Ui:update() self.buf:set_line_highlights(renderer.buffer.line_highlight) self.buf:set_folds(renderer.buffer.fold) self.buf:lock() + self.buf:move_cursor(math.min(cursor_line, #renderer.buffer.line)) if self._old_node_attributes then self:_update_attributes(self.layout, self._old_node_attributes) From d2f985db2a556799978d1aa0008be03d2b6ab138 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 14:13:38 +0100 Subject: [PATCH 163/443] Finish goto next/prev hunk header --- lua/neogit/buffers/status/init.lua | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index d1162d3c1..e03c4bdfb 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -564,30 +564,36 @@ function M:open(kind) end end), [mappings["GoToNextHunkHeader"]] = function() - -- TODO: Doesn't go across file boundaries local c = self.buffer.ui:get_component_under_cursor(function(c) - return c.options.tag == "Diff" or c.options.tag == "Hunk" + return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "File" end) + local section = self.buffer.ui:get_current_section() + + if c and section then + local _, section_last = section:row_range_abs() + local next_location - if c then if c.options.tag == "Diff" then - self.buffer:move_cursor(fn.line(".") + 1) - else + next_location = fn.line(".") + 1 + elseif c.options.tag == "File" then + vim.cmd("normal! zo") + next_location = fn.line(".") + 1 + elseif c.options.tag == "Hunk" then local _, last = c:row_range_abs() - if last == fn.line("$") then - self.buffer:move_cursor(last) - else - self.buffer:move_cursor(last + 1) - end + next_location = last + 1 end + + if next_location < section_last then + self.buffer:move_cursor(next_location) + end + vim.cmd("normal! zt") end end, [mappings["GoToPreviousHunkHeader"]] = function() - -- TODO: Doesn't go across file boundaries local function previous_hunk_header(self, line) local c = self.buffer.ui:get_component_on_line(line, function(c) - return c.options.tag == "Diff" or c.options.tag == "Hunk" + return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "File" end) if c then From 5c9dc1ce16964ca071e09b78cac67493cd70e08d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 14:35:48 +0100 Subject: [PATCH 164/443] When toggling folds, move the cursor the the beginning of the fold. Additionally, do _not_ fold a section if you are on the last line. This blank line is for padding and shouldn't be considered "part" of the section. It's just padding. --- lua/neogit/buffers/status/init.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index e03c4bdfb..8ec06f787 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -391,11 +391,19 @@ function M:open(kind) [mappings["Toggle"]] = function() local fold = self.buffer.ui:get_fold_under_cursor() if fold then + -- Do not allow folding on the last (empty) line of a section. It should be considered "not part of either + -- section" from a UX perspective. + if fold.options.tag == "Section" and self.buffer:get_current_line()[1] == "" then + return + end + if fold.options.on_open then fold.options.on_open(fold, self.buffer.ui) else + local start, _ = fold:row_range_abs() local ok, _ = pcall(vim.cmd, "normal! za") if ok then + self.buffer:move_cursor(start) fold.options.folded = not fold.options.folded end end From 8a535528393ef9a806a384f44ae8cbdd12ce1d61 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 14:42:39 +0100 Subject: [PATCH 165/443] Remove note and use tag function instead of options --- lua/neogit/buffers/status/ui.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index aec13ba15..bc4e18023 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -1,6 +1,3 @@ --- TODO --- - Get fold markers to work --- local Ui = require("neogit.lib.ui") local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") @@ -235,13 +232,12 @@ local SectionItemFile = function(section) mode_text = util.pad_right(mode, 11) end - return col({ + return col.tag("File")({ row { text.highlight(("NeogitChange%s"):format(mode:gsub(" ", "")))(mode_text), text(item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name), }, }, { - tag = "File", foldable = true, folded = true, on_open = load_diff(item), From 9374f47ca8a9073909fdfd53097e0aa218ac5d6d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 14:43:45 +0100 Subject: [PATCH 166/443] Add git.index.checkout_unstaged for discarding the entire unstaged section of status buffer --- lua/neogit/lib/git/index.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index b7187309a..2448bba3e 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -1,6 +1,7 @@ local cli = require("neogit.lib.git.cli") local repo = require("neogit.lib.git.repository") local Path = require("plenary.path") +local util = require("neogit.lib.util") local M = {} @@ -113,6 +114,15 @@ function M.reset(files) return cli.reset.files(unpack(files)).call() end +function M.checkout_unstaged() + local repo = require("neogit.lib.git.repository") + local items = util.map(repo.unstaged.items, function(item) + return item.escaped_path + end) + + return cli.checkout.files(unpack(items)).call() +end + ---Creates a temp index from a revision and calls the provided function with the index path ---@param revision string Revision to create a temp index from ---@param fn fun(index: string): nil From 2e9cc7699d870c9c7391a0d34e54d41ffd3cf603 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 14:48:52 +0100 Subject: [PATCH 167/443] lint --- lua/neogit/lib/ui/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 57460ad4e..d0641032f 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -145,12 +145,12 @@ function Ui:get_hunks_and_filenames_in_selection() hunks = { untracked = {}, unstaged = {}, - staged = {} + staged = {}, }, files = { untracked = {}, unstaged = {}, - staged = {} + staged = {}, }, } From 9a71813f14a28b595efcbcaa5b9dbe66854b183f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 15:04:21 +0100 Subject: [PATCH 168/443] Add back lua/neogit/status.lua with a stub for close because some autocmd seems to still be calling it. Debug and remove later. --- lua/neogit/status.lua | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lua/neogit/status.lua diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua new file mode 100644 index 000000000..3110d071d --- /dev/null +++ b/lua/neogit/status.lua @@ -0,0 +1,7 @@ +local M = {} + +-- Some autocmd seems to be calling this still.. Maybe it's cached? No idea. +-- Without this, closing the neogit status buffer will create an error. +function M.close() end + +return M From 41eae47229a7d9017e43c8c1123227bb98732e1d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 15:05:36 +0100 Subject: [PATCH 169/443] When the kind is tab, just close the whole tab. This solves the issue of a persistent commit view when closing the log/status buffer, and persistent popups when closing the status buffer. --- lua/neogit/lib/buffer.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b376fe1a3..6e52bb6da 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -213,11 +213,17 @@ function Buffer:close(force) return end + if self.kind == "tab" then + vim.cmd("tabclose") + return + end + if api.nvim_buf_is_valid(self.handle) then local winnr = fn.bufwinnr(self.handle) if winnr ~= -1 then local winid = fn.win_getid(winnr) - if not pcall(api.nvim_win_close, winid, force) then + local ok, _ = pcall(api.nvim_win_close, winid, force) + if not ok then vim.cmd("b#") end else From d224b8e8345c78a602b01438916d7879d08a4246 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 15:16:35 +0100 Subject: [PATCH 170/443] Remove defunct notes --- lua/neogit/buffers/status/init.lua | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8ec06f787..8a7550329 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -296,7 +296,6 @@ function M:open(kind) return end - -- TODO: Cursor Placement if unstagable then if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() @@ -416,7 +415,6 @@ function M:open(kind) self:refresh() end), [mappings["Depth1"]] = function() - -- TODO: Need to work with stashes/recent local section = self.buffer.ui:get_current_section() if section then local start, _ = section:row_range_abs() @@ -427,7 +425,6 @@ function M:open(kind) end end, [mappings["Depth2"]] = function() - -- TODO: Need to work with stashes/recent local section = self.buffer.ui:get_current_section() local row = self.buffer.ui:get_component_under_cursor() @@ -447,7 +444,6 @@ function M:open(kind) end end, [mappings["Depth3"]] = function() - -- TODO: Need to work with stashes/recent, but same as depth2 local section = self.buffer.ui:get_current_section() local context = self.buffer.ui:get_cursor_context() @@ -469,7 +465,6 @@ function M:open(kind) end end, [mappings["Depth4"]] = function() - -- TODO: Need to work with stashes/recent, but same as depth2 local section = self.buffer.ui:get_current_section() local context = self.buffer.ui:get_cursor_context() @@ -695,7 +690,6 @@ function M:open(kind) end end), [mappings["UnstageStaged"]] = a.void(function() - -- TODO: Cursor Placement git.status.unstage_all() self:refresh() end), @@ -898,8 +892,6 @@ function M:open(kind) vim.o.autochdir = false end, render = function() - -- TODO: Figure out a way to remove the very last empty line from the last visible section. - -- it's created by the newline spacer between sections. return ui.Status(self.state, self.config) end, after = function() @@ -938,12 +930,6 @@ function M:focus() end end --- TODO: Allow passing some kind of cursor identifier into this, which can be injected into the renderer to --- find the location of a new named element to set the cursor to upon update. --- --- For example, when staging all items in untracked section via `s`, cursor should be updated to go to header of --- staged section --- function M:refresh(partial, reason) -- if self.frozen then -- return From 17382a370580fd99c51eb4922ef789d48695636b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 15:17:28 +0100 Subject: [PATCH 171/443] Remove cursor logic from stage --- lua/neogit/buffers/status/init.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8a7550329..f47ee13ea 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -254,7 +254,6 @@ function M:open(kind) local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() local section = self.buffer.ui:get_current_section() - local cursor = self.buffer:cursor_line() if stagable and section then if section.options.section == "staged" then return @@ -266,7 +265,6 @@ function M:open(kind) git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) - cursor = stagable.hunk.first elseif stagable.filename then if section.options.section == "unstaged" then git.status.stage { stagable.filename } @@ -282,10 +280,6 @@ function M:open(kind) end end - if cursor then - self.buffer:move_cursor(cursor) - end - self:refresh() end), [mappings["Unstage"]] = a.void(function() From aaa9d9c524e200c79dfb88f9b734c36d0d15bfe1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Mar 2024 21:11:22 +0100 Subject: [PATCH 172/443] Allow the help popup to accept a passed in environment, to forward onto each individual popup --- lua/neogit/buffers/status/init.lua | 37 ++++++++++++++++++- lua/neogit/popups/help/actions.lua | 59 ++++++++++++++++++++++++++++-- lua/neogit/popups/help/init.lua | 4 +- lua/neogit/popups/init.lua | 27 -------------- 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index f47ee13ea..b2fb8165c 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -873,10 +873,45 @@ function M:open(kind) git_root = git.repo.git_root } end), + [popups.mapping_for("HelpPopup")] = popups.open("help", function(p) + -- Since any other popup can be launched from help, build an ENV for any of them. + local path = self.buffer.ui:get_hunk_or_filename_under_cursor() + local section = self.buffer.ui:get_current_section().options.section + local item = self.buffer.ui:get_yankable_under_cursor() + local stash = self.buffer.ui:get_yankable_under_cursor() + local commit = self.buffer.ui:get_commit_under_cursor() + local commits = { commit } + + -- TODO: Consume this in help popup + p { + branch = { commits = commits }, + cherry_pick = { commits = commits }, + commit = { commit = commit }, + merge = { commit = commit }, + push = { commit = commit }, + rebase = { commit = commit }, + revert = { commits = commits }, + reset = { commit = commit }, + tag = { commit = commit }, + stash = { name = stash and stash:match("^stash@{%d+}"), }, + diff = { + section = { name = section }, + item = { name = item }, + }, + ignore = { + paths = { path and path.escaped_path }, + git_root = git.repo.git_root, + }, + remote = {}, + fetch = {}, + pull = {}, + log = {}, + worktree = {}, + } + end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("FetchPopup")] = popups.open("fetch"), [popups.mapping_for("PullPopup")] = popups.open("pull"), - [popups.mapping_for("HelpPopup")] = popups.open("help"), [popups.mapping_for("LogPopup")] = popups.open("log"), [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), }, diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 5ea30ccef..980fbdf70 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -32,10 +32,9 @@ local function present(commands) return presenter end -M.popups = function() +M.popups = function(env) local popups = require("neogit.popups") - - local items = vim.list_extend({ + local items = { { "CommandHistory", "History", @@ -44,7 +43,59 @@ M.popups = function() end, }, { "InitRepo", "Init", require("neogit.lib.git").init.init_repo }, - }, popups.mappings_table()) + -- { "HelpPopup", "Help", M.open("help") }, + { "DiffPopup", "Diff", popups.open("diff", function(p) + p(env.diff) + end) }, + { "PullPopup", "Pull", popups.open("pull", function(p) + p(env.pull) + end) }, + { "RebasePopup", "Rebase", popups.open("rebase", function(p) + p(env.rebase) + end) }, + { "MergePopup", "Merge", popups.open("merge", function(p) + p(env.merge) + end) }, + { "PushPopup", "Push", popups.open("push", function(p) + p(env.push) + end) }, + { "CommitPopup", "Commit", popups.open("commit", function(p) + p(env.commit) + end) }, + { "IgnorePopup", "Ignore", popups.open("ignore", function(p) + p(env.ignore) + end) }, + { "TagPopup", "Tag", popups.open("tag", function(p) + p(env.tag) + end) }, + { "LogPopup", "Log", popups.open("log", function(p) + p(env.log) + end) }, + { "CherryPickPopup", "Cherry Pick", popups.open("cherry_pick", function(p) + p(env.cherry_pick) + end) }, + { "BranchPopup", "Branch", popups.open("branch", function(p) + p(env.branch) + end) }, + { "FetchPopup", "Fetch", popups.open("fetch", function(p) + p(env.fetch) + end) }, + { "ResetPopup", "Reset", popups.open("reset", function(p) + p(env.reset) + end) }, + { "RevertPopup", "Revert", popups.open("revert", function(p) + p(env.revert) + end) }, + { "RemotePopup", "Remote", popups.open("remote", function(p) + p(env.remote) + end) }, + { "WorktreePopup", "Worktree", popups.open("worktree", function(p) + p(env.worktree) + end) }, + { "StashPopup", "Stash", popups.open("stash", function(p) + p(env.stash) + end) }, + } return present(items) end diff --git a/lua/neogit/popups/help/init.lua b/lua/neogit/popups/help/init.lua index 9c38a5f0b..2b55046d7 100644 --- a/lua/neogit/popups/help/init.lua +++ b/lua/neogit/popups/help/init.lua @@ -4,10 +4,10 @@ local actions = require("neogit.popups.help.actions") local M = {} -- TODO: Better alignment for labels, keys -function M.create() +function M.create(env) local p = popup.builder():name("NeogitHelpPopup"):group_heading("Commands") - local popups = actions.popups() + local popups = actions.popups(env) for i, cmd in ipairs(popups) do p = p:action(cmd.keys, cmd.name, cmd.fn) diff --git a/lua/neogit/popups/init.lua b/lua/neogit/popups/init.lua index 8fd82a208..ffb483eb4 100644 --- a/lua/neogit/popups/init.lua +++ b/lua/neogit/popups/init.lua @@ -1,6 +1,4 @@ local M = {} -local git = require("neogit.lib.git") -local util = require("neogit.lib.util") ---@param name string ---@param f nil|fun(create: fun(...)): any @@ -37,29 +35,4 @@ function M.mapping_for(name) end end ---- Returns an array useful for creating mappings for the available popups ----@return table -function M.mappings_table() - return { - { "HelpPopup", "Help", M.open("help") }, - { "DiffPopup", "Diff", M.open("diff") }, - { "PullPopup", "Pull", M.open("pull") }, - { "RebasePopup", "Rebase", M.open("rebase") }, - { "MergePopup", "Merge", M.open("merge") }, - { "PushPopup", "Push", M.open("push") }, - { "CommitPopup", "Commit", M.open("commit") }, - { "IgnorePopup", "Ignore", M.open("ignore") }, - { "TagPopup", "Tag", M.open("tag") }, - { "LogPopup", "Log", M.open("log") }, - { "CherryPickPopup", "Cherry Pick", M.open("cherry_pick") }, - { "BranchPopup", "Branch", M.open("branch") }, - { "FetchPopup", "Fetch", M.open("fetch") }, - { "ResetPopup", "Reset", M.open("reset") }, - { "RevertPopup", "Revert", M.open("revert") }, - { "RemotePopup", "Remote", M.open("remote") }, - { "WorktreePopup", "Worktree", M.open("worktree") }, - { "StashPopup", "Stash", M.open("stash") }, - } -end - return M From bd9338155230332b6758a70b5679c086b8c67977 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 7 Mar 2024 22:45:50 +0100 Subject: [PATCH 173/443] Fix visual alignment in commit view changes --- lua/neogit/buffers/commit_view/ui.lua | 6 +++--- lua/neogit/lib/util.lua | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index e4f21db86..be2cf7671 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -13,9 +13,9 @@ local map = util.map function M.OverviewFile(file) return row.tag("OverviewFile") { text.highlight("NeogitFilePath")(file.path), - text(" | "), - text.highlight("Number")(file.changes), - text(" "), + text(" | "), + text.highlight("Number")(util.pad_left(file.changes, 5)), + text(" "), text.highlight("NeogitDiffAdd")(file.insertions), text.highlight("NeogitDiffDelete")(file.deletions), } diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 8b686799a..44914c1cd 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -392,6 +392,10 @@ function M.pad_right(s, len) return s .. string.rep(" ", math.max(len - #s, 0)) end +function M.pad_left(s, len) + return string.rep(" ", math.max(len - #s, 0)) .. s +end + --- http://lua-users.org/wiki/StringInterpolation --- @param template string --- @param values table From e641e329b19511460659989aaca23ed12210bdf6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 22:00:25 +0100 Subject: [PATCH 174/443] Port over old version of discard visual from status --- lua/neogit/buffers/status/init.lua | 202 ++++++++++++----------------- 1 file changed, 85 insertions(+), 117 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index b2fb8165c..dc6cc7fee 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -104,151 +104,119 @@ function M:open(kind) mappings = { v = { [mappings["Discard"]] = a.void(function() - -- TODO: Discard Stash? - -- TODO: Discard Section? - local discardable = self.buffer.ui:get_hunks_and_filenames_in_selection() - - local total_files = #discardable.files.staged - + #discardable.files.unstaged - + #discardable.files.untracked - - local total_hunks = #discardable.hunks.staged - + #discardable.hunks.unstaged - + #discardable.hunks.untracked - - if total_files > 0 then - if input.get_permission(("Discard %s files?"):format(total_files)) then - if #discardable.files.staged > 0 then - local new_files = {} - local modified_files = {} - - for _, file in ipairs(discardable.files.staged) do - if file.mode == "A" then - table.insert(new_files, file.escaped_path) - else - table.insert(modified_files, file.escaped_path) - end - end + local selection = self.buffer.ui:get_selection() - if #modified_files > 0 then - git.index.reset(modified_files) - git.index.checkout(modified_files) - end + local discard_message = "Discard selection?" + local hunk_count = 0 + local file_count = 0 - if #new_files > 0 then - git.index.reset(new_files) + local patches = {} + local untracked_files = {} + local unstaged_files = {} + local staged_files_new = {} + local staged_files_modified = {} - a.util.scheduler() - for _, file in ipairs(new_files) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then - api.nvim_buf_delete(bufnr, { force = true }) - end + for _, section in ipairs(selection.sections) do + file_count = file_count + #section.items - fn.delete(file.escaped_path) - end - end - end + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) - if #discardable.files.unstaged > 0 then - git.index.checkout(util.map(discardable.files.unstaged, function(f) - return f.escaped_path - end)) - end + if #hunks > 0 then + logger.fmt_debug("Discarding %d hunks from %q", #hunks, item.name) - if #discardable.files.untracked > 0 then - a.util.scheduler() + hunk_count = hunk_count + #hunks + if hunk_count > 1 then + discard_message = ("Discard %s hunks?"):format(hunk_count) + end - for _, file in ipairs(discardable.files.untracked) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then - api.nvim_buf_delete(bufnr, { force = true }) + for _, hunk in ipairs(hunks) do + table.insert(patches, function() + local patch = git.index.generate_patch( + item, + hunk, + hunk.from, + hunk.to, + true + ) + + logger.fmt_debug("Discarding Patch: %s", patch) + + git.index.apply( + patch, + { + index = section.name == "staged", + reverse = true + } + ) + end) + end + else + discard_message = ("Discard %s files?"):format(file_count) + logger.fmt_debug("Discarding in section %s %s", section.name, item.name) + + if section.name == "untracked" then + table.insert(untracked_files, item.escaped_path) + elseif section.name == "unstaged" then + table.insert(unstaged_files, item.escaped_path) + elseif section.name == "staged" then + if item.mode == "N" then + table.insert(staged_files_new, item.escaped_path) + else + table.insert(staged_files_modified, item.escaped_path) end - - fn.delete(file.escaped_path) end end end end - if total_hunks > 0 then - if input.get_permission(("Discard %s hunks?"):format(total_hunks)) then - if #discardable.files.staged > 0 then - local new_files = {} - local modified_files = {} + if input.get_permission(discard_message) then + if #patches > 0 then + for _, patch in ipairs(patches) do + patch() + end + end - for _, file in ipairs(discardable.files.staged) do - if file.mode == "A" then - table.insert(new_files, file.escaped_path) - else - table.insert(modified_files, file.escaped_path) - end - end + if #untracked_files > 0 then + a.util.scheduler() - if #modified_files > 0 then - git.index.reset(modified_files) - git.index.checkout(modified_files) + for _, file in ipairs(untracked_files) do + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) end - if #new_files > 0 then - git.index.reset(new_files) - - a.util.scheduler() - - for _, file in ipairs(new_files) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(file.escaped_path) - end - end + fn.delete(file.escaped_path) end + end - if #discardable.files.unstaged > 0 then - git.index.checkout(util.map(discardable.files.unstaged, function(f) - return f.escaped_path - end)) - end + if #unstaged_files > 0 then + git.index.checkout(unstaged_files) + end - if #discardable.files.untracked > 0 then - a.util.scheduler() + if #staged_files_new > 0 then + git.index.reset(staged_files_new) - for _, file in ipairs(discardable.files.untracked) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then - api.nvim_buf_delete(bufnr, { force = true }) - end + a.util.scheduler() - fn.delete(file.escaped_path) + for _, file in ipairs(staged_files_new) do + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) end + + fn.delete(file.escaped_path) end end - end - -- if discardable then - -- local section = self.buffer.ui:get_current_section() - -- local item = self.buffer.ui:get_item_under_cursor() - -- - -- if not section or not item then - -- return - -- end - -- - -- if discardable.hunk then - -- local hunk = discardable.hunk - -- local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) - -- - -- if input.get_permission("Discard hunk?") then - -- if section.options.section == "staged" then - -- git.index.apply(patch, { index = true, reverse = true }) - -- else - -- git.index.apply(patch, { reverse = true }) - -- end - -- end + if #staged_files_modified > 0 then + git.index.reset(staged_files_modified) + git.index.checkout(staged_files_modified) + end - self:refresh() + self:refresh() + end end), [mappings["Stage"]] = a.void(function() local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() From a25a5b31b00815706e297d1f917ba6ccc1de4c85 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 22:01:14 +0100 Subject: [PATCH 175/443] Add logging --- lua/neogit/buffers/status/init.lua | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index dc6cc7fee..1df5f401f 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -76,16 +76,20 @@ end function M:open(kind) if M.instance and M.instance.is_open then + logger.debug("[STATUS] An Instance is already open - closing it") M.instance:close() end M.instance = self if self.is_open then + logger.debug("[STATUS] This Instance is already open - bailing") return end self.is_open = true kind = kind or config.values.kind + logger.debug("[STATUS] Opening kind: " .. kind) + local mappings = config.get_reversed_status_maps() self.buffer = Buffer.create { @@ -96,6 +100,7 @@ function M:open(kind) disable_line_numbers = config.values.disable_line_numbers, autocmds = { ["BufUnload"] = function() + logger.debug("[STATUS] Running BufUnload autocmd") watcher.instance:stop() self.is_open = false vim.o.autochdir = self.prev_autochdir @@ -885,6 +890,7 @@ function M:open(kind) }, }, initialize = function() + logger.debug("[STATUS] Initializing") self.prev_autochdir = vim.o.autochdir vim.o.autochdir = false end, @@ -895,6 +901,7 @@ function M:open(kind) vim.cmd([[setlocal nowrap]]) if config.values.filewatcher.enabled then + logger.debug("[STATUS] Starting file watcher") watcher.new(git.repo:git_path():absolute()):start() end end, @@ -923,11 +930,13 @@ end function M:focus() if self.buffer then + logger.debug("[STATUS] Focusing Buffer") self.buffer:focus() end end function M:refresh(partial, reason) + logger.debug("[STATUS] Beginning refresh from " .. (reason or "unknown")) -- if self.frozen then -- return -- end @@ -995,14 +1004,16 @@ end function M:dispatch_refresh(partial, reason) a.run(function() if self:_is_refresh_locked() then - logger.debug("[STATUS] Refresh lock is active. Skipping refresh from " .. reason) + logger.debug("[STATUS] Refresh lock is active. Skipping refresh from " .. (reason or "unknown")) else + logger.debug("[STATUS] Dispatching Refresh") self:refresh(partial, reason) end end) end function M:reset() + logger.debug("[STATUS] Resetting repo and refreshing") git.repo:reset() self:refresh(nil, "reset") end @@ -1019,14 +1030,12 @@ end function M:_get_refresh_lock(reason) local permit = self.refresh_lock:acquire() - logger.debug(("[STATUS BUFFER]: Acquired refresh lock:"):format(reason or "unknown")) + logger.debug(("[STATUS]: Acquired refresh lock:"):format(reason or "unknown")) vim.defer_fn(function() if self:_is_refresh_locked() then permit:forget() - logger.debug( - ("[STATUS BUFFER]: Refresh lock for %s expired after 10 seconds"):format(reason or "unknown") - ) + logger.debug(("[STATUS]: Refresh lock for %s expired after 10 seconds"):format(reason or "unknown")) end end, 10000) From 165cbc264830b7d921a4d0091ef109680c92adca Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 22:01:25 +0100 Subject: [PATCH 176/443] Attach items to other items so they can be found in selection --- lua/neogit/buffers/status/ui.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index bc4e18023..752814e60 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -256,7 +256,7 @@ local SectionItemStash = Component.new(function(item) text.highlight("Comment")(name), text.highlight("Comment")(": "), text(item.message), - }, { yankable = name }) + }, { yankable = name, item = item }) end) local SectionItemCommit = Component.new(function(item) @@ -264,7 +264,7 @@ local SectionItemCommit = Component.new(function(item) text.highlight("Comment")(item.commit.abbreviated_commit), text(" "), text(item.commit.subject), - }, { oid = item.commit.oid, yankable = item.commit.oid }) + }, { oid = item.commit.oid, yankable = item.commit.oid, item = item }) end) local SectionItemRebase = Component.new(function(item) From 5293dc956eaff48facd016e4ed6902872c6b75f3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 22:01:41 +0100 Subject: [PATCH 177/443] Add first/last line to class definition for diff --- lua/neogit/lib/git/diff.lua | 2 ++ lua/neogit/lib/ui/component.lua | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 18519011a..650638a1b 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -108,6 +108,8 @@ end ---@field index_len number ---@field diff_from number ---@field diff_to number +---@field first number First line number in buffer +---@field last number Last line number in buffer ---@return Hunk local function build_hunks(lines) diff --git a/lua/neogit/lib/ui/component.lua b/lua/neogit/lib/ui/component.lua index 900f360a4..82a55d2cc 100644 --- a/lua/neogit/lib/ui/component.lua +++ b/lua/neogit/lib/ui/component.lua @@ -22,6 +22,8 @@ local default_component_options = { ---@field context boolean ---@field interactive boolean ---@field virtual_text string +---@field section string|nil +---@field item table|nil ---@field id string|nil ---@class Component From efbd1a17ffa0cbb055fecee37afde3d423250287 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 22:02:15 +0100 Subject: [PATCH 178/443] Track items in renderer so we can use them to create a selection in the status buffer --- lua/neogit/lib/ui/renderer.lua | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index a3f22dd42..9b99f9ff1 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -1,5 +1,8 @@ +---@source component.lua + ---@class RendererIndex ---@field index table +---@field items table local RendererIndex = {} RendererIndex.__index = RendererIndex @@ -37,8 +40,31 @@ function RendererIndex:add_id(node, id) self.index[id] = node end +-- For tracking item locations within status buffer. Needed to make selections. +function RendererIndex:add_section(name) + self.items[#self.items].name = name + table.insert(self.items, { items = {} }) +end + +function RendererIndex:add_item(item, first, last) + if not self.items[#self.items].first then + self.items[#self.items].first = first - 1 + end + + self.items[#self.items].last = last + + item.first = first + item.last = last + table.insert(self.items[#self.items].items, item) +end + function RendererIndex.new() - return setmetatable({ index = {} }, RendererIndex) + return setmetatable({ + index = {}, + items = { + { items = {} }, -- First section + }, + }, RendererIndex) end ---@class RendererBuffer @@ -100,6 +126,11 @@ function Renderer:node_index() return self.index end +---@return RendererIndex +function Renderer:item_index() + return self.index.items +end + function Renderer:_build_child(child, parent, index) if child.options.id then self.index:add_id(child) @@ -163,6 +194,15 @@ function Renderer:_render_child(child) child.position.row_end = #self.buffer.line + if child.options.section then + self.index:add_section(child.options.section) + end + + if 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 + local line_hl = child:get_line_highlight() if line_hl then table.insert(self.buffer.line_highlight, { #self.buffer.line - 1, line_hl }) From c600014970129fd158874491b3e1e247e31181bf Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 22:04:31 +0100 Subject: [PATCH 179/443] Improve selection logic --- lua/neogit/buffers/status/init.lua | 7 +- lua/neogit/lib/ui/init.lua | 254 ++++++++++++++++++++++++++++- 2 files changed, 255 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 1df5f401f..46fd68a13 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -287,7 +287,7 @@ function M:open(kind) end end), [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) - p { commits = self.buffer.ui:get_commit_under_cursor() } + p { commits = self.buffer.ui:get_commits_in_selection() } end), [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -332,19 +332,16 @@ function M:open(kind) end end), [popups.mapping_for("StashPopup")] = popups.open("stash", function(p) - -- TODO: Verify local stash = self.buffer.ui:get_yankable_under_cursor() p { name = stash and stash:match("^stash@{%d+}") } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - -- TODO: Verify local section = self.buffer.ui:get_current_section().options.section local item = self.buffer.ui:get_yankable_under_cursor() p { section = { name = section }, item = { name = item } } end), [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) - -- TODO use current absolute paths in selection - p { paths = {}, git_root = git.repo.git_root } + p { paths = self.buffer.ui:get_filepaths_in_selection(), git_root = git.repo.git_root } end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("FetchPopup")] = popups.open("fetch"), diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index d0641032f..0bc48b6da 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -2,6 +2,18 @@ local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") local Renderer = require("neogit.lib.ui.renderer") +---@class Selection +---@field sections SectionSelection[] +---@field first_line number +---@field last_line number +---@field section Section|nil +---@field item StatusItem|nil +---@field commit CommitLogEntry|nil +---@field commits CommitLogEntry[] +---@field items StatusItem[] +local Selection = {} +Selection.__index = Selection + ---@class UiComponent ---@field tag string ---@field options table Component props or arguments @@ -135,6 +147,171 @@ function Ui:get_fold_under_cursor() end) end +---@class StatusItem +---@field name string +---@field first number +---@field last number +---@field oid string|nil optional object id +---@field commit CommitLogEntry|nil optional object id +---@field folded boolean|nil +---@field hunks Hunk[]|nil + +---@class SelectedHunk: Hunk +---@field from number start offset from the first line of the hunk +---@field to number end offset from the first line of the hunk +---@field lines string[] +--- +---@param item StatusItem +---@param first_line number +---@param last_line number +---@param partial boolean +---@return SelectedHunk[] +function Ui:item_hunks(item, first_line, last_line, partial) + local hunks = {} + + if not item.folded and item.diff.hunks then + for _, h in ipairs(item.diff.hunks) do + if h.first <= last_line and h.last >= first_line then + local from, to + + if partial then + local cursor_offset = first_line - h.first + local length = last_line - first_line + + from = h.diff_from + cursor_offset + to = from + length + else + from = h.diff_from + 1 + to = h.diff_to + end + + local hunk_lines = {} + for i = from, to do + table.insert(hunk_lines, item.diff.lines[i]) + end + + local o = { + from = from, + to = to, + __index = h, + hunk = h, + lines = hunk_lines, + } + + setmetatable(o, o) + + table.insert(hunks, o) + end + end + end + + return hunks +end + +-- function Ui:selected_hunks() +-- local selection = self:get_selection() +-- local first_line = selection.first_line +-- local last_line = selection.last_line +-- local item = selection.item +-- +-- local hunks = {} +-- +-- if item and item.diff.hunks then +-- for _, h in ipairs(item.diff.hunks) do +-- if h.first <= last_line and h.last >= first_line then +-- local from, to +-- +-- local cursor_offset = first_line - h.first +-- local length = last_line - first_line +-- +-- from = h.diff_from + cursor_offset +-- to = from + length +-- +-- local hunk_lines = {} +-- for i = from, to do +-- table.insert(hunk_lines, item.diff.lines[i]) +-- end +-- +-- local o = { +-- from = from, +-- to = to, +-- __index = h, +-- hunk = h, +-- lines = hunk_lines, +-- } +-- +-- setmetatable(o, o) +-- +-- table.insert(hunks, o) +-- end +-- end +-- end +-- +-- return hunks +-- end + +function Ui:get_selection() + local visual_pos = vim.fn.getpos("v")[2] + local cursor_pos = vim.fn.getpos(".")[2] + + local first_line = math.min(visual_pos, cursor_pos) + local last_line = math.max(visual_pos, cursor_pos) + + local res = { + sections = {}, + first_line = first_line, + last_line = last_line, + item = nil, + commit = nil, + commits = {}, + items = {}, + } + + for _, section in ipairs(self.item_index) do + local items = {} + + if section.first > last_line then + break + end + + if section.last >= first_line then + if section.first <= first_line and section.last >= last_line then + res.section = section + end + + local entire_section = section.first == first_line and first_line == last_line + + for _, item in pairs(section.items) do + if entire_section or item.first <= last_line and item.last >= first_line then + if not res.item and item.first <= first_line and item.last >= last_line then + res.item = item + + res.commit = item.commit + end + + if item.commit then + table.insert(res.commits, item.commit) + end + + table.insert(res.items, item) + table.insert(items, item) + end + end + + local section = { + section = section, + items = items, + __index = section, + } + + setmetatable(section, section) + table.insert(res.sections, section) + end + end + + return setmetatable(res, Selection) +end + ---@return table function Ui:get_hunks_and_filenames_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } @@ -154,6 +331,8 @@ function Ui:get_hunks_and_filenames_in_selection() }, } + local hunks = {} + for i = start, stop do local section = self:get_current_section(i) @@ -165,7 +344,10 @@ function Ui:get_hunks_and_filenames_in_selection() section = section.options.section if component.options.hunk then - table.insert(items.hunks[section], component.options.hunk) + if not hunks[component.options.hunk.hash] then + table.insert(items.hunks[section], component.options.hunk) + hunks[component.options.hunk.hash] = true + end elseif component.options.filename then table.insert(items.files[section], component.options.item) end @@ -175,6 +357,39 @@ function Ui:get_hunks_and_filenames_in_selection() return items end +function Ui:get_items_in_selection() + local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } + table.sort(range) + local start, stop = unpack(range) + + local items = { + untracked = {}, + unstaged = {}, + staged = {}, + } + + local items_set = {} + + for i = start, stop do + local section = self:get_current_section(i) + + local component = self:_find_component_by_index(i, function(node) + return node.options.item + end) + + if component and section then + section = section.options.section + + if not items_set[component.options.id] then + table.insert(items[section], component.options.item) + items_set[component.options.id] = true + end + end + end + + return items +end + ---@return string[] function Ui:get_commits_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } @@ -195,6 +410,26 @@ function Ui:get_commits_in_selection() return util.deduplicate(commits) end +---@return string[] +function Ui:get_filepaths_in_selection() + local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } + table.sort(range) + local start, stop = unpack(range) + + local paths = {} + for i = start, stop do + local component = self:_find_component_by_index(i, function(node) + return node.options.item and node.options.item.escaped_path + end) + + if component then + table.insert(paths, 1, component.options.item.escaped_path) + end + end + + return util.deduplicate(paths) +end + ---@return string|nil function Ui:get_commit_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) @@ -225,6 +460,22 @@ function Ui:get_current_section(line) return component end +-- ---@return Component[] +-- function Ui:section_items(line) +-- line = line or vim.api.nvim_win_get_cursor(0)[1] +-- local section_component = self:_find_component_by_index(line, function(node) +-- return node.options.section +-- end) +-- +-- if not section_component then +-- return {} +-- end +-- +-- local section = section_component.options.section +-- +-- return component +-- end + ---@return table|nil function Ui:get_hunk_or_filename_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) @@ -346,6 +597,7 @@ function Ui:update() local renderer = Renderer:new(self.layout, self.buf):render() self.node_index = renderer:node_index() + self.item_index = renderer:item_index() local cursor_line = self.buf:cursor_line() self.buf:unlock() From f9d399258e6731d12d8ba5fce387ec9391e6f053 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 23:15:38 +0100 Subject: [PATCH 180/443] Implement proper stage/unstage in visual mode --- lua/neogit/buffers/status/init.lua | 184 +++++++++++++++++------------ 1 file changed, 110 insertions(+), 74 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 46fd68a13..501f175d7 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -121,7 +121,6 @@ function M:open(kind) local staged_files_new = {} local staged_files_modified = {} - for _, section in ipairs(selection.sections) do file_count = file_count + #section.items @@ -138,23 +137,14 @@ function M:open(kind) for _, hunk in ipairs(hunks) do table.insert(patches, function() - local patch = git.index.generate_patch( - item, - hunk, - hunk.from, - hunk.to, - true - ) + local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) logger.fmt_debug("Discarding Patch: %s", patch) - git.index.apply( - patch, - { - index = section.name == "staged", - reverse = true - } - ) + git.index.apply(patch, { + index = section.name == "staged", + reverse = true, + }) end) end else @@ -224,65 +214,83 @@ function M:open(kind) end end), [mappings["Stage"]] = a.void(function() - local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() - local section = self.buffer.ui:get_current_section() + local selection = self.buffer.ui:get_selection() - if stagable and section then - if section.options.section == "staged" then - return - end + local untracked_files = {} + local unstaged_files = {} + local patches = {} - if stagable.hunk then - local item = self.buffer.ui:get_item_under_cursor() - local patch = - git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + for _, section in ipairs(selection.sections) do + if section.name == "unstaged" or section.name == "untracked" then + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) - git.index.apply(patch, { cached = true }) - elseif stagable.filename then - if section.options.section == "unstaged" then - git.status.stage { stagable.filename } - elseif section.options.section == "untracked" then - git.index.add { stagable.filename } + if #hunks > 0 then + for _, hunk in ipairs(hunks) do + table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) + end + else + if section.name == "unstaged" then + table.insert(unstaged_files, item.escaped_path) + else + table.insert(untracked_files, item.escaped_path) + end + end end end - elseif section then - if section.options.section == "untracked" then - git.status.stage_untracked() - elseif section.options.section == "unstaged" then - git.status.stage_modified() + end + + if #untracked_files > 0 then + git.index.add(untracked_files) + end + + if #unstaged_files > 0 then + git.status.stage(unstaged_files) + end + + if #patches > 0 then + for _, patch in ipairs(patches) do + git.index.apply(patch, { cached = true }) end end - self:refresh() + if #untracked_files > 0 or #unstaged_files > 0 or #patches > 0 then + self:refresh() + end end), [mappings["Unstage"]] = a.void(function() - local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local selection = self.buffer.ui:get_selection() - local section = self.buffer.ui:get_current_section() - if section and section.options.section ~= "staged" then - return + local files = {} + local patches = {} + + for _, section in ipairs(selection.sections) do + if section.name == "staged" then + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) + + if #hunks > 0 then + for _, hunk in ipairs(hunks) do + table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) + end + else + table.insert(files, item.escaped_path) + end + end + end end - if unstagable then - if unstagable.hunk then - local item = self.buffer.ui:get_item_under_cursor() - local patch = git.index.generate_patch( - item, - unstagable.hunk, - unstagable.hunk.from, - unstagable.hunk.to, - true - ) + if #files > 0 then + git.status.unstage(files) + end + if #patches > 0 then + for _, patch in ipairs(patches) do git.index.apply(patch, { cached = true, reverse = true }) - elseif unstagable.filename then - local section = self.buffer.ui:get_current_section() - - if section and section.options.section == "staged" then - git.status.unstage { unstagable.filename } - end end + end + if #files > 0 or #patches > 0 then self:refresh() end end), @@ -470,6 +478,7 @@ function M:open(kind) end end, [mappings["Discard"]] = a.void(function() + -- TODO: Use better selection logic -- TODO: Discarding a RENAME should set the filename back to the original git.index.update() @@ -528,6 +537,21 @@ function M:open(kind) end self:refresh() + else + local section = self.buffer.ui:get_current_section() + if section then + if section.options.section == "unstaged" then + git.index.checkout_unstaged() + elseif section.options.section == "untracked" then + P("TODO") + elseif section.options.section == "staged" then + P("TODO") + elseif section.options.section == "stash" then + P("TODO") + end + + self:refresh() + end end end), [mappings["GoToNextHunkHeader"]] = function() @@ -575,7 +599,7 @@ function M:open(kind) local previous_header = previous_hunk_header(self, fn.line(".")) if previous_header then - api.nvim_win_set_cursor(0, { previous_header, 0 }) + self.buffer:move_cursor(previous_header) vim.cmd("normal! zt") end end, @@ -834,13 +858,16 @@ function M:open(kind) [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) local section = self.buffer.ui:get_current_section().options.section local item = self.buffer.ui:get_yankable_under_cursor() - p { section = { name = section }, item = { name = item } } + p { + section = { name = section }, + item = { name = item }, + } end), [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) local path = self.buffer.ui:get_hunk_or_filename_under_cursor() p { paths = { path and path.escaped_path }, - git_root = git.repo.git_root + git_root = git.repo.git_root, } end), [popups.mapping_for("HelpPopup")] = popups.open("help", function(p) @@ -852,7 +879,6 @@ function M:open(kind) local commit = self.buffer.ui:get_commit_under_cursor() local commits = { commit } - -- TODO: Consume this in help popup p { branch = { commits = commits }, cherry_pick = { commits = commits }, @@ -863,7 +889,7 @@ function M:open(kind) revert = { commits = commits }, reset = { commit = commit }, tag = { commit = commit }, - stash = { name = stash and stash:match("^stash@{%d+}"), }, + stash = { name = stash and stash:match("^stash@{%d+}") }, diff = { section = { name = section }, item = { name = item }, @@ -906,6 +932,11 @@ function M:open(kind) end function M:close() + logger.debug("[STATUS] Closing Buffer") + if not self.buffer then + return + end + vim.o.autochdir = self.prev_autochdir watcher.instance:stop() @@ -944,23 +975,25 @@ function M:refresh(partial, reason) source = "status", partial = partial, callback = function() + logger.debug("[STATUS][Refresh Callback] Running") if not self.buffer then + logger.debug("[STATUS][Refresh Callback] Buffer no longer exists - bail") return end local cursor_line = self.buffer:cursor_line() - local cursor_context_start, cursor_goto + local cursor_goto local context = self.buffer.ui:get_cursor_context() if context then if context.options.tag == "Hunk" then if context.index == 1 then if #context.parent.children > 1 then - cursor_context_start = ({ context:row_range_abs() })[1] + cursor_line = ({ context:row_range_abs() })[1] else - cursor_context_start = ({ context:row_range_abs() })[1] - 1 + cursor_line = ({ context:row_range_abs() })[1] - 1 end else - cursor_context_start = ({ context.parent.children[context.index - 1]:row_range_abs() })[1] + cursor_line = ({ context.parent.children[context.index - 1]:row_range_abs() })[1] end elseif context.options.tag == "File" then if context.index == 1 then @@ -979,21 +1012,24 @@ function M:refresh(partial, reason) end end + logger.debug("[STATUS][Refresh Callback] Rendering UI") self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - if cursor_context_start then - self.buffer:move_cursor(cursor_context_start) - elseif cursor_goto then - local line, _ = self.buffer.ui.node_index:find_by_id(cursor_goto):row_range_abs() - self.buffer:move_cursor(line) - else - self.buffer:move_cursor(cursor_line) + if cursor_goto then + logger.debug("[STATUS] Cursor goto: " .. cursor_goto) + local component = self.buffer.ui.node_index:find_by_id(cursor_goto) + if component then + cursor_line, _ = component:row_range_abs() + end end + logger.debug("[STATUS][Refresh Callback] Moving Cursor") + self.buffer:move_cursor(math.min(fn.line("$"), cursor_line)) + api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) permit:forget() - logger.info("[STATUS BUFFER]: Refresh lock is now free") + logger.info("[STATUS] Refresh lock is now free") end, } end From 3d8c7b2b1d4432ca935ddb794fbcaba098f3ddc2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 9 Mar 2024 23:26:56 +0100 Subject: [PATCH 181/443] Allow discarding stashes --- lua/neogit/buffers/status/init.lua | 79 ++++++++++++++++++------------ 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 501f175d7..fef5aa1ba 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -120,49 +120,58 @@ function M:open(kind) local unstaged_files = {} local staged_files_new = {} local staged_files_modified = {} + local stashes = {} for _, section in ipairs(selection.sections) do - file_count = file_count + #section.items + if section.name == "untracked" or section.name == "unstaged" or section.name == "staged" then + file_count = file_count + #section.items - for _, item in ipairs(section.items) do - local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) - if #hunks > 0 then - logger.fmt_debug("Discarding %d hunks from %q", #hunks, item.name) + if #hunks > 0 then + logger.fmt_debug("Discarding %d hunks from %q", #hunks, item.name) - hunk_count = hunk_count + #hunks - if hunk_count > 1 then - discard_message = ("Discard %s hunks?"):format(hunk_count) - end + hunk_count = hunk_count + #hunks + if hunk_count > 1 then + discard_message = ("Discard %s hunks?"):format(hunk_count) + end - for _, hunk in ipairs(hunks) do - table.insert(patches, function() - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + for _, hunk in ipairs(hunks) do + table.insert(patches, function() + local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) - logger.fmt_debug("Discarding Patch: %s", patch) + logger.fmt_debug("Discarding Patch: %s", patch) - git.index.apply(patch, { - index = section.name == "staged", - reverse = true, - }) - end) - end - else - discard_message = ("Discard %s files?"):format(file_count) - logger.fmt_debug("Discarding in section %s %s", section.name, item.name) - - if section.name == "untracked" then - table.insert(untracked_files, item.escaped_path) - elseif section.name == "unstaged" then - table.insert(unstaged_files, item.escaped_path) - elseif section.name == "staged" then - if item.mode == "N" then - table.insert(staged_files_new, item.escaped_path) - else - table.insert(staged_files_modified, item.escaped_path) + git.index.apply(patch, { + index = section.name == "staged", + reverse = true, + }) + end) + end + else + discard_message = ("Discard %s files?"):format(file_count) + logger.fmt_debug("Discarding in section %s %s", section.name, item.name) + + if section.name == "untracked" then + table.insert(untracked_files, item.escaped_path) + elseif section.name == "unstaged" then + table.insert(unstaged_files, item.escaped_path) + elseif section.name == "staged" then + if item.mode == "N" then + table.insert(staged_files_new, item.escaped_path) + else + table.insert(staged_files_modified, item.escaped_path) + end end end end + elseif section.name == "stashes" then + discard_message = ("Discard %s stashes?"):format(#selection.items) + + for _, stash in ipairs(selection.items) do + table.insert(stashes, stash.name:match("(stash@{%d+})")) + end end end @@ -210,6 +219,12 @@ function M:open(kind) git.index.checkout(staged_files_modified) end + if #stashes > 0 then + for _, stash in ipairs(stashes) do + git.stash.drop(stash) + end + end + self:refresh() end end), From ed64fbb26b315e869ff74568d41adf2d6f35bafd Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Mar 2024 15:44:24 +0100 Subject: [PATCH 182/443] Polish for discarding and staging --- lua/neogit/buffers/status/init.lua | 239 ++++++++++++++++++++--------- lua/neogit/lib/git/index.lua | 4 + lua/neogit/lib/git/status.lua | 5 +- lua/neogit/lib/ui/init.lua | 11 +- 4 files changed, 180 insertions(+), 79 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index fef5aa1ba..8a22dd81f 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,19 +1,6 @@ -- TODO -- on-close hook to teardown stuff -- --- Actions: --- Staging / Unstaging / Discarding --- --- Contexts: --- - Normal --- - Section --- - File --- - Hunk --- - Visual --- - Files in selection --- - Hunks in selection --- - Lines in selection - -- TODO: When launching the fuzzy finder, any refresh attempted will raise an exception because the set_folds() function -- cannot be called when the buffer is not focused, as it's not a proper API. We could implement some kind of freeze -- mechanism to prevent the buffer from refreshing while the fuzzy finder is open. @@ -493,81 +480,187 @@ function M:open(kind) end end, [mappings["Discard"]] = a.void(function() - -- TODO: Use better selection logic - -- TODO: Discarding a RENAME should set the filename back to the original git.index.update() - local discardable = self.buffer.ui:get_hunk_or_filename_under_cursor() - - if discardable then - local section = self.buffer.ui:get_current_section() - local item = self.buffer.ui:get_item_under_cursor() + local selection = self.buffer.ui:get_selection() + if not selection.section then + return + end - if not section or not item then - return - end + local section = selection.section.name + local action, message - -- TODO: Discard Stash? - -- TODO: Discard Section? - if discardable.hunk then - local hunk = discardable.hunk - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + if selection.item and selection.item.first == fn.line(".") then -- Discard File + if section == "untracked" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + a.util.scheduler() - if input.get_permission("Discard hunk?") then - if section.options.section == "staged" then - git.index.apply(patch, { index = true, reverse = true }) - else - git.index.apply(patch, { reverse = true }) + local bufnr = fn.bufexists(selection.item.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) end + + fn.delete(selection.item.escaped_path) end - elseif discardable.filename then - if input.get_permission(("Discard %q?"):format(discardable.filename)) then - if section.options.section == "staged" and item.mode == "M" then -- Modified - git.index.reset { discardable.filename } - git.index.checkout { discardable.filename } - elseif section.options.section == "staged" and item.mode == "A" then -- Added - git.index.reset { discardable.filename } + elseif section == "unstaged" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + git.index.checkout { selection.item.name } + end + elseif section == "staged" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "N" then + git.index.reset { selection.item.escaped_path } a.util.scheduler() - local bufnr = fn.bufexists(discardable.filename) + local bufnr = fn.bufexists(selection.item.name) if bufnr and bufnr > 0 then api.nvim_buf_delete(bufnr, { force = true }) end - fn.delete(fn.fnameescape(discardable.filename)) - elseif section.options.section == "unstaged" then - git.index.checkout { discardable.filename } - elseif section.options.section == "untracked" then - a.util.scheduler() - - local bufnr = fn.bufexists(discardable.filename) + fn.delete(selection.item.escaped_path) + elseif selection.item.mode == "M" then + git.index.reset { selection.item.escaped_path } + git.index.checkout { selection.item.escaped_path } + elseif selection.item.mode == "R" then + git.index.reset_HEAD(selection.item.name, selection.item.original_name) + git.index.checkout { selection.item.original_name } + elseif selection.item.mode == "D" then + git.index.reset_HEAD(selection.item.escaped_path) + git.index.checkout { selection.item.escaped_path } + else + error( + ("Unhandled file mode %q for %q"):format(selection.item.mode, selection.item.escaped_path) + ) + end + end + elseif section == "stashes" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + git.stash.drop(selection.item.name:match("(stash@{%d+})")) + end + end + elseif selection.item then -- Discard Hunk + local hunk = + self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] + local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) + + if section == "untracked" then + message = "Discard hunk?" + action = function() + local hunks = + self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) + + local patch = + git.index.generate_patch(selection.item, hunks[1], hunks[1].from, hunks[1].to, true) + + git.index.apply(patch, { reverse = true }) + git.index.apply(patch, { reverse = true }) + end + elseif section == "unstaged" then + message = "Discard hunk?" + action = function() + git.index.apply(patch, { reverse = true }) + end + elseif section == "staged" then + message = "Discard hunk?" + action = function() + git.index.apply(patch, { index = true, reverse = true }) + end + end + else -- Discard Section + if section == "untracked" then + message = ("Discard %s files?"):format(#selection.section.items) + action = function() + a.util.scheduler() + + for _, file in ipairs(selection.section.items) do + local bufnr = fn.bufexists(file.name) if bufnr and bufnr > 0 then api.nvim_buf_delete(bufnr, { force = true }) end - fn.delete(fn.fnameescape(discardable.filename)) + fn.delete(file.escaped_path) end end - end - - self:refresh() - else - local section = self.buffer.ui:get_current_section() - if section then - if section.options.section == "unstaged" then + elseif section == "unstaged" then + message = ("Discard %s files?"):format(#selection.section.items) + action = function() git.index.checkout_unstaged() - elseif section.options.section == "untracked" then - P("TODO") - elseif section.options.section == "staged" then - P("TODO") - elseif section.options.section == "stash" then - P("TODO") end + elseif section == "staged" then + message = ("Discard %s files?"):format(#selection.section.items) + action = function() + local staged_files_new = {} + local staged_files_modified = {} + local staged_files_renamed = {} + local staged_files_deleted = {} + + for _, item in ipairs(selection.section.items) do + if item.mode == "N" then + table.insert(staged_files_new, item.escaped_path) + elseif item.mode == "M" then + table.insert(staged_files_modified, item.escaped_path) + elseif item.mode == "R" then + table.insert(staged_files_renamed, item) + elseif item.mode == "D" then + table.insert(staged_files_deleted, item.escaped_path) + else + error(("Unknown file mode %q for %q"):format(item.mode, item.escaped_path)) + end + end - self:refresh() + if #staged_files_new > 0 then + -- ensure the file is deleted + git.index.reset(staged_files_new) + + a.util.scheduler() + + for _, file in ipairs(staged_files_new) do + local bufnr = fn.bufexists(file.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(file.escaped_path) + end + end + + if #staged_files_modified > 0 then + git.index.reset(staged_files_modified) + git.index.checkout(staged_files_modified) + end + + if #staged_files_renamed > 0 then + for _, item in ipairs(staged_files_renamed) do + git.index.reset_HEAD(item.name, item.original_name) + git.index.checkout { item.original_name } + fn.delete(item.escaped_path) + end + end + + if #staged_files_deleted > 0 then + git.index.reset_HEAD(unpack(staged_files_deleted)) + git.index.checkout(staged_files_deleted) + end + end + elseif section == "stashes" then + message = ("Discard %s stashes?"):format(#selection.section.items) + action = function() + for _, stash in ipairs(selection.section.items) do + git.stash.drop(stash.name:match("(stash@{%d+})")) + end + end end end + + if action and input.get_permission(message) then + action() + self:refresh() + end end), [mappings["GoToNextHunkHeader"]] = function() local c = self.buffer.ui:get_component_under_cursor(function(c) @@ -999,6 +1092,7 @@ function M:refresh(partial, reason) local cursor_line = self.buffer:cursor_line() local cursor_goto local context = self.buffer.ui:get_cursor_context() + if context then if context.options.tag == "Hunk" then if context.index == 1 then @@ -1008,22 +1102,19 @@ function M:refresh(partial, reason) cursor_line = ({ context:row_range_abs() })[1] - 1 end else - cursor_line = ({ context.parent.children[context.index - 1]:row_range_abs() })[1] + local index = math.min(#context.parent.children - 1, context.index) + cursor_line = ({ context.parent.children[index]:row_range_abs() })[1] end elseif context.options.tag == "File" then - if context.index == 1 then - if #context.parent.children > 1 then - -- id is scoped by section. Advance to next file. - cursor_goto = context.parent.children[2].options.id - else - -- Yankable lets us jump from one section to the other. Go to same file in new section. - cursor_goto = context.options.yankable - end + if #context.parent.children == 1 then + -- Yankable lets us jump from one section to the other. Go to same file in new section. + cursor_goto = context.options.yankable else - cursor_goto = context.parent.children[context.index - 1].options.id + local index = math.min(#context.parent.children - 1, context.index) + cursor_goto = context.parent.children[index].options.id end else - -- TODO: profit? + error("Unknown cursor jump") end end diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 2448bba3e..0d4af5f8f 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -114,6 +114,10 @@ function M.reset(files) return cli.reset.files(unpack(files)).call() end +function M.reset_HEAD(...) + return cli.reset.args("HEAD").arg_list({ ... }).call() +end + function M.checkout_unstaged() local repo = require("neogit.lib.git.repository") local items = util.map(repo.unstaged.items, function(item) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index d7928b29d..83e95c8d9 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -2,12 +2,15 @@ local Path = require("plenary.path") local util = require("neogit.lib.util") local Collection = require("neogit.lib.collection") ----@class File: StatusItem +---@class StatusItem ---@field mode string ---@field has_diff boolean ---@field diff string[] ---@field absolute_path string +---@field escaped_path string +---@field original_nae string|nil +---@return StatusItem local function update_file(cwd, file, mode, name, original_name) local mt, diff, has_diff diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 0bc48b6da..dda36ace8 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -2,8 +2,11 @@ local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") local Renderer = require("neogit.lib.ui.renderer") +---@class Section +---@field items StatusItem[] + ---@class Selection ----@field sections SectionSelection[] +---@field sections Section[] ---@field first_line number ---@field last_line number ---@field section Section|nil @@ -251,8 +254,8 @@ end -- end function Ui:get_selection() - local visual_pos = vim.fn.getpos("v")[2] - local cursor_pos = vim.fn.getpos(".")[2] + local visual_pos = vim.fn.line("v") + local cursor_pos = vim.fn.line(".") local first_line = math.min(visual_pos, cursor_pos) local last_line = math.max(visual_pos, cursor_pos) @@ -270,7 +273,7 @@ function Ui:get_selection() for _, section in ipairs(self.item_index) do local items = {} - if section.first > last_line then + if not section.first or section.first > last_line then break end From 689fd6e3e00d2f7f5bc91915b33e816d43348072 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Mar 2024 15:44:52 +0100 Subject: [PATCH 183/443] Use new winfixbuf and winfixheight commands to lock the buffer to the window --- lua/neogit/buffers/status/init.lua | 11 +++++++---- lua/neogit/lib/buffer.lua | 1 + lua/neogit/lib/popup/init.lua | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8a22dd81f..2724f7010 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -819,10 +819,13 @@ function M:open(kind) self:close() - vim.cmd.edit(item.escaped_path) - if cursor then - api.nvim_win_set_cursor(0, cursor) - end + vim.schedule(function() + vim.cmd("edit! " .. item.escaped_path) + + if cursor then + api.nvim_win_set_cursor(0, cursor) + end + end) return end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 6e52bb6da..7f684fcc6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -579,6 +579,7 @@ function Buffer.create(config) if vim.fn.has("nvim-0.10") then buffer:set_window_option("spell", false) buffer:set_window_option("wrap", false) + buffer:set_window_option("winfixbuf", true) end if config.after then diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 02e9f59cd..6c99b53b4 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -659,6 +659,7 @@ function M:show() vim.schedule(function() if buf:is_focused() then vim.cmd.resize(vim.fn.line("$") + 1) + buf:set_window_option("winfixheight", true) end end) end From 6b43c03c615093bb911eab701e90329142939d77 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 11 Mar 2024 15:51:53 +0100 Subject: [PATCH 184/443] Fix: unstaging staged section --- lua/neogit/buffers/status/init.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2724f7010..ff84edfac 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -775,15 +775,13 @@ function M:open(kind) git.index.apply(patch, { cached = true, reverse = true }) elseif unstagable.filename then - local section = self.buffer.ui:get_current_section() - - if section and section.options.section == "staged" then - git.status.unstage { unstagable.filename } - end + git.status.unstage { unstagable.filename } end - - self:refresh() + elseif section then + git.status.unstage_all() end + + self:refresh() end), [mappings["UnstageStaged"]] = a.void(function() git.status.unstage_all() From 292f985450501bda31a085ebd3cb917a52515adc Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:05:07 +0100 Subject: [PATCH 185/443] Move cursor to first section when buffer opens --- lua/neogit/buffers/status/init.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ff84edfac..ebe81eed8 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1029,13 +1029,17 @@ function M:open(kind) render = function() return ui.Status(self.state, self.config) end, - after = function() + ---@param buffer Buffer + ---@param _win any + after = function(buffer, _win) vim.cmd([[setlocal nowrap]]) if config.values.filewatcher.enabled then logger.debug("[STATUS] Starting file watcher") watcher.new(git.repo:git_path():absolute()):start() end + + buffer:move_cursor(buffer.ui:first_section().first) end, } end From ca8e5f433dc9c08c5e1ba9774c80d68ad307bf31 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:08:02 +0100 Subject: [PATCH 186/443] Port old version of cursor logic --- lua/neogit/buffers/status/init.lua | 45 +---- lua/neogit/lib/ui/init.lua | 257 +++++++++++++---------------- 2 files changed, 124 insertions(+), 178 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ebe81eed8..8ea023e27 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1094,48 +1094,19 @@ function M:refresh(partial, reason) return end - local cursor_line = self.buffer:cursor_line() - local cursor_goto - local context = self.buffer.ui:get_cursor_context() - - if context then - if context.options.tag == "Hunk" then - if context.index == 1 then - if #context.parent.children > 1 then - cursor_line = ({ context:row_range_abs() })[1] - else - cursor_line = ({ context:row_range_abs() })[1] - 1 - end - else - local index = math.min(#context.parent.children - 1, context.index) - cursor_line = ({ context.parent.children[index]:row_range_abs() })[1] - end - elseif context.options.tag == "File" then - if #context.parent.children == 1 then - -- Yankable lets us jump from one section to the other. Go to same file in new section. - cursor_goto = context.options.yankable - else - local index = math.min(#context.parent.children - 1, context.index) - cursor_goto = context.parent.children[index].options.id - end - else - error("Unknown cursor jump") - end - end + local cursor = self.buffer.ui:get_cursor_location() + logger.debug("[STATUS][Refresh Callback] Cursor: " .. vim.inspect(cursor)) logger.debug("[STATUS][Refresh Callback] Rendering UI") self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - if cursor_goto then - logger.debug("[STATUS] Cursor goto: " .. cursor_goto) - local component = self.buffer.ui.node_index:find_by_id(cursor_goto) - if component then - cursor_line, _ = component:row_range_abs() - end - end + local cursor_line = math.min( + fn.line("$"), + self.buffer.ui:resolve_cursor_location(cursor) + ) - logger.debug("[STATUS][Refresh Callback] Moving Cursor") - self.buffer:move_cursor(math.min(fn.line("$"), cursor_line)) + logger.debug("[STATUS][Refresh Callback] Moving Cursor to line " .. cursor_line) + self.buffer:move_cursor(cursor_line) api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index dda36ace8..35a829f25 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -1,6 +1,8 @@ local Component = require("neogit.lib.ui.component") local util = require("neogit.lib.util") local Renderer = require("neogit.lib.ui.renderer") +local Collection = require("neogit.lib.collection") +local logger = require("neogit.logger") -- TODO: Add logging ---@class Section ---@field items StatusItem[] @@ -211,48 +213,6 @@ function Ui:item_hunks(item, first_line, last_line, partial) return hunks end --- function Ui:selected_hunks() --- local selection = self:get_selection() --- local first_line = selection.first_line --- local last_line = selection.last_line --- local item = selection.item --- --- local hunks = {} --- --- if item and item.diff.hunks then --- for _, h in ipairs(item.diff.hunks) do --- if h.first <= last_line and h.last >= first_line then --- local from, to --- --- local cursor_offset = first_line - h.first --- local length = last_line - first_line --- --- from = h.diff_from + cursor_offset --- to = from + length --- --- local hunk_lines = {} --- for i = from, to do --- table.insert(hunk_lines, item.diff.lines[i]) --- end --- --- local o = { --- from = from, --- to = to, --- __index = h, --- hunk = h, --- lines = hunk_lines, --- } --- --- setmetatable(o, o) --- --- table.insert(hunks, o) --- end --- end --- end --- --- return hunks --- end - function Ui:get_selection() local visual_pos = vim.fn.line("v") local cursor_pos = vim.fn.line(".") @@ -315,84 +275,6 @@ function Ui:get_selection() return setmetatable(res, Selection) end ----@return table -function Ui:get_hunks_and_filenames_in_selection() - local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } - table.sort(range) - local start, stop = unpack(range) - - local items = { - hunks = { - untracked = {}, - unstaged = {}, - staged = {}, - }, - files = { - untracked = {}, - unstaged = {}, - staged = {}, - }, - } - - local hunks = {} - - for i = start, stop do - local section = self:get_current_section(i) - - local component = self:_find_component_by_index(i, function(node) - return node.options.hunk or node.options.filename - end) - - if component and section then - section = section.options.section - - if component.options.hunk then - if not hunks[component.options.hunk.hash] then - table.insert(items.hunks[section], component.options.hunk) - hunks[component.options.hunk.hash] = true - end - elseif component.options.filename then - table.insert(items.files[section], component.options.item) - end - end - end - - return items -end - -function Ui:get_items_in_selection() - local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } - table.sort(range) - local start, stop = unpack(range) - - local items = { - untracked = {}, - unstaged = {}, - staged = {}, - } - - local items_set = {} - - for i = start, stop do - local section = self:get_current_section(i) - - local component = self:_find_component_by_index(i, function(node) - return node.options.item - end) - - if component and section then - section = section.options.section - - if not items_set[component.options.id] then - table.insert(items[section], component.options.item) - items_set[component.options.id] = true - end - end - end - - return items -end - ---@return string[] function Ui:get_commits_in_selection() local range = { vim.fn.getpos("v")[2], vim.fn.getpos(".")[2] } @@ -437,7 +319,7 @@ end function Ui:get_commit_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) local component = self:_find_component_by_index(cursor[1], function(node) - return node.options.oid + return node.options.oid ~= nil end) return component and component.options.oid @@ -447,37 +329,133 @@ end function Ui:get_yankable_under_cursor() local cursor = vim.api.nvim_win_get_cursor(0) local component = self:_find_component_by_index(cursor[1], function(node) - return node.options.yankable + return node.options.yankable ~= nil end) return component and component.options.yankable end +---@return Section|nil +function Ui:first_section() + return self.item_index[1] +end + ---@return Component|nil function Ui:get_current_section(line) line = line or vim.api.nvim_win_get_cursor(0)[1] local component = self:_find_component_by_index(line, function(node) - return node.options.section + return node.options.section ~= nil end) return component end --- ---@return Component[] --- function Ui:section_items(line) --- line = line or vim.api.nvim_win_get_cursor(0)[1] --- local section_component = self:_find_component_by_index(line, function(node) --- return node.options.section --- end) --- --- if not section_component then --- return {} --- end --- --- local section = section_component.options.section --- --- return component --- end +---@class CursorLocation +---@field first number +---@field last number +---@field section {index: number, name: string}|nil +---@field file {index: number, name: string}|nil +---@field hunk {index: number, name: string}|nil + +---Encode the cursor location into a table +---@param line number? +---@return CursorLocation +function Ui:get_cursor_location(line) + line = line or vim.api.nvim_win_get_cursor(0)[1] + local section_loc, file_loc, hunk_loc, first, last + + for li, loc in ipairs(self.item_index) do + if line == loc.first then + section_loc = { index = li, name = loc.name } + first, last = loc.first, loc.last + + break + elseif line >= loc.first and line <= loc.last then + section_loc = { index = li, name = loc.name } + + for fi, file in ipairs(loc.items) do + if line == file.first then + file_loc = { index = fi, name = file.name } + first, last = file.first, file.last + + break + elseif line >= file.first and line <= file.last then + file_loc = { index = fi, name = file.name } + + for hi, hunk in ipairs(file.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + hunk_loc = { index = hi, name = hunk.hash } + first, last = hunk.first, hunk.last + + break + end + end + + break + end + end + + break + end + end + + return { section = section_loc, file = file_loc, hunk = hunk_loc, first = first, last = last } +end + +---@param cursor CursorLocation +---@return number +function Ui:resolve_cursor_location(cursor) + if #self.item_index == 0 then + logger.debug("[UI] No items to resolve cursor location") + return 1 + end + + if not cursor.section then + logger.debug("[UI] No Cursor Section") + cursor.section = { index = 1, name = "" } + end + + local section = Collection.new(self.item_index):find(function(s) + return s.name == cursor.section.name + end) + + if not section then + logger.debug("[UI] No Section Found '" .. cursor.section.name .. "'") + + cursor.file = nil + cursor.hunk = nil + section = self.item_index[cursor.section.index] or self.item_index[#self.item_index] + end + + if not cursor.file or not section.items or #section.items == 0 then + logger.debug("[UI] No file - using section.first") + return section.first + end + + local file = Collection.new(section.items):find(function(f) + return f.name == cursor.file.name + end) + + if not file then + logger.debug(("[UI] No file found %q"):format(cursor.file.name)) + + cursor.hunk = nil + file = section.items[cursor.file.index] or section.items[#section.items] + end + + if not cursor.hunk or not file.diff.hunks or #file.diff.hunks == 0 then + logger.debug("[UI] No hunk - using file.first") + return file.first + end + + local hunk = Collection.new(file.diff.hunks):find(function(h) + return h.hash == cursor.hunk.name + end) or file.diff.hunks[cursor.hunk.index] or file.diff.hunks[#file.diff.hunks] + + logger.debug(("[UI] Using hunk.first %q"):format(cursor.hunk.name)) + + return hunk.first +end ---@return table|nil function Ui:get_hunk_or_filename_under_cursor() @@ -583,9 +561,6 @@ function Ui:render(...) if not vim.tbl_isempty(self.layout) then self._old_node_attributes = gather_nodes(self.layout) - - -- Restoring cursor location for status buffer on update. Might need to move this, as it doesn't really make sense - -- here. end self.layout = root From d6efb5cdb4485c8070ef3dd204131c14e36bc96f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:08:17 +0100 Subject: [PATCH 187/443] Clean the path when selecting worktree --- lua/neogit/popups/worktree/actions.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 6698ef77b..6f873dcca 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -32,7 +32,8 @@ local function get_path(prompt) end until not dir:exists() - return dir:absolute() + local path, _ = dir:absolute():gsub("%s", "_") + return path end M.checkout_worktree = operations("checkout_worktree", function() From fdc903e2d428159efbe6ba8dc2c824e068b57223 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:08:39 +0100 Subject: [PATCH 188/443] Update license --- LICENSE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 09c1b7ad1..92a740fd8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2020 TimUntersberger +Copyright (c) 2020-2023 TimUntersberger +Copyright (c) 2024- CKolkey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c1e2fc634eafe6977ddefe6d4615e49378418a94 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:08:57 +0100 Subject: [PATCH 189/443] Buggy: try again later --- lua/neogit/lib/buffer.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 7f684fcc6..b947cced0 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -579,7 +579,8 @@ function Buffer.create(config) if vim.fn.has("nvim-0.10") then buffer:set_window_option("spell", false) buffer:set_window_option("wrap", false) - buffer:set_window_option("winfixbuf", true) + -- TODO: Need to find a way to turn this off properly when unloading plugin + -- buffer:set_window_option("winfixbuf", true) end if config.after then From 33766d43523faf0bae411fcda2ef2938fa81d9e9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:09:29 +0100 Subject: [PATCH 190/443] Lint --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/buffers/status/init.lua | 11 +---------- lua/neogit/buffers/status/ui.lua | 19 +++++++------------ lua/neogit/lib/git/rebase.lua | 1 - lua/neogit/lib/git/sequencer.lua | 1 - lua/neogit/popups/help/actions.lua | 10 +++++++--- 6 files changed, 16 insertions(+), 28 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 11b5006ed..2c8f50425 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -114,7 +114,7 @@ function M:open(kind) -- in order to use them as match patterns. local selected_path = vim.fn.trim(c.value) - -- Recursively navigate the layout until we hit NeogitDiffHeader leafs + -- Recursively navigate the layout until we hit NeogitDiffHeader leaf nodes -- Forward declaration required to avoid missing global error local diff_headers = {} local function find_diff_headers(layout) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8ea023e27..9e6550b57 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,6 +1,3 @@ --- TODO --- on-close hook to teardown stuff --- -- TODO: When launching the fuzzy finder, any refresh attempted will raise an exception because the set_folds() function -- cannot be called when the buffer is not focused, as it's not a proper API. We could implement some kind of freeze -- mechanism to prevent the buffer from refreshing while the fuzzy finder is open. @@ -20,10 +17,7 @@ local git = require("neogit.lib.git") local watcher = require("neogit.watcher") local a = require("plenary.async") local input = require("neogit.lib.input") -local util = require("neogit.lib.util") - local logger = require("neogit.logger") -- TODO: Add logging -local notification = require("neogit.lib.notification") -- TODO local api = vim.api local fn = vim.fn @@ -1100,10 +1094,7 @@ function M:refresh(partial, reason) logger.debug("[STATUS][Refresh Callback] Rendering UI") self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - local cursor_line = math.min( - fn.line("$"), - self.buffer.ui:resolve_cursor_location(cursor) - ) + local cursor_line = math.min(fn.line("$"), self.buffer.ui:resolve_cursor_location(cursor)) logger.debug("[STATUS][Refresh Callback] Moving Cursor to line " .. cursor_line) self.buffer:move_cursor(cursor_line) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 752814e60..4b4a7896d 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -150,18 +150,13 @@ end) local RebaseSection = Component.new(function(props) return col.tag("Section")({ - row( - util.merge( - props.title, - { - text(" ("), - text(props.current), - text("/"), - text(#props.items - 1), - text(")") - } - ) - ), + row(util.merge(props.title, { + text(" ("), + text(props.current), + text("/"), + text(#props.items - 1), + text(")"), + })), col(map(props.items, props.render)), EmptyLine, }, { diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 4fe9b71a3..a0d4e5bfc 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -1,7 +1,6 @@ local logger = require("neogit.logger") local client = require("neogit.client") local notification = require("neogit.lib.notification") -local util = require("neogit.lib.util") local cli = require("neogit.lib.git.cli") local M = {} diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index eafd53a6b..1ed3a6c53 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -1,4 +1,3 @@ -local util = require("neogit.lib.util") local M = {} -- .git/sequencer/todo does not exist when there is only one commit left. diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 980fbdf70..8e77151c7 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -71,9 +71,13 @@ M.popups = function(env) { "LogPopup", "Log", popups.open("log", function(p) p(env.log) end) }, - { "CherryPickPopup", "Cherry Pick", popups.open("cherry_pick", function(p) - p(env.cherry_pick) - end) }, + { + "CherryPickPopup", + "Cherry Pick", + popups.open("cherry_pick", function(p) + p(env.cherry_pick) + end), + }, { "BranchPopup", "Branch", popups.open("branch", function(p) p(env.branch) end) }, From 320671e408db549159be16dbd5cdc73250b153d2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:26:04 +0100 Subject: [PATCH 191/443] Remove --- lua/neogit/popups/commit/init.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index a3b8a654d..3262e9fdd 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -19,10 +19,6 @@ function M.create(env) :option("C", "reuse-message", "", "Reuse commit message") :group_heading("Create") :action("c", "Commit", actions.commit) - :action_if(vim.fn.has("git-absorb") == 0, "b", "Absorb", function() - -- TODO: https://github.com/SuperBo/fugit2.nvim/blob/dev/lua/fugit2/git_absorb.lua - vim.system({ "git", "absorb", "--and-rebase" }, { env = { GIT_SEQUENCE_EDITOR = ":" } }):wait() - end) :new_action_group("Edit HEAD") :action("e", "Extend", actions.extend) :action("w", "Reword", actions.reword) From 8357458ea501a0b8229c4b1e1cc546e4ee465b33 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 12 Mar 2024 23:57:17 +0100 Subject: [PATCH 192/443] Don't try to capture cursor location when buffer is unfocused --- lua/neogit/buffers/status/init.lua | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 9e6550b57..675fd7b52 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1088,16 +1088,20 @@ function M:refresh(partial, reason) return end - local cursor = self.buffer.ui:get_cursor_location() - logger.debug("[STATUS][Refresh Callback] Cursor: " .. vim.inspect(cursor)) + local cursor + if self.buffer:is_focused() then + cursor = self.buffer.ui:get_cursor_location() + logger.debug("[STATUS][Refresh Callback] Cursor: " .. vim.inspect(cursor)) + end logger.debug("[STATUS][Refresh Callback] Rendering UI") self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - local cursor_line = math.min(fn.line("$"), self.buffer.ui:resolve_cursor_location(cursor)) - - logger.debug("[STATUS][Refresh Callback] Moving Cursor to line " .. cursor_line) - self.buffer:move_cursor(cursor_line) + if cursor then + local cursor_line = math.min(fn.line("$"), self.buffer.ui:resolve_cursor_location(cursor)) + logger.debug("[STATUS][Refresh Callback] Moving Cursor to line " .. cursor_line) + self.buffer:move_cursor(cursor_line) + end api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) From e8459fdf4a1bfc34663107230834b269ab0302d6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 14 Mar 2024 21:16:08 +0100 Subject: [PATCH 193/443] Better handling for new files that are added to _unstaged_ via intend to add. --- lua/neogit/buffers/status/init.lua | 43 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 675fd7b52..f8df43328 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -99,7 +99,7 @@ function M:open(kind) local patches = {} local untracked_files = {} local unstaged_files = {} - local staged_files_new = {} + local new_files = {} local staged_files_modified = {} local stashes = {} @@ -137,10 +137,14 @@ function M:open(kind) if section.name == "untracked" then table.insert(untracked_files, item.escaped_path) elseif section.name == "unstaged" then - table.insert(unstaged_files, item.escaped_path) + if selection.item.mode == "A" then + table.insert(new_files, item.escaped_path) + else + table.insert(unstaged_files, item.escaped_path) + end elseif section.name == "staged" then if item.mode == "N" then - table.insert(staged_files_new, item.escaped_path) + table.insert(new_files, item.escaped_path) else table.insert(staged_files_modified, item.escaped_path) end @@ -180,12 +184,12 @@ function M:open(kind) git.index.checkout(unstaged_files) end - if #staged_files_new > 0 then - git.index.reset(staged_files_new) + if #new_files > 0 then + git.index.reset(new_files) a.util.scheduler() - for _, file in ipairs(staged_files_new) do + for _, file in ipairs(new_files) do local bufnr = fn.bufexists(file.name) if bufnr and bufnr > 0 then api.nvim_buf_delete(bufnr, { force = true }) @@ -500,7 +504,20 @@ function M:open(kind) elseif section == "unstaged" then message = ("Discard %q?"):format(selection.item.name) action = function() - git.index.checkout { selection.item.name } + if selection.item.mode == "A" then + git.index.reset { selection.item.escaped_path } + + a.util.scheduler() + + local bufnr = fn.bufexists(selection.item.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(selection.item.escaped_path) + else + git.index.checkout { selection.item.name } + end end elseif section == "staged" then message = ("Discard %q?"):format(selection.item.name) @@ -588,14 +605,14 @@ function M:open(kind) elseif section == "staged" then message = ("Discard %s files?"):format(#selection.section.items) action = function() - local staged_files_new = {} + local new_files = {} local staged_files_modified = {} local staged_files_renamed = {} local staged_files_deleted = {} for _, item in ipairs(selection.section.items) do - if item.mode == "N" then - table.insert(staged_files_new, item.escaped_path) + if item.mode == "N" or item.mode == "A" then + table.insert(new_files, item.escaped_path) elseif item.mode == "M" then table.insert(staged_files_modified, item.escaped_path) elseif item.mode == "R" then @@ -607,13 +624,13 @@ function M:open(kind) end end - if #staged_files_new > 0 then + if #new_files > 0 then -- ensure the file is deleted - git.index.reset(staged_files_new) + git.index.reset(new_files) a.util.scheduler() - for _, file in ipairs(staged_files_new) do + for _, file in ipairs(new_files) do local bufnr = fn.bufexists(file.name) if bufnr and bufnr > 0 then api.nvim_buf_delete(bufnr, { force = true }) From aa38b039c73ffcbaa9aa4e8480f8109d89fc0199 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 15 Mar 2024 22:26:56 +0100 Subject: [PATCH 194/443] Reimplement conflict resolution. Still needs visual mode, hunk discarding, and section discarding. --- lua/neogit/buffers/status/init.lua | 78 +++++++++++++++++++++++------- lua/neogit/lib/git/cli.lua | 6 +-- lua/neogit/lib/git/status.lua | 3 +- lua/neogit/lib/ui/init.lua | 21 ++++++++ 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index f8df43328..d985eecc0 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -18,6 +18,7 @@ local watcher = require("neogit.watcher") local a = require("plenary.async") local input = require("neogit.lib.input") local logger = require("neogit.logger") -- TODO: Add logging +local notification = require("neogit.lib.notification") local api = vim.api local fn = vim.fn @@ -486,7 +487,7 @@ function M:open(kind) end local section = selection.section.name - local action, message + local action, message, choices if selection.item and selection.item.first == fn.line(".") then -- Discard File if section == "untracked" then @@ -502,21 +503,42 @@ function M:open(kind) fn.delete(selection.item.escaped_path) end elseif section == "unstaged" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode == "A" then - git.index.reset { selection.item.escaped_path } - - a.util.scheduler() + if selection.item.mode == "UU" then + choices = { "&ours", "&theirs", "&conflict", "&abort" } + action = function() + local choice = input.get_choice( + "Discard conflict by taking...", + { values = choices, default = #choices } + ) - local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 then - api.nvim_buf_delete(bufnr, { force = true }) + if choice == "o" then + git.cli.checkout.ours.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + elseif choice == "t" then + git.cli.checkout.theirs.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + elseif choice == "c" then + git.cli.checkout.merge.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } end + end + else + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "A" then + git.index.reset { selection.item.escaped_path } - fn.delete(selection.item.escaped_path) - else - git.index.checkout { selection.item.name } + a.util.scheduler() + + local bufnr = fn.bufexists(selection.item.name) + if bufnr and bufnr > 0 then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(selection.item.escaped_path) + else + git.index.checkout { selection.item.name } + end end end elseif section == "staged" then @@ -537,6 +559,7 @@ function M:open(kind) git.index.reset { selection.item.escaped_path } git.index.checkout { selection.item.escaped_path } elseif selection.item.mode == "R" then + -- https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#L675 git.index.reset_HEAD(selection.item.name, selection.item.original_name) git.index.checkout { selection.item.original_name } elseif selection.item.mode == "D" then @@ -555,8 +578,15 @@ function M:open(kind) end end elseif selection.item then -- Discard Hunk + if selection.item.mode == "UU" then + -- TODO: https://github.com/emacs-mirror/emacs/blob/master/lisp/vc/smerge-mode.el + notification.warn("Resolve conflicts in file before discarding hunks.") + return + end + local hunk = self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] + local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) if section == "untracked" then @@ -598,9 +628,23 @@ function M:open(kind) end end elseif section == "unstaged" then - message = ("Discard %s files?"):format(#selection.section.items) - action = function() - git.index.checkout_unstaged() + local conflict = false + for _, item in ipairs(selection.section.items) do + if item.mode == "UU" then + conflict = true + break + end + end + + if conflict then + -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#L626 + notification.warn("Resolve conflicts before discarding section.") + return + else + message = ("Discard %s files?"):format(#selection.section.items) + action = function() + git.index.checkout_unstaged() + end end elseif section == "staged" then message = ("Discard %s files?"):format(#selection.section.items) @@ -668,7 +712,7 @@ function M:open(kind) end end - if action and input.get_permission(message) then + if action and (choices or input.get_permission(message)) then action() self:refresh() end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index ead780312..eba7b4562 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -236,6 +236,7 @@ local configurations = { detach = "--detach", ours = "--ours", theirs = "--theirs", + merge = "--merge", }, aliases = { track = function(tbl) @@ -268,11 +269,6 @@ local configurations = { return tbl.args(branch, start_point).b() end end, - file = function(tbl) - return function(file) - return tbl.args(file) - end - end, }, }, diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 83e95c8d9..09cc56cd5 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -38,6 +38,7 @@ local function update_file(cwd, file, mode, name, original_name) end local tag_pattern = "(.-)%-([0-9]+)%-g%x+$" +local match_header = "# ([%w%.]+) (.+)" local match_kind = "(.) (.+)" local match_u = "(..) (....) (%d+) (%d+) (%d+) (%d+) (%w+) (%w+) (%w+) (.+)" local match_1 = "(.)(.) (....) (%d+) (%d+) (%d+) (%w+) (%w+) (.+)" @@ -72,7 +73,7 @@ local function update_status(state) end) for _, l in ipairs(result) do - local header, value = l:match("# ([%w%.]+) (.+)") + local header, value = l:match(match_header) if header then if header == "branch.head" then head.branch = value diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 35a829f25..36f6b47bb 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -174,6 +174,18 @@ end function Ui:item_hunks(item, first_line, last_line, partial) local hunks = {} + -- TODO: Move this to lib.git.diff + -- local diff = require("neogit.lib.git").cli.diff.check.call_sync { hidden = true, ignore_error = true } + -- local conflict_markers = {} + -- if diff.code == 2 then + -- for _, out in ipairs(diff.stdout) do + -- local line = string.gsub(out, "^" .. item.name .. ":", "") + -- if line ~= out and string.match(out, "conflict") then + -- table.insert(conflict_markers, tonumber(string.match(line, "%d+"))) + -- end + -- end + -- end + if not item.folded and item.diff.hunks then for _, h in ipairs(item.diff.hunks) do if h.first <= last_line and h.last >= first_line then @@ -195,12 +207,21 @@ function Ui:item_hunks(item, first_line, last_line, partial) table.insert(hunk_lines, item.diff.lines[i]) end + -- local conflict = false + -- for _, n in ipairs(conflict_markers) do + -- if from <= n and n <= to then + -- conflict = true + -- break + -- end + -- end + local o = { from = from, to = to, __index = h, hunk = h, lines = hunk_lines, + -- conflict = conflict, } setmetatable(o, o) From 1acf155fda9ef0b59a3cad599ab5614fa079ddd3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 16 Mar 2024 15:34:26 +0100 Subject: [PATCH 195/443] Add merge section to status buffer --- lua/neogit/buffers/status/ui.lua | 25 +++++++++++++++++++++++-- lua/neogit/lib/git/merge.lua | 10 ++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 4b4a7896d..a58ed7d99 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -122,6 +122,14 @@ local SectionTitleRebase = Component.new(function(props) end end) +local SectionTitleMerge = Component.new(function(props) + return { + text.highlight("NeogitSectionHeader")(props.title), + text(" "), + text.highlight("NeogitBranch")(props.branch), + } +end) + local Section = Component.new(function(props) return col.tag("Section")({ row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), @@ -290,9 +298,12 @@ local SectionItemSequencer = Component.new(function(item) or (item.action == "onto" and "NeogitGraphBlue") or "NeogitGraphOrange" + local show_action = #item.action > 0 + local action = show_action and util.pad_right(item.action, 6) or "" + return row({ - text.highlight(action_hl)(util.pad_right(item.action, 6)), - text(" "), + text.highlight(action_hl)(action), + text(show_action and " " or ""), text.highlight("Comment")(item.oid:sub(1, 7)), text(" "), text(item.subject), @@ -312,6 +323,9 @@ function M.Status(state, config) local show_tag = state.head.tag.name and state.head.branch ~= "(detached)" + local show_merge = state.merge.head + and not config.sections.sequencer.hidden + local show_rebase = #state.rebase.items > 0 and not config.sections.rebase.hidden @@ -382,6 +396,13 @@ function M.Status(state, config) yankable = state.head.tag.oid, }, EmptyLine, + show_merge and SequencerSection { + title = SectionTitleMerge { title = "Merging", branch = state.merge.branch }, + render = SectionItemSequencer, + items = { { action = "", oid = state.merge.head, subject = state.merge.subject } }, + folded = config.sections.sequencer.folded, + name = "merge", + }, show_rebase and RebaseSection { title = SectionTitleRebase { title = "Rebasing", diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 2ef6d01c2..687dce067 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -37,23 +37,25 @@ function M.abort() end function M.update_merge_status(state) - local repo = require("neogit.lib.git.repository") - if repo.git_root == "" then + local git = require("neogit.lib.git") + if git.repo.git_root == "" then return end state.merge = { head = nil, msg = "", items = {} } - local merge_head = repo:git_path("MERGE_HEAD") + local merge_head = git.repo:git_path("MERGE_HEAD") if not merge_head:exists() then return end state.merge.head = merge_head:read():match("([^\r\n]+)") + state.merge.subject = git.log.message(state.merge.head) - local message = repo:git_path("MERGE_MSG") + local message = git.repo:git_path("MERGE_MSG") if message:exists() then state.merge.msg = message:read():match("([^\r\n]+)") -- we need \r? to support windows + state.merge.branch = state.merge.msg:match("Merge branch '(.*)'$") end end From 077559d2a6a2b646065ed9b5affd89d3b3d4a8c3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 16 Mar 2024 21:01:19 +0100 Subject: [PATCH 196/443] Fix: Sections that have no items still need a first/last so there isn't an error when placing the cursor on them --- lua/neogit/lib/ui/renderer.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 9b99f9ff1..09ba9771d 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -40,17 +40,18 @@ function RendererIndex:add_id(node, id) self.index[id] = node end --- For tracking item locations within status buffer. Needed to make selections. -function RendererIndex:add_section(name) +---For tracking item locations within status buffer. Needed to make selections. +---@param name string +---@param first number +---@param last number +function RendererIndex:add_section(name, first, last) self.items[#self.items].name = name + self.items[#self.items].first = first + self.items[#self.items].last = last table.insert(self.items, { items = {} }) end function RendererIndex:add_item(item, first, last) - if not self.items[#self.items].first then - self.items[#self.items].first = first - 1 - end - self.items[#self.items].last = last item.first = first @@ -195,7 +196,7 @@ function Renderer:_render_child(child) child.position.row_end = #self.buffer.line if child.options.section then - self.index:add_section(child.options.section) + self.index:add_section(child.options.section, child.position.row_start, child.position.row_end) end if child.options.item then From 5b6bdad612cac8292e696167f7f532eb7dfa97ec Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Mar 2024 23:04:01 +0100 Subject: [PATCH 197/443] Add (colorless) fold support for nvim < 0.10 --- lua/neogit/lib/buffer.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b947cced0..802025461 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -576,11 +576,17 @@ function Buffer.create(config) buffer:set_buffer_option("readonly", true) end - if vim.fn.has("nvim-0.10") then + if vim.fn.has("nvim-0.10") == 1 then buffer:set_window_option("spell", false) buffer:set_window_option("wrap", false) -- TODO: Need to find a way to turn this off properly when unloading plugin -- buffer:set_window_option("winfixbuf", true) + else + _G.NeogitFoldText = function() + return vim.fn.getline(vim.v.foldstart) + end + + buffer:set_buffer_option("foldtext", "v:lua._G.NeogitFoldText()") end if config.after then From 76282693c1dbaf2514520d6134a3d2587e907ad4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Mar 2024 23:13:29 +0100 Subject: [PATCH 198/443] Extend copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 92a740fd8..73315a6a0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ MIT License Copyright (c) 2020-2023 TimUntersberger -Copyright (c) 2024- CKolkey +Copyright (c) 2024-2025 CKolkey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From fe4fcb3ca763ba9d861e87e29ab2be935f2140e1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Mar 2024 23:15:39 +0100 Subject: [PATCH 199/443] Remove ftplugin for old status, and old status buffer --- ftplugin/NeogitStatus.vim | 7 ------- lua/neogit/status.lua | 7 ------- 2 files changed, 14 deletions(-) delete mode 100644 ftplugin/NeogitStatus.vim delete mode 100644 lua/neogit/status.lua diff --git a/ftplugin/NeogitStatus.vim b/ftplugin/NeogitStatus.vim deleted file mode 100644 index 318746396..000000000 --- a/ftplugin/NeogitStatus.vim +++ /dev/null @@ -1,7 +0,0 @@ -" Only do this when not done yet for this buffer -if exists("b:did_ftplugin") - finish -endif -let b:did_ftplugin = 1 - -au BufWipeout lua require 'neogit.status'.close(true) diff --git a/lua/neogit/status.lua b/lua/neogit/status.lua deleted file mode 100644 index 3110d071d..000000000 --- a/lua/neogit/status.lua +++ /dev/null @@ -1,7 +0,0 @@ -local M = {} - --- Some autocmd seems to be calling this still.. Maybe it's cached? No idea. --- Without this, closing the neogit status buffer will create an error. -function M.close() end - -return M From 141b6f07a57499618730a9bf1d706437f1e55a56 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 18 Mar 2024 23:52:47 +0100 Subject: [PATCH 200/443] Set signcolumn for buffers --- lua/neogit/lib/buffer.lua | 1 + lua/neogit/lib/popup/init.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 802025461..3f567b88d 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -524,6 +524,7 @@ function Buffer.create(config) buffer:set_buffer_option("swapfile", false) if win then + buffer:set_window_option("statuscolumn", config.status_column or "") buffer:set_window_option("foldenable", true) buffer:set_window_option("foldlevel", 99) buffer:set_window_option("foldminlines", 0) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 6c99b53b4..2e03df8d6 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -632,6 +632,7 @@ function M:show() filetype = "NeogitPopup", kind = config.values.popup.kind, mappings = mappings, + status_column = " ", after = function(buf, _win) buf:set_window_option("cursorline", false) buf:set_window_option("list", false) From 20802f0684b3fed0593ce51cfe7f125fe03f6165 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 20 Mar 2024 11:22:35 +0100 Subject: [PATCH 201/443] add nicer status column --- lua/neogit/buffers/commit_select_view/init.lua | 1 + lua/neogit/buffers/log_view/init.lua | 1 + lua/neogit/buffers/reflog_view/init.lua | 1 + lua/neogit/buffers/status/init.lua | 1 + 4 files changed, 4 insertions(+) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index d8fa7fa40..0d1c1d99c 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -37,6 +37,7 @@ function M:open(action) self.buffer = Buffer.create { name = "NeogitCommitSelectView", filetype = "NeogitCommitSelectView", + status_column = " ", kind = config.values.commit_select_view.kind, mappings = { v = { diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index ac5056006..62518fd29 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -42,6 +42,7 @@ function M:open() filetype = "NeogitLogView", kind = config.values.log_view.kind, context_highlight = false, + status_column = " ", mappings = { v = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 59306749c..27730eeb6 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -34,6 +34,7 @@ function M:open(_) name = "NeogitReflogView", filetype = "NeogitReflogView", kind = config.values.reflog_view.kind, + status_column = " ", context_highlight = true, mappings = { v = { diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index d985eecc0..0743bf484 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -80,6 +80,7 @@ function M:open(kind) context_highlight = true, kind = kind, disable_line_numbers = config.values.disable_line_numbers, + status_column = " ", autocmds = { ["BufUnload"] = function() logger.debug("[STATUS] Running BufUnload autocmd") From c0b82ba91b02342b1f74ecb23ccd15801e713ece Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 20 Mar 2024 11:22:54 +0100 Subject: [PATCH 202/443] check validity of buffer before trying to close it --- lua/neogit/buffers/status/init.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 0743bf484..f21927325 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -174,7 +174,7 @@ function M:open(kind) for _, file in ipairs(untracked_files) do local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end @@ -193,7 +193,7 @@ function M:open(kind) for _, file in ipairs(new_files) do local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end @@ -497,7 +497,7 @@ function M:open(kind) a.util.scheduler() local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end @@ -532,7 +532,7 @@ function M:open(kind) a.util.scheduler() local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end @@ -551,7 +551,7 @@ function M:open(kind) a.util.scheduler() local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end @@ -621,7 +621,7 @@ function M:open(kind) for _, file in ipairs(selection.section.items) do local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end @@ -677,7 +677,7 @@ function M:open(kind) for _, file in ipairs(new_files) do local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 then + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) end From 444bed241daf88b4a570718e8e8d4a78ed564c7e Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 20 Mar 2024 12:53:08 +0100 Subject: [PATCH 203/443] Add status column to editors --- lua/neogit/buffers/editor/init.lua | 1 + lua/neogit/buffers/rebase_editor/init.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 8941a70aa..d52a07256 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -69,6 +69,7 @@ function M:open(kind) buftype = "", kind = kind, modifiable = true, + status_column = " ", readonly = false, after = function(buffer) -- Populate help lines with mappings for buffer diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 9bc196a40..00b880a91 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -73,6 +73,7 @@ function M:open(kind) load = true, filetype = "NeogitRebaseTodo", buftype = "", + status_column = " ", kind = kind, modifiable = true, disable_line_numbers = config.values.disable_line_numbers, From 2f81f3a5512e3fab8a42e00fcae449ebf047a028 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 20:04:23 +0100 Subject: [PATCH 204/443] Pcall this so if it fails it's not a big deal --- lua/neogit/lib/buffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 3f567b88d..5af97c296 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -196,7 +196,7 @@ function Buffer:set_text(first_line, last_line, first_col, last_col, lines) end function Buffer:move_cursor(line) - api.nvim_win_set_cursor(0, { line, 0 }) + pcall(api.nvim_win_set_cursor, 0, { line, 0 }) end function Buffer:cursor_line() From 1fee39256eb2ca557346d3230bde519b56ffe85f Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 21 Mar 2024 23:40:23 +0100 Subject: [PATCH 205/443] Replace json parsing with string eval to lua tables. Having bug reports that we don't properly escape string fields, and this approach basically sidesteps all that. --- lua/neogit/lib/git/log.lua | 3 +- lua/neogit/lib/git/refs.lua | 2 +- lua/neogit/lib/json.lua | 76 +++++++------------------------------ 3 files changed, 16 insertions(+), 65 deletions(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 354b16ebd..b716666b6 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -367,8 +367,7 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) .show_popup(false) .call({ hidden = hidden, ignore_error = hidden }).stdout - local commits = - json.decode(output, { escaped_fields = { "body", "author_name", "committer_name", "subject" } }) + local commits = json.decode(output) if vim.tbl_isempty(commits) then return {} end diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 8d8e9fd75..b2cf11a78 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -26,7 +26,7 @@ local json_template = json.encode { function M.list_parsed() local refs = cli["for-each-ref"].format(json_template).call_sync():trim().stdout - local result = json.decode(refs, { escaped_fields = { "subject" } }) + local result = json.decode(refs) local output = { local_branch = {}, diff --git a/lua/neogit/lib/json.lua b/lua/neogit/lib/json.lua index f80bc94cb..a7485ef3b 100644 --- a/lua/neogit/lib/json.lua +++ b/lua/neogit/lib/json.lua @@ -1,84 +1,36 @@ +local logger = require("neogit.logger") local M = {} -local function array_wrap(lines) - local array = "[" .. table.concat(lines, "\\n") .. "]" - - -- Remove trailing comma from last object in array - array, _ = array:gsub(",]", "]") - - -- Remove escaped newlines from in-between objects - array, _ = array:gsub("},\\n{", "},{") - - return array -end - ----Escape any double-quote characters, or escape codes, in the body ----@param json_str string unparsed json ----@param field string The json key to escape the body for -local function escape_field(json_str, field) - local pattern = ([[("%s":")(.-)(","%%l)]]):format(field) - - json_str, _ = json_str:gsub(pattern, function(before, value, after) - return table.concat({ before, vim.fn.escape(value, [[\"]]), after }, "") - end) - - return json_str -end - -local function error_msg(result, input) - local msg = vim.split(result, " ") - local char_index = tonumber(msg[#msg]) - - return "Failed to parse log json!: " - .. result - .. "\n" - .. input:sub(char_index - 30, char_index - 1) - .. "<" - .. input:sub(char_index, char_index) - .. ">" - .. input:sub(char_index + 1, char_index + 30) +local function parse_line(line) + return assert(loadstring(string.format("return { %s }", line)))() end ---Decode a list of json formatted lines into a lua table ---@param lines table ----@param opts? table ---@return table -function M.decode(lines, opts) +function M.decode(lines) if not lines[1] then return {} end - opts = opts or {} - - local json_array = array_wrap(lines) - - if opts.escaped_fields then - for _, field in ipairs(opts.escaped_fields) do - json_array = escape_field(json_array, field) - end - end - - local ok, result = pcall(vim.json.decode, json_array, { luanil = { object = true, array = true } }) - if not ok then - error(error_msg(result, json_array)) - end - - if not result then - error("Json failed to parse!") + local result = {} + for _, line in ipairs(lines) do + table.insert(result, parse_line(line)) end + logger.debug(result) return result end ----Convert a lua table to json string. Trailing comma is added because the expectation ----is to use json.decode from this same module to parse the result. ----The 'null' key is because the escape_field function won't match the _last_ field in an object, ----so by adding a null field, we can guarantee that the last _real_ field will be escaped. ---@param tbl table Key/value pairs to encode as json ---@return string function M.encode(tbl) - local json, _ = string.format([[%s,]], vim.json.encode(tbl)):gsub([[}]], [[,"null":null}]]) - return json + local out = {} + for k, v in pairs(tbl) do + table.insert(out, string.format([=[["%s"]=[===[%s]===]]=], k, v)) + end + + return table.concat(out, ",") end return M From fde95b7bf6a1235ebd6db5f49267d1d7b5295462 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 00:00:47 +0100 Subject: [PATCH 206/443] Handle multi-line commit messages by joining all lines, then re-splitting on the record separators. --- lua/neogit/lib/json.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/json.lua b/lua/neogit/lib/json.lua index a7485ef3b..1ab54c26a 100644 --- a/lua/neogit/lib/json.lua +++ b/lua/neogit/lib/json.lua @@ -1,4 +1,3 @@ -local logger = require("neogit.logger") local M = {} local function parse_line(line) @@ -13,12 +12,13 @@ function M.decode(lines) return {} end + lines = vim.split(table.concat(lines, ""), "\30", { trimempty = true }) + local result = {} for _, line in ipairs(lines) do table.insert(result, parse_line(line)) end - logger.debug(result) return result end @@ -30,7 +30,7 @@ function M.encode(tbl) table.insert(out, string.format([=[["%s"]=[===[%s]===]]=], k, v)) end - return table.concat(out, ",") + return table.concat(out, ",") .. "%x1E" end return M From 396e736a0ec14cc06040ef58e22d879b4b3ffeae Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 10:05:04 +0100 Subject: [PATCH 207/443] Use record separators instead of eval'ing the git output. --- lua/neogit/lib/json.lua | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lua/neogit/lib/json.lua b/lua/neogit/lib/json.lua index 1ab54c26a..0533ad782 100644 --- a/lua/neogit/lib/json.lua +++ b/lua/neogit/lib/json.lua @@ -1,8 +1,8 @@ local M = {} -local function parse_line(line) - return assert(loadstring(string.format("return { %s }", line)))() -end +local record_separator = { dec = "\30", hex = "%x1E" } +local field_separator = { dec = "\31", hex = "%x1F" } +local pair_separator = { dec = "\29", hex = "%x1D" } ---Decode a list of json formatted lines into a lua table ---@param lines table @@ -12,14 +12,22 @@ function M.decode(lines) return {} end - lines = vim.split(table.concat(lines, ""), "\30", { trimempty = true }) + local lines = table.concat(lines, "") + local records = vim.tbl_map(function(record) + local fields = vim.tbl_map(function(field) + return vim.split(field, pair_separator.dec, { trimempty = true }) + end, vim.split(record, field_separator.dec, { trimempty = true })) - local result = {} - for _, line in ipairs(lines) do - table.insert(result, parse_line(line)) - end + local output = {} + for _, field in ipairs(fields) do + local key, value = unpack(field) + output[key] = value or "" + end + + return output + end, vim.split(lines, record_separator.dec, { trimempty = true })) - return result + return records end ---@param tbl table Key/value pairs to encode as json @@ -27,10 +35,10 @@ end function M.encode(tbl) local out = {} for k, v in pairs(tbl) do - table.insert(out, string.format([=[["%s"]=[===[%s]===]]=], k, v)) + table.insert(out, string.format("%s%s%s", k, pair_separator.hex, v)) end - return table.concat(out, ",") .. "%x1E" + return table.concat(out, field_separator.hex) .. record_separator.hex end return M From d61310caf20b81f7746c1a227f2129872998a6b9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 11:08:55 +0100 Subject: [PATCH 208/443] Better parsing with fewer loops/allocations --- lua/neogit/lib/json.lua | 47 ++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/lua/neogit/lib/json.lua b/lua/neogit/lib/json.lua index 0533ad782..d04d36cce 100644 --- a/lua/neogit/lib/json.lua +++ b/lua/neogit/lib/json.lua @@ -4,33 +4,46 @@ local record_separator = { dec = "\30", hex = "%x1E" } local field_separator = { dec = "\31", hex = "%x1F" } local pair_separator = { dec = "\29", hex = "%x1D" } ----Decode a list of json formatted lines into a lua table ----@param lines table +-- Matches/captures each key/value pair of fields in a record +-- 1. \31? - Optionally has a leading field separator (first field won't have this) +-- 2. ([^\31\29]*) - Capture all characters that are not field or pair separators +-- 3. \29 - Pair separator +-- 4. ([^\31]*) - Capture all characters that are not field separators +-- 5. \31? - Optionally has a trailing field separator (last field won't have this) +local pattern = "\31?([^\31\29]*)\29([^\31]*)\31?" + +---Parses a record string into a lua table +---@param record_string string +---@return table +local function parse_record(record_string) + local record = {} + + for key, value in string.gmatch(record_string, pattern) do + record[key] = value or "" + end + + return record +end + +---Decode a list of delimeted lines into lua tables +---@param lines string[] ---@return table function M.decode(lines) if not lines[1] then return {} end + -- join lines into one string, since a record could potentially span multiple + -- lines if the subject/body fields contain \n or \r characters. local lines = table.concat(lines, "") - local records = vim.tbl_map(function(record) - local fields = vim.tbl_map(function(field) - return vim.split(field, pair_separator.dec, { trimempty = true }) - end, vim.split(record, field_separator.dec, { trimempty = true })) - - local output = {} - for _, field in ipairs(fields) do - local key, value = unpack(field) - output[key] = value or "" - end - - return output - end, vim.split(lines, record_separator.dec, { trimempty = true })) - return records + -- Split the string into records, using the record separator character as a delimeter. + -- If you commit message contains record separator control characters... this won't work, + -- and you should feel bad about your choices. + return vim.tbl_map(parse_record, vim.split(lines, record_separator.dec, { trimempty = true })) end ----@param tbl table Key/value pairs to encode as json +---@param tbl table Key/value pairs to format with delimeters ---@return string function M.encode(tbl) local out = {} From b807e1ae69254443a189db4a58a8d003f4eac702 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 19:59:06 +0100 Subject: [PATCH 209/443] Update json spec --- tests/specs/neogit/lib/json_spec.lua | 32 +++++++++------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/tests/specs/neogit/lib/json_spec.lua b/tests/specs/neogit/lib/json_spec.lua index f9f5b5ce5..1837b607b 100644 --- a/tests/specs/neogit/lib/json_spec.lua +++ b/tests/specs/neogit/lib/json_spec.lua @@ -2,44 +2,32 @@ local subject = require("neogit.lib.json") describe("lib.json", function() describe("#encode", function() - it("turns a lua table into json with a trailing comma", function() - assert.are.same('{"foo":"bar","null":null},', subject.encode { foo = "bar" }) + it("turns lua table into delimeted string", function() + assert.are.same("foo%x1Dbar%x1E", subject.encode { foo = "bar" }) end) end) describe("#decode", function() it("can decode multiple json objects", function() local input = { - '{"foo":"bar"},', - '{"baz":"daz"},', + "baz\29boo\31foo\29bar\30", + "biz\29bip\31bop\29bip\30", } - assert.are.same({ { foo = "bar" }, { baz = "daz" } }, subject.decode(input)) - end) - - it("can escape specified fields", function() - local input = { - [[{"foo":""invalid"","bar":"valid"},]], - } - - assert.are.same( - { { foo = '"invalid"', bar = "valid" } }, - subject.decode(input, { escaped_fields = { "foo" } }) - ) + assert.are.same({ { foo = "bar", baz = "boo" }, { biz = "bip", bop = "bip" } }, subject.decode(input)) end) it("can decode git log output", function() local input = { - '{"tree":"d7636d8291992cd11f514b4a5e7fcd3148ed4cf4","subject":"Pull encode logic into json module","oid":"ce412df53d565c8c496cfcc806fe11582b6a9b10","encoding":"","rel_date":"5 minutes ago","abbreviated_parent":"33fe8284","abbreviated_commit":"ce412df5","abbreviated_tree":"d7636d82","author_name":"Cameron","parent":"33fe8284052c90d050fb1557eb8f51e4224a16d5","author_date":"Mon, 1 Jan 2024 21:58:45 +0100","body":"","ref_name":"HEAD -> fix/json-parsing, origin/fix/json-parsing","committer_name":"Cameron","committer_email":"Alleyria@gmail.com","sanitized_subject_line":"Pull-encode-logic-into-json-module","commit_notes":"","committer_date":"Mon, 1 Jan 2024 21:58:45 +0100","author_email":"Alleyria@gmail.com"},', + "tree\29d7636d8291992cd11f514b4a5e7fcd3148ed4cf4\31subject\29Pull encode logic into json module\31oid\29ce412df53d565c8c496cfcc806fe11582b6a9b10\31encoding\29\31rel_date\29Five minutes ago\31abbreviated_parent\29f3fe8284\31abbreviated_commit\29ce412df5\31abbreviated_tree\29d7636d82\31author_name\29Cameron\31parent\29f3fe8284052c90d050fb1557eb8f51e4224a16d5\31author_date\29Mon, 1 Jan 2024 21:58:45 +0100\31body\29\31ref_name\29HEAD -> fix/json-parsing, origin/fix/json-parsing\31committer_name\29Cameron\31committer_email\29Alleyria@gmail.com\31sanitized_subject_line\29Pull-encode-logic-into-json-module\31commit_notes\29\31committer_date\29Mon, 1 Jan 2024 21:58:45 +0100\31author_email\29Alleyria@gmail.com\30", } - local parsed = - subject.decode(input, { escaped_fields = { "body", "author_name", "committer_name", "subject" } }) + local parsed = subject.decode(input) assert.are.same({ { abbreviated_commit = "ce412df5", - abbreviated_parent = "33fe8284", + abbreviated_parent = "f3fe8284", abbreviated_tree = "d7636d82", author_date = "Mon, 1 Jan 2024 21:58:45 +0100", author_email = "Alleyria@gmail.com", @@ -51,9 +39,9 @@ describe("lib.json", function() committer_name = "Cameron", encoding = "", oid = "ce412df53d565c8c496cfcc806fe11582b6a9b10", - parent = "33fe8284052c90d050fb1557eb8f51e4224a16d5", + parent = "f3fe8284052c90d050fb1557eb8f51e4224a16d5", ref_name = "HEAD -> fix/json-parsing, origin/fix/json-parsing", - rel_date = "5 minutes ago", + rel_date = "Five minutes ago", sanitized_subject_line = "Pull-encode-logic-into-json-module", subject = "Pull encode logic into json module", tree = "d7636d8291992cd11f514b4a5e7fcd3148ed4cf4", From fdeec2a45dc8a1159fe0b5608725fd9c80e8e9de Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 20:02:09 +0100 Subject: [PATCH 210/443] Rename json -> record since it's not json anymore --- lua/neogit/lib/git/log.lua | 6 +++--- lua/neogit/lib/git/refs.lua | 8 ++++---- lua/neogit/lib/{json.lua => record.lua} | 0 tests/specs/neogit/lib/{json_spec.lua => record_spec.lua} | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) rename lua/neogit/lib/{json.lua => record.lua} (100%) rename tests/specs/neogit/lib/{json_spec.lua => record_spec.lua} (94%) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index b716666b6..1b576d711 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -2,7 +2,7 @@ local cli = require("neogit.lib.git.cli") local diff_lib = require("neogit.lib.git.diff") local util = require("neogit.lib.util") local config = require("neogit.config") -local json = require("neogit.lib.json") +local record = require("neogit.lib.record") local M = {} @@ -341,7 +341,7 @@ local function format(show_signature) fields.verification_flag = "%G?" end - return json.encode(fields) + return record.encode(fields) end ---@param options? string[] @@ -367,7 +367,7 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) .show_popup(false) .call({ hidden = hidden, ignore_error = hidden }).stdout - local commits = json.decode(output) + local commits = record.decode(output) if vim.tbl_isempty(commits) then return {} end diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index b2cf11a78..ebdc918fc 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -1,5 +1,5 @@ local cli = require("neogit.lib.git.cli") -local json = require("neogit.lib.json") +local record = require("neogit.lib.record") local repo = require("neogit.lib.git.repository") local M = {} @@ -14,7 +14,7 @@ function M.list() return revisions end -local json_template = json.encode { +local record_template = record.encode { head = "%(HEAD)", oid = "%(objectname)", ref = "%(refname)", @@ -25,8 +25,8 @@ local json_template = json.encode { } function M.list_parsed() - local refs = cli["for-each-ref"].format(json_template).call_sync():trim().stdout - local result = json.decode(refs) + local refs = cli["for-each-ref"].format(record_template).call_sync():trim().stdout + local result = record.decode(refs) local output = { local_branch = {}, diff --git a/lua/neogit/lib/json.lua b/lua/neogit/lib/record.lua similarity index 100% rename from lua/neogit/lib/json.lua rename to lua/neogit/lib/record.lua diff --git a/tests/specs/neogit/lib/json_spec.lua b/tests/specs/neogit/lib/record_spec.lua similarity index 94% rename from tests/specs/neogit/lib/json_spec.lua rename to tests/specs/neogit/lib/record_spec.lua index 1837b607b..6526ceda5 100644 --- a/tests/specs/neogit/lib/json_spec.lua +++ b/tests/specs/neogit/lib/record_spec.lua @@ -1,6 +1,6 @@ -local subject = require("neogit.lib.json") +local subject = require("neogit.lib.record") -describe("lib.json", function() +describe("lib.record", function() describe("#encode", function() it("turns lua table into delimeted string", function() assert.are.same("foo%x1Dbar%x1E", subject.encode { foo = "bar" }) @@ -8,7 +8,7 @@ describe("lib.json", function() end) describe("#decode", function() - it("can decode multiple json objects", function() + it("can decode multiple delimeted objects", function() local input = { "baz\29boo\31foo\29bar\30", "biz\29bip\31bop\29bip\30", From 7979eedd5b5805c58348a94fbe5da7a38061fd79 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 20:16:45 +0100 Subject: [PATCH 211/443] Spelling --- lua/neogit/lib/record.lua | 6 +++--- tests/specs/neogit/lib/record_spec.lua | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/record.lua b/lua/neogit/lib/record.lua index d04d36cce..f50ade820 100644 --- a/lua/neogit/lib/record.lua +++ b/lua/neogit/lib/record.lua @@ -25,7 +25,7 @@ local function parse_record(record_string) return record end ----Decode a list of delimeted lines into lua tables +---Decode a list of delimited lines into lua tables ---@param lines string[] ---@return table function M.decode(lines) @@ -37,13 +37,13 @@ function M.decode(lines) -- lines if the subject/body fields contain \n or \r characters. local lines = table.concat(lines, "") - -- Split the string into records, using the record separator character as a delimeter. + -- Split the string into records, using the record separator character as a delimiter. -- If you commit message contains record separator control characters... this won't work, -- and you should feel bad about your choices. return vim.tbl_map(parse_record, vim.split(lines, record_separator.dec, { trimempty = true })) end ----@param tbl table Key/value pairs to format with delimeters +---@param tbl table Key/value pairs to format with delimiters ---@return string function M.encode(tbl) local out = {} diff --git a/tests/specs/neogit/lib/record_spec.lua b/tests/specs/neogit/lib/record_spec.lua index 6526ceda5..5e8331a14 100644 --- a/tests/specs/neogit/lib/record_spec.lua +++ b/tests/specs/neogit/lib/record_spec.lua @@ -2,13 +2,13 @@ local subject = require("neogit.lib.record") describe("lib.record", function() describe("#encode", function() - it("turns lua table into delimeted string", function() + it("turns lua table into delimited string", function() assert.are.same("foo%x1Dbar%x1E", subject.encode { foo = "bar" }) end) end) describe("#decode", function() - it("can decode multiple delimeted objects", function() + it("can decode multiple delimited objects", function() local input = { "baz\29boo\31foo\29bar\30", "biz\29bip\31bop\29bip\30", From e464bd2b3023bf16e77355f71f71e206a590fb68 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 22 Mar 2024 20:31:47 +0100 Subject: [PATCH 212/443] Debounce dispatching refresh so things like stashing and other actions that take a few ms from the inital dispatch can be properly captured. Thanks gitsigns for the debounce funciton. --- lua/neogit/buffers/status/init.lua | 8 +++++--- lua/neogit/lib/util.lua | 33 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index f21927325..0ae8102d6 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -19,6 +19,7 @@ local a = require("plenary.async") local input = require("neogit.lib.input") local logger = require("neogit.logger") -- TODO: Add logging local notification = require("neogit.lib.notification") +local util = require("neogit.lib.util") local api = vim.api local fn = vim.fn @@ -1173,8 +1174,9 @@ function M:refresh(partial, reason) } end -function M:dispatch_refresh(partial, reason) - a.run(function() +M.dispatch_refresh = util.debounce_trailing( + 100, + a.void(function(self, partial, reason) if self:_is_refresh_locked() then logger.debug("[STATUS] Refresh lock is active. Skipping refresh from " .. (reason or "unknown")) else @@ -1182,7 +1184,7 @@ function M:dispatch_refresh(partial, reason) self:refresh(partial, reason) end end) -end +) function M:reset() logger.debug("[STATUS] Resetting repo and refreshing") diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 44914c1cd..1819029aa 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -489,4 +489,37 @@ function M.memoize(f, opts) end end +--- Debounces a function on the trailing edge. +--- +--- @generic F: function +--- @param ms number Timeout in ms +--- @param fn F Function to debounce +--- @param hash? integer|fun(...): any Function that determines id from arguments to fn +--- @return F Debounced function. +function M.debounce_trailing(ms, fn, hash) + local running = {} --- @type table + + if type(hash) == "number" then + local hash_i = hash + hash = function(...) + return select(hash_i, ...) + end + end + + return function(...) + local id = hash and hash(...) or true + if running[id] == nil then + running[id] = assert(vim.loop.new_timer()) + end + + local timer = running[id] + local argv = { ... } + timer:start(ms, 0, function() + timer:stop() + running[id] = nil + fn(unpack(argv, 1, table.maxn(argv))) + end) + end +end + return M From fe8ea4d48815e58734b033e9a9ef622e2560994b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 13:16:50 +0100 Subject: [PATCH 213/443] Fix: No more status on nightly --- lua/neogit/popups/rebase/actions.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 3f0f4f43d..4fc025f38 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -2,7 +2,6 @@ local git = require("neogit.lib.git") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") local operation = require("neogit.operations") -local status = require("neogit.status") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -127,7 +126,6 @@ M.modify = operation("rebase_modify", function(popup) end end git.rebase.modify(commit) - status.refresh(nil, "rebase_modify") end) function M.subset(popup) From cd84d516c9c4c86951908dd353763db4b385d369 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 13:17:40 +0100 Subject: [PATCH 214/443] Use abbreviated commit to ensure it's the correct length. --- lua/neogit/buffers/common.lua | 2 +- lua/neogit/buffers/reflog_view/ui.lua | 2 +- lua/neogit/buffers/refs_view/ui.lua | 8 ++++---- lua/neogit/buffers/status/ui.lua | 4 ++-- lua/neogit/lib/git/log.lua | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 4248fd217..7f5f03f37 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -219,7 +219,7 @@ M.CommitEntry = Component.new(function(commit, args) return col.tag("commit")({ row( util.merge({ - text(commit.oid:sub(1, 7), { + text(commit.abbreviated_commit, { highlight = commit.verification_flag and highlight_for_signature[commit.verification_flag] or "Comment", }), diff --git a/lua/neogit/buffers/reflog_view/ui.lua b/lua/neogit/buffers/reflog_view/ui.lua index ab342d375..014d47f67 100644 --- a/lua/neogit/buffers/reflog_view/ui.lua +++ b/lua/neogit/buffers/reflog_view/ui.lua @@ -29,7 +29,7 @@ M.Entry = Component.new(function(entry, total) return col({ row({ - text(entry.oid:sub(1, 7), { highlight = "Comment" }), + text(entry.abbreviated_commit, { highlight = "Comment" }), text(" "), text(tostring(entry.index), { align_right = #tostring(total) + 1 }), text(entry.type, { highlight = highlight_for_type(entry.type), align_right = 16 }), diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 79c021aa0..0024fb110 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -27,7 +27,7 @@ local function Cherries(ref, head) return row({ text.highlight(highlights[cherry.status])(cherry.status), text(" "), - text.highlight("Comment")(cherry.oid:sub(1, 7)), + text.highlight("Comment")(cherry.abbreviated_commit), text(" "), text.highlight("NeogitGraphWhite")(cherry.subject), }, { oid = cherry.oid }) @@ -61,7 +61,7 @@ local function section(refs, heading, head) ---@param this Component ---@param ui Ui on_open = a.void(function(this, ui) - vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.oid:sub(1, 7))) + vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.abbreviated_commit)) local cherries = Cherries(ref, head) if cherries.children[1] then @@ -77,11 +77,11 @@ local function section(refs, heading, head) string.format( "redraw | echomsg 'Got %d cherries for %s'", #cherries.children - 1, - ref.oid:sub(1, 7) + ref.abbreviated_commit ) ) else - vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.oid:sub(1, 7))) + vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.abbreviated_commit)) end end), }) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index a58ed7d99..b52c3ea0c 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -280,7 +280,7 @@ local SectionItemRebase = Component.new(function(item) text(item.stopped and "> " or " "), text.highlight(action_hl)(util.pad_right(item.action, 6)), text(" "), - text.highlight("NeogitRebaseDone")(item.oid:sub(1, 7)), + text.highlight("NeogitRebaseDone")(item.abbreviated_commit), text(" "), text.highlight(item.done and "NeogitRebaseDone")(item.subject), }, { yankable = item.oid, oid = item.oid }) @@ -304,7 +304,7 @@ local SectionItemSequencer = Component.new(function(item) return row({ text.highlight(action_hl)(action), text(show_action and " " or ""), - text.highlight("Comment")(item.oid:sub(1, 7)), + text.highlight("Comment")(item.abbreviated_commit), text(" "), text(item.subject), }, { yankable = item.oid, oid = item.oid }) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 2a08609cf..96770cc11 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -430,7 +430,7 @@ function M.present_commit(commit) end return { - name = string.format("%s %s", commit.oid:sub(1, 7), commit.subject or ""), + name = string.format("%s %s", commit.abbreviated_commit, commit.subject or ""), oid = commit.oid, commit = commit, } From f9e6e0dd28e242afbbc15ef9270df558b7a1244a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 13:18:02 +0100 Subject: [PATCH 215/443] Fix commit parsing to handle binary files --- lua/neogit/buffers/commit_view/init.lua | 10 +++++++-- lua/neogit/buffers/commit_view/parsing.lua | 25 ++++++++-------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 2c8f50425..ef275853d 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -19,8 +19,14 @@ local api = vim.api ---@field description table ---@class CommitOverview ----@field summary string ----@field files table +---@field summary string a short summary about what happened +---@field files CommitOverviewFile[] a list of CommitOverviewFile + +---@class CommitOverviewFile +---@field path string the path to the file relative to the git root +---@field changes string how many changes were made to the file +---@field insertions string insertion count visualized as list of `+` +---@field deletions string deletion count visualized as list of `-` --- @class CommitViewBuffer --- @field is_open boolean whether the buffer is currently shown diff --git a/lua/neogit/buffers/commit_view/parsing.lua b/lua/neogit/buffers/commit_view/parsing.lua index 23c719bb0..869ed3e2c 100644 --- a/lua/neogit/buffers/commit_view/parsing.lua +++ b/lua/neogit/buffers/commit_view/parsing.lua @@ -2,18 +2,10 @@ local M = {} local util = require("neogit.lib.util") --- @class CommitOverviewFile --- @field path the path to the file relative to the git root --- @field changes how many changes were made to the file --- @field insertions insertion count visualized as list of `+` --- @field deletions deletion count visualized as list of `-` - --- @class CommitOverview --- @field summary a short summary about what happened --- @field files a list of CommitOverviewFile --- @see CommitOverviewFile local CommitOverview = {} +---@param raw table +---@return CommitOverview function M.parse_commit_overview(raw) local overview = { summary = util.trim(raw[#raw]), @@ -23,7 +15,14 @@ function M.parse_commit_overview(raw) for i = 2, #raw - 1 do local file = {} if raw[i] ~= "" then + -- matches: tests/specs/neogit/popups/rebase_spec.lua | 2 +- file.path, file.changes, file.insertions, file.deletions = raw[i]:match(" (.*)%s+|%s+(%d+) ?(%+*)(%-*)") + + if vim.tbl_isempty(file) then + -- matches: .../db/b8571c4f873daff059c04443077b43a703338a | Bin 0 -> 192 bytes + file.path, file.changes = raw[i]:match(" (.*)%s+|%s+(Bin .*)$") + end + table.insert(overview.files, file) end end @@ -33,10 +32,4 @@ function M.parse_commit_overview(raw) return overview end ----@return string the abbreviation of the oid ----@param commit CommitLogEntry -function M.abbrev(commit) - return commit.oid:sub(1, 7) -end - return M From e6a9f2392ab83f20b891765c42bab59766d6017a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 13:18:34 +0100 Subject: [PATCH 216/443] Memoize abbreviated size call as it should change very very infrequently. Use it when building graph to align properly --- lua/neogit/buffers/common.lua | 2 +- lua/neogit/lib/git/log.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 7f5f03f37..5e25e2600 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -241,7 +241,7 @@ M.CommitEntry = Component.new(function(commit, args) end) M.CommitGraph = Component.new(function(commit, _) - return col.tag("graph").padding_left(8) { row(build_graph(commit.graph)) } + return col.tag("graph").padding_left(git.log.abbreviated_size() + 1) { row(build_graph(commit.graph)) } end) M.Grid = Component.new(function(props) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 96770cc11..d876b264a 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -514,8 +514,8 @@ function M.reflog_message(skip) .call_sync({ ignore_error = true }).stdout end -function M.abbreviated_size() +M.abbreviated_size = util.memoize(function() return string.len(cli.log.format("%h").max_count(1).call({ hidden = true }).stdout[1]) -end +end, { timeout = math.huge }) return M From 48ab48908eb920349d0edf6d251b9e02e8a61f21 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 13:19:11 +0100 Subject: [PATCH 217/443] I forgot we had this - use it to get the abbreviated commit. --- lua/neogit/lib/git/rebase.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index e35c9ba4c..1e9bafebc 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -2,7 +2,7 @@ local logger = require("neogit.logger") local client = require("neogit.client") local notification = require("neogit.lib.notification") local cli = require("neogit.lib.git.cli") -local log = require("neogit.lib.git.log") +local rev_parse = require("neogit.lib.git.rev_parse") local M = {} @@ -94,7 +94,7 @@ function M.reword(commit, message) end function M.modify(commit) - local short_commit = string.sub(commit, 1, log.abbreviated_size()) + local short_commit = rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/edit \\1/' -c 'wq'" local result = cli.rebase .env({ GIT_SEQUENCE_EDITOR = editor }).interactive.autosquash.autostash From 746988c4d033e62e83090c366b4bae572d203341 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 14:36:39 +0100 Subject: [PATCH 218/443] Implement rebase autosquash --- lua/neogit/lib/git/rebase.lua | 9 +++++++++ lua/neogit/popups/rebase/actions.lua | 18 ++++++++++++++++++ lua/neogit/popups/rebase/init.lua | 4 ++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 1e9bafebc..218b18a9e 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -119,6 +119,15 @@ function M.edit() return rebase_command(cli.rebase.edit_todo) end +---Find the merge base for HEAD and it's upstream +---@return string|nil +function M.merge_base_HEAD() + local result = cli["merge-base"].args("HEAD", "HEAD@{upstream}").call { ignore_error = true, hidden = true } + if result.code == 0 then + return result.stdout[1] + end +end + function M.update_rebase_status(state) local git = require("neogit.lib.git") if git.repo.git_root == "" then diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 4fc025f38..8e090379a 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") local operation = require("neogit.operations") +local util = require("neogit.lib.util") local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -162,6 +163,23 @@ function M.edit() git.rebase.edit() end +function M.autosquash(popup) + local base + if popup.state.env.commit and git.log.is_ancestor(popup.state.env.commit, "HEAD") then + base = popup.state.env.commit + else + base = git.rebase.merge_base_HEAD() + end + + if base then + git.rebase.onto( + "HEAD", + base, + util.deduplicate(util.merge(popup:get_arguments(), { "--autosquash", "--keep-empty" })) + ) + end +end + -- TODO: Extract to rebase lib? function M.abort() if input.get_permission("Abort rebase?") then diff --git a/lua/neogit/popups/rebase/init.lua b/lua/neogit/popups/rebase/init.lua index cdd1f3faa..70d201ebd 100644 --- a/lua/neogit/popups/rebase/init.lua +++ b/lua/neogit/popups/rebase/init.lua @@ -26,7 +26,7 @@ function M.create(env) :switch_if(not in_rebase, "u", "update-refs", "Update branches") :switch_if(not in_rebase, "d", "committer-date-is-author-date", "Use author date as committer date") :switch_if(not in_rebase, "t", "ignore-date", "Use current time as author date") - :switch_if(not in_rebase, "a", "autosquash", "Autosquash fixup and squash commits") + :switch_if(not in_rebase, "a", "autosquash", "Autosquash") :switch_if(not in_rebase, "A", "autostash", "Autostash", { enabled = true }) :switch_if(not in_rebase, "i", "interactive", "Interactive") :switch_if(not in_rebase, "h", "no-verify", "Disable hooks") @@ -43,7 +43,7 @@ function M.create(env) :action_if(not in_rebase, "m", "to modify a commit", actions.modify) :action_if(not in_rebase, "w", "to reword a commit", actions.reword) :action_if(not in_rebase, "k", "to remove a commit") - :action_if(not in_rebase, "f", "to autosquash") + :action_if(not in_rebase, "f", "to autosquash", actions.autosquash) :env({ commit = env.commit, highlight = { branch, git.repo.upstream.ref, base_branch }, From d543e725c085839ba9ca5cc9062e19388b129527 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 14:49:49 +0100 Subject: [PATCH 219/443] Cleanup rebase actions --- lua/neogit/popups/rebase/actions.lua | 48 ++++++++-------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index 4d9dc0ccd..fca68ea24 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -9,6 +9,10 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} +local function base_commit(popup, list) + return popup.state.env.commit or CommitSelectViewBuffer.new(list or git.log.list()):open_async()[1] +end + function M.onto_base(popup) git.rebase.onto_branch(git.branch.base_branch(), popup:get_arguments()) end @@ -51,13 +55,7 @@ function M.onto_elsewhere(popup) end function M.interactively(popup) - local commit - if popup.state.env.commit then - commit = popup.state.env.commit - else - commit = CommitSelectViewBuffer.new(git.log.list({}, {}, {}, true)):open_async()[1] - end - + local commit = base_commit(popup, git.log.list({}, {}, {}, true)) if commit then if not git.log.is_ancestor(commit, "HEAD") then notification.warn("Commit isn't an ancestor of HEAD") @@ -96,14 +94,9 @@ function M.interactively(popup) end M.reword = operation("rebase_reword", function(popup) - local commit - if popup.state.env.commit then - commit = popup.state.env.commit - else - commit = CommitSelectViewBuffer.new(git.log.list()):open_async()[1] - if not commit then - return - end + local commit = base_commit(popup) + if not commit then + return end -- TODO: Support multiline input for longer commit messages @@ -117,30 +110,17 @@ M.reword = operation("rebase_reword", function(popup) end) M.modify = operation("rebase_modify", function(popup) - local commit - if popup.state.env.commit then - commit = popup.state.env.commit - else - commit = CommitSelectViewBuffer.new(git.log.list()):open_async()[1] - if not commit then - return - end + local commit = base_commit(popup) + if commit then + git.rebase.modify(commit) end - git.rebase.modify(commit) end) M.drop = operation("rebase_drop", function(popup) - local commit - if popup.state.env.commit then - commit = popup.state.env.commit - else - commit = CommitSelectViewBuffer.new(git.log.list()):open_async()[1] - if not commit then - return - end + local commit = base_commit(popup) + if commit then + git.rebase.drop(commit) end - - git.rebase.drop(commit) end) function M.subset(popup) From 3b9269eecdd2d3f31714045f2ffcf10e868d94c0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 14:57:45 +0100 Subject: [PATCH 220/443] Update rebase docs --- doc/neogit.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 68d7db4f8..921062e26 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1233,16 +1233,16 @@ Actions: *neogit_rebase_popup_actions* then a START commit. • Rebase to modify a commit *neogit_rebase_modify_commit* - (TODO) + Begins an interactive rebase, allowing you to edit a single commit. • Rebase to reword a commit *neogit_rebase_reword_commit* - (TODO) + Begins an interactive rebase, letting you reword an single older commit. • Rebase to remove a commit *neogit_rebase_remove_commit* - (TODO) + Uses rebase to remove a single commit from the history. • Rebase to autosquash *neogit_rebase_autosquash* - (TODO) + Combines `squash!` and `fixup!` commits with their target commits. When a rebase is in progress, the following actions are available instead: • Continue *neogit_rebase_continue* From f42d1fc0f00409d1487d764968c14bb4bba6e435 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 17:30:55 +0100 Subject: [PATCH 221/443] Ensure we set the fold-method correctly --- lua/neogit/lib/buffer.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 5af97c296..aa735acc4 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -580,6 +580,8 @@ function Buffer.create(config) if vim.fn.has("nvim-0.10") == 1 then buffer:set_window_option("spell", false) buffer:set_window_option("wrap", false) + buffer:set_window_option("foldmethod", "manual") + -- TODO: Need to find a way to turn this off properly when unloading plugin -- buffer:set_window_option("winfixbuf", true) else From c1160062caac2c2d18e4fbc127c48bab771ea63f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 17:31:48 +0100 Subject: [PATCH 222/443] Simplify how these are set --- lua/neogit/lib/buffer.lua | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index aa735acc4..78cf421b4 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -568,14 +568,9 @@ function Buffer.create(config) buffer.mmanager.register() - if not config.modifiable then - buffer:set_buffer_option("modifiable", false) - buffer:set_buffer_option("modified", false) - end - - if config.readonly == true then - buffer:set_buffer_option("readonly", true) - end + buffer:set_buffer_option("modifiable", config.modifiable or false) + buffer:set_buffer_option("modified", config.modifiable or false) + buffer:set_buffer_option("readonly", config.readonly or false) if vim.fn.has("nvim-0.10") == 1 then buffer:set_window_option("spell", false) From b8c8b7cd76cd1a6a337b36ff98750261ae9742dc Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 17:31:57 +0100 Subject: [PATCH 223/443] Change the filewatcher to be ENABLED by default, remove unused config --- lua/neogit/config.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 2cbecc22a..b89329340 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -107,7 +107,6 @@ end ---@field underline? boolean ---@class NeogitFilewatcherConfig ----@field interval number ---@field enabled boolean ---@field filewatcher NeogitFilewatcherConfig|nil @@ -179,8 +178,7 @@ function M.get_default_values() disable_signs = false, graph_style = "ascii", filewatcher = { - interval = 1000, - enabled = false, + enabled = true, }, telescope_sorter = function() return nil From 95b51c239396ebe1488b6c14324f7c844c28d72f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 17:32:27 +0100 Subject: [PATCH 224/443] Capture filewatcher as field on status buffer instance, close it only if it exists. --- lua/neogit/buffers/status/init.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 0ae8102d6..3568c1e11 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -85,7 +85,10 @@ function M:open(kind) autocmds = { ["BufUnload"] = function() logger.debug("[STATUS] Running BufUnload autocmd") - watcher.instance:stop() + if self.watcher then + self.watcher:stop() + end + self.is_open = false vim.o.autochdir = self.prev_autochdir end, @@ -1093,7 +1096,7 @@ function M:open(kind) if config.values.filewatcher.enabled then logger.debug("[STATUS] Starting file watcher") - watcher.new(git.repo:git_path():absolute()):start() + self.watcher = watcher.new(git.repo:git_path():absolute()):start() end buffer:move_cursor(buffer.ui:first_section().first) @@ -1109,7 +1112,10 @@ function M:close() vim.o.autochdir = self.prev_autochdir - watcher.instance:stop() + if self.watcher then + self.watcher:stop() + end + self.is_open = false self.buffer:close() self.buffer = nil From 9818448e46d771deb36c5fe37ffe6d5ab51704da Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 17:54:03 +0100 Subject: [PATCH 225/443] Stage/Unstage: Only refresh if we actually did something --- lua/neogit/buffers/status/init.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 3568c1e11..fa512de29 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -789,22 +789,25 @@ function M:open(kind) git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) + self:refresh() elseif stagable.filename then if section.options.section == "unstaged" then git.status.stage { stagable.filename } + self:refresh() elseif section.options.section == "untracked" then git.index.add { stagable.filename } + self:refresh() end end elseif section then if section.options.section == "untracked" then git.status.stage_untracked() + self:refresh() elseif section.options.section == "unstaged" then git.status.stage_modified() + self:refresh() end end - - self:refresh() end), [mappings["StageAll"]] = a.void(function() git.status.stage_all() @@ -834,14 +837,15 @@ function M:open(kind) ) git.index.apply(patch, { cached = true, reverse = true }) + self:refresh() elseif unstagable.filename then git.status.unstage { unstagable.filename } + self:refresh() end elseif section then git.status.unstage_all() + self:refresh() end - - self:refresh() end), [mappings["UnstageStaged"]] = a.void(function() git.status.unstage_all() From e3d70842676cd94b42b132d7da3aa349ed12d5af Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 19:30:37 +0100 Subject: [PATCH 226/443] Use the correct diff popup --- lua/neogit/buffers/log_view/init.lua | 34 ++++++++++--------------- lua/neogit/buffers/reflog_view/init.lua | 23 ++++++++++------- lua/neogit/buffers/refs_view/init.lua | 32 ++++++++++------------- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 62518fd29..0dc82df74 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -75,16 +75,13 @@ function M:open() p { commit = self.buffer.ui:get_commit_under_cursor() } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), - ["d"] = function() - -- TODO: Use diff popup - if not config.check_integration("diffview") then - notification.error("Diffview integration must be enabled for log diff") - return - end - - local dv = require("neogit.integrations.diffview") - dv.open("log", self.buffer.ui:get_commits_in_selection()) - end, + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + local items = self.buffer.ui:get_commits_in_selection() + p { + section = { name = "log" }, + item = { name = items }, + } + end), }, n = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) @@ -116,6 +113,13 @@ function M:open() [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + local item = self.buffer.ui:get_commit_under_cursor() + p { + section = { name = "log" }, + item = { name = item }, + } + end), [popups.mapping_for("PullPopup")] = popups.open("pull"), [status_maps["YankSelected"]] = function() local yank = self.buffer.ui:get_commit_under_cursor() @@ -172,16 +176,6 @@ function M:open() [""] = function() pcall(vim.cmd, "normal! za") end, - ["d"] = function() - -- TODO: Use diff popup - if not config.check_integration("diffview") then - notification.error("Diffview integration must be enabled for log diff") - return - end - - local dv = require("neogit.integrations.diffview") - dv.open("log", self.buffer.ui:get_commit_under_cursor()) - end, }, }, render = function() diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 27730eeb6..85f092be6 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -67,6 +67,13 @@ function M:open(_) p { commit = self.buffer.ui:get_commit_under_cursor() } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + local items = self.buffer.ui:get_commits_in_selection() + p { + section = { name = "log" }, + item = { name = items }, + } + end), }, n = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) @@ -99,6 +106,13 @@ function M:open(_) p { commit = self.buffer.ui:get_commit_under_cursor() } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + local item = self.buffer.ui:get_commit_under_cursor() + p { + section = { name = "log" }, + item = { name = item }, + } + end), [status_maps["YankSelected"]] = function() local yank = self.buffer.ui:get_commit_under_cursor() if yank then @@ -121,15 +135,6 @@ function M:open(_) CommitViewBuffer.new(oid):open() end end, - [popups.mapping_for("DiffPopup")] = function() - if not config.check_integration("diffview") then - notification.error("Diffview integration must be enabled for reflog diff") - return - end - - local dv = require("neogit.integrations.diffview") - dv.open("log", self.buffer.ui:get_commit_under_cursor()) - end, }, }, render = function() diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 4fc3caaa6..7eae44cce 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -95,15 +95,13 @@ function M:open() p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), - ["d"] = function() - if not config.check_integration("diffview") then - notification.error("Diffview integration must be enabled for log diff") - return - end - - local dv = require("neogit.integrations.diffview") - dv.open("log", self.buffer.ui:get_commits_in_selection()) - end, + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + local items = self.buffer.ui:get_commits_in_selection() + p { + section = { name = "log" }, + item = { name = items }, + } + end), }, n = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) @@ -135,6 +133,13 @@ function M:open() p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + local item = self.buffer.ui:get_commit_under_cursor() + p { + section = { name = "log" }, + item = { name = item }, + } + end), ["q"] = function() self:close() end, @@ -187,15 +192,6 @@ function M:open() end end end, - ["d"] = function() - if not config.check_integration("diffview") then - notification.error("Diffview integration must be enabled for log diff") - return - end - - local dv = require("neogit.integrations.diffview") - dv.open("log", self.buffer.ui:get_commits_in_selection()[1]) - end, }, }, render = function() From 1372dc8dadf44dcb4106506649add5772bfb3bac Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 20:11:21 +0100 Subject: [PATCH 227/443] Pad graph properly --- lua/neogit/buffers/common.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 5e25e2600..7a0c7360d 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -162,7 +162,7 @@ M.CommitEntry = Component.new(function(commit, args) local details if args.details then - details = col.padding_left(8) { + details = col.padding_left(git.log.abbreviated_size() + 1) { row(util.merge(graph, { text(" "), text("Author: ", { highlight = "Comment" }), From 529bc7e9fd16cedc13620eafb08d8ea4d599a7f4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 20:53:52 +0100 Subject: [PATCH 228/443] Fix: for-each-ref takes a slightly different printf format for escape literals. No 'x' for the hex code.. So.. We need to differentiate for git log and git for-each-ref --- lua/neogit/buffers/refs_view/init.lua | 2 -- lua/neogit/lib/git/log.lua | 2 +- lua/neogit/lib/git/refs.lua | 4 ++-- lua/neogit/lib/record.lua | 14 ++++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 7eae44cce..d13fad2cd 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -2,8 +2,6 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.refs_view.ui") local popups = require("neogit.popups") local CommitViewBuffer = require("neogit.buffers.commit_view") -local config = require("neogit.config") -local notification = require("neogit.lib.notification") --- @class RefsViewBuffer --- @field is_open boolean whether the buffer is currently shown diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index d876b264a..4cdbaf4fe 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -341,7 +341,7 @@ local function format(show_signature) fields.verification_flag = "%G?" end - return record.encode(fields) + return record.encode(fields, "log") end ---@param options? string[] diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index ebdc918fc..ebfcfb526 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -14,7 +14,7 @@ function M.list() return revisions end -local record_template = record.encode { +local record_template = record.encode({ head = "%(HEAD)", oid = "%(objectname)", ref = "%(refname)", @@ -22,7 +22,7 @@ local record_template = record.encode { upstream_status = "%(upstream:trackshort)", upstream_name = "%(upstream:short)", subject = "%(subject)", -} +}, "ref") function M.list_parsed() local refs = cli["for-each-ref"].format(record_template).call_sync():trim().stdout diff --git a/lua/neogit/lib/record.lua b/lua/neogit/lib/record.lua index f50ade820..ac37e2f3f 100644 --- a/lua/neogit/lib/record.lua +++ b/lua/neogit/lib/record.lua @@ -1,8 +1,8 @@ local M = {} -local record_separator = { dec = "\30", hex = "%x1E" } -local field_separator = { dec = "\31", hex = "%x1F" } -local pair_separator = { dec = "\29", hex = "%x1D" } +local record_separator = { dec = "\30", hex_log = "%x1E", hex_ref = "%1E" } +local field_separator = { dec = "\31", hex_log = "%x1F", hex_ref = "%1F" } +local pair_separator = { dec = "\29", hex_log = "%x1D", hex_ref = "%1D" } -- Matches/captures each key/value pair of fields in a record -- 1. \31? - Optionally has a leading field separator (first field won't have this) @@ -44,14 +44,16 @@ function M.decode(lines) end ---@param tbl table Key/value pairs to format with delimiters +---@param type string Git log takes a different formatting string for escape literals than for-each-ref. ---@return string -function M.encode(tbl) +function M.encode(tbl, type) + local hex = "hex_" .. type local out = {} for k, v in pairs(tbl) do - table.insert(out, string.format("%s%s%s", k, pair_separator.hex, v)) + table.insert(out, string.format("%s%s%s", k, pair_separator[hex], v)) end - return table.concat(out, field_separator.hex) .. record_separator.hex + return table.concat(out, field_separator[hex]) .. record_separator[hex] end return M From b6ad341c2802b942ffdaf6f53037dfd8c26740ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 20:55:29 +0100 Subject: [PATCH 229/443] Void-wrap the action so we can use call inside the closure, and hide it from user command log --- lua/neogit/buffers/status/init.lua | 8 ++++---- lua/neogit/lib/git/refs.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index fa512de29..3d1ef0af1 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -460,15 +460,15 @@ function M:open(kind) end end end, - [mappings["CommandHistory"]] = function() + [mappings["CommandHistory"]] = a.void(function() require("neogit.buffers.git_command_history"):new():show() - end, + end), [mappings["Console"]] = function() require("neogit.process").show_console() end, - [mappings["ShowRefs"]] = function() + [mappings["ShowRefs"]] = a.void(function() require("neogit.buffers.refs_view").new(git.refs.list_parsed()):open() - end, + end), [mappings["YankSelected"]] = function() local yank = self.buffer.ui:get_yankable_under_cursor() if yank then diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index ebfcfb526..04b251775 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -7,7 +7,7 @@ local M = {} --- Lists revisions ---@return table function M.list() - local revisions = cli["for-each-ref"].format('"%(refname:short)"').call().stdout + local revisions = cli["for-each-ref"].format('"%(refname:short)"').call({ hidden = true }).stdout for i, str in ipairs(revisions) do revisions[i] = string.sub(str, 2, -2) end @@ -25,7 +25,7 @@ local record_template = record.encode({ }, "ref") function M.list_parsed() - local refs = cli["for-each-ref"].format(record_template).call_sync():trim().stdout + local refs = cli["for-each-ref"].format(record_template).show_popup(false).call({ hidden = true }).stdout local result = record.decode(refs) local output = { From 6aab26e7d8b4f94e2c5d9ca6ee1368216629d1ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 20:55:58 +0100 Subject: [PATCH 230/443] Trim is automatic now --- lua/neogit/lib/git/cherry.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/cherry.lua b/lua/neogit/lib/git/cherry.lua index 15e70fda2..ea59e8aa2 100644 --- a/lua/neogit/lib/git/cherry.lua +++ b/lua/neogit/lib/git/cherry.lua @@ -3,7 +3,7 @@ local cli = require("neogit.lib.git.cli") local util = require("neogit.lib.util") function M.list(upstream, head) - local result = cli.cherry.verbose.args(upstream, head).call():trim().stdout + local result = cli.cherry.verbose.args(upstream, head).call().stdout return util.reverse(util.map(result, function(cherry) local status, oid, subject = cherry:match("([%+%-]) (%x+) (.*)") return { status = status, oid = oid, subject = subject } From 0c48c59dfbcc0f8c97ae17e5d80bceac993205e9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 21:06:25 +0100 Subject: [PATCH 231/443] Fix: Refs view uses correct length OIDs --- lua/neogit/buffers/refs_view/ui.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 0024fb110..c561e5cd7 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -27,7 +27,7 @@ local function Cherries(ref, head) return row({ text.highlight(highlights[cherry.status])(cherry.status), text(" "), - text.highlight("Comment")(cherry.abbreviated_commit), + text.highlight("Comment")(cherry.oid:sub(1, git.log.abbreviated_size())), text(" "), text.highlight("NeogitGraphWhite")(cherry.subject), }, { oid = cherry.oid }) @@ -61,7 +61,7 @@ local function section(refs, heading, head) ---@param this Component ---@param ui Ui on_open = a.void(function(this, ui) - vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.abbreviated_commit)) + vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.oid:sub(1, git.log.abbreviated_size()))) local cherries = Cherries(ref, head) if cherries.children[1] then @@ -77,11 +77,11 @@ local function section(refs, heading, head) string.format( "redraw | echomsg 'Got %d cherries for %s'", #cherries.children - 1, - ref.abbreviated_commit + ref.oid:sub(1, git.log.abbreviated_size()) ) ) else - vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.abbreviated_commit)) + vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.oid:sub(1, git.log.abbreviated_size()))) end end), }) From e7348c3e57aacd191792b556e4869dea650a65ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 21:16:11 +0100 Subject: [PATCH 232/443] Reflog has some alignment constraints, so just have this be 7. --- lua/neogit/buffers/reflog_view/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/reflog_view/ui.lua b/lua/neogit/buffers/reflog_view/ui.lua index 014d47f67..ab342d375 100644 --- a/lua/neogit/buffers/reflog_view/ui.lua +++ b/lua/neogit/buffers/reflog_view/ui.lua @@ -29,7 +29,7 @@ M.Entry = Component.new(function(entry, total) return col({ row({ - text(entry.abbreviated_commit, { highlight = "Comment" }), + text(entry.oid:sub(1, 7), { highlight = "Comment" }), text(" "), text(tostring(entry.index), { align_right = #tostring(total) + 1 }), text(entry.type, { highlight = highlight_for_type(entry.type), align_right = 16 }), From 113852396e077e5b16954b7c2b13aac2adfffbfd Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 21:18:13 +0100 Subject: [PATCH 233/443] Add abbreviated_commit interface to items here --- lua/neogit/lib/git/rebase.lua | 9 +++++++-- lua/neogit/lib/git/sequencer.lua | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 48e3ffc58..16da5796b 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -186,9 +186,11 @@ function M.update_rebase_status(state) if done:exists() then for line in done:iter() do if line:match("^[^#]") and line ~= "" then + local oid = line:match("^%w+ (%x+)") table.insert(state.rebase.items, { action = line:match("^(%w+) "), - oid = line:match("^%w+ (%x+)"), + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), subject = line:match("^%w+ %x+ (.+)$"), done = true, }) @@ -207,10 +209,12 @@ function M.update_rebase_status(state) if todo:exists() then for line in todo:iter() do if line:match("^[^#]") and line ~= "" then + local oid = line:match("^%w+ (%x+)") table.insert(state.rebase.items, { done = false, action = line:match("^(%w+) "), - oid = line:match("^%w+ (%x+)"), + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), subject = line:match("^%w+ %x+ (.+)$"), }) end @@ -222,6 +226,7 @@ function M.update_rebase_status(state) done = false, action = "onto", oid = state.rebase.onto.oid, + abbreviated_commit = state.rebase.onto.oid:sub(1, git.log.abbreviated_size()), subject = state.rebase.onto.subject, }) end diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 1ed3a6c53..830abe7ea 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -40,6 +40,7 @@ function M.update_sequencer_status(state) table.insert(state.sequencer.items, { action = "onto", oid = HEAD_oid, + abbreviated_commit = HEAD_oid:sub(1, git.log.abbreviated_size()), subject = git.log.message(HEAD_oid), }) @@ -47,9 +48,11 @@ function M.update_sequencer_status(state) if todo:exists() then for line in todo:iter() do if line:match("^[^#]") and line ~= "" then + local oid = line:match("^%w+ (%x+)") table.insert(state.sequencer.items, { action = line:match("^(%w+) "), - oid = line:match("^%w+ (%x+)"), + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), subject = line:match("^%w+ %x+ (.+)$"), }) end @@ -58,6 +61,7 @@ function M.update_sequencer_status(state) table.insert(state.sequencer.items, { action = "join", oid = state.sequencer.head_oid, + abbreviated_commit = state.sequencer.head_oid:sub(1, git.log.abbreviated_size()), subject = git.log.message(state.sequencer.head_oid), }) end From b3e60e49a1f0c79e4b0eac224bf6fc0ba68b1c35 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 22:37:27 +0100 Subject: [PATCH 234/443] Save/restore view in status buffer --- lua/neogit/buffers/status/init.lua | 13 +++++++------ lua/neogit/lib/buffer.lua | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 3d1ef0af1..53e53e5af 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1161,19 +1161,20 @@ function M:refresh(partial, reason) return end - local cursor + local cursor, view if self.buffer:is_focused() then cursor = self.buffer.ui:get_cursor_location() - logger.debug("[STATUS][Refresh Callback] Cursor: " .. vim.inspect(cursor)) + view = self.buffer:save_view() end logger.debug("[STATUS][Refresh Callback] Rendering UI") self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) - if cursor then - local cursor_line = math.min(fn.line("$"), self.buffer.ui:resolve_cursor_location(cursor)) - logger.debug("[STATUS][Refresh Callback] Moving Cursor to line " .. cursor_line) - self.buffer:move_cursor(cursor_line) + if cursor and view then + self.buffer:restore_view( + view, + self.buffer.ui:resolve_cursor_location(cursor) + ) end api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 78cf421b4..7ddffd81c 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -79,6 +79,25 @@ function Buffer:clear() api.nvim_buf_set_lines(self.handle, 0, -1, false, {}) end +---@return table +function Buffer:save_view() + local view = fn.winsaveview() + return { + topline = view.topline, + leftcol = 0, + } +end + +---@param view table output of Buffer:save_view() +---@param cursor? number +function Buffer:restore_view(view, cursor) + if cursor then + view.lnum = math.min(fn.line("$"), cursor) + end + + fn.winrestview(view) +end + function Buffer:write() self:call(function() vim.cmd("silent w!") From 050287d641afd905a09e453c28e4399737d39819 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 23 Mar 2024 22:37:58 +0100 Subject: [PATCH 235/443] Cleanup --- lua/neogit/lib/buffer.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 7ddffd81c..c7c944d8e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -58,10 +58,12 @@ function Buffer:focus() return windows[1] end +---@return boolean function Buffer:is_focused() return api.nvim_win_get_buf(0) == self.handle end +---@return number function Buffer:get_changedtick() return api.nvim_buf_get_changedtick(self.handle) end @@ -71,10 +73,6 @@ function Buffer:lock() self:set_buffer_option("modifiable", false) end -function Buffer:define_autocmd(events, script) - vim.cmd(string.format("au %s %s", events, self.handle, script)) -end - function Buffer:clear() api.nvim_buf_set_lines(self.handle, 0, -1, false, {}) end @@ -265,7 +263,7 @@ function Buffer:hide() api.nvim_set_current_buf(self.old_buf) end else - api.nvim_win_close(0, {}) + api.nvim_win_close(0, true) end end From 09b2ab2d0842622df99f3ca8768354c773dc07c1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 24 Mar 2024 22:40:45 +0100 Subject: [PATCH 236/443] Feature: Commit select buffer header --- doc/neogit.txt | 4 +- .../buffers/commit_select_view/init.lua | 6 ++- lua/neogit/lib/buffer.lua | 47 +++++++++++++++++++ lua/neogit/lib/hl.lua | 6 +-- lua/neogit/popups/cherry_pick/actions.lua | 5 +- lua/neogit/popups/commit/actions.lua | 11 ++--- lua/neogit/popups/rebase/actions.lua | 32 ++++++++----- lua/neogit/popups/revert/actions.lua | 2 +- 8 files changed, 87 insertions(+), 26 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 921062e26..627bab2c2 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -213,7 +213,6 @@ NeogitRebaseDone Current position within rebase NeogitTagName Closest Tag name NeogitTagDistance Number of commits between the tag and HEAD - STATUS BUFFER SECTION HEADERS NeogitSectionHeader @@ -328,6 +327,9 @@ NeogitCommandTime Execution time NeogitCommandCodeNormal Applied to a successful command's exit status (0) NeogitCommandCodeError When command exits with non-zero status +COMMIT SELECT BUFFER +NeogitFloatHeader Foreground/Background for header text at top of win +NeogitFloatHeaderHighlight Emphasized text in header ============================================================================== 5. Lua API *neogit_api* *neogit-lua* diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 0d1c1d99c..89b880aba 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -7,15 +7,18 @@ local status_maps = require("neogit.config").get_reversed_status_maps() ---@class CommitSelectViewBuffer ---@field commits CommitLogEntry[] +---@field header string|nil local M = {} M.__index = M ---Opens a popup for selecting a commit ---@param commits CommitLogEntry[]|nil +---@param header? string ---@return CommitSelectViewBuffer -function M.new(commits) +function M.new(commits, header) local instance = { commits = commits, + header = header, buffer = nil, } @@ -39,6 +42,7 @@ function M:open(action) filetype = "NeogitCommitSelectView", status_column = " ", kind = config.values.commit_select_view.kind, + header = self.header or "Select a commit with , or to abort", mappings = { v = { [""] = function() diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index c7c944d8e..a161218ba 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -498,6 +498,49 @@ function Buffer:set_decorations(namespace, opts) end end +function Buffer:set_header(text) + -- Create a blank line at the top of the buffer so our floating window doesn't + -- hide any content + self:set_extmark(self:get_namespace_id("default"), 0, 0, { + virt_lines = { { { "", "Comment" } } }, + virt_lines_above = true, + }) + + -- Create a new buffer with the header text + local buf = api.nvim_create_buf(false, true) + api.nvim_buf_set_lines(buf, 0, -1, false, { (" %s"):format(text) }) + vim.bo[buf].undolevels = -1 + vim.bo[buf].bufhidden = "wipe" + vim.bo[buf].modified = false + + -- Display the buffer in a floating window + local winid = api.nvim_open_win(buf, false, { + relative = "win", + width = vim.o.columns, + height = 1, + row = 0, + col = 0, + focusable = false, + style = "minimal", + noautocmd = true, + }) + vim.wo[winid].wrap = false + vim.wo[winid].winhl = "NormalFloat:NeogitFloatHeader" + + fn.matchadd( + "NeogitFloatHeaderHighlight", + [[\v\|\]], + 100, + -1, + { window = winid } + ) + + -- Scroll the buffer viewport to the top so the header is visible + self:call(function() + api.nvim_input("") + end) +end + ---@class BufferConfig ---@field name string ---@field load boolean @@ -659,6 +702,10 @@ function Buffer.create(config) }) end + if config.header then + buffer:set_header(config.header) + end + return buffer end diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 522d39b5a..6ddcafd35 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -112,7 +112,6 @@ end function M.setup() local palette = make_palette() - -- stylua: ignore start hl_store = { NeogitGraphAuthor = { fg = palette.orange }, NeogitGraphRed = { fg = palette.red }, @@ -206,9 +205,10 @@ function M.setup() NeogitPicking = { link = "NeogitSectionHeader" }, NeogitReverting = { link = "NeogitSectionHeader" }, NeogitTagName = { fg = palette.yellow }, - NeogitTagDistance = { fg = palette.cyan } + NeogitTagDistance = { fg = palette.cyan }, + NeogitFloatHeader = { bg = palette.bg0, bold = palette.bold }, + NeogitFloatHeaderHighlight = { bg = palette.bg2, fg = palette.cyan, bold = palette.bold }, } - -- stylua: ignore end for group, hl in pairs(hl_store) do if not is_set(group) then diff --git a/lua/neogit/popups/cherry_pick/actions.lua b/lua/neogit/popups/cherry_pick/actions.lua index e7032abe2..28a7ea833 100644 --- a/lua/neogit/popups/cherry_pick/actions.lua +++ b/lua/neogit/popups/cherry_pick/actions.lua @@ -11,7 +11,10 @@ local function get_commits(popup) if #popup.state.env.commits > 0 then commits = popup.state.env.commits else - commits = CommitSelectViewBuffer.new(git.log.list { "--max-count=256" }):open_async() + commits = CommitSelectViewBuffer.new( + git.log.list { "--max-count=256" }, + "Select one or more commits to cherry pick with , or to abort" + ):open_async() end return commits or {} diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 65b4478d2..71bf456e9 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -48,14 +48,9 @@ local function commit_special(popup, method, opts) end end - local commit - if popup.state.env.commit then - commit = popup.state.env.commit - else - commit = CommitSelectViewBuffer.new(git.log.list()):open_async()[1] - if not commit then - return - end + local commit = popup.state.env.commit or CommitSelectViewBuffer.new(git.log.list()):open_async()[1] + if not commit then + return end if opts.rebase and not git.log.is_ancestor(commit, "HEAD") then diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index fca68ea24..b1cbd5d53 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -9,8 +9,8 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} -local function base_commit(popup, list) - return popup.state.env.commit or CommitSelectViewBuffer.new(list or git.log.list()):open_async()[1] +local function base_commit(popup, list, header) + return popup.state.env.commit or CommitSelectViewBuffer.new(list, header):open_async()[1] end function M.onto_base(popup) @@ -55,7 +55,11 @@ function M.onto_elsewhere(popup) end function M.interactively(popup) - local commit = base_commit(popup, git.log.list({}, {}, {}, true)) + local commit = base_commit( + popup, + git.log.list({}, {}, {}, true), + "Select a commit with to rebase it and all commits above it, or to abort" + ) if commit then if not git.log.is_ancestor(commit, "HEAD") then notification.warn("Commit isn't an ancestor of HEAD") @@ -94,7 +98,11 @@ function M.interactively(popup) end M.reword = operation("rebase_reword", function(popup) - local commit = base_commit(popup) + local commit = base_commit( + popup, + git.log.list(), + "Select a commit to with to reword its message, or to abort" + ) if not commit then return end @@ -110,14 +118,14 @@ M.reword = operation("rebase_reword", function(popup) end) M.modify = operation("rebase_modify", function(popup) - local commit = base_commit(popup) + local commit = base_commit(popup, git.log.list(), "Select a commit to edit with , or to abort") if commit then git.rebase.modify(commit) end end) M.drop = operation("rebase_drop", function(popup) - local commit = base_commit(popup) + local commit = base_commit(popup, git.log.list(), "Select a commit to remove with , or to abort") if commit then git.rebase.drop(commit) end @@ -135,14 +143,16 @@ function M.subset(popup) if popup.state.env.commit and git.log.is_ancestor(popup.state.env.commit, "HEAD") then start = popup.state.env.commit else - start = CommitSelectViewBuffer.new(git.log.list { "HEAD" }):open_async()[1] + start = CommitSelectViewBuffer.new( + git.log.list { "HEAD" }, + "Select a commit with to rebase it and commits above it onto " .. newbase .. ", or to abort" + ) + :open_async()[1] end - if not start then - return + if start then + git.rebase.onto(start, newbase, popup:get_arguments()) end - - git.rebase.onto(start, newbase, popup:get_arguments()) end function M.continue() diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 5c65b569a..8de937858 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -12,7 +12,7 @@ local function get_commits(popup) if #popup.state.env.commits > 0 then commits = popup.state.env.commits else - commits = CommitSelectViewBuffer.new(git.log.list { "--max-count=256" }):open_async() + commits = CommitSelectViewBuffer.new(git.log.list({ "--max-count=256" }), "Select one or more commits to revert with , or to abort"):open_async() end return commits or {} From 1c503db34b22d16523982f9ad74724dc78483a66 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 24 Mar 2024 23:06:08 +0100 Subject: [PATCH 237/443] Add basic bisect popup --- lua/neogit/buffers/status/init.lua | 1 + lua/neogit/config.lua | 3 +- lua/neogit/lib/git.lua | 1 + lua/neogit/popups/bisect/actions.lua | 68 ++++++++++++++++++++++++++++ lua/neogit/popups/bisect/init.lua | 31 +++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 lua/neogit/popups/bisect/actions.lua create mode 100644 lua/neogit/popups/bisect/init.lua diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 53e53e5af..032e520c6 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1000,6 +1000,7 @@ function M:open(kind) [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), + [popups.mapping_for("BisectPopup")] = popups.open("bisect"), [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index b89329340..186529683 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -114,7 +114,7 @@ end ---@alias NeogitConfigMappingsStatus "Close" | "Depth1" | "Depth2" | "Depth3" | "Depth4" | "Toggle" | "Discard" | "Stage" | "StageUnstaged" | "StageAll" | "Unstage" | "UnstageStaged" | "RefreshBuffer" | "GoToFile" | "VSplitOpen" | "SplitOpen" | "TabOpen" | "GoToPreviousHunkHeader" | "GoToNextHunkHeader" | "Console" | "CommandHistory" | "ShowRefs" | "InitRepo" | "YankSelected" | false | fun() ----@alias NeogitConfigMappingsPopup "HelpPopup" | "DiffPopup" | "PullPopup" | "RebasePopup" | "MergePopup" | "PushPopup" | "CommitPopup" | "LogPopup" | "RevertPopup" | "StashPopup" | "IgnorePopup" | "CherryPickPopup" | "BranchPopup" | "FetchPopup" | "ResetPopup" | "RemotePopup" | "TagPopup" | "WorktreePopup" | false +---@alias NeogitConfigMappingsPopup "HelpPopup" | "DiffPopup" | "PullPopup" | "RebasePopup" | "MergePopup" | "PushPopup" | "CommitPopup" | "LogPopup" | "RevertPopup" | "StashPopup" | "IgnorePopup" | "CherryPickPopup" | "BisectPopup" | "BranchPopup" | "FetchPopup" | "ResetPopup" | "RemotePopup" | "TagPopup" | "WorktreePopup" | false ---@alias NeogitConfigMappingsRebaseEditor "Pick" | "Reword" | "Edit" | "Squash" | "Fixup" | "Execute" | "Drop" | "Break" | "MoveUp" | "MoveDown" | "Close" | "OpenCommit" | "Submit" | "Abort" | false | fun() --- @@ -353,6 +353,7 @@ function M.get_default_values() ["i"] = "IgnorePopup", ["t"] = "TagPopup", ["b"] = "BranchPopup", + ["B"] = "BisectPopup", ["w"] = "WorktreePopup", ["c"] = "CommitPopup", ["f"] = "FetchPopup", diff --git a/lua/neogit/lib/git.lua b/lua/neogit/lib/git.lua index 8a12c75e9..5a857cc03 100644 --- a/lua/neogit/lib/git.lua +++ b/lua/neogit/lib/git.lua @@ -26,4 +26,5 @@ return { push = require("neogit.lib.git.push"), index = require("neogit.lib.git.index"), worktree = require("neogit.lib.git.worktree"), + bisect = require("neogit.lib.git.bisect"), } diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua new file mode 100644 index 000000000..47022fd99 --- /dev/null +++ b/lua/neogit/popups/bisect/actions.lua @@ -0,0 +1,68 @@ +local M = {} +local git = require("neogit.lib.git") +local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") +local notification = require("neogit.lib.notification") +local a = require("plenary.async") + +function M.start(popup) + if git.status.is_dirty() then + notification.warn("Cannot bisect with uncommitted changes") + return + end + + local commits = git.log.list { "HEAD" } + local bad_commit = CommitSelectViewBuffer.new( + commits, + "Select bad revision with to start bisecting, or abort with " + ) + :open_async()[1] + if not bad_commit then + return + end + + a.util.scheduler() -- Needed for second select buffer to appear + local good_commit = + CommitSelectViewBuffer.new(commits, "Select good revision with , or abort with "):open_async()[1] + if not good_commit then + return + end + + if git.log.is_ancestor(good_commit, bad_commit) then + notification.info("Bisecting...") + git.bisect.start(good_commit, bad_commit, popup:get_arguments()) + else + local message = ("The %s revision (%s) has to be an ancestor of the %s one (%s)"):format( + "good", + good_commit, + "bad", + bad_commit + ) + notification.warn(message) + end +end + +function M.scripted() + print("scripted") +end + +function M.good() + print("good") +end + +function M.bad() + print("bad") +end + +function M.skip() + print("skip") +end + +function M.reset() + print("reset") +end + +function M.run_script() + print("run_script") +end + +return M diff --git a/lua/neogit/popups/bisect/init.lua b/lua/neogit/popups/bisect/init.lua new file mode 100644 index 000000000..37bd0c10b --- /dev/null +++ b/lua/neogit/popups/bisect/init.lua @@ -0,0 +1,31 @@ +local M = {} + +local popup = require("neogit.lib.popup") +local git = require("neogit.lib.git") +local actions = require("neogit.popups.bisect.actions") + +function M.create(env) + local in_progress = git.bisect.in_progress() + + local p = popup + .builder() + :name("NeogitBisectPopup") + :switch("r", "no-checkout", "Don't checkout commits") + :switch("p", "first-parent", "Follow only first parent of a merge") + :group_heading("Bisect") + :action("B", "Start", actions.start) + :action("S", "Scripted", actions.scripted) + :action_if(in_progress, "b", "Bad", actions.bad) + :action_if(in_progress, "g", "Good", actions.good) + :action_if(in_progress, "s", "Skip", actions.skip) + :action_if(in_progress, "r", "Reset", actions.reset) -- call it abort? + :action_if(in_progress, "S", "Run script", actions.run_script) + :env(env) + :build() + + p:show() + + return p +end + +return M From bc99be87835f3cf6e7b5a30c9ce57e0b8d693d31 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 25 Mar 2024 22:28:39 +0100 Subject: [PATCH 238/443] Add "reset branch" to reset popup --- lua/neogit/popups/reset/init.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/reset/init.lua b/lua/neogit/popups/reset/init.lua index 7df38ede1..d723c3a3f 100644 --- a/lua/neogit/popups/reset/init.lua +++ b/lua/neogit/popups/reset/init.lua @@ -1,5 +1,6 @@ local popup = require("neogit.lib.popup") local actions = require("neogit.popups.reset.actions") +local branch_actions = require("neogit.popups.branch.actions") local M = {} @@ -8,14 +9,15 @@ function M.create(env) .builder() :name("NeogitResetPopup") :group_heading("Reset") + :action("f", "file", actions.a_file) + :action("b", "branch", branch_actions.reset_branch) + :new_action_group("Reset this") :action("m", "mixed (HEAD and index)", actions.mixed) :action("s", "soft (HEAD only)", actions.soft) :action("h", "hard (HEAD, index and files)", actions.hard) :action("k", "keep (HEAD and index, keeping uncommitted)", actions.keep) :action("i", "index (only)", actions.index) :action("w", "worktree (only)", actions.worktree) - :group_heading("") - :action("f", "a file", actions.a_file) :env(env) :build() From a37ebee3b9d742e78caba183450c9cdba7013718 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 25 Mar 2024 22:32:11 +0100 Subject: [PATCH 239/443] Improve how buffer context highlights work: We only need to redraw the context lines, not every line in the buffer --- lua/neogit/lib/buffer.lua | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index a161218ba..b9379cc6b 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -666,12 +666,11 @@ function Buffer.create(config) if config.context_highlight then buffer:create_namespace("ViewContext") - buffer:set_decorations("ViewContext", { on_start = function() return buffer:exists() and buffer:is_focused() end, - on_win = function(_, _, _, top, bottom) + on_win = function() buffer:clear_namespace("ViewContext") local context = buffer.ui:get_cursor_context() @@ -679,22 +678,17 @@ function Buffer.create(config) return end - local first = context.position.row_start - local last = context.position.row_end local cursor = vim.fn.line(".") - - for line = top, bottom do - if line >= first and line <= last then - local line_hl = ("%s%s"):format( - buffer.ui:get_line_highlight(line) or "NeogitDiffContext", - line == cursor and "Cursor" or "Highlight" - ) - - buffer:buffered_add_line_highlight(line - 1, line_hl, { - priority = 200, - namespace = "ViewContext", - }) - end + for line = context.position.row_start, context.position.row_end do + local line_hl = ("%s%s"):format( + buffer.ui:get_line_highlight(line) or "NeogitDiffContext", + line == cursor and "Cursor" or "Highlight" + ) + + buffer:buffered_add_line_highlight(line - 1, line_hl, { + priority = 200, + namespace = "ViewContext", + }) end buffer:flush_line_highlight_buffer() From 0bedd9bd916d98b392c3983b377e713375ed9a4b Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 25 Mar 2024 22:32:47 +0100 Subject: [PATCH 240/443] Add redraw after choice input so any followup input calls will have a correct UI --- lua/neogit/lib/input.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index 187ee22c3..7e263572f 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -34,6 +34,7 @@ end ---@return string First letter of the selected choice function M.get_choice(msg, options) local choice = vim.fn.confirm(msg, table.concat(options.values, "\n"), options.default) + vim.cmd("redraw") return options.values[choice]:match("&(.)") end From 341dc635bc4c7a9de18c9f60ef40c392c61adcbf Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 20:29:30 +0100 Subject: [PATCH 241/443] Use new refs lib for refs. Should help simplify a lot of calls to various places - they can now all get refs from refs. --- lua/neogit/lib/git/cli.lua | 1 + lua/neogit/lib/git/refs.lua | 50 ++++++++++++++++++--- lua/neogit/popups/branch/actions.lua | 31 +++++++------ lua/neogit/popups/branch_config/actions.lua | 6 +-- lua/neogit/popups/fetch/actions.lua | 2 +- lua/neogit/popups/log/actions.lua | 4 +- lua/neogit/popups/merge/actions.lua | 2 +- lua/neogit/popups/pull/actions.lua | 5 +-- lua/neogit/popups/push/actions.lua | 4 +- lua/neogit/popups/rebase/actions.lua | 8 ++-- lua/neogit/popups/reset/actions.lua | 5 +-- lua/neogit/popups/worktree/actions.lua | 4 +- 12 files changed, 76 insertions(+), 46 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index eba7b4562..62c673e1c 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -538,6 +538,7 @@ local configurations = { ["for-each-ref"] = config { options = { format = "--format", + sort = "--sort", }, }, diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 04b251775..c4a9d5f52 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -1,17 +1,55 @@ local cli = require("neogit.lib.git.cli") +local config = require("neogit.config") local record = require("neogit.lib.record") local repo = require("neogit.lib.git.repository") +local util = require("neogit.lib.util") local M = {} ---- Lists revisions +---@param namespaces? string[] +---@param format? string format string passed to `git for-each-ref` +---@param sortby? string sort by string passed to `git for-each-ref` ---@return table -function M.list() - local revisions = cli["for-each-ref"].format('"%(refname:short)"').call({ hidden = true }).stdout - for i, str in ipairs(revisions) do - revisions[i] = string.sub(str, 2, -2) +function M.list(namespaces, format, sortby) + return util.filter_map( + cli["for-each-ref"].format(format or "%(refname)").sort(sortby or config.values.sort_branches).call({ hidden = true }).stdout, + function(revision) + for _, namespace in ipairs(namespaces or { "refs/heads", "refs/remotes", "refs/tags" }) do + if revision:match("^" .. namespace) then + local name, _ = revision:gsub("^" .. namespace .. "/", "") + return name + end + end + end) +end + +---@return string[] +function M.list_tags() + return M.list({ "refs/tags" }) +end + +---@return string[] +function M.list_branches() + return util.merge(M.list_local_branches(), M.list_remote_branches()) +end + +---@return string[] +function M.list_local_branches() + return M.list({ "refs/heads" }) +end + +---@param remote? string Filter branches by remote +---@return string[] +function M.list_remote_branches(remote) + local remote_branches = M.list({ "refs/remotes" }) + + if remote then + return vim.tbl_filter(function(ref) + return ref:match("^" .. remote .. "/") + end, remote_branches) + else + return remote_branches end - return revisions end local record_template = record.encode({ diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 29c829918..e585383b7 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -54,8 +54,8 @@ local function create_branch(popup, prompt, checkout) local options = util.deduplicate(util.merge( { popup.state.env.commits[1] }, { git.branch.current() or "HEAD" }, - git.branch.get_all_branches(false), - git.tag.list(), + git.refs.list_branches(), + git.refs.list_tags(), git.refs.heads() )) @@ -87,7 +87,12 @@ M.spin_out_branch = operation("spin_out_branch", function() end) M.checkout_branch_revision = operation("checkout_branch_revision", function(popup) - local options = util.merge(popup.state.env.commits, git.branch.get_all_branches(false), git.tag.list()) + local options = util.merge( + popup.state.env.commits, + git.refs.list_branches(), + git.refs.list_tags(), + git.refs.heads() + ) local selected_branch = FuzzyFinderBuffer.new(options):open_async() if not selected_branch then return @@ -98,8 +103,8 @@ M.checkout_branch_revision = operation("checkout_branch_revision", function(popu end) M.checkout_local_branch = operation("checkout_local_branch", function(popup) - local local_branches = git.branch.get_local_branches(true) - local remote_branches = util.filter_map(git.branch.get_remote_branches(), function(name) + local local_branches = git.refs.list_local_branches() + local remote_branches = util.filter_map(git.refs.list_remote_branches(), function(name) local branch_name = name:match([[%/(.*)$]]) -- Remove remote branches that have a local branch by the same name if branch_name and not vim.tbl_contains(local_branches, branch_name) then @@ -140,7 +145,7 @@ M.create_branch = operation("create_branch", function(popup) end) M.configure_branch = operation("configure_branch", function() - local branch_name = FuzzyFinderBuffer.new(git.branch.get_local_branches(true)):open_async() + local branch_name = FuzzyFinderBuffer.new(git.refs.list_local_branches()):open_async() if not branch_name then return end @@ -149,13 +154,7 @@ M.configure_branch = operation("configure_branch", function() end) M.rename_branch = operation("rename_branch", function() - local current_branch = git.branch.current() - local branches = git.branch.get_local_branches(false) - if current_branch then - table.insert(branches, 1, current_branch) - end - - local selected_branch = FuzzyFinderBuffer.new(branches):open_async() + local selected_branch = FuzzyFinderBuffer.new(git.refs.list_local_branches()):open_async() if not selected_branch then return end @@ -187,8 +186,8 @@ M.reset_branch = operation("reset_branch", function(popup) util.merge( popup.state.env.commits, relatives, - git.branch.get_all_branches(false), - git.tag.list(), + git.refs.list_branches(), + git.refs.list_refs(), git.refs.heads() ) ) @@ -210,7 +209,7 @@ M.reset_branch = operation("reset_branch", function(popup) end) M.delete_branch = operation("delete_branch", function() - local branches = git.branch.get_all_branches(true) + local branches = git.refs.list_branches() local selected_branch = FuzzyFinderBuffer.new(branches):open_async() if not selected_branch then return diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index f322be3c0..6b4102149 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -41,12 +41,8 @@ function M.update_pull_rebase() end function M.merge_config(branch) - local local_branches = git.branch.get_local_branches() - local remote_branches = git.branch.get_remote_branches() - local branches = util.merge(local_branches, remote_branches) - return a.void(function(popup, c) - local target = FuzzyFinderBuffer.new(branches):open_async { prompt_prefix = "upstream" } + local target = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = "upstream" } if not target then return end diff --git a/lua/neogit/popups/fetch/actions.lua b/lua/neogit/popups/fetch/actions.lua index f96cb7986..abd28228c 100644 --- a/lua/neogit/popups/fetch/actions.lua +++ b/lua/neogit/popups/fetch/actions.lua @@ -85,7 +85,7 @@ function M.fetch_another_branch(popup) return end - local branches = util.filter_map(git.branch.get_all_branches(true), function(branch) + local branches = util.filter_map(git.refs.list_branches(), function(branch) return branch:match("^" .. remote .. "/(.*)") end) diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 6a035c532..1ae50143d 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -41,7 +41,7 @@ function M.log_local_branches(popup) end function M.log_other(popup) - local branch = FuzzyFinderBuffer.new(git.branch.get_all_branches()):open_async() + local branch = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() if branch then LogViewBuffer.new(commits(popup, { branch }), popup:get_internal_arguments(), popup.state.env.files) :open() @@ -73,7 +73,7 @@ function M.reflog_head(popup) end function M.reflog_other(popup) - local branch = FuzzyFinderBuffer.new(git.branch.get_local_branches()):open_async() + local branch = FuzzyFinderBuffer.new(git.refs.list_local_branches()):open_async() if branch then ReflogViewBuffer.new(git.reflog.list(branch, popup:get_arguments())):open() end diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index bf4e6c39d..cdd46fa58 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -20,7 +20,7 @@ function M.abort() end function M.merge(popup) - local branch = FuzzyFinderBuffer.new(git.branch.get_all_branches()):open_async() + local branch = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() if branch then git.merge.merge(branch, popup:get_arguments()) end diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index f7b7c7798..dfe1b76f3 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -48,7 +48,7 @@ function M.from_upstream(popup) if not upstream then set_upstream = true - upstream = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async { + upstream = FuzzyFinderBuffer.new(git.refs.list_remote_branches()):open_async { prompt_prefix = "set upstream", } @@ -62,8 +62,7 @@ function M.from_upstream(popup) end function M.from_elsewhere(popup) - local target = FuzzyFinderBuffer.new(git.branch.get_all_branches(false)) - :open_async { prompt_prefix = "pull" } + local target = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = "pull" } if not target then return end diff --git a/lua/neogit/popups/push/actions.lua b/lua/neogit/popups/push/actions.lua index f6495c6e0..dea4347e6 100644 --- a/lua/neogit/popups/push/actions.lua +++ b/lua/neogit/popups/push/actions.lua @@ -72,7 +72,7 @@ function M.to_upstream(popup) end function M.to_elsewhere(popup) - local target = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async { + local target = FuzzyFinderBuffer.new(git.refs.list_remote_branches()):open_async { prompt_prefix = "push", } @@ -96,7 +96,7 @@ function M.push_other(popup) return end - local destinations = git.branch.get_remote_branches() + local destinations = git.refs.list_remote_branches() for _, remote in ipairs(git.remote.list()) do table.insert(destinations, 1, remote .. "/" .. source) end diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index b1cbd5d53..c3c2bb210 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -36,7 +36,7 @@ function M.onto_upstream(popup) if git.repo.upstream.ref then upstream = string.format("refs/remotes/%s", git.repo.upstream.ref) else - local target = FuzzyFinderBuffer.new(git.branch.get_remote_branches()):open_async() + local target = FuzzyFinderBuffer.new(git.refs.list_remote_branches()):open_async() if not target then return end @@ -48,7 +48,7 @@ function M.onto_upstream(popup) end function M.onto_elsewhere(popup) - local target = FuzzyFinderBuffer.new(git.branch.get_all_branches()):open_async() + local target = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() if target then git.rebase.onto_branch(target, popup:get_arguments()) end @@ -132,9 +132,7 @@ M.drop = operation("rebase_drop", function(popup) end) function M.subset(popup) - local newbase = FuzzyFinderBuffer.new(git.branch.get_all_branches()) - :open_async { prompt_prefix = "rebase subset onto" } - + local newbase = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = "rebase subset onto" } if not newbase then return end diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 4db6198b3..9c39ea418 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -13,9 +13,8 @@ local function commit(popup, prompt) commit = popup.state.env.commit else local commits = util.merge( - { git.branch.current() }, - git.branch.get_all_branches(false), - git.tag.list(), + git.refs.list_branches(), + git.refs.list_tags(), git.refs.heads() ) commit = FuzzyFinderBuffer.new(commits):open_async { prompt_prefix = prompt } diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 6f873dcca..120afe176 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -37,7 +37,7 @@ local function get_path(prompt) end M.checkout_worktree = operations("checkout_worktree", function() - local options = util.merge(git.branch.get_all_branches(), git.tag.list(), git.refs.heads()) + local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) local selected = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "checkout" } if not selected then return @@ -60,7 +60,7 @@ M.create_worktree = operations("create_worktree", function() return end - local options = util.merge(git.branch.get_all_branches(), git.tag.list(), git.refs.heads()) + local options = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) local selected = FuzzyFinderBuffer.new(options) :open_async { prompt_prefix = "Create and checkout branch starting at" } if not selected then From 7397650fd8ac2512c299f2d278cfb71e7dca59cd Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 22:18:31 +0100 Subject: [PATCH 242/443] Initial working bisect --- lua/neogit/buffers/commit_view/init.lua | 3 + lua/neogit/buffers/log_view/init.lua | 6 ++ lua/neogit/buffers/reflog_view/init.lua | 6 ++ lua/neogit/buffers/refs_view/init.lua | 6 ++ lua/neogit/buffers/status/init.lua | 7 +- lua/neogit/buffers/status/ui.lua | 30 ++++++++ lua/neogit/config.lua | 5 ++ lua/neogit/lib/git/bisect.lua | 99 +++++++++++++++++++++++++ lua/neogit/lib/git/cli.lua | 2 + lua/neogit/lib/git/repository.lua | 2 + lua/neogit/lib/hl.lua | 1 + lua/neogit/popups/bisect/actions.lua | 73 ++++++++++-------- lua/neogit/popups/bisect/init.lua | 20 ++--- 13 files changed, 219 insertions(+), 41 deletions(-) create mode 100644 lua/neogit/lib/git/bisect.lua diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index ef275853d..af08c8168 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -226,6 +226,9 @@ function M:open(kind) p { commit = self.commit_info.oid } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = { self.commit_info.oid } } + end), ["q"] = function() self:close() end, diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 0dc82df74..25d922e35 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -75,6 +75,9 @@ function M:open() p { commit = self.buffer.ui:get_commit_under_cursor() } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) local items = self.buffer.ui:get_commits_in_selection() p { @@ -84,6 +87,9 @@ function M:open() end), }, n = { + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 85f092be6..7ac87d43c 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -74,8 +74,14 @@ function M:open(_) item = { name = items }, } end), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), }, n = { + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index d13fad2cd..328e15d41 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -93,6 +93,9 @@ function M:open() p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) local items = self.buffer.ui:get_commits_in_selection() p { @@ -127,6 +130,9 @@ function M:open() p() -- p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commits_in_selection()[1] } end), diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 032e520c6..b94d5ebe2 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -357,6 +357,9 @@ function M:open(kind) [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) p { paths = self.buffer.ui:get_filepaths_in_selection(), git_root = git.repo.git_root } end), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), [popups.mapping_for("FetchPopup")] = popups.open("fetch"), [popups.mapping_for("PullPopup")] = popups.open("pull"), @@ -1000,7 +1003,9 @@ function M:open(kind) [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), - [popups.mapping_for("BisectPopup")] = popups.open("bisect"), + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end), [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index b52c3ea0c..fab4a00bd 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -310,6 +310,26 @@ local SectionItemSequencer = Component.new(function(item) }, { yankable = item.oid, oid = item.oid }) end) +local SectionItemBisect = Component.new(function(item) + local highlight + if item.action == "good" then + highlight = "NeogitGraphGreen" + elseif item.action == "bad" then + highlight = "NeogitGraphRed" + elseif item.finished then + highlight = "NeogitGraphBoldOrange" + end + + return row({ + text(item.finished and "> " or " "), + text.highlight(highlight)(util.pad_right(item.action, 5)), + text(" "), + text.highlight("Comment")(item.abbreviated_commit), + text(" "), + text(item.subject), + }, { yankable = item.oid, oid = item.oid }) +end) + function M.Status(state, config) -- stylua: ignore start local show_hint = not config.disable_hint @@ -335,6 +355,9 @@ function M.Status(state, config) local show_revert = state.sequencer.revert and not config.sections.sequencer.hidden + local show_bisect = #state.bisect.items > 0 + and not config.sections.bisect.hidden + local show_untracked = #state.untracked.items > 0 and not config.sections.untracked.hidden @@ -431,6 +454,13 @@ function M.Status(state, config) folded = config.sections.sequencer.folded, name = "revert", }, + show_bisect and SequencerSection { + title = SectionTitle { title = "Bisecting" }, + render = SectionItemBisect, + items = state.bisect.items, + folded = config.sections.bisect.folded, + name = "bisect", + }, show_untracked and Section { title = SectionTitle { title = "Untracked files" }, render = SectionItemFile("untracked"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 186529683..11ce2df29 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -100,6 +100,7 @@ end ---@field recent NeogitConfigSection|nil ---@field rebase NeogitConfigSection|nil ---@field sequencer NeogitConfigSection|nil +---@field bisect NeogitConfigSection|nil ---@class HighlightOptions ---@field italic? boolean @@ -258,6 +259,10 @@ function M.get_default_values() folded = false, hidden = false, }, + bisect = { + folded = false, + hidden = false, + }, untracked = { folded = false, hidden = false, diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua new file mode 100644 index 000000000..1be66fb65 --- /dev/null +++ b/lua/neogit/lib/git/bisect.lua @@ -0,0 +1,99 @@ +local cli = require("neogit.lib.git.cli") + +local M = {} + +local function fire_bisect_event(data) + vim.api.nvim_exec_autocmds("User", { pattern = "NeogitBisect", modeline = false, data = data }) +end + +function M.in_progress() + local git = require("neogit.lib.git") + return git.repo:git_path("BISECT_LOG"):exists() +end + +function M.is_finished() + local git = require("neogit.lib.git") + return git.repo.state.bisect.finished +end + +---@param bad_revision string +---@param good_revision string +---@param args? table +function M.start(bad_revision, good_revision, args) + local result = cli.bisect.args("start").arg_list(args).args(good_revision, bad_revision).call() + + if result.code == 0 then + fire_bisect_event({ type = "start" }) + end +end + +---@param state string +local function cmd(state) + local result = cli.bisect.args(state).call() + + if result.code == 0 then + fire_bisect_event({ type = state }) + end +end + +function M.good() + cmd("good") +end + +function M.bad() + cmd("bad") +end + +function M.skip() + cmd("skip") +end + +function M.reset() + cmd("reset") +end + +---@class BisectItem +---@field action string +---@field oid string +---@field subject string +---@field abbreviated_commit string + +local function update_bisect_information(state) + state.bisect = { items = {}, finished = false } + + local finished + local git = require("neogit.lib.git") + local bisect_log = git.repo:git_path("BISECT_LOG") + + if bisect_log:exists() then + for line in bisect_log:iter() do + if line:match("^#") and line ~= "" then + local action, oid, subject = line:match("^# ([^:]+): %[(.+)%] (.+)") + + finished = action == "first bad commit" + if finished then + fire_bisect_event({ type = "finished", oid = oid }) + end + + ---@type BisectItem + local item = { + finished = finished, + action = action, + subject = subject, + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()) + } + + table.insert(state.bisect.items, item) + end + end + + state.bisect.finished = finished + end +end + +M.register = function(meta) + meta.update_bisect_information = update_bisect_information +end + +return M diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 62c673e1c..e78aa53c7 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -579,6 +579,8 @@ local configurations = { }, ["verify-commit"] = config {}, + + ["bisect"] = config {}, } -- NOTE: Use require("neogit.lib.git.repository").git_root instead of calling this function. diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index e374ca633..af31d6475 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -44,6 +44,7 @@ local function empty_state() rebase = { items = {}, head = nil }, sequencer = { items = {}, head = nil }, merge = { items = {}, head = nil, msg = nil }, + bisect = { items = {}, finished = false }, } end @@ -125,6 +126,7 @@ if not M.initialized then "rebase", "sequencer", "merge", + "bisect", } for _, m in ipairs(modules) do diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 6ddcafd35..8dea970bc 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -123,6 +123,7 @@ function M.setup() NeogitGraphPurple = { fg = palette.purple }, NeogitGraphGray = { fg = palette.grey }, NeogitGraphOrange = { fg = palette.orange }, + NeogitGraphBoldOrange = { fg = palette.orange, bold = palette.bold }, NeogitGraphBoldRed = { fg = palette.red, bold = palette.bold }, NeogitGraphBoldWhite = { fg = palette.white, bold = palette.bold }, NeogitGraphBoldYellow = { fg = palette.yellow, bold = palette.bold }, diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua index 47022fd99..72fcd1691 100644 --- a/lua/neogit/popups/bisect/actions.lua +++ b/lua/neogit/popups/bisect/actions.lua @@ -1,8 +1,9 @@ local M = {} local git = require("neogit.lib.git") -local CommitSelectViewBuffer = require("neogit.buffers.commit_select_view") local notification = require("neogit.lib.notification") -local a = require("plenary.async") +local input = require("neogit.lib.input") +local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") +local util = require("neogit.lib.util") function M.start(popup) if git.status.is_dirty() then @@ -10,55 +11,65 @@ function M.start(popup) return end - local commits = git.log.list { "HEAD" } - local bad_commit = CommitSelectViewBuffer.new( - commits, - "Select bad revision with to start bisecting, or abort with " - ) - :open_async()[1] - if not bad_commit then - return - end + popup.state.env.commits = popup.state.env.commits or {} - a.util.scheduler() -- Needed for second select buffer to appear - local good_commit = - CommitSelectViewBuffer.new(commits, "Select good revision with , or abort with "):open_async()[1] - if not good_commit then - return + local bad_revision, good_revision + if popup.state.env.commits and #popup.state.env.commits > 1 then + bad_revision = popup.state.env.commits[1] + good_revision = popup.state.env.commits[#popup.state.env.commits] + else + local refs = util.merge({ popup.state.env.commits[1] }, git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) + bad_revision = FuzzyFinderBuffer.new(refs):open_async { + prompt_prefix = "Start bisect with bad revision" + } + + if not bad_revision then + return + end + + good_revision = FuzzyFinderBuffer.new(refs):open_async { + prompt_prefix = "Good revision" + } + + if not good_revision then + return + end end - if git.log.is_ancestor(good_commit, bad_commit) then + if git.log.is_ancestor(bad_revision, good_revision) then notification.info("Bisecting...") - git.bisect.start(good_commit, bad_commit, popup:get_arguments()) + git.bisect.start(bad_revision, good_revision, popup:get_arguments()) else - local message = ("The %s revision (%s) has to be an ancestor of the %s one (%s)"):format( - "good", - good_commit, - "bad", - bad_commit + local message = ("The good revision (%s) has to be an ancestor of the bad one (%s)"):format( + good_revision, + bad_revision ) + notification.warn(message) end end -function M.scripted() - print("scripted") -end - function M.good() - print("good") + git.bisect.good() end function M.bad() - print("bad") + git.bisect.bad() end function M.skip() - print("skip") + git.bisect.skip() end function M.reset() - print("reset") + git.bisect.reset() +end + +function M.scripted() + local command = input.get_user_input("Bisect shell command") + if command then + git.bisect.run(command) + end end function M.run_script() diff --git a/lua/neogit/popups/bisect/init.lua b/lua/neogit/popups/bisect/init.lua index 37bd0c10b..c20c0ff1e 100644 --- a/lua/neogit/popups/bisect/init.lua +++ b/lua/neogit/popups/bisect/init.lua @@ -6,20 +6,22 @@ local actions = require("neogit.popups.bisect.actions") function M.create(env) local in_progress = git.bisect.in_progress() + local finished = git.bisect.is_finished() local p = popup .builder() :name("NeogitBisectPopup") - :switch("r", "no-checkout", "Don't checkout commits") - :switch("p", "first-parent", "Follow only first parent of a merge") - :group_heading("Bisect") - :action("B", "Start", actions.start) - :action("S", "Scripted", actions.scripted) - :action_if(in_progress, "b", "Bad", actions.bad) - :action_if(in_progress, "g", "Good", actions.good) - :action_if(in_progress, "s", "Skip", actions.skip) + :switch_if(not in_progress, "r", "no-checkout", "Don't checkout commits") + :switch_if(not in_progress, "p", "first-parent", "Follow only first parent of a merge") + :group_heading_if(not in_progress, "Bisect") + :group_heading_if(in_progress, "Actions") + :action_if(not in_progress, "B", "Start", actions.start) + :action_if(not in_progress, "S", "Scripted", actions.scripted) + :action_if(not finished and in_progress, "b", "Bad", actions.bad) + :action_if(not finished and in_progress, "g", "Good", actions.good) + :action_if(not finished and in_progress, "s", "Skip", actions.skip) :action_if(in_progress, "r", "Reset", actions.reset) -- call it abort? - :action_if(in_progress, "S", "Run script", actions.run_script) + :action_if(not finished and in_progress, "S", "Run script", actions.run_script) :env(env) :build() From e1dd00a347299a619e59d8688908ec9202ac923e Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 22:18:57 +0100 Subject: [PATCH 243/443] Only show tag distance when not detached --- lua/neogit/buffers/status/ui.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index fab4a00bd..4f55e1ef1 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -341,6 +341,8 @@ function M.Status(state, config) and state.head.branch ~= "(detached)" local show_tag = state.head.tag.name + + local show_tag_distance = state.head.tag.name and state.head.branch ~= "(detached)" local show_merge = state.merge.head @@ -415,7 +417,7 @@ function M.Status(state, config) }, show_tag and Tag { name = state.head.tag.name, - distance = state.head.tag.distance, + distance = show_tag_distance and state.head.tag.distance, yankable = state.head.tag.oid, }, EmptyLine, From 27d1835b489293f7c03c4d7715f475db8e70d69d Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 22:19:25 +0100 Subject: [PATCH 244/443] Improve Head and Tag when detached --- lua/neogit/buffers/status/ui.lua | 35 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 4f55e1ef1..9a1b76aa0 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -71,8 +71,17 @@ local HINT = Component.new(function(props) end) local HEAD = Component.new(function(props) - local highlight = props.remote and "NeogitRemote" or "NeogitBranch" - local ref = props.remote and ("%s/%s"):format(props.remote, props.branch) or props.branch + local highlight, ref + if props.branch == "(detached)" then + highlight = "Comment" + ref = props.oid + elseif props.remote then + highlight = "NeogitRemote" + ref = ("%s/%s"):format(props.remote, props.branch) + else + highlight = "NeogitBranch" + ref = props.branch + end return row({ text(util.pad_right(props.name .. ":", 10)), @@ -83,13 +92,20 @@ local HEAD = Component.new(function(props) end) local Tag = Component.new(function(props) - return row({ - text(util.pad_right("Tag:", 10)), - text.highlight("NeogitTagName")(props.name), - text(" ("), - text.highlight("NeogitTagDistance")(props.distance), - text(")"), - }, { yankable = props.yankable }) + if props.distance then + return row({ + text(util.pad_right("Tag:", 10)), + text.highlight("NeogitTagName")(props.name), + text(" ("), + text.highlight("NeogitTagDistance")(props.distance), + text(")"), + }, { yankable = props.yankable }) + else + return row({ + text(util.pad_right("Tag:", 10)), + text.highlight("NeogitTagName")(props.name), + }, { yankable = props.yankable }) + end end) local SectionTitle = Component.new(function(props) @@ -398,6 +414,7 @@ function M.Status(state, config) HEAD { name = "Head", branch = state.head.branch, + oid = state.head.abbrev, msg = state.head.commit_message, yankable = state.head.oid, }, From 5770bf42ec7d86e51380b8e3186d3279a78579e8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 22:19:42 +0100 Subject: [PATCH 245/443] Fix bug in reflog when rebase finished --- lua/neogit/lib/git/reflog.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index 36941cef2..12f127a0b 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -26,8 +26,7 @@ local function parse(entries) message = command:match("^merge (.*)") .. ": " .. message command = "merge" elseif command:match("^rebase") then - local type = command:match("%((.-)%)") - command = "rebase " .. type + command = "rebase " .. (command:match("%((.-)%)") or command) elseif command:match("commit %(.-%)") then -- amend and merge command = command:match("%((.-)%)") end From ac32caff383128bedb5278e4c0db04ba35af0e89 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 22:22:08 +0100 Subject: [PATCH 246/443] Lint --- lua/neogit/buffers/refs_view/ui.lua | 11 +++++++++-- lua/neogit/buffers/status/init.lua | 5 +---- lua/neogit/lib/buffer.lua | 8 +------- lua/neogit/lib/git/bisect.lua | 8 ++++---- lua/neogit/lib/git/refs.lua | 14 +++++++++----- lua/neogit/popups/bisect/actions.lua | 11 ++++++++--- lua/neogit/popups/branch/actions.lua | 8 ++------ lua/neogit/popups/rebase/actions.lua | 3 ++- lua/neogit/popups/reset/actions.lua | 6 +----- lua/neogit/popups/revert/actions.lua | 5 ++++- 10 files changed, 41 insertions(+), 38 deletions(-) diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index c561e5cd7..51cffffb1 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -61,7 +61,9 @@ local function section(refs, heading, head) ---@param this Component ---@param ui Ui on_open = a.void(function(this, ui) - vim.cmd(string.format("echomsg 'Getting cherries for %s'", ref.oid:sub(1, git.log.abbreviated_size()))) + vim.cmd( + string.format("echomsg 'Getting cherries for %s'", ref.oid:sub(1, git.log.abbreviated_size())) + ) local cherries = Cherries(ref, head) if cherries.children[1] then @@ -81,7 +83,12 @@ local function section(refs, heading, head) ) ) else - vim.cmd(string.format("redraw | echomsg 'No cherries found for %s'", ref.oid:sub(1, git.log.abbreviated_size()))) + vim.cmd( + string.format( + "redraw | echomsg 'No cherries found for %s'", + ref.oid:sub(1, git.log.abbreviated_size()) + ) + ) end end), }) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index b94d5ebe2..8ba673136 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1177,10 +1177,7 @@ function M:refresh(partial, reason) self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) if cursor and view then - self.buffer:restore_view( - view, - self.buffer.ui:resolve_cursor_location(cursor) - ) + self.buffer:restore_view(view, self.buffer.ui:resolve_cursor_location(cursor)) end api.nvim_exec_autocmds("User", { pattern = "NeogitStatusRefreshed", modeline = false }) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b9379cc6b..f2fd2529b 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -527,13 +527,7 @@ function Buffer:set_header(text) vim.wo[winid].wrap = false vim.wo[winid].winhl = "NormalFloat:NeogitFloatHeader" - fn.matchadd( - "NeogitFloatHeaderHighlight", - [[\v\|\]], - 100, - -1, - { window = winid } - ) + fn.matchadd("NeogitFloatHeaderHighlight", [[\v\|\]], 100, -1, { window = winid }) -- Scroll the buffer viewport to the top so the header is visible self:call(function() diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index 1be66fb65..5b1c33f58 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -23,7 +23,7 @@ function M.start(bad_revision, good_revision, args) local result = cli.bisect.args("start").arg_list(args).args(good_revision, bad_revision).call() if result.code == 0 then - fire_bisect_event({ type = "start" }) + fire_bisect_event { type = "start" } end end @@ -32,7 +32,7 @@ local function cmd(state) local result = cli.bisect.args(state).call() if result.code == 0 then - fire_bisect_event({ type = state }) + fire_bisect_event { type = state } end end @@ -72,7 +72,7 @@ local function update_bisect_information(state) finished = action == "first bad commit" if finished then - fire_bisect_event({ type = "finished", oid = oid }) + fire_bisect_event { type = "finished", oid = oid } end ---@type BisectItem @@ -81,7 +81,7 @@ local function update_bisect_information(state) action = action, subject = subject, oid = oid, - abbreviated_commit = oid:sub(1, git.log.abbreviated_size()) + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), } table.insert(state.bisect.items, item) diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index c4a9d5f52..d7415285b 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -12,7 +12,10 @@ local M = {} ---@return table function M.list(namespaces, format, sortby) return util.filter_map( - cli["for-each-ref"].format(format or "%(refname)").sort(sortby or config.values.sort_branches).call({ hidden = true }).stdout, + cli["for-each-ref"] + .format(format or "%(refname)") + .sort(sortby or config.values.sort_branches) + .call({ hidden = true }).stdout, function(revision) for _, namespace in ipairs(namespaces or { "refs/heads", "refs/remotes", "refs/tags" }) do if revision:match("^" .. namespace) then @@ -20,12 +23,13 @@ function M.list(namespaces, format, sortby) return name end end - end) + end + ) end ---@return string[] function M.list_tags() - return M.list({ "refs/tags" }) + return M.list { "refs/tags" } end ---@return string[] @@ -35,13 +39,13 @@ end ---@return string[] function M.list_local_branches() - return M.list({ "refs/heads" }) + return M.list { "refs/heads" } end ---@param remote? string Filter branches by remote ---@return string[] function M.list_remote_branches(remote) - local remote_branches = M.list({ "refs/remotes" }) + local remote_branches = M.list { "refs/remotes" } if remote then return vim.tbl_filter(function(ref) diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua index 72fcd1691..b556dc1da 100644 --- a/lua/neogit/popups/bisect/actions.lua +++ b/lua/neogit/popups/bisect/actions.lua @@ -18,9 +18,14 @@ function M.start(popup) bad_revision = popup.state.env.commits[1] good_revision = popup.state.env.commits[#popup.state.env.commits] else - local refs = util.merge({ popup.state.env.commits[1] }, git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) + local refs = util.merge( + { popup.state.env.commits[1] }, + git.refs.list_branches(), + git.refs.list_tags(), + git.refs.heads() + ) bad_revision = FuzzyFinderBuffer.new(refs):open_async { - prompt_prefix = "Start bisect with bad revision" + prompt_prefix = "Start bisect with bad revision", } if not bad_revision then @@ -28,7 +33,7 @@ function M.start(popup) end good_revision = FuzzyFinderBuffer.new(refs):open_async { - prompt_prefix = "Good revision" + prompt_prefix = "Good revision", } if not good_revision then diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index e585383b7..7880e716b 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -87,12 +87,8 @@ M.spin_out_branch = operation("spin_out_branch", function() end) M.checkout_branch_revision = operation("checkout_branch_revision", function(popup) - local options = util.merge( - popup.state.env.commits, - git.refs.list_branches(), - git.refs.list_tags(), - git.refs.heads() - ) + local options = + util.merge(popup.state.env.commits, git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) local selected_branch = FuzzyFinderBuffer.new(options):open_async() if not selected_branch then return diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index c3c2bb210..f1e2850a5 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -132,7 +132,8 @@ M.drop = operation("rebase_drop", function(popup) end) function M.subset(popup) - local newbase = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = "rebase subset onto" } + local newbase = FuzzyFinderBuffer.new(git.refs.list_branches()) + :open_async { prompt_prefix = "rebase subset onto" } if not newbase then return end diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 9c39ea418..51453ab37 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -12,11 +12,7 @@ local function commit(popup, prompt) if popup.state.env.commit then commit = popup.state.env.commit else - local commits = util.merge( - git.refs.list_branches(), - git.refs.list_tags(), - git.refs.heads() - ) + local commits = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) commit = FuzzyFinderBuffer.new(commits):open_async { prompt_prefix = prompt } if not commit then return diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 8de937858..e317b20a5 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -12,7 +12,10 @@ local function get_commits(popup) if #popup.state.env.commits > 0 then commits = popup.state.env.commits else - commits = CommitSelectViewBuffer.new(git.log.list({ "--max-count=256" }), "Select one or more commits to revert with , or to abort"):open_async() + commits = CommitSelectViewBuffer.new( + git.log.list { "--max-count=256" }, + "Select one or more commits to revert with , or to abort" + ):open_async() end return commits or {} From cd3f1c32b4482318b0efd48b7ef4642f91cfeb34 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 26 Mar 2024 22:27:54 +0100 Subject: [PATCH 247/443] Don't show script action in bisect --- lua/neogit/popups/bisect/init.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/neogit/popups/bisect/init.lua b/lua/neogit/popups/bisect/init.lua index c20c0ff1e..90925c7c2 100644 --- a/lua/neogit/popups/bisect/init.lua +++ b/lua/neogit/popups/bisect/init.lua @@ -16,12 +16,12 @@ function M.create(env) :group_heading_if(not in_progress, "Bisect") :group_heading_if(in_progress, "Actions") :action_if(not in_progress, "B", "Start", actions.start) - :action_if(not in_progress, "S", "Scripted", actions.scripted) + :action_if(not in_progress, "S", "Scripted") :action_if(not finished and in_progress, "b", "Bad", actions.bad) :action_if(not finished and in_progress, "g", "Good", actions.good) :action_if(not finished and in_progress, "s", "Skip", actions.skip) - :action_if(in_progress, "r", "Reset", actions.reset) -- call it abort? - :action_if(not finished and in_progress, "S", "Run script", actions.run_script) + :action_if(in_progress, "r", "Reset", actions.reset) + :action_if(not finished and in_progress, "S", "Run script") :env(env) :build() From 89edf921a174399f487481ffae6fc97a9f435c43 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 12:26:39 +0100 Subject: [PATCH 248/443] add bisect to help popup --- lua/neogit/buffers/status/init.lua | 1 + lua/neogit/popups/help/actions.lua | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8ba673136..db8c108df 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1066,6 +1066,7 @@ function M:open(kind) push = { commit = commit }, rebase = { commit = commit }, revert = { commits = commits }, + bisect = { commits = commits }, reset = { commit = commit }, tag = { commit = commit }, stash = { name = stash and stash:match("^stash@{%d+}") }, diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 8e77151c7..77ffa4f52 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -81,6 +81,9 @@ M.popups = function(env) { "BranchPopup", "Branch", popups.open("branch", function(p) p(env.branch) end) }, + { "BisectPopup", "Bisect", popups.open("bisect", function(p) + p(env.bisect) + end) }, { "FetchPopup", "Fetch", popups.open("fetch", function(p) p(env.fetch) end) }, From 071000eac492e2cb0d69f296dc69205f4a336e52 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 15:01:16 +0100 Subject: [PATCH 249/443] improve bisecting --- lua/neogit/buffers/status/ui.lua | 36 +++++++++- lua/neogit/lib/git/bisect.lua | 94 +++++++++++++----------- lua/neogit/lib/git/log.lua | 10 +-- lua/neogit/lib/git/repository.lua | 2 +- lua/neogit/popups/bisect/actions.lua | 102 ++++++++++++++++++--------- lua/neogit/popups/bisect/init.lua | 2 +- 6 files changed, 166 insertions(+), 80 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 9a1b76aa0..47a6ac4e1 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -346,6 +346,34 @@ local SectionItemBisect = Component.new(function(item) }, { yankable = item.oid, oid = item.oid }) end) +local BisectDetailsSection = Component.new(function(props) + return col.tag("Section")({ + row(util.merge(props.title, { text(" "), text.highlight("Comment")(props.commit.oid) })), + row { + text.highlight("Comment")("Author: "), + text((props.commit.author_name or "") .. " <" .. (props.commit.author_email or "") .. ">"), + }, + row { text.highlight("Comment")("AuthorDate: "), text(props.commit.author_date) }, + row { + text.highlight("Comment")("Committer: "), + text((props.commit.committer_name or "") .. " <" .. (props.commit.committer_email or "") .. ">"), + }, + row { text.highlight("Comment")("CommitDate: "), text(props.commit.committer_date) }, + EmptyLine, + col( + map(props.commit.description, text), + { highlight = "NeogitCommitViewDescription", tag = "Description" } + ), + EmptyLine, + }, { + foldable = true, + folded = props.folded, + section = props.name, + yankable = props.commit.oid, + id = props.name, + }) +end) + function M.Status(state, config) -- stylua: ignore start local show_hint = not config.disable_hint @@ -473,8 +501,14 @@ function M.Status(state, config) folded = config.sections.sequencer.folded, name = "revert", }, + show_bisect and BisectDetailsSection { + commit = state.bisect.current, + title = SectionTitle { title = "Bisecting at" }, + folded = config.sections.bisect.folded, + name = "bisect_details", + }, show_bisect and SequencerSection { - title = SectionTitle { title = "Bisecting" }, + title = SectionTitle { title = "Bisecting Log" }, render = SectionItemBisect, items = state.bisect.items, folded = config.sections.bisect.folded, diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index 5b1c33f58..74b41fa13 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -1,4 +1,5 @@ local cli = require("neogit.lib.git.cli") +local logger = require("neogit.logger") local M = {} @@ -6,6 +7,15 @@ local function fire_bisect_event(data) vim.api.nvim_exec_autocmds("User", { pattern = "NeogitBisect", modeline = false, data = data }) end +---@param cmd string +local function bisect(cmd) + local result = cli.bisect.args(cmd).call() + + if result.code == 0 then + fire_bisect_event { type = cmd } + end +end + function M.in_progress() local git = require("neogit.lib.git") return git.repo:git_path("BISECT_LOG"):exists() @@ -13,43 +23,39 @@ end function M.is_finished() local git = require("neogit.lib.git") - return git.repo.state.bisect.finished + return git.repo.bisect.finished end ---@param bad_revision string ---@param good_revision string ---@param args? table function M.start(bad_revision, good_revision, args) - local result = cli.bisect.args("start").arg_list(args).args(good_revision, bad_revision).call() + local result = cli.bisect.args("start").arg_list(args).args(bad_revision, good_revision).call() if result.code == 0 then fire_bisect_event { type = "start" } end end ----@param state string -local function cmd(state) - local result = cli.bisect.args(state).call() - - if result.code == 0 then - fire_bisect_event { type = state } - end -end - function M.good() - cmd("good") + bisect("good") end function M.bad() - cmd("bad") + bisect("bad") end function M.skip() - cmd("skip") + bisect("skip") end function M.reset() - cmd("reset") + bisect("reset") +end + +---@param command string +function M.run(command) + cli.bisect.args("run", command).call() end ---@class BisectItem @@ -59,37 +65,45 @@ end ---@field abbreviated_commit string local function update_bisect_information(state) - state.bisect = { items = {}, finished = false } + logger.debug("[GIT BISECT] Starting") + state.bisect = { items = {}, finished = false, current = {} } + + if not M.in_progress() then + logger.debug("[GIT BISECT] Not in progress") + return + end + logger.debug("[GIT BISECT] In progress") local finished local git = require("neogit.lib.git") - local bisect_log = git.repo:git_path("BISECT_LOG") - - if bisect_log:exists() then - for line in bisect_log:iter() do - if line:match("^#") and line ~= "" then - local action, oid, subject = line:match("^# ([^:]+): %[(.+)%] (.+)") - - finished = action == "first bad commit" - if finished then - fire_bisect_event { type = "finished", oid = oid } - end - - ---@type BisectItem - local item = { - finished = finished, - action = action, - subject = subject, - oid = oid, - abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), - } - - table.insert(state.bisect.items, item) + + for line in git.repo:git_path("BISECT_LOG"):iter() do + if line:match("^#") and line ~= "" then + local action, oid, subject = line:match("^# ([^:]+): %[(.+)%] (.+)") + + finished = action == "first bad commit" + if finished then + fire_bisect_event { type = "finished", oid = oid } end - end - state.bisect.finished = finished + ---@type BisectItem + local item = { + finished = finished, + action = action, + subject = subject, + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), + } + + table.insert(state.bisect.items, item) + end end + + local expected = vim.trim(git.repo:git_path("BISECT_EXPECTED_REV"):read()) + state.bisect.current = + git.log.parse(git.cli.show.format("fuller").args(expected).call_sync({ trim = false }).stdout)[1] + + state.bisect.finished = finished end M.register = function(meta) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 4cdbaf4fe..3b52f189e 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -388,11 +388,13 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) end) ---Determines if commit a is an ancestor of commit b ----@param a string commit hash ----@param b string commit hash +---@param ancestor string commit hash +---@param descendant string commit hash ---@return boolean -function M.is_ancestor(a, b) - return cli["merge-base"].is_ancestor.args(a, b).call_sync({ ignore_error = true, hidden = true }).code == 0 +function M.is_ancestor(ancestor, descendant) + return cli["merge-base"].is_ancestor + .args(ancestor, descendant) + .call_sync({ ignore_error = true, hidden = true }).code == 0 end ---Finds parent commit of a commit. If no parent exists, will return nil diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index af31d6475..6fc499ab0 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -44,7 +44,7 @@ local function empty_state() rebase = { items = {}, head = nil }, sequencer = { items = {}, head = nil }, merge = { items = {}, head = nil, msg = nil }, - bisect = { items = {}, finished = false }, + bisect = { items = {}, finished = false, current = {} }, } end diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua index b556dc1da..7950b89d1 100644 --- a/lua/neogit/popups/bisect/actions.lua +++ b/lua/neogit/popups/bisect/actions.lua @@ -5,45 +5,48 @@ local input = require("neogit.lib.input") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local util = require("neogit.lib.util") -function M.start(popup) - if git.status.is_dirty() then - notification.warn("Cannot bisect with uncommitted changes") - return - end +---@return table|nil +local function use_popup_revisions(popup) + local bad_revision = popup.state.env.commits[1] + local good_revision = popup.state.env.commits[#popup.state.env.commits] - popup.state.env.commits = popup.state.env.commits or {} - - local bad_revision, good_revision - if popup.state.env.commits and #popup.state.env.commits > 1 then - bad_revision = popup.state.env.commits[1] - good_revision = popup.state.env.commits[#popup.state.env.commits] + if git.log.is_ancestor(good_revision, bad_revision) then + return { bad_revision, good_revision } + elseif git.log.is_ancestor(bad_revision, good_revision) then + return { good_revision, bad_revision } else - local refs = util.merge( - { popup.state.env.commits[1] }, - git.refs.list_branches(), - git.refs.list_tags(), - git.refs.heads() + local message = ("The first revision selected (%s) has to be an ancestor of the last one (%s)"):format( + bad_revision, + good_revision ) - bad_revision = FuzzyFinderBuffer.new(refs):open_async { - prompt_prefix = "Start bisect with bad revision", - } - if not bad_revision then - return - end + notification.warn(message) + end +end + +---@return table|nil +local function get_user_revisions(popup) + local refs = + util.merge(popup.state.env.commits, git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) + local bad_revision = FuzzyFinderBuffer.new(refs):open_async { + prompt_prefix = "Start bisect with bad revision", + } - good_revision = FuzzyFinderBuffer.new(refs):open_async { - prompt_prefix = "Good revision", - } + if not bad_revision then + return + end - if not good_revision then - return - end + util.remove_item_from_table(refs, bad_revision) + local good_revision = FuzzyFinderBuffer.new(refs):open_async { + prompt_prefix = "Good revision", + } + + if not good_revision then + return end - if git.log.is_ancestor(bad_revision, good_revision) then - notification.info("Bisecting...") - git.bisect.start(bad_revision, good_revision, popup:get_arguments()) + if git.log.is_ancestor(good_revision, bad_revision) then + return { bad_revision, good_revision } else local message = ("The good revision (%s) has to be an ancestor of the bad one (%s)"):format( good_revision, @@ -54,6 +57,36 @@ function M.start(popup) end end +---@param popup table +---@return table|nil +local function revisions(popup) + popup.state.env.commits = popup.state.env.commits or {} + local revisions + if #popup.state.env.commits > 1 then + revisions = use_popup_revisions(popup) + else + revisions = get_user_revisions(popup) + end + + if revisions then + return revisions + end +end + +function M.start(popup) + if git.status.is_dirty() then + notification.warn("Cannot bisect with uncommitted changes") + return + end + + local revisions = revisions(popup) + if revisions then + notification.info("Bisecting...") + local bad_revision, good_revision = unpack(revisions) + git.bisect.start(bad_revision, good_revision, popup:get_arguments()) + end +end + function M.good() git.bisect.good() end @@ -77,8 +110,11 @@ function M.scripted() end end -function M.run_script() - print("run_script") +function M.run() + local command = input.get_user_input("Bisect shell command") + if command then + git.bisect.run(command) + end end return M diff --git a/lua/neogit/popups/bisect/init.lua b/lua/neogit/popups/bisect/init.lua index 90925c7c2..987544cab 100644 --- a/lua/neogit/popups/bisect/init.lua +++ b/lua/neogit/popups/bisect/init.lua @@ -21,7 +21,7 @@ function M.create(env) :action_if(not finished and in_progress, "g", "Good", actions.good) :action_if(not finished and in_progress, "s", "Skip", actions.skip) :action_if(in_progress, "r", "Reset", actions.reset) - :action_if(not finished and in_progress, "S", "Run script") + :action_if(not finished and in_progress, "S", "Run script", actions.run) :env(env) :build() From 13d607954ecf366ce5126e8705830e2aa75a3708 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 15:03:37 +0100 Subject: [PATCH 250/443] add bisect scripted --- lua/neogit/popups/bisect/actions.lua | 27 ++++++++++++++++++++------- lua/neogit/popups/bisect/init.lua | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua index 7950b89d1..f04850586 100644 --- a/lua/neogit/popups/bisect/actions.lua +++ b/lua/neogit/popups/bisect/actions.lua @@ -87,6 +87,26 @@ function M.start(popup) end end +function M.scripted(popup) + if git.status.is_dirty() then + notification.warn("Cannot bisect with uncommitted changes") + return + end + + local revisions = revisions(popup) + if revisions then + local command = input.get_user_input("Bisect shell command") + if command then + notification.info("Bisecting...") + + local bad_revision, good_revision = unpack(revisions) + git.bisect.start(bad_revision, good_revision, popup:get_arguments()) + git.bisect.run(command) + end + end +end + + function M.good() git.bisect.good() end @@ -103,13 +123,6 @@ function M.reset() git.bisect.reset() end -function M.scripted() - local command = input.get_user_input("Bisect shell command") - if command then - git.bisect.run(command) - end -end - function M.run() local command = input.get_user_input("Bisect shell command") if command then diff --git a/lua/neogit/popups/bisect/init.lua b/lua/neogit/popups/bisect/init.lua index 987544cab..febbcc82d 100644 --- a/lua/neogit/popups/bisect/init.lua +++ b/lua/neogit/popups/bisect/init.lua @@ -16,7 +16,7 @@ function M.create(env) :group_heading_if(not in_progress, "Bisect") :group_heading_if(in_progress, "Actions") :action_if(not in_progress, "B", "Start", actions.start) - :action_if(not in_progress, "S", "Scripted") + :action_if(not in_progress, "S", "Scripted", actions.scripted) :action_if(not finished and in_progress, "b", "Bad", actions.bad) :action_if(not finished and in_progress, "g", "Good", actions.good) :action_if(not finished and in_progress, "s", "Skip", actions.skip) From af487f2a6050a76ce7396d8e5ba92908965bd142 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 18:31:55 +0100 Subject: [PATCH 251/443] Bisect: Guard resetting bisection behind confirmation except when bisection is finished --- lua/neogit/popups/bisect/actions.lua | 6 ++++++ lua/neogit/popups/bisect/init.lua | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua index f04850586..4e72c669a 100644 --- a/lua/neogit/popups/bisect/actions.lua +++ b/lua/neogit/popups/bisect/actions.lua @@ -119,6 +119,12 @@ function M.skip() git.bisect.skip() end +function M.reset_with_permission() + if input.get_permission("End bisection?") then + git.bisect.reset() + end +end + function M.reset() git.bisect.reset() end diff --git a/lua/neogit/popups/bisect/init.lua b/lua/neogit/popups/bisect/init.lua index febbcc82d..31d14f89d 100644 --- a/lua/neogit/popups/bisect/init.lua +++ b/lua/neogit/popups/bisect/init.lua @@ -20,7 +20,8 @@ function M.create(env) :action_if(not finished and in_progress, "b", "Bad", actions.bad) :action_if(not finished and in_progress, "g", "Good", actions.good) :action_if(not finished and in_progress, "s", "Skip", actions.skip) - :action_if(in_progress, "r", "Reset", actions.reset) + :action_if(not finished and in_progress, "r", "Reset", actions.reset_with_permission) + :action_if(finished and in_progress, "r", "Reset", actions.reset) :action_if(not finished and in_progress, "S", "Run script", actions.run) :env(env) :build() From 37675a1bf7a72beb2a751933c42f9b978da0342f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 18:32:51 +0100 Subject: [PATCH 252/443] Correctly capture cursor location in a section that has no items/hunks. This is for bisection. --- lua/neogit/lib/ui/init.lua | 51 ++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 36f6b47bb..3c3b7d845 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -383,7 +383,7 @@ end ---@return CursorLocation function Ui:get_cursor_location(line) line = line or vim.api.nvim_win_get_cursor(0)[1] - local section_loc, file_loc, hunk_loc, first, last + local section_loc, section_offset, file_loc, hunk_loc, first, last for li, loc in ipairs(self.item_index) do if line == loc.first then @@ -394,33 +394,44 @@ function Ui:get_cursor_location(line) elseif line >= loc.first and line <= loc.last then section_loc = { index = li, name = loc.name } - for fi, file in ipairs(loc.items) do - if line == file.first then - file_loc = { index = fi, name = file.name } - first, last = file.first, file.last + if #loc.items > 0 then + for fi, file in ipairs(loc.items) do + if line == file.first then + file_loc = { index = fi, name = file.name } + first, last = file.first, file.last - break - elseif line >= file.first and line <= file.last then - file_loc = { index = fi, name = file.name } + break + elseif line >= file.first and line <= file.last then + file_loc = { index = fi, name = file.name } - for hi, hunk in ipairs(file.diff.hunks) do - if line >= hunk.first and line <= hunk.last then - hunk_loc = { index = hi, name = hunk.hash } - first, last = hunk.first, hunk.last + for hi, hunk in ipairs(file.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + hunk_loc = { index = hi, name = hunk.hash } + first, last = hunk.first, hunk.last - break + break + end end - end - break + break + end end + else + section_offset = line - loc.first end break end end - return { section = section_loc, file = file_loc, hunk = hunk_loc, first = first, last = last } + return { + section = section_loc, + file = file_loc, + hunk = hunk_loc, + first = first, + last = last, + section_offset = section_offset + } end ---@param cursor CursorLocation @@ -449,8 +460,12 @@ function Ui:resolve_cursor_location(cursor) end if not cursor.file or not section.items or #section.items == 0 then - logger.debug("[UI] No file - using section.first") - return section.first + if cursor.section_offset then + return section.first + cursor.section_offset + else + logger.debug("[UI] No file - using section.first") + return section.first + end end local file = Collection.new(section.items):find(function(f) From db7217cfc99eccf40137ea2b1f66b0e60976d5fc Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 18:33:15 +0100 Subject: [PATCH 253/443] Dispatch refresh when re-entering status buffer. The issue this solves is when you start a bisect from the log buffer with a selection - re-entering the status buffer wouldn't be refreshed properly to show bisection. --- lua/neogit/buffers/status/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index db8c108df..a4c182e17 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -92,6 +92,9 @@ function M:open(kind) self.is_open = false vim.o.autochdir = self.prev_autochdir end, + ["BufEnter"] = function() + self:dispatch_refresh() + end }, mappings = { v = { From e17922e2a99d92960c5c9d2ba75d5a16a773aace Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 18:40:03 +0100 Subject: [PATCH 254/443] fix: When there are no commits, use an empty table so we still show the correct list. --- lua/neogit/popups/branch/actions.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index 7880e716b..f0bf39f8a 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -180,10 +180,11 @@ M.reset_branch = operation("reset_branch", function(popup) local options = util.deduplicate( util.merge( - popup.state.env.commits, + popup.state.env.commits or {}, relatives, git.refs.list_branches(), - git.refs.list_refs(), + git.refs.list_tags(), + git.stash.list_refs(), git.refs.heads() ) ) From e21a5218ef075e0339c70b2b748a7990bcf33d1a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 22:25:18 +0100 Subject: [PATCH 255/443] Extract the UI lib from popup - this makes updating state way simpler, without needing to muck about in the component tree. Just update the internal state, and rerender. --- lua/neogit/lib/popup/init.lua | 412 +++----------------- lua/neogit/lib/popup/ui.lua | 281 +++++++++++++ lua/neogit/popups/branch_config/actions.lua | 18 +- lua/neogit/popups/log/actions.lua | 23 +- lua/neogit/popups/log/init.lua | 2 +- 5 files changed, 354 insertions(+), 382 deletions(-) create mode 100644 lua/neogit/lib/popup/ui.lua diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 2e03df8d6..dbbae1183 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -1,7 +1,5 @@ local PopupBuilder = require("neogit.lib.popup.builder") local Buffer = require("neogit.lib.buffer") -local common = require("neogit.buffers.common") -local Ui = require("neogit.lib.ui") local logger = require("neogit.logger") local util = require("neogit.lib.util") local config = require("neogit.config") @@ -9,18 +7,16 @@ local state = require("neogit.lib.state") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") +local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") + local git = require("neogit.lib.git") -local col = Ui.col -local row = Ui.row -local text = Ui.text -local Component = Ui.Component -local map = util.map +local a = require("plenary.async") + local filter_map = util.filter_map local build_reverse_lookup = util.build_reverse_lookup -local intersperse = util.intersperse -local List = common.List -local Grid = common.Grid + +local ui = require("neogit.lib.popup.ui") ---@class PopupState @@ -90,133 +86,6 @@ function M:close() end end --- Determines the correct highlight group for a switch based on it's state. ----@return string -local function get_highlight_for_switch(switch) - if switch.enabled then - return "NeogitPopupSwitchEnabled" - end - - return "NeogitPopupSwitchDisabled" -end - --- Determines the correct highlight group for an option based on it's state. ----@return string -local function get_highlight_for_option(option) - if option.value ~= nil and option.value ~= "" then - return "NeogitPopupOptionEnabled" - end - - return "NeogitPopupOptionDisabled" -end - --- Determines the correct highlight group for a config based on it's type and state. ----@return string -local function get_highlight_for_config(config) - if config.value and config.value ~= "" then - return config.type or "NeogitPopupConfigEnabled" - end - - return "NeogitPopupConfigDisabled" -end - --- Builds config component to be rendered ----@return table -local function construct_config_options(config, prefix, suffix) - local set = false - local options = filter_map(config.options, function(option) - if option.display == "" then - return - end - - if option.condition and not option.condition() then - return - end - - local highlight - if config.value == option.value then - set = true - highlight = "NeogitPopupConfigEnabled" - else - highlight = "NeogitPopupConfigDisabled" - end - - return text.highlight(highlight)(option.display) - end) - - local value = intersperse(options, text.highlight("NeogitPopupConfigDisabled")("|")) - table.insert(value, 1, text.highlight("NeogitPopupConfigDisabled")("[")) - table.insert(value, #value + 1, text.highlight("NeogitPopupConfigDisabled")("]")) - - if prefix then - table.insert( - value, - 1, - text.highlight(set and "NeogitPopupConfigEnabled" or "NeogitPopupConfigDisabled")(prefix) - ) - end - - if suffix then - table.insert( - value, - #value + 1, - text.highlight(set and "NeogitPopupConfigEnabled" or "NeogitPopupConfigDisabled")(suffix) - ) - end - - return value -end - ----@param id integer ID of component to be updated ----@param highlight? string New highlight group for value ----@param value? string|table New value to display ----@return nil -function M:update_component(id, highlight, value) - local component = self.buffer.ui:find_by_id(id) - - assert(component, "Component not found! Cannot update.") - - if highlight then - if component.options.highlight then - component.options.highlight = highlight - elseif component.children then - component.children[1].options.highlight = highlight - end - end - - if type(value) == "string" then - local new - if value == "" then - local last_child = component.children[#component.children - 1] - if (last_child and last_child.value == "=") or component.options.id == "--" then - -- Check if this is a CLI option - the value should get blanked out for these - new = "" - else - -- If the component is NOT a cli option, use "unset" string - new = "unset" - end - else - new = value - end - - component.children[#component.children].value = new - elseif type(value) == "table" then - -- Remove last n children from row - for _ = 1, #value do - table.remove(component.children) - end - - -- insert new items to row - for _, text in ipairs(value) do - table.insert(component.children, text) - end - else - logger.error(string.format("[POPUP]: Unhandled component value type! (%s)", type(value))) - end - - self.buffer.ui:update() -end - -- Toggle a switch on/off ---@param switch table ---@return nil @@ -233,15 +102,8 @@ function M:toggle_switch(switch) local index = options[switch.cli or ""] switch.cli = options[(index + 1)] or options[1] switch.value = switch.cli - switch.enabled = switch.cli ~= "" - state.set({ self.state.name, switch.cli_suffix }, switch.cli) - self:update_component( - switch.id, - get_highlight_for_switch(switch), - construct_config_options(switch, switch.cli_prefix, switch.cli_suffix) - ) return end @@ -260,9 +122,7 @@ function M:toggle_switch(switch) end end - -- Update internal state and UI. state.set({ self.state.name, switch.cli }, switch.enabled) - self:update_component(switch.id, get_highlight_for_switch(switch), switch.cli) -- Ensure that other switches that are incompatible with this one are disabled if switch.enabled and #switch.incompatible > 0 then @@ -270,7 +130,6 @@ function M:toggle_switch(switch) if var.type == "switch" and var.enabled and switch.incompatible[var.cli] then var.enabled = false state.set({ self.state.name, var.cli }, var.enabled) - self:update_component(var.id, get_highlight_for_switch(var)) end end end @@ -281,7 +140,6 @@ function M:toggle_switch(switch) if var.type == "switch" and var.enabled and switch.dependant[var.cli] then var.enabled = false state.set({ self.state.name, var.cli }, var.enabled) - self:update_component(var.id, get_highlight_for_switch(var)) end end end @@ -291,22 +149,22 @@ end ---@param option table ---@return nil function M:set_option(option) - local set = function(value) - option.value = value - state.set({ self.state.name, option.cli }, option.value) - self:update_component(option.id, get_highlight_for_option(option), option.value) - end - -- Prompt user to select from predetermined choices if option.choices then if not option.value or option.value == "" then - -- TODO: Use input.get_choice here instead - vim.ui.select(option.choices, { prompt = option.description }, set) + local choice = FuzzyFinderBuffer.new(option.choices):open_async { + prompt_prefix = option.description + } + if choice then + option.value = choice + else + option.value = "" + end else - set("") + option.value = "" end elseif option.fn then - option.fn(self, option, set) + option.value = option.fn(self, option) else -- ...Otherwise get the value via input. local input = vim.fn.input { @@ -318,11 +176,13 @@ function M:set_option(option) -- If the option specifies a default value, and the user set the value to be empty, defer to default value. -- This is handy to prevent the user from accidentally loading thousands of log entries by accident. if option.default and input == "" then - set(option.default) + option.value = option.default else - set(input) + option.value = input end end + + state.set({ self.state.name, option.cli }, option.value) end -- Set a config value @@ -340,9 +200,9 @@ function M:set_config(config) local index = options[config.value or ""] config.value = options[(index + 1)] or options[1] + git.config.set(config.name, config.value) elseif config.fn then - config.fn(self, config) - return + config.value = config.fn(self, config) else local result = vim.fn.input { prompt = config.name .. " > ", @@ -351,170 +211,25 @@ function M:set_config(config) } config.value = result + git.config.set(config.name, config.value) end - git.config.set(config.name, config.value) - - self:repaint_config() - if config.callback then - config.callback(self, config) - end -end - -function M:repaint_config() for _, var in ipairs(self.state.config) do if var.passive then local c_value = git.config.get(var.name) if c_value:is_set() then var.value = c_value.value - self:update_component(var.id, nil, var.value) end - elseif var.options then - self:update_component(var.id, nil, construct_config_options(var)) - else - self:update_component(var.id, get_highlight_for_config(var), var.value) end end -end - -local Switch = Component.new(function(switch) - local value - if switch.options then - value = row.id(switch.id)(construct_config_options(switch, switch.cli_prefix, switch.cli_suffix)) - else - value = row - .id(switch.id) - .highlight(get_highlight_for_switch(switch)) { text(switch.cli_prefix), text(switch.cli) } - end - - return row.tag("Switch").value(switch)({ - row.highlight("NeogitPopupSwitchKey") { - text(switch.key_prefix), - text(switch.key), - }, - text(" "), - text(switch.description), - text(" ("), - value, - text(")"), - }, { interactive = true }) -end) - -local Option = Component.new(function(option) - return row.tag("Option").value(option)({ - row.highlight("NeogitPopupOptionKey") { - text(option.key_prefix), - text(option.key), - }, - text(" "), - text(option.description), - text(" ("), - row.id(option.id).highlight(get_highlight_for_option(option)) { - text(option.cli_prefix), - text(option.cli), - text(option.separator), - text(option.value or ""), - }, - text(")"), - }, { interactive = true }) -end) - -local Section = Component.new(function(title, items) - return col { - text.highlight("NeogitPopupSectionTitle")(title), - col(items), - } -end) - -local Config = Component.new(function(props) - local c = {} - - if not props.state[1].heading then - table.insert(c, text.highlight("NeogitPopupSectionTitle")("Variables")) - end - - table.insert( - c, - col(map(props.state, function(config) - if config.heading then - return row.highlight("NeogitPopupSectionTitle") { text(config.heading) } - end - - local value - if config.options then - value = construct_config_options(config) - else - local value_text - if not config.value or config.value == "" then - value_text = "unset" - else - value_text = config.value - end - - value = { text.highlight(get_highlight_for_config(config))(value_text) } - end - local key - if config.passive then - key = " " - elseif #config.key > 1 then - key = table.concat(vim.split(config.key, ""), " ") - else - key = config.key - end - - return row.tag("Config").value(config)({ - row.highlight("NeogitPopupConfigKey") { text(key) }, - text(" " .. config.name .. " "), - row.id(config.id) { unpack(value) }, - }, { interactive = true }) - end)) - ) - - return col(c) -end) - -local function render_action(action) - local items = {} - - -- selene: allow(empty_if) - if action.keys == nil then - -- Action group heading - elseif #action.keys == 0 then - table.insert(items, text.highlight("NeogitPopupActionDisabled")("_")) - else - for i, key in ipairs(action.keys) do - table.insert(items, text.highlight("NeogitPopupActionKey")(key)) - if i < #action.keys then - table.insert(items, text(",")) - end - end + if config.callback then + config.callback(self, config) end - table.insert(items, text(" ")) - table.insert(items, text(action.description)) - return items end -local Actions = Component.new(function(props) - return col { - Grid.padding_left(1) { - items = props.state, - gap = 3, - render_item = function(item) - if item.heading then - return row.highlight("NeogitPopupSectionTitle") { text(item.heading) } - elseif not item.callback then - return row.highlight("NeogitPopupActionDisabled")(render_action(item)) - else - return row(render_action(item)) - end - end, - }, - } -end) - -function M:show() +function M:mappings() local mappings = { n = { ["q"] = function() @@ -536,6 +251,8 @@ function M:show() elseif component.options.tag == "Option" then self:set_option(component.options.value) end + + self:refresh() end, }, } @@ -544,15 +261,18 @@ function M:show() for _, arg in pairs(self.state.args) do if arg.id then arg_prefixes[arg.key_prefix] = true - mappings.n[arg.id] = function() + mappings.n[arg.id] = a.void(function() if arg.type == "switch" then self:toggle_switch(arg) elseif arg.type == "option" then self:set_option(arg) end - end + + self:refresh() + end) end end + for prefix, _ in pairs(arg_prefixes) do mappings.n[prefix] = function() local c = vim.fn.getcharstr() @@ -567,9 +287,10 @@ function M:show() if config.heading then -- nothing elseif not config.passive then - mappings.n[config.id] = function() + mappings.n[config.id] = a.void(function() self:set_config(config) - end + self:refresh() + end) end end @@ -596,43 +317,29 @@ function M:show() end end - local items = {} - - if self.state.config[1] then - table.insert(items, Config { state = self.state.config }) - end - - if self.state.args[1] then - local section = {} - local name = "Arguments" - for _, item in ipairs(self.state.args) do - if item.type == "option" then - table.insert(section, Option(item)) - elseif item.type == "switch" then - table.insert(section, Switch(item)) - elseif item.type == "heading" then - if section[1] then -- If there are items in the section, flush to items table with current name - table.insert(items, Section(name, section)) - section = {} - end - - name = item.heading - end - end - - table.insert(items, Section(name, section)) - end + return mappings +end - if self.state.actions[1] then - table.insert(items, Actions { state = self.state.actions }) - end +function M:refresh() + self.buffer:focus() + self.buffer.ui:render(unpack(ui.Popup(self.state))) +end +function M:show() self.buffer = Buffer.create { name = self.state.name, filetype = "NeogitPopup", kind = config.values.popup.kind, - mappings = mappings, + mappings = self:mappings(), status_column = " ", + autocmds = { + ["WinLeave"] = function() + if self.buffer and self.buffer.kind == "floating" then + -- We pcall this because it's possible the window was closed by a command invocation, e.g. "cc" for commits + pcall(self.close, self) + end + end, + }, after = function(buf, _win) buf:set_window_option("cursorline", false) buf:set_window_option("list", false) @@ -666,21 +373,8 @@ function M:show() end end, render = function() - return { - List { - separator = "", - items = items, - }, - } + return ui.Popup(self.state) end, - autocmds = { - ["WinLeave"] = function() - if self.buffer and self.buffer.kind == "floating" then - -- We pcall this because it's possible the window was closed by a command invocation, e.g. "cc" for commits - pcall(self.close, self) - end - end, - }, } end diff --git a/lua/neogit/lib/popup/ui.lua b/lua/neogit/lib/popup/ui.lua new file mode 100644 index 000000000..e2da5ecfb --- /dev/null +++ b/lua/neogit/lib/popup/ui.lua @@ -0,0 +1,281 @@ +local M = {} + +local common = require("neogit.buffers.common") +local Ui = require("neogit.lib.ui") +local util = require("neogit.lib.util") + +local List = common.List +local Grid = common.Grid +local col = Ui.col +local row = Ui.row +local text = Ui.text +local Component = Ui.Component + +local intersperse = util.intersperse +local filter_map = util.filter_map +local map = util.map + +local EmptyLine = col { row { text("") } } + +-- Builds config component to be rendered +---@return table +local function construct_config_options(config, prefix, suffix) + local set = false + local options = filter_map(config.options, function(option) + if option.display == "" then + return + end + + if option.condition and not option.condition() then + return + end + + local highlight + if config.value == option.value then + set = true + highlight = "NeogitPopupConfigEnabled" + else + highlight = "NeogitPopupConfigDisabled" + end + + return text.highlight(highlight)(option.display) + end) + + local value = intersperse(options, text.highlight("NeogitPopupConfigDisabled")("|")) + table.insert(value, 1, text.highlight("NeogitPopupConfigDisabled")("[")) + table.insert(value, #value + 1, text.highlight("NeogitPopupConfigDisabled")("]")) + + if prefix then + table.insert( + value, + 1, + text.highlight(set and "NeogitPopupConfigEnabled" or "NeogitPopupConfigDisabled")(prefix) + ) + end + + if suffix then + table.insert( + value, + #value + 1, + text.highlight(set and "NeogitPopupConfigEnabled" or "NeogitPopupConfigDisabled")(suffix) + ) + end + + return value +end + +-- Determines the correct highlight group for a switch based on it's state. +---@return string +local function get_highlight_for_switch(switch) + if switch.enabled then + return "NeogitPopupSwitchEnabled" + end + + return "NeogitPopupSwitchDisabled" +end + +-- Determines the correct highlight group for an option based on it's state. +---@return string +local function get_highlight_for_option(option) + if option.value ~= nil and option.value ~= "" then + return "NeogitPopupOptionEnabled" + end + + return "NeogitPopupOptionDisabled" +end + +-- Determines the correct highlight group for a config based on it's type and state. +---@return string +local function get_highlight_for_config(config) + if config.value and config.value ~= "" then + return config.type or "NeogitPopupConfigEnabled" + end + + return "NeogitPopupConfigDisabled" +end + +local Switch = Component.new(function(switch) + local value + if switch.options then + value = row.id(switch.id)(construct_config_options(switch, switch.cli_prefix, switch.cli_suffix)) + else + value = row + .id(switch.id) + .highlight(get_highlight_for_switch(switch)) { text(switch.cli_prefix), text(switch.cli) } + end + + return row.tag("Switch").value(switch)({ + row.highlight("NeogitPopupSwitchKey") { + text(switch.key_prefix), + text(switch.key), + }, + text(" "), + text(switch.description), + text(" ("), + value, + text(")"), + }, { interactive = true }) +end) + +local Option = Component.new(function(option) + return row.tag("Option").value(option)({ + row.highlight("NeogitPopupOptionKey") { + text(option.key_prefix), + text(option.key), + }, + text(" "), + text(option.description), + text(" ("), + row.id(option.id).highlight(get_highlight_for_option(option)) { + text(option.cli_prefix), + text(option.cli), + text(option.separator), + text(option.value or ""), + }, + text(")"), + }, { interactive = true }) +end) + +local Section = Component.new(function(title, items) + return col { + text.highlight("NeogitPopupSectionTitle")(title), + col(items), + } +end) + +local Config = Component.new(function(props) + local c = {} + + if not props.state[1].heading then + table.insert(c, text.highlight("NeogitPopupSectionTitle")("Variables")) + end + + table.insert( + c, + col(map(props.state, function(config) + if config.heading then + return row.highlight("NeogitPopupSectionTitle") { text(config.heading) } + end + + local value + if config.options then + value = construct_config_options(config) + else + local value_text + if not config.value or config.value == "" then + value_text = "unset" + else + value_text = config.value + end + + value = { text.highlight(get_highlight_for_config(config))(value_text) } + end + + local key + if config.passive then + key = " " + elseif #config.key > 1 then + key = table.concat(vim.split(config.key, ""), " ") + else + key = config.key + end + + return row.tag("Config").value(config)({ + row.highlight("NeogitPopupConfigKey") { text(key) }, + text(" " .. config.name .. " "), + row.id(config.id) { unpack(value) }, + }, { interactive = true }) + end)) + ) + + return col(c) +end) + +local function render_action(action) + local items = {} + + -- selene: allow(empty_if) + if action.keys == nil then + -- Action group heading + elseif #action.keys == 0 then + table.insert(items, text.highlight("NeogitPopupActionDisabled")("_")) + else + for i, key in ipairs(action.keys) do + table.insert(items, text.highlight("NeogitPopupActionKey")(key)) + if i < #action.keys then + table.insert(items, text(",")) + end + end + end + + table.insert(items, text(" ")) + table.insert(items, text(action.description)) + + return items +end + +local Actions = Component.new(function(props) + return col { + Grid.padding_left(1) { + items = props.state, + gap = 3, + render_item = function(item) + if item.heading then + return row.highlight("NeogitPopupSectionTitle") { text(item.heading) } + elseif not item.callback then + return row.highlight("NeogitPopupActionDisabled")(render_action(item)) + else + return row(render_action(item)) + end + end, + }, + } +end) + +function M.items(state) + local items = {} + + if state.config[1] then + table.insert(items, Config { state = state.config }) + table.insert(items, EmptyLine) + end + + if state.args[1] then + local section = {} + local name = "Arguments" + for _, item in ipairs(state.args) do + if item.type == "option" then + table.insert(section, Option(item)) + elseif item.type == "switch" then + table.insert(section, Switch(item)) + elseif item.type == "heading" then + if section[1] then -- If there are items in the section, flush to items table with current name + table.insert(items, Section(name, section)) + table.insert(items, EmptyLine) + section = {} + end + + name = item.heading + end + end + + table.insert(items, Section(name, section)) + table.insert(items, EmptyLine) + end + + if state.actions[1] then + table.insert(items, Actions { state = state.actions }) + end + + return items +end + +function M.Popup(state) + return { + List { + items = M.items(state), + }, + } +end + +return M diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index 6b4102149..576206341 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -41,7 +41,7 @@ function M.update_pull_rebase() end function M.merge_config(branch) - return a.void(function(popup, c) + local fn = function() local target = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async { prompt_prefix = "upstream" } if not target then return @@ -60,13 +60,14 @@ function M.merge_config(branch) git.config.set("branch." .. branch .. ".merge", merge_value) git.config.set("branch." .. branch .. ".remote", remote_value) - c.value = merge_value - popup:repaint_config() - end) + return merge_value + end + + return a.wrap(fn, 2) end function M.description_config(branch) - return a.void(function(popup, c) + local fn = function() client.wrap(git.cli.branch.edit_description, { autocmd = "NeogitDescriptionComplete", msg = { @@ -74,9 +75,10 @@ function M.description_config(branch) }, }) - c.value = git.config.get("branch." .. branch .. ".description"):read() - popup:repaint_config() - end) + return git.config.get("branch." .. branch .. ".description"):read() + end + + return a.wrap(fn, 2) end return M diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 1ae50143d..8768700ba 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -8,6 +8,7 @@ local ReflogViewBuffer = require("neogit.buffers.reflog_view") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local operation = require("neogit.operations") +local a = require("plenary.async") --- Runs `git log` and parses the commits ---@param popup table Contains the argument list @@ -81,18 +82,11 @@ end -- TODO: Prefill the fuzzy finder with the filepath under cursor, if there is one ---comment ----@param popup Popup ----@param option table ----@param set function ----@return nil -function M.limit_to_files(popup, option, set) - local a = require("plenary.async") - - a.run(function() +function M.limit_to_files() + local fn = function(popup, option) if option.value ~= "" then popup.state.env.files = nil - set("") - return + return "" end local files = FuzzyFinderBuffer.new(git.files.all_tree()):open_async { @@ -102,8 +96,7 @@ function M.limit_to_files(popup, option, set) if not files or vim.tbl_isempty(files) then popup.state.env.files = nil - set("") - return + return "" end popup.state.env.files = files @@ -111,8 +104,10 @@ function M.limit_to_files(popup, option, set) return string.format([[ "%s"]], file) end) - set(table.concat(files, "")) - end) + return table.concat(files, "") + end + + return a.wrap(fn, 2) end return M diff --git a/lua/neogit/popups/log/init.lua b/lua/neogit/popups/log/init.lua index d2bbeda81..c579cce29 100644 --- a/lua/neogit/popups/log/init.lua +++ b/lua/neogit/popups/log/init.lua @@ -29,7 +29,7 @@ function M.create() :option("-", "", "", "Limit to files", { key_prefix = "-", separator = "", - fn = actions.limit_to_files, + fn = actions.limit_to_files(), setup = function(popup) local state = require("neogit.lib.state").get { "NeogitLogPopup", "" } if state then From d23c33077c4a3075f6c60c0ef7c9a630b093962c Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 22:26:14 +0100 Subject: [PATCH 256/443] Fix: Fuzzy finder popup should be the right height now --- lua/neogit/buffers/fuzzy_finder.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/fuzzy_finder.lua b/lua/neogit/buffers/fuzzy_finder.lua index e26a674d2..ae90a5df3 100644 --- a/lua/neogit/buffers/fuzzy_finder.lua +++ b/lua/neogit/buffers/fuzzy_finder.lua @@ -1,7 +1,7 @@ local Finder = require("neogit.lib.finder") local function buffer_height(count) - if count < (vim.fn.winheight(0) / 2) then + if count < (vim.o.lines / 2) then return count + 2 else return 0.5 @@ -30,10 +30,9 @@ function M.new(list) end function M:open(opts, action) - opts = opts or { - allow_multi = false, + opts = vim.tbl_deep_extend("keep", opts or {}, { layout_config = { height = buffer_height(#self.list) }, - } + }) Finder.create(opts):add_entries(self.list):find(action) end @@ -42,10 +41,9 @@ end ---@return any|nil --- Asynchronously prompt the user for the selection, and return the selected item or nil if aborted. function M:open_async(opts) - opts = opts or { - allow_multi = false, + opts = vim.tbl_deep_extend("keep", opts or {}, { layout_config = { height = buffer_height(#self.list) }, - } + }) return Finder.create(opts):add_entries(self.list):find_async() end From 74ec42071266e63e2b73cb268e5438545891d410 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 22:29:49 +0100 Subject: [PATCH 257/443] Update some flags to be more in line with Magit --- lua/neogit/popups/commit/init.lua | 9 ++++----- lua/neogit/popups/merge/init.lua | 7 +++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index 3262e9fdd..7f0fc37da 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -11,12 +11,11 @@ function M.create(env) :switch("e", "allow-empty", "Allow empty commit") :switch("v", "verbose", "Show diff of changes to be committed") :switch("h", "no-verify", "Disable hooks") - :switch("s", "signoff", "Add Signed-off-by line") - :switch("S", "no-gpg-sign", "Do not sign this commit") :switch("R", "reset-author", "Claim authorship and reset author date") - :option("A", "author", "", "Override the author") - :option("S", "gpg-sign", "", "Sign using gpg") - :option("C", "reuse-message", "", "Reuse commit message") + :option("A", "author", "", "Override the author", { key_prefix = "-" }) + :switch("s", "signoff", "Add Signed-off-by line") + :option("S", "gpg-sign", "", "Sign using gpg", { key_prefix = "-" }) + :option("C", "reuse-message", "", "Reuse commit message", { key_prefix = "-" }) :group_heading("Create") :action("c", "Commit", actions.commit) :new_action_group("Edit HEAD") diff --git a/lua/neogit/popups/merge/init.lua b/lua/neogit/popups/merge/init.lua index d1c389a85..21fd09c75 100644 --- a/lua/neogit/popups/merge/init.lua +++ b/lua/neogit/popups/merge/init.lua @@ -29,15 +29,18 @@ function M.create() ) :option_if(not in_merge, "s", "strategy", "", "Strategy", { choices = { "resolve", "recursive", "octopus", "ours", "subtree" }, + key_prefix = "-", }) :option_if(not in_merge, "X", "strategy-option", "", "Strategy Option", { choices = { "ours", "theirs", "patience" }, + key_prefix = "-", }) :option_if(not in_merge, "A", "Xdiff-algorithm", "", "Diff algorithm", { - cli_prefix = "-", choices = { "default", "minimal", "patience", "histogram" }, + cli_prefix = "-", + key_prefix = "-", }) - :option_if(not in_merge, "S", "gpg-sign", "", "Sign using gpg") + :option_if(not in_merge, "S", "gpg-sign", "", "Sign using gpg", { key_prefix = "-" }) :group_heading_if(not in_merge, "Actions") :action_if(not in_merge, "m", "Merge", actions.merge) :action_if(not in_merge, "e", "Merge and edit message") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L105 From 83690f52f1125936e2cfb25618ca8a24c51ad908 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Jan 2024 23:21:14 +0100 Subject: [PATCH 258/443] Send \04 (ctrl-d) for all callers.. They shouldn't need to know this detail in order to create proper input --- lua/neogit/lib/git/cli.lua | 2 +- lua/neogit/popups/revert/actions.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index e78aa53c7..01036073b 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -323,7 +323,7 @@ local configurations = { aliases = { with_message = function(tbl) return function(message) - return tbl.args("-F", "-").input(message) + return tbl.args("-F", "-").input(message .. "\04") end end, message = function(tbl) diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index e317b20a5..321e6ae5a 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -29,7 +29,7 @@ local function build_commit_message(commits) table.insert(message, string.format("%s '%s'", commit:sub(1, 7), git.log.message(commit))) end - return table.concat(message, "\n") .. "\04" + return table.concat(message, "\n") end function M.commits(popup) From 96b9a348a3bae47b60eb19519a1e92eef8d035d1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Jan 2024 23:40:50 +0100 Subject: [PATCH 259/443] When an "amend! xxxxx" declaration is used, capture it and hide it away. User shouldn't change or mess with it, and we plop it back in place just before submitting the commit. Hopefully this doesn't mess stuff up. We _might_ need to use something like an ENV var to flag this.. donno. That feels a bit hack. --- lua/neogit/buffers/editor/init.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index d52a07256..f778245b1 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -45,7 +45,7 @@ function M:open(kind) local message_index = 1 local message_buffer = { { "" } } - local footer + local amend_header, footer local function reflog_message(index) return git.log.reflog_message(index - 2) @@ -105,6 +105,13 @@ function M:open(kind) buffer:write() buffer:move_cursor(1) + amend_header = buffer:get_lines(0, 2) + if amend_header[1]:match("^amend! %x+$") then + buffer:set_lines(0, 2, false, {}) -- remove captured header from buffer + else + amend_header = nil + end + footer = buffer:get_lines(1, -1) -- Start insert mode if user has configured it @@ -142,6 +149,10 @@ function M:open(kind) i = { [mapping["Submit"]] = function(buffer) vim.cmd.stopinsert() + if amend_header then + buffer:set_lines(0, 0, false, amend_header) + end + buffer:write() buffer:close(true) end, @@ -154,6 +165,10 @@ function M:open(kind) }, n = { [mapping["Close"]] = function(buffer) + if amend_header then + buffer:set_lines(0, 0, false, amend_header) + end + if buffer:get_option("modified") and not input.get_confirmation("Save changes?") then aborted = true end @@ -162,6 +177,10 @@ function M:open(kind) buffer:close(true) end, [mapping["Submit"]] = function(buffer) + if amend_header then + buffer:set_lines(0, 0, false, amend_header) + end + buffer:write() buffer:close(true) end, From 8c11617ad5ad23ba428c7f45adecdb2164ab64de Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Jan 2024 23:42:51 +0100 Subject: [PATCH 260/443] Refactor rebase/rewrite action. 1. Reuse the git.rebase.instantly() function 2. Allow full editor experience to edit/update the commit message. --- lua/neogit/lib/git/rebase.lua | 28 +++++++++++++++------------- lua/neogit/popups/rebase/actions.lua | 9 +-------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 16da5796b..81f163fa2 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -3,6 +3,7 @@ local client = require("neogit.client") local notification = require("neogit.lib.notification") local cli = require("neogit.lib.git.cli") local rev_parse = require("neogit.lib.git.rev_parse") +local log = require("neogit.lib.git.log") local M = {} @@ -77,20 +78,21 @@ function M.onto(start, newbase, args) end ---@param commit string rev name of the commit to reword ----@param message string new message to put onto `commit` ----@return nil -function M.reword(commit, message) - local result = cli.commit.allow_empty.only.message("amend! " .. commit .. "\n\n" .. message).call() - if result.code ~= 0 then - return - end - - result = - cli.rebase.env({ GIT_SEQUENCE_EDITOR = ":" }).interactive.autosquash.autostash.commit(commit).call() - if result.code ~= 0 then - return +---@return ProcessResult|nil +function M.reword(commit) + local status = client.wrap( + cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, log.message(commit))), + { + autocmd = "NeogitCommitComplete", + msg = { + success = "Commit Updated", + }, + } + ) + + if status == 0 then + return M.instantly(commit, { "--autostash", "--autosquash" }) end - fire_rebase_event("ok") end function M.modify(commit) diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index f1e2850a5..e3fbddf67 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -107,14 +107,7 @@ M.reword = operation("rebase_reword", function(popup) return end - -- TODO: Support multiline input for longer commit messages - local old_message = git.log.message(commit) - local new_message = input.get_user_input("Message", { default = old_message }) - if not new_message then - return - end - - git.rebase.reword(commit, new_message) + git.rebase.reword(commit) end) M.modify = operation("rebase_modify", function(popup) From dfd1dc0a21bab91d85dd68390b15f085bd27bd01 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Jan 2024 23:44:16 +0100 Subject: [PATCH 261/443] This is unused now. --- lua/neogit/popups/revert/actions.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/popups/revert/actions.lua b/lua/neogit/popups/revert/actions.lua index 321e6ae5a..bf0a2638a 100644 --- a/lua/neogit/popups/revert/actions.lua +++ b/lua/neogit/popups/revert/actions.lua @@ -56,7 +56,6 @@ function M.commits(popup) client.wrap(commit_cmd, { autocmd = "NeogitRevertComplete", - refresh = "do_revert", msg = { success = "Reverted", }, From fd86e0a6bbe88c89fad1c1785710da066fd97064 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 15 Jan 2024 09:36:13 +0100 Subject: [PATCH 262/443] This should be local function --- tests/specs/neogit/popups/rebase_spec.lua | 116 ++-------------------- 1 file changed, 8 insertions(+), 108 deletions(-) diff --git a/tests/specs/neogit/popups/rebase_spec.lua b/tests/specs/neogit/popups/rebase_spec.lua index b86e27228..fd7495cb9 100644 --- a/tests/specs/neogit/popups/rebase_spec.lua +++ b/tests/specs/neogit/popups/rebase_spec.lua @@ -15,106 +15,34 @@ local function act(normal_cmd) end describe("rebase popup", function() - before_each(function() - vim.fn.feedkeys("q", "x") - CommitSelectViewBufferMock.clear() - end) - - function test_reword(commit_to_reword, new_commit_message, selected) + local function test_reword(commit_to_reword, new_commit_message) local original_branch = git.branch.current() - if selected == false then - CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_reword)) - end + CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_reword)) input.values = { new_commit_message } act("rw") operations.wait("rebase_reword") assert.are.same(original_branch, git.branch.current()) - assert.are.same(new_commit_message, git.log.message(commit_to_reword)) - end - - function test_modify(commit_to_modify, selected) - local new_head = git.rev_parse.oid(commit_to_modify) - if selected == false then - CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_modify)) - end - act("rm") - operations.wait("rebase_modify") - assert.are.same(new_head, git.rev_parse.oid("HEAD")) - end - - function test_drop(commit_to_drop, selected) - local dropped_commit = git.rev_parse.oid(commit_to_drop) - if selected == false then - CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_drop)) - end - act("rd") - operations.wait("rebase_drop") - assert.is_not.same(dropped_commit, git.rev_parse.oid(commit_to_drop)) + assert.are.same(new_commit_message, git.log.message("HEAD")) end - it( - "rebase to drop HEAD", - in_prepared_repo(function() - test_drop("HEAD", false) - end) - ) - it( - "rebase to drop HEAD~1", - in_prepared_repo(function() - test_drop("HEAD~1", false) - end) - ) - it( - "rebase to drop HEAD~1 from log view", - in_prepared_repo(function() - act("ll") -- log commits - operations.wait("log_current") - act("j") -- go down one commit - test_drop("HEAD~1", true) - end) - ) - it( "rebase to reword HEAD", in_prepared_repo(function() - test_reword("HEAD", "foobar", false) + test_reword("HEAD", "foobar") end) ) it( "rebase to reword HEAD~1", in_prepared_repo(function() - test_reword("HEAD~1", "barbaz", false) + test_reword("HEAD~1", "barbaz") end) ) it( "rebase to reword HEAD~1 from log view", in_prepared_repo(function() - act("ll") -- log commits + act("ll") -- log branches and go down one commit operations.wait("log_current") - act("j") -- go down one commit - test_reword("HEAD~1", "foo", true) - end) - ) - - it( - "rebase to modify HEAD", - in_prepared_repo(function() - test_modify("HEAD", false) - end) - ) - it( - "rebase to modify HEAD~1", - in_prepared_repo(function() - test_modify("HEAD~1", false) - end) - ) - it( - "rebase to modify HEAD~1 from log view", - in_prepared_repo(function() - act("ll") - operations.wait("log_current") - act("j") - test_modify("HEAD~1", true) + test_reword("HEAD~1", "foo") end) ) @@ -139,35 +67,7 @@ describe("rebase popup", function() end) -- Act - test_reword("HEAD", "foobar", false) - - -- Assert - assert.are.same(true, rx()) - end) - ) - - it( - "rebase to modify HEAD fires NeogitRebase autocmd", - in_prepared_repo(function() - -- Arange - local tx, rx = async.control.channel.oneshot() - local group = vim.api.nvim_create_augroup("TestCustomNeogitEvents", { clear = true }) - vim.api.nvim_create_autocmd("User", { - pattern = "NeogitRebase", - group = group, - callback = function() - tx(true) - end, - }) - - -- Timeout - local timer = vim.loop.new_timer() - timer:start(500, 0, function() - tx(false) - end) - - -- Act - test_modify("HEAD", false) + test_reword("HEAD", "foobar") -- Assert assert.are.same(true, rx()) From 5161e80d604dabcddac07de8cf079cf4580c973f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Jan 2024 11:52:54 +0100 Subject: [PATCH 263/443] Autosquash and autostash should always happen --- lua/neogit/lib/git/rebase.lua | 11 +++++++---- lua/neogit/popups/commit/actions.lua | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 81f163fa2..3fce743a5 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -20,11 +20,14 @@ end ---Instant rebase. This is a way to rebase without using the interactive editor ---@param commit string ----@param args string[] list of arguments to pass to git rebase +---@param args? string[] list of arguments to pass to git rebase ---@return ProcessResult function M.instantly(commit, args) - local result = - cli.rebase.env({ GIT_SEQUENCE_EDITOR = ":" }).interactive.arg_list(args).commit(commit).call() + local result = cli.rebase + .env({ GIT_SEQUENCE_EDITOR = ":" }).interactive.autostash.autosquash + .arg_list(args or {}) + .commit(commit) + .call() if result.code ~= 0 then fire_rebase_event { commit = commit, status = "failed" } @@ -91,7 +94,7 @@ function M.reword(commit) ) if status == 0 then - return M.instantly(commit, { "--autostash", "--autosquash" }) + return M.instantly(commit) end end diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 71bf456e9..0909f689d 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -89,7 +89,7 @@ local function commit_special(popup, method, opts) if opts.rebase then a.util.scheduler() - git.rebase.instantly(commit .. "~1", { "--autosquash", "--autostash", "--keep-empty" }) + git.rebase.instantly(commit .. "~1", { "--keep-empty" }) end end From 77c63947f9e991c2eed4fd52cc308cc62251bda9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 23:09:18 +0100 Subject: [PATCH 264/443] fix specs --- tests/specs/neogit/popups/rebase_spec.lua | 116 ++++++++++++++++++++-- 1 file changed, 108 insertions(+), 8 deletions(-) diff --git a/tests/specs/neogit/popups/rebase_spec.lua b/tests/specs/neogit/popups/rebase_spec.lua index fd7495cb9..d9dbc22ac 100644 --- a/tests/specs/neogit/popups/rebase_spec.lua +++ b/tests/specs/neogit/popups/rebase_spec.lua @@ -15,34 +15,106 @@ local function act(normal_cmd) end describe("rebase popup", function() - local function test_reword(commit_to_reword, new_commit_message) + before_each(function() + vim.fn.feedkeys("q", "x") + CommitSelectViewBufferMock.clear() + end) + + local function test_reword(commit_to_reword, new_commit_message, selected) local original_branch = git.branch.current() - CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_reword)) + if selected == false then + CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_reword)) + end input.values = { new_commit_message } act("rw") operations.wait("rebase_reword") assert.are.same(original_branch, git.branch.current()) - assert.are.same(new_commit_message, git.log.message("HEAD")) + assert.are.same(new_commit_message, git.log.message(commit_to_reword)) + end + + local function test_modify(commit_to_modify, selected) + local new_head = git.rev_parse.oid(commit_to_modify) + if selected == false then + CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_modify)) + end + act("rm") + operations.wait("rebase_modify") + assert.are.same(new_head, git.rev_parse.oid("HEAD")) + end + + local function test_drop(commit_to_drop, selected) + local dropped_commit = git.rev_parse.oid(commit_to_drop) + if selected == false then + CommitSelectViewBufferMock.add(git.rev_parse.oid(commit_to_drop)) + end + act("rd") + operations.wait("rebase_drop") + assert.is_not.same(dropped_commit, git.rev_parse.oid(commit_to_drop)) end + it( + "rebase to drop HEAD", + in_prepared_repo(function() + test_drop("HEAD", false) + end) + ) + it( + "rebase to drop HEAD~1", + in_prepared_repo(function() + test_drop("HEAD~1", false) + end) + ) + it( + "rebase to drop HEAD~1 from log view", + in_prepared_repo(function() + act("ll") -- log commits + operations.wait("log_current") + act("j") -- go down one commit + test_drop("HEAD~1", true) + end) + ) + it( "rebase to reword HEAD", in_prepared_repo(function() - test_reword("HEAD", "foobar") + test_reword("HEAD", "foobar", false) end) ) it( "rebase to reword HEAD~1", in_prepared_repo(function() - test_reword("HEAD~1", "barbaz") + test_reword("HEAD~1", "barbaz", false) end) ) it( "rebase to reword HEAD~1 from log view", in_prepared_repo(function() - act("ll") -- log branches and go down one commit + act("ll") -- log commits operations.wait("log_current") - test_reword("HEAD~1", "foo") + act("j") -- go down one commit + test_reword("HEAD~1", "foo", true) + end) + ) + + it( + "rebase to modify HEAD", + in_prepared_repo(function() + test_modify("HEAD", false) + end) + ) + it( + "rebase to modify HEAD~1", + in_prepared_repo(function() + test_modify("HEAD~1", false) + end) + ) + it( + "rebase to modify HEAD~1 from log view", + in_prepared_repo(function() + act("ll") + operations.wait("log_current") + act("j") + test_modify("HEAD~1", true) end) ) @@ -67,7 +139,35 @@ describe("rebase popup", function() end) -- Act - test_reword("HEAD", "foobar") + test_reword("HEAD", "foobar", false) + + -- Assert + assert.are.same(true, rx()) + end) + ) + + it( + "rebase to modify HEAD fires NeogitRebase autocmd", + in_prepared_repo(function() + -- Arange + local tx, rx = async.control.channel.oneshot() + local group = vim.api.nvim_create_augroup("TestCustomNeogitEvents", { clear = true }) + vim.api.nvim_create_autocmd("User", { + pattern = "NeogitRebase", + group = group, + callback = function() + tx(true) + end, + }) + + -- Timeout + local timer = vim.loop.new_timer() + timer:start(500, 0, function() + tx(false) + end) + + -- Act + test_modify("HEAD", false) -- Assert assert.are.same(true, rx()) From 58af2bc4d274d629dec051489fcb1d170a8e3068 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 23:27:04 +0100 Subject: [PATCH 265/443] Correctly capture multi-line messages --- lua/neogit/lib/git/log.lua | 4 ++++ lua/neogit/lib/git/rebase.lua | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 3b52f189e..2bf560178 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -426,6 +426,10 @@ function M.message(commit) return cli.log.max_count(1).format("%s").args(commit).call({ hidden = true }).stdout[1] end +function M.full_message(commit) + return cli.log.max_count(1).format("%B").args(commit).call({ hidden = true }).stdout +end + function M.present_commit(commit) if not commit.oid then return diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 3fce743a5..1e2e3b4c1 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -83,8 +83,9 @@ end ---@param commit string rev name of the commit to reword ---@return ProcessResult|nil function M.reword(commit) + local message = table.concat(log.full_message(commit), "\n") local status = client.wrap( - cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, log.message(commit))), + cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), { autocmd = "NeogitCommitComplete", msg = { From 3b6cb8484a3b171f2404ce296be405f4d658b427 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 23:30:39 +0100 Subject: [PATCH 266/443] Lint --- lua/neogit/buffers/status/init.lua | 2 +- lua/neogit/lib/git/rebase.lua | 8 +++----- lua/neogit/lib/popup/init.lua | 3 +-- lua/neogit/lib/ui/init.lua | 2 +- lua/neogit/popups/bisect/actions.lua | 1 - 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index a4c182e17..6aad66770 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -94,7 +94,7 @@ function M:open(kind) end, ["BufEnter"] = function() self:dispatch_refresh() - end + end, }, mappings = { v = { diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 1e2e3b4c1..6f1431f13 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -84,15 +84,13 @@ end ---@return ProcessResult|nil function M.reword(commit) local message = table.concat(log.full_message(commit), "\n") - local status = client.wrap( - cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), - { + local status = + client.wrap(cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), { autocmd = "NeogitCommitComplete", msg = { success = "Commit Updated", }, - } - ) + }) if status == 0 then return M.instantly(commit) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index dbbae1183..5fe70ee92 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -153,7 +153,7 @@ function M:set_option(option) if option.choices then if not option.value or option.value == "" then local choice = FuzzyFinderBuffer.new(option.choices):open_async { - prompt_prefix = option.description + prompt_prefix = option.description, } if choice then option.value = choice @@ -214,7 +214,6 @@ function M:set_config(config) git.config.set(config.name, config.value) end - for _, var in ipairs(self.state.config) do if var.passive then local c_value = git.config.get(var.name) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 3c3b7d845..dd8c96e1a 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -430,7 +430,7 @@ function Ui:get_cursor_location(line) hunk = hunk_loc, first = first, last = last, - section_offset = section_offset + section_offset = section_offset, } end diff --git a/lua/neogit/popups/bisect/actions.lua b/lua/neogit/popups/bisect/actions.lua index 4e72c669a..2e1d91b1a 100644 --- a/lua/neogit/popups/bisect/actions.lua +++ b/lua/neogit/popups/bisect/actions.lua @@ -106,7 +106,6 @@ function M.scripted(popup) end end - function M.good() git.bisect.good() end From b17fce2185d565842868c8481a6346d407c02394 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 27 Mar 2024 23:32:26 +0100 Subject: [PATCH 267/443] Make selene happy --- lua/neogit/buffers/log_view/init.lua | 1 - lua/neogit/buffers/reflog_view/init.lua | 1 - lua/neogit/lib/buffer.lua | 1 + lua/neogit/popups/branch_config/actions.lua | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 25d922e35..2ba0ef028 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -2,7 +2,6 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.log_view.ui") local config = require("neogit.config") local popups = require("neogit.popups") -local notification = require("neogit.lib.notification") local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 7ac87d43c..cc304a9ae 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -2,7 +2,6 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.reflog_view.ui") local config = require("neogit.config") local popups = require("neogit.popups") -local notification = require("neogit.lib.notification") local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index f2fd2529b..3dc814cd9 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -634,6 +634,7 @@ function Buffer.create(config) -- TODO: Need to find a way to turn this off properly when unloading plugin -- buffer:set_window_option("winfixbuf", true) else + -- selene: allow(global_usage) _G.NeogitFoldText = function() return vim.fn.getline(vim.v.foldstart) end diff --git a/lua/neogit/popups/branch_config/actions.lua b/lua/neogit/popups/branch_config/actions.lua index 576206341..74f20a1a2 100644 --- a/lua/neogit/popups/branch_config/actions.lua +++ b/lua/neogit/popups/branch_config/actions.lua @@ -1,6 +1,5 @@ local a = require("plenary.async") local git = require("neogit.lib.git") -local util = require("neogit.lib.util") local client = require("neogit.client") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") From c0b31d83cfae3e7478e3741f2ce528dc138933ff Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 22:24:04 +0100 Subject: [PATCH 268/443] Handle unmerged/added files in conflict resolution --- lua/neogit/buffers/status/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 6aad66770..86d39ce91 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -514,7 +514,7 @@ function M:open(kind) fn.delete(selection.item.escaped_path) end elseif section == "unstaged" then - if selection.item.mode == "UU" then + if selection.item.mode:match("^[UA][UA]") then choices = { "&ours", "&theirs", "&conflict", "&abort" } action = function() local choice = input.get_choice( From 7904cf3de6a397466022a911aff1fb568b48a29c Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:43:32 +0100 Subject: [PATCH 269/443] Cleanup: Rewrite how is_open check is done. Don't manage the state ourselves, but expose a buffer method to check if it's got a window ID that it's visible within --- lua/neogit/buffers/git_command_history.lua | 21 ++++++++---- lua/neogit/buffers/refs_view/init.lua | 40 +++++++++------------- lua/neogit/buffers/status/init.lua | 17 ++++----- lua/neogit/lib/buffer.lua | 4 +++ lua/neogit/lib/popup/init.lua | 14 ++++++++ 5 files changed, 54 insertions(+), 42 deletions(-) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index bc6acf51b..547aa4d64 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -2,6 +2,7 @@ local Buffer = require("neogit.lib.buffer") local Git = require("neogit.lib.git") local Ui = require("neogit.lib.ui") local util = require("neogit.lib.util") +local status_maps = require("neogit.config").get_reversed_status_maps() local map = util.map local filter_map = util.filter_map @@ -19,7 +20,6 @@ function M:new(state) local this = { buffer = nil, state = state or Git.cli.history, - is_open = false, } setmetatable(this, { __index = M }) @@ -28,23 +28,30 @@ function M:new(state) end function M:close() - self.is_open = false - self.buffer:close() - self.buffer = nil + if self.buffer then + self.buffer:close() + self.buffer = nil + end +end + +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end function M:show() - if self.is_open then + if M.is_open() then return end - self.is_open = true + + M.instance = self self.buffer = Buffer.create { name = "NeogitGitCommandHistory", filetype = "NeogitGitCommandHistory", mappings = { n = { - ["q"] = function() + [status_maps["Close"]] = function() self:close() end, [""] = function() diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 328e15d41..144407c39 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -1,10 +1,11 @@ local Buffer = require("neogit.lib.buffer") +local config = require("neogit.config") local ui = require("neogit.buffers.refs_view.ui") local popups = require("neogit.popups") +local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") --- @class RefsViewBuffer ---- @field is_open boolean whether the buffer is currently shown --- @field buffer Buffer --- @field open fun() --- @field close fun() @@ -21,7 +22,6 @@ function M.new(refs) local instance = { refs = refs, head = "HEAD", - is_open = false, buffer = nil, } @@ -29,39 +29,31 @@ function M.new(refs) return instance end ---- Closes the RefsViewBuffer function M:close() - self.is_open = false - self.buffer:close() - self.buffer = nil + if self.buffer then + self.buffer:close() + self.buffer = nil + end +end + +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end --- Opens the RefsViewBuffer ---- If already open will close the buffer function M:open() - if M.instance and M.instance.is_open then - M.instance:close() - end - - M.instance = self - - if self.is_open then + if M.is_open() then return end - self.hovered_component = nil - self.is_open = true + M.instance = self self.buffer = Buffer.create { name = "NeogitRefsView", filetype = "NeogitRefsView", - kind = "tab", + kind = config.values.refs.kind, context_highlight = false, - autocmds = { - ["BufUnload"] = function() - M.instance.is_open = false - end, - }, mappings = { v = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) @@ -144,13 +136,13 @@ function M:open() item = { name = item }, } end), - ["q"] = function() + [status_maps["Close"]] = function() self:close() end, [""] = function() self:close() end, - [""] = function() + [status_maps["GoToFile"]] = function() CommitViewBuffer.new(self.buffer.ui:get_commits_in_selection()[1]):open() end, -- ["{"] = function() diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 86d39ce91..bfaa71d55 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -29,7 +29,6 @@ local fn = vim.fn ---@field acquire function ---@class StatusBuffer ----@field is_open boolean whether the buffer is currently visible ---@field buffer Buffer instance ---@field state NeogitRepo ---@field config NeogitConfig @@ -43,7 +42,6 @@ M.__index = M ---@return StatusBuffer function M.new(state, config) local instance = { - is_open = false, -- frozen = false, state = state, config = config, @@ -57,19 +55,18 @@ function M.new(state, config) return instance end +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true +end + function M:open(kind) - if M.instance and M.instance.is_open then + if M.instance and M.is_open() then logger.debug("[STATUS] An Instance is already open - closing it") M.instance:close() end M.instance = self - if self.is_open then - logger.debug("[STATUS] This Instance is already open - bailing") - return - end - self.is_open = true - kind = kind or config.values.kind logger.debug("[STATUS] Opening kind: " .. kind) @@ -89,7 +86,6 @@ function M:open(kind) self.watcher:stop() end - self.is_open = false vim.o.autochdir = self.prev_autochdir end, ["BufEnter"] = function() @@ -1130,7 +1126,6 @@ function M:close() self.watcher:stop() end - self.is_open = false self.buffer:close() self.buffer = nil end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 3dc814cd9..751572d2a 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -267,6 +267,10 @@ function Buffer:hide() end end +function Buffer:is_visible() + return #fn.win_findbuf(self.handle) > 0 +end + ---@return number function Buffer:show() local windows = fn.win_findbuf(self.handle) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 5fe70ee92..77405669f 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -29,12 +29,14 @@ function M.builder() return PopupBuilder.new(M.new) end +---@param state PopupState function M.new(state) local instance = { state = state, buffer = nil, } setmetatable(instance, { __index = M }) + return instance end @@ -324,7 +326,19 @@ function M:refresh() self.buffer.ui:render(unpack(ui.Popup(self.state))) end +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true +end + function M:show() + if M.is_open() then + logger.debug("[POPUP] An Instance is already open - closing it") + M.instance:close() + end + + M.instance = self + self.buffer = Buffer.create { name = self.state.name, filetype = "NeogitPopup", From fd27613c48fc3ec6b0a10b41c310d182d493cf5a Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:45:46 +0100 Subject: [PATCH 270/443] Rewrite: OpenOrScrollDown and OpenOrScrollUp mapping can now be configured by the user. Also available on the status and reflog buffer. --- README.md | 2 ++ lua/neogit/buffers/commit_view/init.lua | 39 ++++++++++++++----------- lua/neogit/buffers/log_view/init.lua | 22 +++++--------- lua/neogit/buffers/reflog_view/init.lua | 10 +++++++ lua/neogit/buffers/status/init.lua | 11 +++++++ lua/neogit/config.lua | 2 ++ lua/neogit/lib/buffer.lua | 4 +++ 7 files changed, 59 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6ce6cbf26..a845dc947 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,8 @@ neogit.setup { [""] = "TabOpen", ["{"] = "GoToPreviousHunkHeader", ["}"] = "GoToNextHunkHeader", + ["["] = "OpenOrScrollUp", + ["]"] = "OpenOrScrollDown", }, }, } diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index c1f711ad7..d689943c1 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -81,28 +81,33 @@ end ---which is passed the window id of the commit view buffer ---@param commit_id string commit ---@param filter string[]? Filter diffs to filepaths in table ----@param action fun(window_id) -function M.open_or_run_in_window(commit_id, filter, action) - if not commit_id then - return - end - local cvb = M.instance - if cvb and cvb.is_open and cvb.commit_info.commit_arg == commit_id and cvb.buffer and cvb.buffer.handle then - local ct = vim.api.nvim_get_current_tabpage() - for _, win in ipairs(vim.api.nvim_tabpage_list_wins(ct)) do - local buf = vim.api.nvim_win_get_buf(win) - if buf == cvb.buffer.handle then - pcall(action, win) - break - end - end +---@param cmd string vim command to run in window +function M.open_or_run_in_window(commit_id, filter, cmd) + assert(commit_id, "commit id cannot be nil") + + if + M.is_open() + and M.instance.commit_info.commit_arg == commit_id + then + M.instance.buffer:win_exec(cmd) else - local cw = vim.api.nvim_get_current_win() + local cw = api.nvim_get_current_win() M.new(commit_id, filter):open() - vim.api.nvim_set_current_win(cw) + api.nvim_set_current_win(cw) end end +---@param commit_id string commit +---@param filter string[]? Filter diffs to filepaths in table +function M.open_or_scroll_down(commit_id, filter) + M.open_or_run_in_window(commit_id, filter, "normal! " .. vim.keycode("")) +end + +---@param commit_id string commit +---@param filter string[]? Filter diffs to filepaths in table +function M.open_or_scroll_up(commit_id, filter) + M.open_or_run_in_window(commit_id, filter, "normal! " .. vim.keycode("")) +end ---Opens the CommitViewBuffer ---If already open will close the buffer ---@param kind? string diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index b2c3ee9f8..773ce397e 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -148,22 +148,16 @@ function M:open() CommitViewBuffer.new(commit, self.files):open() end end, - [";"] = function() - if self.buffer and self.buffer.ui then - local commit_id = self.buffer.ui:get_commit_under_cursor() - CommitViewBuffer.open_or_run_in_window(commit_id, self.files, function(window_id) - local key = vim.api.nvim_replace_termcodes("", true, false, true) - vim.fn.win_execute(window_id, "normal! " .. key) - end) + [status_maps["OpenOrScrollDown"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.open_or_scroll_down(commit, self.files) end end, - [","] = function() - if self.buffer and self.buffer.ui then - local commit_id = self.buffer.ui:get_commit_under_cursor() - CommitViewBuffer.open_or_run_in_window(commit_id, self.files, function(window_id) - local key = vim.api.nvim_replace_termcodes("", true, false, true) - vim.fn.win_execute(window_id, "normal! " .. key) - end) + [status_maps["OpenOrScrollUp"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.open_or_scroll_up(commit, self.files) end end, [""] = function() diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index cc304a9ae..cb3911348 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -138,6 +138,16 @@ function M:open(_) local oid = self.buffer.ui:get_commit_under_cursor() if oid then CommitViewBuffer.new(oid):open() + [status_maps["OpenOrScrollDown"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.open_or_scroll_down(commit) + end + end, + [status_maps["OpenOrScrollUp"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.open_or_scroll_up(commit) end end, }, diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index bfaa71d55..ea2d2e236 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -390,6 +390,17 @@ function M:open(kind) end, [mappings["Close"]] = function() self:close() + [mappings["OpenOrScrollDown"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + require("neogit.buffers.commit_view").open_or_scroll_down(commit) + end + end, + [mappings["OpenOrScrollUp"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + require("neogit.buffers.commit_view").open_or_scroll_up(commit) + end end, [mappings["RefreshBuffer"]] = a.void(function() self:refresh() diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 11ce2df29..9841ad2b6 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -393,6 +393,8 @@ function M.get_default_values() [""] = "TabOpen", ["{"] = "GoToPreviousHunkHeader", ["}"] = "GoToNextHunkHeader", + ["["] = "OpenOrScrollUp", + ["]"] = "OpenOrScrollDown", }, }, } diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 751572d2a..c5b896370 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -487,6 +487,10 @@ function Buffer:call(f) api.nvim_buf_call(self.handle, f) end +function Buffer:win_exec(cmd) + fn.win_execute(self.win_handle, cmd) +end + function Buffer:exists() return fn.bufnr(self.handle) ~= -1 end From a1e03228b119bd41bb60d121c8e4bcf177548857 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:46:23 +0100 Subject: [PATCH 271/443] allow refs buffer type to be user configurable --- lua/neogit/config.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 9841ad2b6..757b9937b 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -244,6 +244,9 @@ function M.get_default_values() popup = { kind = "split", }, + refs = { + kind = "tab", + }, signs = { hunk = { "", "" }, item = { ">", "v" }, From ae3d035848fbea6a5cd272f5a3020077cb460bd5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:49:19 +0100 Subject: [PATCH 272/443] squash! allow refs buffer type to be user configurable --- lua/neogit/buffers/log_view/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 773ce397e..8b9d208e4 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -162,7 +162,7 @@ function M:open() end, [""] = function() pcall(vim.cmd, "normal! zc") - + vim.cmd("normal! k") for _ = vim.fn.line("."), 0, -1 do if vim.fn.foldlevel(".") > 0 then From 2ec1a923a20c6cb9b4d3354006a9e09e5512fdb5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:51:22 +0100 Subject: [PATCH 273/443] When closing the status buffer, check for commit view, and close that first, or popup, and close that instead --- lua/neogit/buffers/status/init.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ea2d2e236..12bb27fa6 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -389,7 +389,20 @@ function M:open(kind) end end, [mappings["Close"]] = function() - self:close() + local commit_view = require("neogit.buffers.commit_view") + local popup = require("neogit.lib.popup") + local history = require("neogit.buffers.git_command_history") + + if popup.is_open() then + popup.instance:close() + elseif commit_view.is_open() then + commit_view.instance:close() + elseif history.is_open() then + history.instance:close() + else + self:close() + end + end, [mappings["OpenOrScrollDown"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() if commit then From 3217f92a73aad4b8e9d641a1ffb8064302ed718e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:53:11 +0100 Subject: [PATCH 274/443] add is_open to commit view --- lua/neogit/buffers/commit_view/init.lua | 28 ++++++++++--------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index d689943c1..9f49b1760 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -29,7 +29,6 @@ local api = vim.api ---@field deletions string deletion count visualized as list of `-` --- @class CommitViewBuffer ---- @field is_open boolean whether the buffer is currently shown --- @field commit_info CommitInfo --- @field commit_signature table|nil --- @field commit_overview CommitOverview @@ -57,7 +56,6 @@ function M.new(commit_id, filter) parser.parse_commit_overview(git.cli.show.stat.oneline.args(commit_id).call_sync().stdout) local instance = { - is_open = false, item_filter = filter, commit_info = commit_info, commit_overview = commit_overview, @@ -72,9 +70,10 @@ end --- Closes the CommitViewBuffer function M:close() - self.is_open = false - self.buffer:close() - self.buffer = nil + if self.buffer then + self.buffer:close() + self.buffer = nil + end end ---Opens the CommitViewBuffer if it isn't open or performs the given action @@ -108,34 +107,29 @@ end function M.open_or_scroll_up(commit_id, filter) M.open_or_run_in_window(commit_id, filter, "normal! " .. vim.keycode("")) end + +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true +end + ---Opens the CommitViewBuffer ---If already open will close the buffer ---@param kind? string function M:open(kind) kind = kind or config.values.commit_view.kind - if M.instance and M.instance.is_open then + if M.is_open() then M.instance:close() end M.instance = self - if self.is_open then - return - end - - self.hovered_component = nil - self.is_open = true self.buffer = Buffer.create { name = "NeogitCommitView", filetype = "NeogitCommitView", kind = kind, context_highlight = true, - autocmds = { - ["BufUnload"] = function() - M.instance.is_open = false - end, - }, mappings = { n = { [""] = function() From 4078d6205ad074c295f0f0f89a2579fd06cb174c Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 28 Mar 2024 23:58:49 +0100 Subject: [PATCH 275/443] Update config --- lua/neogit/buffers/refs_view/init.lua | 2 +- lua/neogit/config.lua | 100 +++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 144407c39..72664bd95 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -52,7 +52,7 @@ function M:open() self.buffer = Buffer.create { name = "NeogitRefsView", filetype = "NeogitRefsView", - kind = config.values.refs.kind, + kind = config.values.refs_view.kind, context_highlight = false, mappings = { v = { diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 757b9937b..b9e357c45 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -111,15 +111,90 @@ end ---@field enabled boolean ---@field filewatcher NeogitFilewatcherConfig|nil ----@alias NeogitConfigMappingsFinder "Select" | "Close" | "Next" | "Previous" | "MultiselectToggleNext" | "MultiselectTogglePrevious" | "NOP" | false - ----@alias NeogitConfigMappingsStatus "Close" | "Depth1" | "Depth2" | "Depth3" | "Depth4" | "Toggle" | "Discard" | "Stage" | "StageUnstaged" | "StageAll" | "Unstage" | "UnstageStaged" | "RefreshBuffer" | "GoToFile" | "VSplitOpen" | "SplitOpen" | "TabOpen" | "GoToPreviousHunkHeader" | "GoToNextHunkHeader" | "Console" | "CommandHistory" | "ShowRefs" | "InitRepo" | "YankSelected" | false | fun() - ----@alias NeogitConfigMappingsPopup "HelpPopup" | "DiffPopup" | "PullPopup" | "RebasePopup" | "MergePopup" | "PushPopup" | "CommitPopup" | "LogPopup" | "RevertPopup" | "StashPopup" | "IgnorePopup" | "CherryPickPopup" | "BisectPopup" | "BranchPopup" | "FetchPopup" | "ResetPopup" | "RemotePopup" | "TagPopup" | "WorktreePopup" | false - ----@alias NeogitConfigMappingsRebaseEditor "Pick" | "Reword" | "Edit" | "Squash" | "Fixup" | "Execute" | "Drop" | "Break" | "MoveUp" | "MoveDown" | "Close" | "OpenCommit" | "Submit" | "Abort" | false | fun() ---- ----@alias NeogitConfigMappingsCommitEditor "Close" | "Submit" | "Abort" | "PrevMessage" | "ResetMessage" | "NextMessage" | false | fun() +---@alias NeogitConfigMappingsFinder "Select" +---| "Close" +---| "Next" +---| "Previous" +---| "MultiselectToggleNext" +---| "MultiselectTogglePrevious" +---| "NOP" +---| false + +---@alias NeogitConfigMappingsStatus "Close" +---| "Depth1" +---| "Depth2" +---| "Depth3" +---| "Depth4" +---| "Toggle" +---| "Discard" +---| "Stage" +---| "StageUnstaged" +---| "StageAll" +---| "Unstage" +---| "UnstageStaged" +---| "RefreshBuffer" +---| "GoToFile" +---| "VSplitOpen" +---| "SplitOpen" +---| "TabOpen" +---| "GoToPreviousHunkHeader" +---| "GoToNextHunkHeader" +---| "Console" +---| "CommandHistory" +---| "ShowRefs" +---| "InitRepo" +---| "YankSelected" +---| "OpenOrScrollUp" +---| "OpenOrScrollDown" +---| false +---| fun() + +---@alias NeogitConfigMappingsPopup "HelpPopup" +---| "DiffPopup" +---| "PullPopup" +---| "RebasePopup" +---| "MergePopup" +---| "PushPopup" +---| "CommitPopup" +---| "LogPopup" +---| "RevertPopup" +---| "StashPopup" +---| "IgnorePopup" +---| "CherryPickPopup" +---| "BisectPopup" +---| "BranchPopup" +---| "FetchPopup" +---| "ResetPopup" +---| "RemotePopup" +---| "TagPopup" +---| "WorktreePopup" +---| false + +---@alias NeogitConfigMappingsRebaseEditor "Pick" +---| "Reword" +---| "Edit" +---| "Squash" +---| "Fixup" +---| "Execute" +---| "Drop" +---| "Break" +---| "MoveUp" +---| "MoveDown" +---| "Close" +---| "OpenCommit" +---| "Submit" +---| "Abort" +---| false +---| fun() + +---@alias NeogitConfigMappingsCommitEditor "Close" +---| "Submit" +---| "Abort" +---| "PrevMessage" +---| "ResetMessage" +---| "NextMessage" +---| false +---| fun() ---@class NeogitConfigMappings Consult the config file or documentation for values ---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds @@ -155,6 +230,7 @@ end ---@field log_view? NeogitConfigPopup Log view options ---@field rebase_editor? NeogitConfigPopup Rebase editor options ---@field reflog_view? NeogitConfigPopup Reflog view options +---@field refs_view? NeogitConfigPopup Refs view options ---@field merge_editor? NeogitConfigPopup Merge editor options ---@field description_editor? NeogitConfigPopup Merge editor options ---@field tag_editor? NeogitConfigPopup Tag editor options @@ -244,7 +320,7 @@ function M.get_default_values() popup = { kind = "split", }, - refs = { + refs_view = { kind = "tab", }, signs = { @@ -810,6 +886,10 @@ function M.validate_config() if validate_type(config.reflog_view, "reflog_view", "table") then validate_kind(config.reflog_view.kind, "reflog_view.kind") end + -- refs view + if validate_type(config.refs_view, "refs_view", "table") then + validate_kind(config.refs_view.kind, "refs_view.kind") + end -- Merge Editor if validate_type(config.merge_editor, "merge_editor", "table") then validate_kind(config.merge_editor.kind, "merge_editor.kind") From 9d4e9d4e1df72e4f1fb910b0a09b2c7003c8c048 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Mar 2024 00:04:05 +0100 Subject: [PATCH 276/443] Test using lua callbacks for buffer detach --- lua/neogit/buffers/editor/init.lua | 24 ++++++++++++----------- lua/neogit/buffers/rebase_editor/init.lua | 24 ++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index f778245b1..277f8286e 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -71,6 +71,19 @@ function M:open(kind) modifiable = true, status_column = " ", readonly = false, + initialize = function(buffer) + vim.api.nvim_buf_attach(buffer.handle, false, { + on_detach = function() + pcall(vim.treesitter.stop, buffer.handle) + + if self.on_unload then + self.on_unload(aborted and 1 or 0) + end + + require("neogit.process").defer_show_preview_buffers() + end, + }) + end, after = function(buffer) -- Populate help lines with mappings for buffer local padding = util.max_length(util.flatten(vim.tbl_values(mapping))) @@ -134,17 +147,6 @@ function M:open(kind) vim.cmd.source("$VIMRUNTIME/syntax/gitcommit.vim") end end, - autocmds = { - ["BufUnload"] = function() - pcall(vim.treesitter.stop, self.buffer.handle) - - if self.on_unload then - self.on_unload(aborted and 1 or 0) - end - - require("neogit.process").defer_show_preview_buffers() - end, - }, mappings = { i = { [mapping["Submit"]] = function(buffer) diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 00b880a91..624dca23b 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -78,6 +78,19 @@ function M:open(kind) modifiable = true, disable_line_numbers = config.values.disable_line_numbers, readonly = false, + initialize = function(buffer) + vim.api.nvim_buf_attach(buffer.handle, false, { + on_detach = function() + pcall(vim.treesitter.stop, buffer.handle) + + if self.on_unload then + self.on_unload(aborted and 1 or 0) + end + + require("neogit.process").defer_show_preview_buffers() + end, + }) + end, after = function(buffer) local padding = util.max_length(util.flatten(vim.tbl_values(mapping))) local pad_mapping = function(name) @@ -131,17 +144,6 @@ function M:open(kind) vim.cmd.source("$VIMRUNTIME/syntax/gitrebase.vim") end end, - autocmds = { - ["BufUnload"] = function() - pcall(vim.treesitter.stop, self.buffer.handle) - - if self.on_unload then - self.on_unload(aborted and 1 or 0) - end - - require("neogit.process").defer_show_preview_buffers() - end, - }, mappings = { i = { [mapping["Submit"]] = function(buffer) From a380d41b3bfa39b70061945f058f34a24fbd1a79 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Mar 2024 10:00:37 +0100 Subject: [PATCH 277/443] Allow separate mappings for insert mode mappings in commit/rebase editor buffers --- README.md | 8 + lua/neogit/buffers/editor/init.lua | 5 +- lua/neogit/buffers/rebase_editor/init.lua | 5 +- lua/neogit/config.lua | 177 ++++++++++++++++------ 4 files changed, 148 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index a845dc947..8c6d88768 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,10 @@ neogit.setup { [""] = "Submit", [""] = "Abort", }, + commit_editor_I = { + [""] = "Submit", + [""] = "Abort", + }, rebase_editor = { ["p"] = "Pick", ["r"] = "Reword", @@ -261,6 +265,10 @@ neogit.setup { [""] = "Submit", [""] = "Abort", }, + rebase_editor_I = { + [""] = "Submit", + [""] = "Abort", + }, finder = { [""] = "Select", [""] = "Close", diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 277f8286e..77f634fa5 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -41,6 +41,7 @@ function M:open(kind) assert(kind, "Editor must specify a kind") local mapping = config.get_reversed_commit_editor_maps() + local mapping_I = config.get_reversed_commit_editor_maps_I() local aborted = false local message_index = 1 @@ -149,7 +150,7 @@ function M:open(kind) end, mappings = { i = { - [mapping["Submit"]] = function(buffer) + [mapping_I["Submit"]] = function(buffer) vim.cmd.stopinsert() if amend_header then buffer:set_lines(0, 0, false, amend_header) @@ -158,7 +159,7 @@ function M:open(kind) buffer:write() buffer:close(true) end, - [mapping["Abort"]] = function(buffer) + [mapping_I["Abort"]] = function(buffer) vim.cmd.stopinsert() aborted = true buffer:write() diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 624dca23b..4d711e277 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -66,6 +66,7 @@ function M:open(kind) or "#" local mapping = config.get_reversed_rebase_editor_maps() + local mapping_I = config.get_reversed_rebase_editor_maps_I() local aborted = false self.buffer = Buffer.create { @@ -146,12 +147,12 @@ function M:open(kind) end, mappings = { i = { - [mapping["Submit"]] = function(buffer) + [mapping_I["Submit"]] = function(buffer) vim.cmd.stopinsert() buffer:write() buffer:close(true) end, - [mapping["Abort"]] = function(buffer) + [mapping_I["Abort"]] = function(buffer) vim.cmd.stopinsert() aborted = true buffer:write() diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index b9e357c45..feb3c2c63 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -1,67 +1,59 @@ local util = require("neogit.lib.util") local M = {} +local mappings = {} + +---Returns a map of commands, mapped to the list of keys which trigger them. ---@return table ---- Returns a map of commands, mapped to the list of keys which trigger them. -local function get_reversed_maps(tbl) - local result = {} - for k, v in pairs(tbl) do - -- If `v == false` the mapping is disabled - if v then - local current = result[v] - if current then - table.insert(current, k) - else - result[v] = { k } +local function get_reversed_maps(set) + if not mappings[set] then + local result = {} + for k, v in pairs(M.values.mappings[set]) do + -- If `v == false` the mapping is disabled + if v then + local current = result[v] + if current then + table.insert(current, k) + else + result[v] = { k } + end end end + + mappings[set] = result end - return result + return mappings[set] end -local reversed_status_maps ---@return table ---- Returns a map of commands, mapped to the list of keys which trigger them. function M.get_reversed_status_maps() - if not reversed_status_maps then - reversed_status_maps = get_reversed_maps(M.values.mappings.status) - end - - return reversed_status_maps + return get_reversed_maps("status") end -local reversed_popup_maps ---@return table ---- Returns a map of commands, mapped to the list of keys which trigger them. function M.get_reversed_popup_maps() - if not reversed_popup_maps then - reversed_popup_maps = get_reversed_maps(M.values.mappings.popup) - end - - return reversed_popup_maps + return get_reversed_maps("popup") end -local reversed_rebase_editor_maps ---@return table ---- Returns a map of commands, mapped to the list of keys which trigger them. function M.get_reversed_rebase_editor_maps() - if not reversed_rebase_editor_maps then - reversed_rebase_editor_maps = get_reversed_maps(M.values.mappings.rebase_editor) - end + return get_reversed_maps("rebase_editor") +end - return reversed_rebase_editor_maps +---@return table +function M.get_reversed_rebase_editor_maps_I() + return get_reversed_maps("rebase_editor_I") end -local reversed_commit_editor_maps ---@return table ---- Returns a map of commands, mapped to the list of keys which trigger them. function M.get_reversed_commit_editor_maps() - if not reversed_commit_editor_maps then - reversed_commit_editor_maps = get_reversed_maps(M.values.mappings.commit_editor) - end + return get_reversed_maps("commit_editor") +end - return reversed_commit_editor_maps +---@return table +function M.get_reversed_commit_editor_maps_I() + return get_reversed_maps("commit_editor_I") end ---@alias WindowKind @@ -111,7 +103,8 @@ end ---@field enabled boolean ---@field filewatcher NeogitFilewatcherConfig|nil ----@alias NeogitConfigMappingsFinder "Select" +---@alias NeogitConfigMappingsFinder +---| "Select" ---| "Close" ---| "Next" ---| "Previous" @@ -120,7 +113,8 @@ end ---| "NOP" ---| false ----@alias NeogitConfigMappingsStatus "Close" +---@alias NeogitConfigMappingsStatus +---| "Close" ---| "Depth1" ---| "Depth2" ---| "Depth3" @@ -149,7 +143,8 @@ end ---| false ---| fun() ----@alias NeogitConfigMappingsPopup "HelpPopup" +---@alias NeogitConfigMappingsPopup +---| "HelpPopup" ---| "DiffPopup" ---| "PullPopup" ---| "RebasePopup" @@ -170,7 +165,8 @@ end ---| "WorktreePopup" ---| false ----@alias NeogitConfigMappingsRebaseEditor "Pick" +---@alias NeogitConfigMappingsRebaseEditor +---| "Pick" ---| "Reword" ---| "Edit" ---| "Squash" @@ -187,7 +183,8 @@ end ---| false ---| fun() ----@alias NeogitConfigMappingsCommitEditor "Close" +---@alias NeogitConfigMappingsCommitEditor +---| "Close" ---| "Submit" ---| "Abort" ---| "PrevMessage" @@ -196,12 +193,26 @@ end ---| false ---| fun() +---@alias NeogitConfigMappingsCommitEditor_I +---| "Submit" +---| "Abort" +---| false +---| fun() + +---@alias NeogitConfigMappingsRebaseEditor_I +---| "Submit" +---| "Abort" +---| false +---| fun() + ---@class NeogitConfigMappings Consult the config file or documentation for values ---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds ---@field status? { [string]: NeogitConfigMappingsStatus } A dictionary that uses status commands to set a single keybind ---@field popup? { [string]: NeogitConfigMappingsPopup } A dictionary that uses popup commands to set a single keybind ---@field rebase_editor? { [string]: NeogitConfigMappingsRebaseEditor } A dictionary that uses Rebase editor commands to set a single keybind +---@field rebase_editor_I? { [string]: NeogitConfigMappingsRebaseEditor_I } A dictionary that uses Rebase editor commands to set a single keybind ---@field commit_editor? { [string]: NeogitConfigMappingsCommitEditor } A dictionary that uses Commit editor commands to set a single keybind +---@field commit_editor_I? { [string]: NeogitConfigMappingsCommitEditor_I } A dictionary that uses Commit editor commands to set a single keybind ---@alias NeogitGraphStyle "ascii" | "unicode" @@ -398,6 +409,10 @@ function M.get_default_values() [""] = "NextMessage", [""] = "ResetMessage", }, + commit_editor_I = { + [""] = "Submit", + [""] = "Abort", + }, rebase_editor = { ["p"] = "Pick", ["r"] = "Reword", @@ -414,6 +429,10 @@ function M.get_default_values() [""] = "Submit", [""] = "Abort", }, + rebase_editor_I = { + [""] = "Submit", + [""] = "Abort", + }, finder = { [""] = "Select", [""] = "Close", @@ -806,6 +825,78 @@ function M.validate_config() end end + local valid_rebase_editor_I_commands = { + false, + } + + for _, cmd in pairs(M.get_default_values().mappings.rebase_editor_I) do + table.insert(valid_rebase_editor_I_commands, cmd) + end + + if validate_type(config.mappings.rebase_editor_I, "mappings.rebase_editor_I", "table") then + for key, command in pairs(config.mappings.rebase_editor_I) do + if + validate_type(key, "mappings.rebase_editor_I -> " .. vim.inspect(key), "string") + and validate_type( + command, + string.format("mappings.rebase_editor_I['%s']", key), + { "string", "boolean", "function" } + ) + then + if type(command) == "string" and not vim.tbl_contains(valid_rebase_editor_I_commands, command) then + local valid_rebase_editor_I_commands = util.map(valid_rebase_editor_I_commands, function(command) + return vim.inspect(command) + end) + + err( + string.format("mappings.rebase_editor_I['%s']", key), + string.format( + "Expected a valid rebase_editor_I command, got '%s'. Valid rebase_editor_I commands: { %s }", + command, + table.concat(valid_rebase_editor_I_commands, ", ") + ) + ) + end + end + end + end + + local valid_commit_editor_I_commands = { + false, + } + + for _, cmd in pairs(M.get_default_values().mappings.commit_editor_I) do + table.insert(valid_commit_editor_I_commands, cmd) + end + + if validate_type(config.mappings.commit_editor_I, "mappings.commit_editor_I", "table") then + for key, command in pairs(config.mappings.commit_editor_I) do + if + validate_type(key, "mappings.commit_editor_I -> " .. vim.inspect(key), "string") + and validate_type( + command, + string.format("mappings.commit_editor_I['%s']", key), + { "string", "boolean", "function" } + ) + then + if type(command) == "string" and not vim.tbl_contains(valid_commit_editor_I_commands, command) then + local valid_commit_editor_I_commands = util.map(valid_commit_editor_I_commands, function(command) + return vim.inspect(command) + end) + + err( + string.format("mappings.commit_editor_I['%s']", key), + string.format( + "Expected a valid commit_editor_I command, got '%s'. Valid commit_editor_I commands: { %s }", + command, + table.concat(valid_commit_editor_I_commands, ", ") + ) + ) + end + end + end + end + local valid_commit_editor_commands = { false, } From 584f980a96d29918b2727c4b62371883d7ccb0cc Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Mar 2024 12:39:20 +0100 Subject: [PATCH 278/443] Allow for a popup launched BEFORE launching the status buffer to populate the repo state correctly. --- lua/neogit.lua | 9 ++++++++- lua/neogit/lib/popup/builder.lua | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 9417b31ca..dc90a6cb3 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -152,7 +152,14 @@ function M.open(opts) end if opts[1] ~= nil then - open_popup(opts[1]) + local a = require("plenary.async") + local cb = function() + open_popup(opts[1]) + end + + a.run(function() + git.repo:refresh { source = "popup", callback = cb } + end) else open_status_buffer(opts) end diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 48f3ec495..428f05599 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -377,8 +377,11 @@ function M:action(keys, description, callback) watcher.suspend(callback, { ... }) permit:forget() - logger.debug("[ACTION] Dispatching Refresh") - require("neogit.buffers.status").instance:dispatch_refresh(nil, "action") + local status = require("neogit.buffers.status").instance + if status then + logger.debug("[ACTION] Dispatching Refresh") + status:dispatch_refresh(nil, "action") + end end) end From c5eb50f4273ec99a39eb04622a3ea709856bed19 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Mar 2024 12:47:34 +0100 Subject: [PATCH 279/443] Update annotations --- lua/neogit/lib/popup/builder.lua | 2 +- lua/neogit/lib/popup/init.lua | 3 ++- lua/neogit/popups/branch/actions.lua | 2 +- lua/neogit/popups/reset/actions.lua | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 428f05599..588e9929f 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -8,7 +8,7 @@ local watcher = require("neogit.watcher") local M = {} ----@class Popup +---@class PopupData ---@field state PopupState ---@class PopupState diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 77405669f..0d294e2fa 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -20,7 +20,7 @@ local ui = require("neogit.lib.popup.ui") ---@class PopupState ----@class Popup +---@class PopupData ---@field state PopupState ---@field buffer Buffer local M = {} @@ -30,6 +30,7 @@ function M.builder() end ---@param state PopupState +---@return PopupData function M.new(state) local instance = { state = state, diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index f0bf39f8a..a9b69f78c 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -44,7 +44,7 @@ local function spin_off_branch(checkout) end end ----@param popup Popup +---@param popup PopupData ---@param prompt string ---@param checkout boolean ---@return string|nil diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 51453ab37..37035c8ff 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -5,7 +5,7 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} ----@param popup Popup +---@param popup PopupData ---@return string|nil local function commit(popup, prompt) local commit From 8c063580c5e749e45678909eb2e0bb976a45c9d0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 29 Mar 2024 12:54:22 +0100 Subject: [PATCH 280/443] Use correct maps in reflog view --- lua/neogit/buffers/reflog_view/init.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index cb3911348..0b7a95959 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -128,16 +128,18 @@ function M:open(_) vim.cmd("echo ''") end end, - ["q"] = function() + [""] = function() self:close() end, - [""] = function() + [status_maps["Close"]] = function() self:close() end, - [""] = function() - local oid = self.buffer.ui:get_commit_under_cursor() - if oid then - CommitViewBuffer.new(oid):open() + [status_maps["GoToFile"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.new(commit):open() + end + end, [status_maps["OpenOrScrollDown"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() if commit then From b34cc56650e88bfd43c8a262fdafca45a3323570 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 15:20:21 +0200 Subject: [PATCH 281/443] Do not show the notificiation in the nightly branch --- lua/neogit.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 88243bfdb..dc90a6cb3 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -72,10 +72,6 @@ function M.setup(opts) signs.setup() state.setup() autocmds.setup() - - if vim.fn.has("nvim-0.10") == 1 then - M.notification.info("The 'nightly' branch for Neogit provides support for nvim-0.10") - end end local function construct_opts(opts) From 86863aa753541b306775ea9a76ac69a722adb4c5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 15:57:33 +0200 Subject: [PATCH 282/443] Add OpenOrScrollUp/OpenOrScrollDown to refs view --- lua/neogit/buffers/refs_view/init.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 72664bd95..0c63fc7c6 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -145,6 +145,18 @@ function M:open() [status_maps["GoToFile"]] = function() CommitViewBuffer.new(self.buffer.ui:get_commits_in_selection()[1]):open() end, + [status_maps["OpenOrScrollDown"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.open_or_scroll_down(commit, self.files) + end + end, + [status_maps["OpenOrScrollUp"]] = function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.open_or_scroll_up(commit, self.files) + end + end, -- ["{"] = function() -- pcall(vim.cmd, "normal! zc") -- From 4baaa76c86b3f5d9f6cd171cf4922818e30b36ab Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 15:58:34 +0200 Subject: [PATCH 283/443] Standardize UI closing --- lua/neogit/buffers/log_view/init.lua | 10 +++------- lua/neogit/buffers/reflog_view/init.lua | 8 ++------ lua/neogit/buffers/refs_view/init.lua | 13 ++++++------- lua/neogit/buffers/status/init.lua | 16 +--------------- lua/neogit/lib/ui/helpers.lua | 23 +++++++++++++++++++++++ 5 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 lua/neogit/lib/ui/helpers.lua diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 8b9d208e4..54691a46f 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -136,13 +136,9 @@ function M:open() vim.cmd("echo ''") end end, - ["q"] = function() - self:close() - end, - [""] = function() - self:close() - end, - [""] = function() + [""] = require("neogit.lib.ui.helpers").close_topmost(self), + [status_maps["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), + [status_maps["GoToFile"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() if commit then CommitViewBuffer.new(commit, self.files):open() diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 0b7a95959..f125c35c9 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -128,12 +128,8 @@ function M:open(_) vim.cmd("echo ''") end end, - [""] = function() - self:close() - end, - [status_maps["Close"]] = function() - self:close() - end, + [""] = require("neogit.lib.ui.helpers").close_topmost(self), + [status_maps["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), [status_maps["GoToFile"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() if commit then diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 0c63fc7c6..03fb25779 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -136,14 +136,13 @@ function M:open() item = { name = item }, } end), - [status_maps["Close"]] = function() - self:close() - end, - [""] = function() - self:close() - end, + [""] = require("neogit.lib.ui.helpers").close_topmost(self), + [status_maps["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), [status_maps["GoToFile"]] = function() - CommitViewBuffer.new(self.buffer.ui:get_commits_in_selection()[1]):open() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + CommitViewBuffer.new(commit):open() + end end, [status_maps["OpenOrScrollDown"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 12bb27fa6..81db0a470 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -388,21 +388,7 @@ function M:open(kind) end end end, - [mappings["Close"]] = function() - local commit_view = require("neogit.buffers.commit_view") - local popup = require("neogit.lib.popup") - local history = require("neogit.buffers.git_command_history") - - if popup.is_open() then - popup.instance:close() - elseif commit_view.is_open() then - commit_view.instance:close() - elseif history.is_open() then - history.instance:close() - else - self:close() - end - end, + [mappings["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), [mappings["OpenOrScrollDown"]] = function() local commit = self.buffer.ui:get_commit_under_cursor() if commit then diff --git a/lua/neogit/lib/ui/helpers.lua b/lua/neogit/lib/ui/helpers.lua new file mode 100644 index 000000000..84b000621 --- /dev/null +++ b/lua/neogit/lib/ui/helpers.lua @@ -0,0 +1,23 @@ +local M = {} + +---Closable must implement self:close() method +---@return function +function M.close_topmost(closable) + return function() + local commit_view = require("neogit.buffers.commit_view") + local popup = require("neogit.lib.popup") + local history = require("neogit.buffers.git_command_history") + + if popup.is_open() then + popup.instance:close() + elseif commit_view.is_open() then + commit_view.instance:close() + elseif history.is_open() then + history.instance:close() + else + closable:close() + end + end +end + +return M From d3d98172b5d49f9120c5ab113f0d4854c4c766ea Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 16:01:36 +0200 Subject: [PATCH 284/443] Better control of buffer instances --- .../buffers/commit_select_view/init.lua | 20 +++++++++++++++++-- lua/neogit/buffers/commit_view/init.lua | 2 ++ lua/neogit/buffers/git_command_history.lua | 3 +++ lua/neogit/buffers/log_view/init.lua | 20 +++++++++++++++++-- lua/neogit/buffers/reflog_view/init.lua | 20 +++++++++++++++++-- lua/neogit/buffers/refs_view/init.lua | 3 +++ lua/neogit/buffers/status/init.lua | 17 +++++++++------- 7 files changed, 72 insertions(+), 13 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 89b880aba..9b51984ab 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -28,12 +28,28 @@ function M.new(commits, header) end function M:close() - self.buffer:close() - self.buffer = nil + if self.buffer then + self.buffer:close() + self.buffer = nil + end + + M.instance = nil +end + +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end ---@param action fun(commit: CommitLogEntry[]) function M:open(action) + if M.is_open() then + M.instance.buffer:focus() + return + end + + M.instance = self + ---@type fun(commit: CommitLogEntry[])|nil local action = action diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 9f49b1760..12c4647b3 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -74,6 +74,8 @@ function M:close() self.buffer:close() self.buffer = nil end + + M.instance = nil end ---Opens the CommitViewBuffer if it isn't open or performs the given action diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index 547aa4d64..356fd74f1 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -32,6 +32,8 @@ function M:close() self.buffer:close() self.buffer = nil end + + M.instance = nil end ---@return boolean @@ -41,6 +43,7 @@ end function M:show() if M.is_open() then + M.instance.buffer:focus() return end diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 54691a46f..bc634c08e 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -31,11 +31,27 @@ function M.new(commits, internal_args, files) end function M:close() - self.buffer:close() - self.buffer = nil + if self.buffer then + self.buffer:close() + self.buffer = nil + end + + M.instance = nil +end + +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end function M:open() + if M.is_open() then + M.instance.buffer:focus() + return + end + + M.instance = self + self.buffer = Buffer.create { name = "NeogitLogView", filetype = "NeogitLogView", diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index f125c35c9..1530e503f 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -24,11 +24,27 @@ function M.new(entries) end function M:close() - self.buffer:close() - self.buffer = nil + if self.buffer then + self.buffer:close() + self.buffer = nil + end + + M.instance = nil +end + +---@return boolean +function M.is_open() + return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true end function M:open(_) + if M.is_open() then + M.instance.buffer:focus() + return + end + + M.instance = self + self.buffer = Buffer.create { name = "NeogitReflogView", filetype = "NeogitReflogView", diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 03fb25779..0947e258b 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -34,6 +34,8 @@ function M:close() self.buffer:close() self.buffer = nil end + + M.instance = nil end ---@return boolean @@ -44,6 +46,7 @@ end --- Opens the RefsViewBuffer function M:open() if M.is_open() then + M.instance.buffer:focus() return end diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 81db0a470..2f4a2011d 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1125,19 +1125,22 @@ function M:open(kind) end function M:close() - logger.debug("[STATUS] Closing Buffer") - if not self.buffer then - return + if self.buffer then + logger.debug("[STATUS] Closing Buffer") + self.buffer:close() + self.buffer = nil end - vim.o.autochdir = self.prev_autochdir - if self.watcher then + logger.debug("[STATUS] Stopping Watcher") self.watcher:stop() end - self.buffer:close() - self.buffer = nil + if self.prev_autochdir then + vim.o.autochdir = self.prev_autochdir + end + + M.instance = nil end function M:chdir(dir) From 4ede974a61f9aa13bf32150d1efc4e14d1f4fd88 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 22:17:56 +0200 Subject: [PATCH 285/443] Guard calls to status.instance as it may be nil --- lua/neogit/autocmds.lua | 10 ++++++---- lua/neogit/integrations/diffview.lua | 5 ++++- lua/neogit/lib/git/init.lua | 8 ++++++-- lua/neogit/popups/help/actions.lua | 5 ++++- lua/neogit/popups/worktree/actions.lua | 14 +++++++++----- lua/neogit/watcher.lua | 4 +++- tests/util/git_harness.lua | 9 ++++++--- 7 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lua/neogit/autocmds.lua b/lua/neogit/autocmds.lua index 5cd89b0f1..b0e834c25 100644 --- a/lua/neogit/autocmds.lua +++ b/lua/neogit/autocmds.lua @@ -29,10 +29,12 @@ function M.setup() return end - status_buffer.instance:dispatch_refresh( - { update_diffs = { "*:" .. path } }, - string.format("%s:%s", o.event, o.file) - ) + if status_buffer.is_open() then + status_buffer.instance:dispatch_refresh( + { update_diffs = { "*:" .. path } }, + string.format("%s:%s", o.event, o.file) + ) + end end, function() end) end, group = group, diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index ec148b449..3ce9fef2f 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -101,7 +101,10 @@ local function get_local_diff_view(section_name, item_name, opts) } view:on_files_staged(a.void(function(_) - status.instance:dispatch_refresh({ update_diffs = true }, "on_files_staged") + if status.is_open() then + status.instance:dispatch_refresh({ update_diffs = true }, "on_files_staged") + end + view:update_files() end)) diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 0445c2157..e3e9e8712 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -29,7 +29,9 @@ M.init_repo = function() return end local status = require("neogit.buffers.status") - status.instance:chdir(directory) + if status.is_open() then + status.instance:chdir(directory) + end if cli.is_inside_worktree() then if not input.get_permission(("Reinitialize existing repository %s?"):format(directory)) then @@ -38,7 +40,9 @@ M.init_repo = function() end M.create(directory) - status.instance:dispatch_refresh(nil, "InitRepo") + if status.is_open() then + status.instance:dispatch_refresh(nil, "InitRepo") + end end return M diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 77ffa4f52..f49a7d89c 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -124,7 +124,10 @@ M.essential = function() "RefreshBuffer", "Refresh", function() - require("neogit.buffers.status").instance:dispatch_refresh(nil, "user_refresh") + local status = require("neogit.buffers.status") + if status.is_open() then + status.instance:dispatch_refresh(nil, "user_refresh") + end end, }, { "GoToFile", "Go to file", NONE }, diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index 120afe176..f96633ad5 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -50,7 +50,9 @@ M.checkout_worktree = operations("checkout_worktree", function() if git.worktree.add(selected, path) then notification.info("Added worktree") - status.instance:chdir(path) + if status.is_open() then + status.instance:chdir(path) + end end end) @@ -74,7 +76,9 @@ M.create_worktree = operations("create_worktree", function() if git.worktree.add(selected, path, { "-b", name }) then notification.info("Added worktree") - status.instance:chdir(path) + if status.is_open() then + status.instance:chdir(path) + end end end) @@ -103,7 +107,7 @@ M.move = operations("move_worktree", function() if git.worktree.move(selected, path) then notification.info(("Moved worktree to %s"):format(path)) - if change_dir then + if change_dir and status.is_open() then status.instance:chdir(path) end end @@ -128,7 +132,7 @@ M.delete = operations("delete_worktree", function() local success = false if input.get_permission("Remove worktree?") then - if change_dir then + if change_dir and status.is_open() then status.instance:chdir(git.worktree.main().path) end @@ -160,7 +164,7 @@ M.visit = operations("visit_worktree", function() end local selected = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "visit worktree" } - if selected then + if selected and status.is_open() then status.instance:chdir(selected) end end) diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index b20802839..947802842 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -44,7 +44,9 @@ function Watcher:fs_event_callback() end logger.debug(info) - status.instance:dispatch_refresh(nil, "watcher") + if status.is_open() then + status.instance:dispatch_refresh(nil, "watcher") + end end end diff --git a/tests/util/git_harness.lua b/tests/util/git_harness.lua index e7da40314..388328a43 100644 --- a/tests/util/git_harness.lua +++ b/tests/util/git_harness.lua @@ -54,13 +54,14 @@ end function M.in_prepared_repo(cb) return function() local dir = M.prepare_repository() - require("neogit").setup() + require("neogit").setup({}) + local status = require("neogit.buffers.status") vim.cmd("Neogit") a.util.block_on(neogit.reset) vim.wait(1000, function() - return not neogit.status.instance:_is_refresh_locked() + return not status.instance and status.instance:_is_refresh_locked() end, 100) a.util.block_on(function() @@ -70,7 +71,9 @@ function M.in_prepared_repo(cb) end a.util.block_on(function() - neogit.status.instance:close() + if status.instance then + status.instance:close() + end end) end) end From 377648ef89768622ee39056f74090b60b2a453d8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 22:18:08 +0200 Subject: [PATCH 286/443] is_open() checks M.instance already --- lua/neogit/buffers/status/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2f4a2011d..9c09fad55 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -61,7 +61,7 @@ function M.is_open() end function M:open(kind) - if M.instance and M.is_open() then + if M.is_open() then logger.debug("[STATUS] An Instance is already open - closing it") M.instance:close() end From f2c4316394bcc2009df67fa8020fe093dbc78db5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 23:01:34 +0200 Subject: [PATCH 287/443] Fix: Change how this checks for status instance - we can't call is_open() in lua callback --- lua/neogit/watcher.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 947802842..9d98eee22 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -44,7 +44,7 @@ function Watcher:fs_event_callback() end logger.debug(info) - if status.is_open() then + if status.instance then status.instance:dispatch_refresh(nil, "watcher") end end From 3701d9599ee9ef2c7460b0627e5f4154a7ac7224 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 23:15:33 +0200 Subject: [PATCH 288/443] Change how we get the length of abbreviated commits --- lua/neogit/lib/git/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 2bf560178..0d0460d72 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -521,7 +521,7 @@ function M.reflog_message(skip) end M.abbreviated_size = util.memoize(function() - return string.len(cli.log.format("%h").max_count(1).call({ hidden = true }).stdout[1]) + return string.len(M.list({ "HEAD", "--max-count=1" })[1].abbreviated_commit) end, { timeout = math.huge }) return M From 4fa379c01040f8a51cd34be29c7e038a34c9f0a5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 31 Mar 2024 23:15:45 +0200 Subject: [PATCH 289/443] Use status maps for commit close/toggle --- lua/neogit/buffers/commit_view/init.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 12c4647b3..976dee1a8 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -256,7 +256,10 @@ function M:open(kind) [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.commit_info.oid } } end), - ["q"] = function() + [status_maps["Close"]] = function() + self:close() + end, + [""] = function() self:close() end, [status_maps["YankSelected"]] = function() @@ -264,7 +267,7 @@ function M:open(kind) vim.cmd.let("@+=" .. yank) vim.cmd.echo(yank) end, - [""] = function() + [status_maps["Toggle"]] = function() pcall(vim.cmd, "normal! za") end, [""] = function() From 10d1fe02a8d183fda2cc1997cda28bfef3ebc10b Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Apr 2024 23:04:16 +0200 Subject: [PATCH 290/443] Fix: Do not early-return from fold-toggle if the section is closed. This fixes an issue where the "recent commits" section wouldn't open because the cursor was on the last line of the folded section, which is a blank line. Issue #1150 --- lua/neogit/buffers/status/init.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 9c09fad55..051017c3d 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -371,8 +371,12 @@ function M:open(kind) local fold = self.buffer.ui:get_fold_under_cursor() if fold then -- Do not allow folding on the last (empty) line of a section. It should be considered "not part of either - -- section" from a UX perspective. - if fold.options.tag == "Section" and self.buffer:get_current_line()[1] == "" then + -- section" from a UX perspective. Only applies to unfolded sections. + if fold.options.tag == "Section" + and not fold.options.folded + and self.buffer:get_current_line()[1] == "" + then + logger.info("Toggle early return") return end From 634b11b45deb73e101c25634898649aa8db27ede Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Apr 2024 23:32:03 +0200 Subject: [PATCH 291/443] Improve annotations for mappings manager --- lua/neogit/lib/mappings_manager.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/mappings_manager.lua b/lua/neogit/lib/mappings_manager.lua index 191880451..cec3d02dd 100644 --- a/lua/neogit/lib/mappings_manager.lua +++ b/lua/neogit/lib/mappings_manager.lua @@ -8,21 +8,31 @@ local managers = {} ---@class MappingsManager ---@field mappings table +---@field callbacks table +---@field id number +---@field register fun():nil local MappingsManager = {} +MappingsManager.__index = MappingsManager function MappingsManager.invoke(id, map_id) managers[id].callbacks[map_id]() end -function MappingsManager.build_call_string(id, k, mode) +---@param id number The id of the manager +---@param index number The index of the map +---@param mode string vim mode from vim.fn.mode() +---@return string +function MappingsManager.build_call_string(id, index, mode) return string.format( "lua require('neogit.lib.mappings_manager').invoke(%d, %d)%s", id, - k, + index, mode == "v" and "" or "" ) end +---@param id number The id of the manager +---@return nil function MappingsManager.delete(id) managers[id] = nil end From 9043ad7a188f171a4fb6099a2d9280f31bfb443c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 1 Apr 2024 23:32:38 +0200 Subject: [PATCH 292/443] Do not set buffer/window options if buffer/window handle is nil --- lua/neogit/lib/buffer.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index c5b896370..f093448b6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -378,15 +378,21 @@ function Buffer:unlock() end function Buffer:get_option(name) - return api.nvim_get_option_value(name, { buf = self.handle }) + if self.handle ~= nil then + return api.nvim_get_option_value(name, { buf = self.handle }) + end end function Buffer:set_buffer_option(name, value) - api.nvim_set_option_value(name, value, { buf = self.handle }) + if self.handle ~= nil then + api.nvim_set_option_value(name, value, { buf = self.handle }) + end end function Buffer:set_window_option(name, value) - api.nvim_set_option_value(name, value, { win = self.win_handle }) + if self.win_handle ~= nil then + api.nvim_set_option_value(name, value, { win = self.win_handle }) + end end function Buffer:set_name(name) From 9413e4eb97c51ae4ead3e3a5e26298dd666a8225 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 00:01:51 +0200 Subject: [PATCH 293/443] Use "on_detach" callback in buffer setup --- lua/neogit/buffers/commit_select_view/init.lua | 14 ++++++-------- lua/neogit/buffers/editor/init.lua | 17 +++++++---------- lua/neogit/buffers/rebase_editor/init.lua | 16 ++++++---------- lua/neogit/buffers/status/init.lua | 16 ++++++++-------- lua/neogit/lib/buffer.lua | 10 ++++++++++ 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index 9b51984ab..b48c55e62 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -106,14 +106,12 @@ function M:open(action) end, }, }, - autocmds = { - ["BufUnload"] = function() - self.buffer = nil - if action then - action {} - end - end, - }, + on_detach = function() + self.buffer = nil + if action then + action {} + end + end, render = function() return ui.View(self.commits) end, diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 77f634fa5..05852160e 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -72,18 +72,15 @@ function M:open(kind) modifiable = true, status_column = " ", readonly = false, - initialize = function(buffer) - vim.api.nvim_buf_attach(buffer.handle, false, { - on_detach = function() - pcall(vim.treesitter.stop, buffer.handle) + on_detach = function(buffer) + pcall(vim.treesitter.stop, buffer.handle) - if self.on_unload then - self.on_unload(aborted and 1 or 0) - end + if self.on_unload then + self.on_unload(aborted and 1 or 0) + end + + require("neogit.process").defer_show_preview_buffers() - require("neogit.process").defer_show_preview_buffers() - end, - }) end, after = function(buffer) -- Populate help lines with mappings for buffer diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 4d711e277..16c890fc3 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -79,18 +79,14 @@ function M:open(kind) modifiable = true, disable_line_numbers = config.values.disable_line_numbers, readonly = false, - initialize = function(buffer) - vim.api.nvim_buf_attach(buffer.handle, false, { - on_detach = function() - pcall(vim.treesitter.stop, buffer.handle) + on_detach = function(buffer) + pcall(vim.treesitter.stop, buffer.handle) - if self.on_unload then - self.on_unload(aborted and 1 or 0) - end + if self.on_unload then + self.on_unload(aborted and 1 or 0) + end - require("neogit.process").defer_show_preview_buffers() - end, - }) + require("neogit.process").defer_show_preview_buffers() end, after = function(buffer) local padding = util.max_length(util.flatten(vim.tbl_values(mapping))) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 051017c3d..ff1b63895 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -79,15 +79,15 @@ function M:open(kind) kind = kind, disable_line_numbers = config.values.disable_line_numbers, status_column = " ", - autocmds = { - ["BufUnload"] = function() - logger.debug("[STATUS] Running BufUnload autocmd") - if self.watcher then - self.watcher:stop() - end + on_detach = function() + logger.debug("[STATUS] Running on_detach") + if self.watcher then + self.watcher:stop() + end - vim.o.autochdir = self.prev_autochdir - end, + vim.o.autochdir = self.prev_autochdir + end, + autocmds = { ["BufEnter"] = function() self:dispatch_refresh() end, diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index f093448b6..67b3cd148 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -662,6 +662,16 @@ function Buffer.create(config) end) end + if config.on_detach then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up on_detach callback") + api.nvim_buf_attach(buffer.handle, false, { + on_detach = function() + logger.debug("[BUFFER:" .. buffer.handle .. "] Running on_detach") + config.on_detach(buffer) + end + }) + end + buffer:call(function() -- Set fold styling for Neogit windows while preserving user styling vim.opt_local.winhl:append("Folded:NeogitFold") From 2430e5b55e1ba96b12807544ed1ce8fa2ebd2536 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 00:17:27 +0200 Subject: [PATCH 294/443] Clean up buffer creation code, add logging, and do a little refactoring --- lua/neogit/lib/buffer.lua | 120 +++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 67b3cd148..b2609f69e 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -1,5 +1,6 @@ local api = vim.api local fn = vim.fn +local logger = require("neogit.logger") local mappings_manager = require("neogit.lib.mappings_manager") local signs = require("neogit.lib.signs") @@ -401,6 +402,9 @@ end function Buffer:replace_content_with(lines) api.nvim_buf_set_lines(self.handle, 0, -1, false, lines) + self:call(function() + vim.cmd("silent w!") + end) end function Buffer:open_fold(line, reset_pos) @@ -551,59 +555,91 @@ end ---@class BufferConfig ---@field name string ----@field load boolean +---@field kind string +---@field filetype string|nil ---@field bufhidden string|nil +---@field header string|nil ---@field buftype string|nil ----@field swapfile boolean ----@field filetype string|nil +---@field status_column string|nil +---@field load boolean|nil +---@field context_highlight boolean|nil +---@field open boolean|nil ---@field disable_line_numbers boolean|nil +---@field disable_signs boolean|nil +---@field swapfile boolean|nil +---@field modifiable boolean|nil +---@field readonly boolean|nil +---@field mappings table|nil +---@field autocmds table|nil +---@field initialize function|nil +---@field after function|nil +---@field on_detach function|nil +---@field render function|nil + +---@param config BufferConfig ---@return Buffer function Buffer.create(config) - config = config or {} - local kind = config.kind or "split" - local disable_line_numbers = (config.disable_line_numbers == nil) and true or config.disable_line_numbers - --- This reuses a buffer with the same name - local buffer = fn.bufnr(config.name) + assert(config, "Buffers work better if you configure them") - if buffer == -1 then - buffer = api.nvim_create_buf(false, false) - api.nvim_buf_set_name(buffer, config.name) - end + local buffer = Buffer.from_name(config.name) + + buffer.kind = config.kind or "split" + buffer.disable_line_numbers = (config.disable_line_numbers == nil) and true or config.disable_line_numbers if config.load then - local content = Path:new(config.name):readlines() - api.nvim_buf_set_lines(buffer, 0, -1, false, content) - api.nvim_buf_call(buffer, function() - vim.cmd("silent w!") - end) + logger.debug("[BUFFER:" .. buffer.handle .. "] Loading content from file: " .. config.name) + buffer:replace_content_with(Path:new(config.name):readlines()) end - local buffer = Buffer:new(buffer) - buffer.kind = kind - buffer.disable_line_numbers = disable_line_numbers - local win if config.open ~= false then + logger.debug("[BUFFER:" .. buffer.handle .. "] Showing buffer in window") win = buffer:show() end + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting buffer options") + buffer:set_buffer_option("swapfile", false) buffer:set_buffer_option("bufhidden", config.bufhidden or "wipe") buffer:set_buffer_option("buftype", config.buftype or "nofile") - buffer:set_buffer_option("swapfile", false) + buffer:set_buffer_option("modifiable", config.modifiable or false) + buffer:set_buffer_option("modified", config.modifiable or false) + buffer:set_buffer_option("readonly", config.readonly or false) + + if vim.fn.has("nvim-0.10") ~= 1 then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting foldtext function for nvim < 0.10") + -- selene: allow(global_usage) + _G.NeogitFoldText = function() + return vim.fn.getline(vim.v.foldstart) + end + + buffer:set_buffer_option("foldtext", "v:lua._G.NeogitFoldText()") + end if win then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting window options") + buffer:set_window_option("statuscolumn", config.status_column or "") buffer:set_window_option("foldenable", true) buffer:set_window_option("foldlevel", 99) buffer:set_window_option("foldminlines", 0) buffer:set_window_option("foldtext", "") + + if vim.fn.has("nvim-0.10") == 1 then + buffer:set_window_option("spell", false) + buffer:set_window_option("wrap", false) + buffer:set_window_option("foldmethod", "manual") + -- TODO: Need to find a way to turn this off properly when unloading plugin + -- buffer:set_window_option("winfixbuf", true) + end end if config.filetype then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting filetype: " .. config.filetype) buffer:set_filetype(config.filetype) end if config.mappings then + logger.debug("[BUFFER:" .. buffer.handle .. "] Building mappings table") for mode, val in pairs(config.mappings) do for key, cb in pairs(val) do if type(key) == "string" then @@ -622,41 +658,26 @@ function Buffer.create(config) end if config.initialize then + logger.debug("[BUFFER:" .. buffer.handle .. "] Initializing buffer") config.initialize(buffer, win) end if config.render then + logger.debug("[BUFFER:" .. buffer.handle .. "] Rendering buffer") buffer.ui:render(unpack(config.render(buffer))) end local neogit_augroup = require("neogit").autocmd_group for event, callback in pairs(config.autocmds or {}) do + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting autocmd: " .. event) api.nvim_create_autocmd(event, { callback = callback, buffer = buffer.handle, group = neogit_augroup }) end + logger.debug("[BUFFER:" .. buffer.handle .. "] Mappings Registered") buffer.mmanager.register() - buffer:set_buffer_option("modifiable", config.modifiable or false) - buffer:set_buffer_option("modified", config.modifiable or false) - buffer:set_buffer_option("readonly", config.readonly or false) - - if vim.fn.has("nvim-0.10") == 1 then - buffer:set_window_option("spell", false) - buffer:set_window_option("wrap", false) - buffer:set_window_option("foldmethod", "manual") - - -- TODO: Need to find a way to turn this off properly when unloading plugin - -- buffer:set_window_option("winfixbuf", true) - else - -- selene: allow(global_usage) - _G.NeogitFoldText = function() - return vim.fn.getline(vim.v.foldstart) - end - - buffer:set_buffer_option("foldtext", "v:lua._G.NeogitFoldText()") - end - if config.after then + logger.debug("[BUFFER:" .. buffer.handle .. "] Running config.after callback") buffer:call(function() config.after(buffer, win) end) @@ -673,6 +694,7 @@ function Buffer.create(config) end buffer:call(function() + logger.debug("[BUFFER:" .. buffer.handle .. "] Running buffer:call") -- Set fold styling for Neogit windows while preserving user styling vim.opt_local.winhl:append("Folded:NeogitFold") vim.opt_local.fillchars:append("fold: ") @@ -684,6 +706,7 @@ function Buffer.create(config) end) if config.context_highlight then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up context highlighting") buffer:create_namespace("ViewContext") buffer:set_decorations("ViewContext", { on_start = function() @@ -716,10 +739,23 @@ function Buffer.create(config) end if config.header then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting header") buffer:set_header(config.header) end return buffer end +---@param name string +---@return Buffer +function Buffer.from_name(name) + local buffer_handle = fn.bufnr(name) + if buffer_handle == -1 then + buffer_handle = api.nvim_create_buf(false, false) + api.nvim_buf_set_name(buffer_handle, name) + end + + return Buffer:new(buffer_handle) +end + return Buffer From c3d6429b0527697ea4915a218ab2441ed97dcbcc Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 11:48:12 +0200 Subject: [PATCH 295/443] remove trailing whitespace from readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c6d88768..b1ebf38d4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - + [![Lua](https://img.shields.io/badge/Lua-blue.svg?style=for-the-badge&logo=lua)](http://www.lua.org) [![Neovim](https://img.shields.io/badge/Neovim%200.9+-green.svg?style=for-the-badge&logo=neovim)](https://neovim.io) [![MIT](https://img.shields.io/badge/MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) @@ -55,7 +55,7 @@ neogit.setup {} The `master` branch will always be compatible with the latest **stable** release of Neovim, and with the latest **nightly** build as well. -Some features may only be available using unreleased (neovim nightly) API's - to use them, set your plugin manager to track the `nightly` branch instead. +Some features may only be available using unreleased (neovim nightly) API's - to use them, set your plugin manager to track the `nightly` branch instead. The `nightly` branch has the same stability guarantees as the `master` branch. @@ -88,7 +88,7 @@ neogit.setup { }, -- "ascii" is the graph the git CLI generates -- "unicode" is the graph like https://github.com/rbong/vim-flog - graph_style = "ascii", + graph_style = "ascii", -- Used to generate URL's for branch popup action "pull request". git_services = { ["github.com"] = "https://github.com/${owner}/${repository}/compare/${branch_name}?expand=1", From 186cf80be9d1730409f258cffe36d96677a8ba06 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 11:51:04 +0200 Subject: [PATCH 296/443] Update "OpenOrScrollUp/Down" mapping to be [c and ]c. User remappable, of course. Also now available on rebase buffer --- README.md | 6 ++++-- lua/neogit/buffers/rebase_editor/init.lua | 14 +++++++++++++- lua/neogit/config.lua | 10 +++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b1ebf38d4..0d53c118b 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,8 @@ neogit.setup { ["gj"] = "MoveDown", [""] = "Submit", [""] = "Abort", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", }, rebase_editor_I = { [""] = "Submit", @@ -324,8 +326,8 @@ neogit.setup { [""] = "TabOpen", ["{"] = "GoToPreviousHunkHeader", ["}"] = "GoToNextHunkHeader", - ["["] = "OpenOrScrollUp", - ["]"] = "OpenOrScrollDown", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", }, }, } diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 16c890fc3..8e933b32f 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -205,11 +205,23 @@ function M:open(kind) vim.cmd("move +1") end, [mapping["OpenCommit"]] = function() - local oid = vim.api.nvim_get_current_line():match("(%x%x%x%x%x%x%x)") + local oid = vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") if oid then CommitViewBuffer.new(oid):open("tab") end end, + [mapping["OpenOrScrollDown"]] = function() + local oid = vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") + if oid then + CommitViewBuffer.open_or_scroll_down(oid) + end + end, + [mapping["OpenOrScrollUp"]] = function() + local oid = vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") + if oid then + CommitViewBuffer.open_or_scroll_up(oid) + end + end, }, }, } diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index feb3c2c63..5a41fb926 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -180,6 +180,8 @@ end ---| "OpenCommit" ---| "Submit" ---| "Abort" +---| "OpenOrScrollUp" +---| "OpenOrScrollDown" ---| false ---| fun() @@ -428,6 +430,8 @@ function M.get_default_values() ["gj"] = "MoveDown", [""] = "Submit", [""] = "Abort", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", }, rebase_editor_I = { [""] = "Submit", @@ -485,14 +489,14 @@ function M.get_default_values() ["#"] = "Console", ["Y"] = "YankSelected", [""] = "RefreshBuffer", - [""] = "GoToFile", + [""] = "GoToFile", [""] = "VSplitOpen", [""] = "SplitOpen", [""] = "TabOpen", ["{"] = "GoToPreviousHunkHeader", ["}"] = "GoToNextHunkHeader", - ["["] = "OpenOrScrollUp", - ["]"] = "OpenOrScrollDown", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", }, }, } From 17159dc5044bea2d5dddd90998aa5c9324599816 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 11:52:10 +0200 Subject: [PATCH 297/443] Update documentation for key mappings --- README.md | 1 + doc/neogit.txt | 192 ++++++++++++++++++++++++++----------------------- 2 files changed, 102 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 0d53c118b..06288adc9 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ neogit.setup { ["X"] = "ResetPopup", ["Z"] = "StashPopup", ["b"] = "BranchPopup", + ["B"] = "BisectPopup", ["c"] = "CommitPopup", ["f"] = "FetchPopup", ["l"] = "LogPopup", diff --git a/doc/neogit.txt b/doc/neogit.txt index 627bab2c2..2a5d38620 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -104,97 +104,107 @@ to Neovim users. ============================================================================== 3. Mappings *neogit_mappings* - *neogit_status_maps* -Status Mappings - - *neogit_s* -s Stage file/hunk/visual-selection - - *neogit_S* -S Stage unstaged changes - - *neogit_* - Stage Everything - - *neogit_u* -u Unstage file/hunk/visual-selection - - *neogit_U* -U Unstage staged changes - - *neogit_x* -x Discard changes for file/hunk/visual-selection - - *neogit_* - Goto item at cursor - - *neogit_* - Toggle diff - - *neogit_{* -{ Goto previous hunk - - *neogit_}* -} Goto next hunk - - *neogit_* - Refresh Buffer - - *neogit_fold* -1, 2, 3, 4 Set a foldlevel - - - *neogit_popup_maps* -Popup maps ~ - - *neogit_$* -$ Command History - - *neogit_#* -# Console Output - - *neogit_L* -l Open log popup - - *neogit_p* -p Open pull popup - - *neogit_P* -P Open push popup - - *neogit_f* -f Open fetch popup - - *neogit_X* -X Open reset popup - - *neogit_A* -A Open cherry-pick popup - - *neogit_m* -m Open merge popup - - *neogit_v* -v Open revert popup - - *neogit_M* -M Open remotes popup - - *neogit_?* -? Open help popup - - *neogit_b* -b Open branch popup - - *neogit_c* -c Open commit popup - - *neogit_r* -r Open rebase popup - - *neogit_Z* -Z Open stash popup - +The following mappings can all be customized via the setup function. +>lua + commit_editor = { + ["q"] = "Close", + [""] = "PrevMessage", + [""] = "NextMessage", + [""] = "ResetMessage", + [""] = "Submit", + [""] = "Abort", + } + commit_editor_I = { + [""] = "Submit", + [""] = "Abort", + } + + rebase_editor = { + ["p"] = "Pick", + ["r"] = "Reword", + ["e"] = "Edit", + ["s"] = "Squash", + ["f"] = "Fixup", + ["x"] = "Execute", + ["d"] = "Drop", + ["b"] = "Break", + ["q"] = "Close", + [""] = "OpenCommit", + ["gk"] = "MoveUp", + ["gj"] = "MoveDown", + [""] = "Submit", + [""] = "Abort", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", + } + rebase_editor_I = { + [""] = "Submit", + [""] = "Abort", + } + + finder = { + [""] = "Select", + [""] = "Close", + [""] = "Close", + [""] = "Next", + [""] = "Previous", + [""] = "Next", + [""] = "Previous", + [""] = "MultiselectToggleNext", + [""] = "MultiselectTogglePrevious", + } + + popup = { + ["?"] = "HelpPopup", + ["A"] = "CherryPickPopup", + ["B"] = "BisectPopup", + ["b"] = "BranchPopup", + ["c"] = "CommitPopup", + ["d"] = "DiffPopup", + ["f"] = "FetchPopup", + ["i"] = "IgnorePopup", + ["l"] = "LogPopup", + ["m"] = "MergePopup", + ["M"] = "RemotePopup", + ["p"] = "PullPopup", + ["P"] = "PushPopup", + ["r"] = "RebasePopup", + ["t"] = "TagPopup", + ["v"] = "RevertPopup", + ["w"] = "WorktreePopup", + ["X"] = "ResetPopup", + ["Z"] = "StashPopup", + } + + status = { + ["q"] = "Close", + ["I"] = "InitRepo", + ["1"] = "Depth1", + ["2"] = "Depth2", + ["3"] = "Depth3", + ["4"] = "Depth4", + [""] = "Toggle", + ["x"] = "Discard", + ["s"] = "Stage", + ["S"] = "StageUnstaged", + [""] = "StageAll", + ["u"] = "Unstage", + ["U"] = "UnstageStaged", + ["y"] = "ShowRefs", + ["$"] = "CommandHistory", + ["#"] = "Console", + ["Y"] = "YankSelected", + [""] = "RefreshBuffer", + [""] = "GoToFile", + [""] = "VSplitOpen", + [""] = "SplitOpen", + [""] = "TabOpen", + ["{"] = "GoToPreviousHunkHeader", + ["}"] = "GoToNextHunkHeader", + ["[c"] = "OpenOrScrollUp", + ["]c"] = "OpenOrScrollDown", + } +< ============================================================================== 4. Highlights *neogit_highlights* From 3354d0240ce7a4ee117f46ab39dc818086ab2be1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 23:29:15 +0200 Subject: [PATCH 298/443] Cleanup: All calls to internal git lib should go through "neogit.lib.git"'s new dynamic module resolution. This fixes the circular import issue of linking to it directly from modules. It also sets up the possibility to use the cwd to get the "right" repo. --- lua/neogit/integrations/diffview.lua | 14 +++---- lua/neogit/lib/git.lua | 39 +++++-------------- lua/neogit/lib/git/bisect.lua | 10 ++--- lua/neogit/lib/git/branch.lua | 44 ++++++++++----------- lua/neogit/lib/git/cherry.lua | 4 +- lua/neogit/lib/git/cherry_pick.lua | 12 +++--- lua/neogit/lib/git/cli.lua | 6 +-- lua/neogit/lib/git/config.lua | 13 +++---- lua/neogit/lib/git/diff.lua | 16 ++++---- lua/neogit/lib/git/fetch.lua | 6 +-- lua/neogit/lib/git/files.lua | 12 +++--- lua/neogit/lib/git/index.lua | 22 +++++------ lua/neogit/lib/git/init.lua | 8 ++-- lua/neogit/lib/git/log.lua | 27 +++++++------ lua/neogit/lib/git/merge.lua | 12 +++--- lua/neogit/lib/git/pull.lua | 9 ++--- lua/neogit/lib/git/push.lua | 9 ++--- lua/neogit/lib/git/rebase.lua | 35 ++++++++--------- lua/neogit/lib/git/reflog.lua | 4 +- lua/neogit/lib/git/refs.lua | 9 ++--- lua/neogit/lib/git/remote.lua | 16 ++++---- lua/neogit/lib/git/repository.lua | 10 +++-- lua/neogit/lib/git/reset.lua | 14 +++---- lua/neogit/lib/git/rev_parse.lua | 6 +-- lua/neogit/lib/git/revert.lua | 10 ++--- lua/neogit/lib/git/stash.lua | 54 ++++++++++++-------------- lua/neogit/lib/git/status.lua | 12 +++--- lua/neogit/lib/git/tag.lua | 8 ++-- lua/neogit/lib/git/worktree.lua | 10 ++--- lua/neogit/lib/popup/builder.lua | 3 +- lua/neogit/popups/cherry_pick/init.lua | 3 +- lua/neogit/popups/ignore/init.lua | 3 +- lua/neogit/popups/revert/init.lua | 3 +- 33 files changed, 213 insertions(+), 250 deletions(-) diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 3ce9fef2f..9bdb1a8bb 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -9,7 +9,7 @@ local dv_lib = require("diffview.lib") local dv_utils = require("diffview.utils") local neogit = require("neogit") -local repo = require("neogit.lib.git.repository") +local git = require("neogit.lib.git") local status = require("neogit.buffers.status") local a = require("plenary.async") @@ -42,10 +42,10 @@ local function get_local_diff_view(section_name, item_name, opts) conflicting = { items = vim.tbl_filter(function(o) return o.mode and o.mode:sub(2, 2) == "U" - end, repo.untracked.items), + end, git.repo.untracked.items), }, - working = repo.unstaged, - staged = repo.staged, + working = git.repo.unstaged, + staged = git.repo.staged, } for kind, section in pairs(sections) do @@ -80,7 +80,7 @@ local function get_local_diff_view(section_name, item_name, opts) local files = update_files() local view = CDiffView { - git_root = repo.git_root, + git_root = git.repo.git_root, left = left, right = right, files = files, @@ -92,9 +92,9 @@ local function get_local_diff_view(section_name, item_name, opts) table.insert(args, "HEAD") end - return neogit.cli.show.file(unpack(args)).call_sync({ trim = false }).stdout + return git.cli.show.file(unpack(args)).call_sync({ trim = false }).stdout elseif kind == "working" then - local fdata = neogit.cli.show.file(path).call_sync({ trim = false }).stdout + local fdata = git.cli.show.file(path).call_sync({ trim = false }).stdout return side == "left" and fdata end end, diff --git a/lua/neogit/lib/git.lua b/lua/neogit/lib/git.lua index 5a857cc03..e1463c01d 100644 --- a/lua/neogit/lib/git.lua +++ b/lua/neogit/lib/git.lua @@ -1,30 +1,11 @@ -return { - repo = require("neogit.lib.git.repository"), - rev_parse = require("neogit.lib.git.rev_parse"), - cli = require("neogit.lib.git.cli"), - cherry = require("neogit.lib.git.cherry"), - init = require("neogit.lib.git.init"), - status = require("neogit.lib.git.status"), - stash = require("neogit.lib.git.stash"), - files = require("neogit.lib.git.files"), - fetch = require("neogit.lib.git.fetch"), - log = require("neogit.lib.git.log"), - refs = require("neogit.lib.git.refs"), - tag = require("neogit.lib.git.tag"), - reflog = require("neogit.lib.git.reflog"), - branch = require("neogit.lib.git.branch"), - diff = require("neogit.lib.git.diff"), - rebase = require("neogit.lib.git.rebase"), - merge = require("neogit.lib.git.merge"), - cherry_pick = require("neogit.lib.git.cherry_pick"), - reset = require("neogit.lib.git.reset"), - revert = require("neogit.lib.git.revert"), - remote = require("neogit.lib.git.remote"), - config = require("neogit.lib.git.config"), - sequencer = require("neogit.lib.git.sequencer"), - pull = require("neogit.lib.git.pull"), - push = require("neogit.lib.git.push"), - index = require("neogit.lib.git.index"), - worktree = require("neogit.lib.git.worktree"), - bisect = require("neogit.lib.git.bisect"), +local Git = { + __index = function(_, k) + if k == "repo" then + return require("neogit.lib.git.repository").instance() + else + return require("neogit.lib.git." .. k) + end + end, } + +return setmetatable({}, Git) diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index 74b41fa13..424151319 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local logger = require("neogit.logger") local M = {} @@ -9,7 +9,7 @@ end ---@param cmd string local function bisect(cmd) - local result = cli.bisect.args(cmd).call() + local result = git.cli.bisect.args(cmd).call() if result.code == 0 then fire_bisect_event { type = cmd } @@ -17,12 +17,10 @@ local function bisect(cmd) end function M.in_progress() - local git = require("neogit.lib.git") return git.repo:git_path("BISECT_LOG"):exists() end function M.is_finished() - local git = require("neogit.lib.git") return git.repo.bisect.finished end @@ -30,7 +28,7 @@ end ---@param good_revision string ---@param args? table function M.start(bad_revision, good_revision, args) - local result = cli.bisect.args("start").arg_list(args).args(bad_revision, good_revision).call() + local result = git.cli.bisect.args("start").arg_list(args).args(bad_revision, good_revision).call() if result.code == 0 then fire_bisect_event { type = "start" } @@ -55,7 +53,7 @@ end ---@param command string function M.run(command) - cli.bisect.args("run", command).call() + git.cli.bisect.args("run", command).call() end ---@class BisectItem diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index c2f62c838..3f3a66956 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -1,5 +1,4 @@ -local cli = require("neogit.lib.git.cli") -local config_lib = require("neogit.lib.git.config") +local git = require("neogit.lib.git") local config = require("neogit.config") local util = require("neogit.lib.util") @@ -41,7 +40,7 @@ function M.get_recent_local_branches() local valid_branches = M.get_local_branches() local branches = util.filter_map( - cli.reflog.show.format("%gs").date("relative").call_sync().stdout, + git.cli.reflog.show.format("%gs").date("relative").call_sync().stdout, function(ref) local name = ref:match("^checkout: moving from .* to (.*)$") if vim.tbl_contains(valid_branches, name) then @@ -54,41 +53,40 @@ function M.get_recent_local_branches() end function M.checkout(name, args) - cli.checkout.branch(name).arg_list(args or {}).call_sync() + git.cli.checkout.branch(name).arg_list(args or {}).call_sync() if config.values.fetch_after_checkout then - local fetch = require("neogit.lib.git.fetch") local pushRemote = M.pushRemote_ref(name) local upstream = M.upstream(name) if upstream and upstream == pushRemote then local remote, branch = M.parse_remote_branch(upstream) - fetch.fetch(remote, branch) + git.fetch.fetch(remote, branch) else if upstream then local remote, branch = M.parse_remote_branch(upstream) - fetch.fetch(remote, branch) + git.fetch.fetch(remote, branch) end if pushRemote then local remote, branch = M.parse_remote_branch(pushRemote) - fetch.fetch(remote, branch) + git.fetch.fetch(remote, branch) end end end end function M.track(name, args) - cli.checkout.track(name).arg_list(args or {}).call_sync() + git.cli.checkout.track(name).arg_list(args or {}).call_sync() end function M.get_local_branches(include_current) - local branches = cli.branch.list(config.values.sort_branches).call_sync().stdout + local branches = git.cli.branch.list(config.values.sort_branches).call_sync().stdout return parse_branches(branches, include_current) end function M.get_remote_branches(include_current) - local branches = cli.branch.remotes.list(config.values.sort_branches).call_sync().stdout + local branches = git.cli.branch.remotes.list(config.values.sort_branches).call_sync().stdout return parse_branches(branches, include_current) end @@ -97,11 +95,11 @@ function M.get_all_branches(include_current) end function M.is_unmerged(branch, base) - return cli.cherry.arg_list({ base or M.base_branch(), branch }).call_sync().stdout[1] ~= nil + return git.cli.cherry.arg_list({ base or M.base_branch(), branch }).call_sync().stdout[1] ~= nil end function M.base_branch() - local value = config_lib.get("neogit.baseBranch") + local value = git.config.get("neogit.baseBranch") if value:is_set() then return value:read() else @@ -117,7 +115,7 @@ end ---@param branch string ---@return boolean function M.exists(branch) - local result = cli["rev-parse"].verify.quiet + local result = git.cli["rev-parse"].verify.quiet .args(string.format("refs/heads/%s", branch)) .call_sync { hidden = true, ignore_error = true } @@ -140,7 +138,7 @@ end ---@param name string ---@param base_branch? string function M.create(name, base_branch) - cli.branch.args(name, base_branch).call() + git.cli.branch.args(name, base_branch).call() end function M.delete(name) @@ -150,10 +148,10 @@ function M.delete(name) if M.is_unmerged(name) then local message = ("'%s' contains unmerged commits! Are you sure you want to delete it?"):format(name) if input.get_permission(message) then - result = cli.branch.delete.force.name(name).call_sync() + result = git.cli.branch.delete.force.name(name).call_sync() end else - result = cli.branch.delete.name(name).call_sync() + result = git.cli.branch.delete.name(name).call_sync() end return result and result.code == 0 or false @@ -166,7 +164,7 @@ function M.current() if head and head ~= "(detached)" then return head else - local branch_name = cli.branch.current.call_sync().stdout + local branch_name = git.cli.branch.current.call_sync().stdout if #branch_name > 0 then return branch_name[1] end @@ -178,7 +176,7 @@ end function M.current_full_name() local current = M.current() if current then - return cli["rev-parse"].symbolic_full_name.args(current).call_sync().stdout[1] + return git.cli["rev-parse"].symbolic_full_name.args(current).call_sync().stdout[1] end end @@ -186,7 +184,7 @@ function M.pushRemote(branch) branch = branch or M.current() if branch then - local remote = config_lib.get("branch." .. branch .. ".pushRemote") + local remote = git.config.get("branch." .. branch .. ".pushRemote") if remote:is_set() then return remote.value end @@ -228,7 +226,7 @@ function M.set_pushRemote() end if pushRemote then - config_lib.set(string.format("branch.%s.pushRemote", M.current()), pushRemote) + git.config.set(string.format("branch.%s.pushRemote", M.current()), pushRemote) end return pushRemote @@ -240,7 +238,7 @@ end ---@return string|nil function M.upstream(name) if name then - local result = cli["rev-parse"].symbolic_full_name + local result = git.cli["rev-parse"].symbolic_full_name .abbrev_ref() .args(name .. "@{upstream}") .call { ignore_error = true } @@ -278,8 +276,6 @@ function M.upstream_remote() end local function update_branch_information(state) - local git = require("neogit.lib.git") - if state.head.oid ~= "(initial)" then state.head.commit_message = git.log.message(state.head.oid) diff --git a/lua/neogit/lib/git/cherry.lua b/lua/neogit/lib/git/cherry.lua index ea59e8aa2..60c39b5d2 100644 --- a/lua/neogit/lib/git/cherry.lua +++ b/lua/neogit/lib/git/cherry.lua @@ -1,9 +1,9 @@ local M = {} -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") function M.list(upstream, head) - local result = cli.cherry.verbose.args(upstream, head).call().stdout + local result = git.cli.cherry.verbose.args(upstream, head).call().stdout return util.reverse(util.map(result, function(cherry) local status, oid, subject = cherry:match("([%+%-]) (%x+) (.*)") return { status = status, oid = oid, subject = subject } diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index 17793c881..bbf782cb4 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") @@ -9,7 +9,7 @@ local function fire_cherrypick_event(data) end function M.pick(commits, args) - local result = cli["cherry-pick"].arg_list(util.merge(args, commits)).call() + local result = git.cli["cherry-pick"].arg_list(util.merge(args, commits)).call() if result.code ~= 0 then notification.error("Cherry Pick failed. Resolve conflicts before continuing") else @@ -24,7 +24,7 @@ function M.apply(commits, args) end end) - local result = cli["cherry-pick"].no_commit.arg_list(util.merge(args, commits)).call() + local result = git.cli["cherry-pick"].no_commit.arg_list(util.merge(args, commits)).call() if result.code ~= 0 then notification.error("Cherry Pick failed. Resolve conflicts before continuing") else @@ -33,15 +33,15 @@ function M.apply(commits, args) end function M.continue() - cli["cherry-pick"].continue.call_sync() + git.cli["cherry-pick"].continue.call_sync() end function M.skip() - cli["cherry-pick"].skip.call_sync() + git.cli["cherry-pick"].skip.call_sync() end function M.abort() - cli["cherry-pick"].abort.call_sync() + git.cli["cherry-pick"].abort.call_sync() end return M diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 01036073b..d0ba0b9f0 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -1,4 +1,5 @@ local logger = require("neogit.logger") +local git = require("neogit.lib.git") local process = require("neogit.process") local util = require("neogit.lib.util") local Path = require("plenary.path") @@ -583,7 +584,7 @@ local configurations = { ["bisect"] = config {}, } --- NOTE: Use require("neogit.lib.git.repository").git_root instead of calling this function. +-- NOTE: Use require("neogit.lib.git").repo.git_root instead of calling this function. -- repository.git_root is used by all other library functions, so it's most likely the one you want to use. -- git_root_of_cwd() returns the git repo of the cwd, which can change anytime -- after git_root_of_cwd() has been called. @@ -895,10 +896,9 @@ local function new_builder(subcommand) logger.trace(string.format("[CLI]: Executing '%s': '%s'", subcommand, table.concat(cmd, " "))) - local repo = require("neogit.lib.git.repository") return process.new { cmd = cmd, - cwd = repo.git_root, + cwd = git.repo.git_root, env = state.env, pty = state.in_pty, verbose = opts.verbose, diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index 46915c16c..01d0ec448 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local logger = require("neogit.logger") local M = {} @@ -72,8 +72,7 @@ local config_cache = {} local cache_key = nil local function make_cache_key() - local repo = require("neogit.lib.git.repository") - local stat = vim.loop.fs_stat(repo.git_root .. "/.git/config") + local stat = vim.loop.fs_stat(git.repo.git_root .. "/.git/config") if stat then return stat.mtime.sec end @@ -83,7 +82,7 @@ local function build_config() local result = {} local out = - vim.split(table.concat(cli.config.list.null._local.call_sync({ hidden = true }).stdout_raw, "\0"), "\n") + vim.split(table.concat(git.cli.config.list.null._local.call_sync({ hidden = true }).stdout_raw, "\0"), "\n") for _, option in ipairs(out) do local key, value = unpack(vim.split(option, "\0")) @@ -112,7 +111,7 @@ end ---@return ConfigEntry function M.get_global(key) - local result = cli.config.global.get(key).call_sync({ ignore_error = true }).stdout[1] + local result = git.cli.config.global.get(key).call_sync({ ignore_error = true }).stdout[1] return ConfigEntry.new(key, result, "global") end @@ -133,7 +132,7 @@ function M.set(key, value) if not value or value == "" then M.unset(key) else - cli.config.set(key, value).call_sync() + git.cli.config.set(key, value).call_sync() end end @@ -144,7 +143,7 @@ function M.unset(key) end cache_key = nil - cli.config.unset(key).call_sync() + git.cli.config.unset(key).call_sync() end return M diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 650638a1b..62c81238b 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -1,7 +1,7 @@ local a = require("plenary.async") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local logger = require("neogit.logger") -local cli = require("neogit.lib.git.cli") local ItemFilter = require("neogit.lib.item_filter") @@ -211,7 +211,7 @@ end -- Doing a git-diff with untracked files will exit(1) if a difference is observed, which we can ignore. local function raw_untracked(name) return function() - local diff = cli.diff.no_ext_diff.no_index + local diff = git.cli.diff.no_ext_diff.no_index .files("/dev/null", name) .call({ hidden = true, ignore_error = true }).stdout local stats = {} @@ -222,8 +222,8 @@ end local function raw_unstaged(name) return function() - local diff = cli.diff.no_ext_diff.files(name).call({ hidden = true }).stdout - local stats = cli.diff.no_ext_diff.shortstat.files(name).call({ hidden = true }).stdout + local diff = git.cli.diff.no_ext_diff.files(name).call({ hidden = true }).stdout + local stats = git.cli.diff.no_ext_diff.shortstat.files(name).call({ hidden = true }).stdout return { diff, stats } end @@ -231,8 +231,8 @@ end local function raw_staged(name) return function() - local diff = cli.diff.no_ext_diff.cached.files(name).call({ hidden = true }).stdout - local stats = cli.diff.no_ext_diff.cached.shortstat.files(name).call({ hidden = true }).stdout + local diff = git.cli.diff.no_ext_diff.cached.files(name).call({ hidden = true }).stdout + local stats = git.cli.diff.no_ext_diff.cached.shortstat.files(name).call({ hidden = true }).stdout return { diff, stats } end @@ -240,8 +240,8 @@ end local function raw_staged_renamed(name, original) return function() - local diff = cli.diff.no_ext_diff.cached.files(name, original).call({ hidden = true }).stdout - local stats = cli.diff.no_ext_diff.cached.shortstat.files(name, original).call({ hidden = true }).stdout + local diff = git.cli.diff.no_ext_diff.cached.files(name, original).call({ hidden = true }).stdout + local stats = git.cli.diff.no_ext_diff.cached.shortstat.files(name, original).call({ hidden = true }).stdout return { diff, stats } end diff --git a/lua/neogit/lib/git/fetch.lua b/lua/neogit/lib/git/fetch.lua index 997444332..0503585e2 100644 --- a/lua/neogit/lib/git/fetch.lua +++ b/lua/neogit/lib/git/fetch.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local M = {} @@ -8,11 +8,11 @@ local M = {} ---@param args string[] ---@return ProcessResult function M.fetch_interactive(remote, branch, args) - return cli.fetch.args(remote or "", branch or "").arg_list(args).call_interactive() + return git.cli.fetch.args(remote or "", branch or "").arg_list(args).call_interactive() end function M.fetch(remote, branch) - cli.fetch.args(remote, branch).call { ignore_error = true } + git.cli.fetch.args(remote, branch).call { ignore_error = true } end return M diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index d6756f71f..ae3e9f75a 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -1,25 +1,25 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local M = {} function M.all() - return cli["ls-files"].full_name.deleted.modified.exclude_standard.deduplicate.call_sync({ hidden = true }).stdout + return git.cli["ls-files"].full_name.deleted.modified.exclude_standard.deduplicate.call_sync({ hidden = true }).stdout end function M.untracked() - return cli["ls-files"].others.exclude_standard.call_sync({ hidden = true }).stdout + return git.cli["ls-files"].others.exclude_standard.call_sync({ hidden = true }).stdout end function M.all_tree() - return cli["ls-tree"].full_tree.name_only.recursive.args("HEAD").call_sync({ hidden = true }).stdout + return git.cli["ls-tree"].full_tree.name_only.recursive.args("HEAD").call_sync({ hidden = true }).stdout end function M.diff(commit) - return cli.diff.name_only.args(commit .. "...").call_sync({ hidden = true }).stdout + return git.cli.diff.name_only.args(commit .. "...").call_sync({ hidden = true }).stdout end function M.relpath_from_repository(path) - local result = cli["ls-files"].others.cached.modified.deleted.full_name + local result = git.cli["ls-files"].others.cached.modified.deleted.full_name .args(path) .show_popup(false) .call { hidden = true } diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index ccc10cdac..d1adb3699 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -1,5 +1,4 @@ -local cli = require("neogit.lib.git.cli") -local repo = require("neogit.lib.git.repository") +local git = require("neogit.lib.git") local Path = require("plenary.path") local util = require("neogit.lib.util") @@ -67,7 +66,7 @@ function M.generate_patch(item, hunk, from, to, reverse) string.format("@@ -%d,%d +%d,%d @@", hunk.index_from, len_start, hunk.index_from, len_start + len_offset) ) - local git_root = repo.git_root + local git_root = git.repo.git_root assert(item.absolute_path, "Item is not a path") local path = Path:new(item.absolute_path):make_relative(git_root) @@ -85,7 +84,7 @@ end function M.apply(patch, opts) opts = opts or { reverse = false, cached = false, index = false } - local cmd = cli.apply + local cmd = git.cli.apply if opts.reverse then cmd = cmd.reverse @@ -103,28 +102,27 @@ function M.apply(patch, opts) end function M.add(files) - return cli.add.files(unpack(files)).call() + return git.cli.add.files(unpack(files)).call() end function M.checkout(files) - return cli.checkout.files(unpack(files)).call() + return git.cli.checkout.files(unpack(files)).call() end function M.reset(files) - return cli.reset.files(unpack(files)).call() + return git.cli.reset.files(unpack(files)).call() end function M.reset_HEAD(...) - return cli.reset.args("HEAD").arg_list({ ... }).call() + return git.cli.reset.args("HEAD").arg_list({ ... }).call() end function M.checkout_unstaged() - local repo = require("neogit.lib.git.repository") - local items = util.map(repo.unstaged.items, function(item) + local items = util.map(git.repo.unstaged.items, function(item) return item.escaped_path end) - return cli.checkout.files(unpack(items)).call() + return git.cli.checkout.files(unpack(items)).call() end ---Creates a temp index from a revision and calls the provided function with the index path @@ -135,7 +133,7 @@ function M.with_temp_index(revision, fn) assert(fn, "Pass a function to call with temp index") local tmp_index = Path:new(vim.uv.os_tmpdir(), ("index.neogit.%s"):format(revision)) - cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call { hidden = true } + git.cli["read-tree"].args(revision).index_output(tmp_index:absolute()).call { hidden = true } assert(tmp_index:exists(), "Failed to create temp index") fn(tmp_index:absolute()) diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index e3e9e8712..84585d733 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") local input = require("neogit.lib.input") @@ -8,9 +8,9 @@ M.create = function(directory, sync) sync = sync or false if sync then - cli.init.args(directory).call_sync() + git.cli.init.args(directory).call_sync() else - cli.init.args(directory).call() + git.cli.init.args(directory).call() end end @@ -33,7 +33,7 @@ M.init_repo = function() status.instance:chdir(directory) end - if cli.is_inside_worktree() then + if git.cli.is_inside_worktree() then if not input.get_permission(("Reinitialize existing repository %s?"):format(directory)) then return end diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 0d0460d72..06f4ed10b 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -1,5 +1,4 @@ -local cli = require("neogit.lib.git.cli") -local diff_lib = require("neogit.lib.git.diff") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local config = require("neogit.config") local record = require("neogit.lib.record") @@ -134,7 +133,7 @@ function M.parse(raw) if not line or vim.startswith(line, "diff") then -- There was a previous diff, parse it if in_diff then - table.insert(commit.diffs, diff_lib.parse(current_diff)) + table.insert(commit.diffs, git.diff.parse(current_diff)) current_diff = {} end @@ -142,7 +141,7 @@ function M.parse(raw) elseif line == "" then -- A blank line signifies end of diffs -- Parse the last diff, consume the blankline, and exit if in_diff then - table.insert(commit.diffs, diff_lib.parse(current_diff)) + table.insert(commit.diffs, git.diff.parse(current_diff)) current_diff = {} end @@ -301,7 +300,7 @@ M.graph = util.memoize(function(options, files, color) options = ensure_max(options or {}) files = files or {} - local result = cli.log + local result = git.cli.log .format("%x1E%H%x00").graph.color .arg_list(options) .files(unpack(files)) @@ -359,7 +358,7 @@ M.list = util.memoize(function(options, graph, files, hidden, graph_color) options = determine_order(options, graph) options, signature = show_signature(options) - local output = cli.log + local output = git.cli.log .format(format(signature)) .args("--no-patch") .arg_list(options) @@ -392,7 +391,7 @@ end) ---@param descendant string commit hash ---@return boolean function M.is_ancestor(ancestor, descendant) - return cli["merge-base"].is_ancestor + return git.cli["merge-base"].is_ancestor .args(ancestor, descendant) .call_sync({ ignore_error = true, hidden = true }).code == 0 end @@ -401,7 +400,7 @@ end ---@param commit string ---@return string|nil function M.parent(commit) - return vim.split(cli["rev-list"].max_count(1).parents.args(commit).call({ hidden = true }).stdout[1], " ")[2] + return vim.split(git.cli["rev-list"].max_count(1).parents.args(commit).call({ hidden = true }).stdout[1], " ")[2] end local function update_recent(state) @@ -419,15 +418,15 @@ function M.register(meta) end function M.update_ref(from, to) - cli["update-ref"].message(string.format("reset: moving to %s", to)).args(from, to).call() + git.cli["update-ref"].message(string.format("reset: moving to %s", to)).args(from, to).call() end function M.message(commit) - return cli.log.max_count(1).format("%s").args(commit).call({ hidden = true }).stdout[1] + return git.cli.log.max_count(1).format("%s").args(commit).call({ hidden = true }).stdout[1] end function M.full_message(commit) - return cli.log.max_count(1).format("%B").args(commit).call({ hidden = true }).stdout + return git.cli.log.max_count(1).format("%B").args(commit).call({ hidden = true }).stdout end function M.present_commit(commit) @@ -446,7 +445,7 @@ end ---@param commit string Hash of commit ---@return string The stderr output of the command function M.verify_commit(commit) - return cli["verify-commit"].args(commit).call_sync({ ignore_error = true }).stderr + return git.cli["verify-commit"].args(commit).call_sync({ ignore_error = true }).stderr end ---@class CommitBranchInfo @@ -459,7 +458,7 @@ end ---@param ref string comma separated list of branches, tags and remotes, e.g.: --- * "origin/main, main, origin/HEAD, tag: 1.2.3, fork/develop" --- * "HEAD -> main, origin/main, origin/HEAD, tag: 1.2.3, fork/develop" ----@param remotes string[] list of remote names, e.g. by calling `require("neogit.lib.git.remote").list()` +---@param remotes string[] list of remote names, e.g. by calling `require("neogit.lib.git").remote.list()` ---@return CommitBranchInfo M.branch_info = util.memoize(function(ref, remotes) local parts = vim.split(ref, ", ") @@ -513,7 +512,7 @@ M.branch_info = util.memoize(function(ref, remotes) end) function M.reflog_message(skip) - return cli.log + return git.cli.log .format("%B") .max_count(1) .args("--reflog", "--no-merges", "--skip=" .. tostring(skip)) diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index 687dce067..da49c7331 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -1,7 +1,6 @@ local client = require("neogit.client") +local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") -local cli = require("neogit.lib.git.cli") -local branch_lib = require("neogit.lib.git.branch") local M = {} @@ -18,26 +17,25 @@ end function M.merge(branch, args) a.util.scheduler() - local result = merge_command(cli.merge.args(branch).arg_list(args)) + local result = merge_command(git.cli.merge.args(branch).arg_list(args)) if result.code ~= 0 then notification.error("Merging failed. Resolve conflicts before continuing") fire_merge_event { branch = branch, args = args, status = "conflict" } else - notification.info("Merged '" .. branch .. "' into '" .. branch_lib.current() .. "'") + notification.info("Merged '" .. branch .. "' into '" .. git.branch.current() .. "'") fire_merge_event { branch = branch, args = args, status = "ok" } end end function M.continue() - return merge_command(cli.merge.continue) + return merge_command(git.cli.merge.continue) end function M.abort() - return merge_command(cli.merge.abort) + return merge_command(git.cli.merge.abort) end function M.update_merge_status(state) - local git = require("neogit.lib.git") if git.repo.git_root == "" then return end diff --git a/lua/neogit/lib/git/pull.lua b/lua/neogit/lib/git/pull.lua index d21028011..9b8108c03 100644 --- a/lua/neogit/lib/git/pull.lua +++ b/lua/neogit/lib/git/pull.lua @@ -1,5 +1,4 @@ -local cli = require("neogit.lib.git.cli") -local log = require("neogit.lib.git.log") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local M = {} @@ -7,7 +6,7 @@ local M = {} function M.pull_interactive(remote, branch, args) local client = require("neogit.client") local envs = client.get_envs_git_editor() - return cli.pull.env(envs).args(remote or "", branch or "").arg_list(args).call_interactive() + return git.cli.pull.env(envs).args(remote or "", branch or "").arg_list(args).call_interactive() end local function update_unpulled(state) @@ -20,13 +19,13 @@ local function update_unpulled(state) if state.upstream.ref then state.upstream.unpulled.items = - util.filter_map(log.list({ "..@{upstream}" }, nil, {}, true), log.present_commit) + util.filter_map(git.log.list({ "..@{upstream}" }, nil, {}, true), git.log.present_commit) end local pushRemote = require("neogit.lib.git").branch.pushRemote_ref() if pushRemote then state.pushRemote.unpulled.items = - util.filter_map(log.list({ string.format("..%s", pushRemote) }, nil, {}, true), log.present_commit) + util.filter_map(git.log.list({ string.format("..%s", pushRemote) }, nil, {}, true), git.log.present_commit) end end diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index f76fa8fd7..a33b639cf 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -1,5 +1,4 @@ -local cli = require("neogit.lib.git.cli") -local log = require("neogit.lib.git.log") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local M = {} @@ -10,7 +9,7 @@ local M = {} ---@param args string[] ---@return ProcessResult function M.push_interactive(remote, branch, args) - return cli.push.args(remote or "", branch or "").arg_list(args).call_interactive() + return git.cli.push.args(remote or "", branch or "").arg_list(args).call_interactive() end local function update_unmerged(state) @@ -23,13 +22,13 @@ local function update_unmerged(state) if state.upstream.ref then state.upstream.unmerged.items = - util.filter_map(log.list({ "@{upstream}.." }, nil, {}, true), log.present_commit) + util.filter_map(git.log.list({ "@{upstream}.." }, nil, {}, true), git.log.present_commit) end local pushRemote = require("neogit.lib.git").branch.pushRemote_ref() if pushRemote then state.pushRemote.unmerged.items = - util.filter_map(log.list({ pushRemote .. ".." }, nil, {}, true), log.present_commit) + util.filter_map(git.log.list({ pushRemote .. ".." }, nil, {}, true), git.log.present_commit) end end diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 6f1431f13..173c2b6b4 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -1,9 +1,7 @@ local logger = require("neogit.logger") +local git = require("neogit.lib.git") local client = require("neogit.client") local notification = require("neogit.lib.notification") -local cli = require("neogit.lib.git.cli") -local rev_parse = require("neogit.lib.git.rev_parse") -local log = require("neogit.lib.git.log") local M = {} @@ -23,7 +21,7 @@ end ---@param args? string[] list of arguments to pass to git rebase ---@return ProcessResult function M.instantly(commit, args) - local result = cli.rebase + local result = git.cli.rebase .env({ GIT_SEQUENCE_EDITOR = ":" }).interactive.autostash.autosquash .arg_list(args or {}) .commit(commit) @@ -43,7 +41,7 @@ function M.rebase_interactive(commit, args) commit = "" end - local result = rebase_command(cli.rebase.interactive.arg_list(args).args(commit)) + local result = rebase_command(git.cli.rebase.interactive.arg_list(args).args(commit)) if result.code ~= 0 then if result.stdout[1]:match("^hint: Waiting for your editor to close the file%.%.%. error") then notification.info("Rebase aborted") @@ -59,7 +57,7 @@ function M.rebase_interactive(commit, args) end function M.onto_branch(branch, args) - local result = rebase_command(cli.rebase.args(branch).arg_list(args)) + local result = rebase_command(git.cli.rebase.args(branch).arg_list(args)) if result.code ~= 0 then notification.error("Rebasing failed. Resolve conflicts before continuing") fire_rebase_event("conflict") @@ -70,7 +68,7 @@ function M.onto_branch(branch, args) end function M.onto(start, newbase, args) - local result = rebase_command(cli.rebase.onto.args(newbase, start).arg_list(args)) + local result = rebase_command(git.cli.rebase.onto.args(newbase, start).arg_list(args)) if result.code ~= 0 then notification.error("Rebasing failed. Resolve conflicts before continuing") fire_rebase_event("conflict") @@ -83,9 +81,9 @@ end ---@param commit string rev name of the commit to reword ---@return ProcessResult|nil function M.reword(commit) - local message = table.concat(log.full_message(commit), "\n") + local message = table.concat(git.log.full_message(commit), "\n") local status = - client.wrap(cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), { + client.wrap(git.cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), { autocmd = "NeogitCommitComplete", msg = { success = "Commit Updated", @@ -98,9 +96,9 @@ function M.reword(commit) end function M.modify(commit) - local short_commit = rev_parse.abbreviate_commit(commit) + local short_commit = git.rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/edit \\1/' -c 'wq'" - local result = cli.rebase + local result = git.cli.rebase .env({ GIT_SEQUENCE_EDITOR = editor }).interactive.autosquash.autostash .in_pty(true) .commit(commit) @@ -112,9 +110,9 @@ function M.modify(commit) end function M.drop(commit) - local short_commit = rev_parse.abbreviate_commit(commit) + local short_commit = git.rev_parse.abbreviate_commit(commit) local editor = "nvim -c '%s/^pick \\(" .. short_commit .. ".*\\)/drop \\1/' -c 'wq'" - local result = cli.rebase + local result = git.cli.rebase .env({ GIT_SEQUENCE_EDITOR = editor }).interactive.autosquash.autostash .in_pty(true) .commit(commit) @@ -126,28 +124,27 @@ function M.drop(commit) end function M.continue() - return rebase_command(cli.rebase.continue) + return rebase_command(git.cli.rebase.continue) end function M.skip() - return rebase_command(cli.rebase.skip) + return rebase_command(git.cli.rebase.skip) end function M.edit() - return rebase_command(cli.rebase.edit_todo) + return rebase_command(git.cli.rebase.edit_todo) end ---Find the merge base for HEAD and it's upstream ---@return string|nil function M.merge_base_HEAD() - local result = cli["merge-base"].args("HEAD", "HEAD@{upstream}").call { ignore_error = true, hidden = true } + local result = git.cli["merge-base"].args("HEAD", "HEAD@{upstream}").call { ignore_error = true, hidden = true } if result.code == 0 then return result.stdout[1] end end function M.update_rebase_status(state) - local git = require("neogit.lib.git") if git.repo.git_root == "" then return end @@ -177,7 +174,7 @@ function M.update_rebase_status(state) if onto:exists() then state.rebase.onto.oid = vim.trim(onto:read()) state.rebase.onto.subject = git.log.message(state.rebase.onto.oid) - state.rebase.onto.ref = cli["name-rev"].name_only.no_undefined + state.rebase.onto.ref = git.cli["name-rev"].name_only.no_undefined .refs("refs/heads/*") .exclude("*/HEAD") .exclude("*/refs/heads/*") diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index 12f127a0b..003f5fb16 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local M = {} @@ -53,7 +53,7 @@ function M.list(refname, options) }, "%x1E") return parse( - cli.reflog.show.format(format).date("raw").arg_list(options or {}).args(refname, "--").call().stdout + git.cli.reflog.show.format(format).date("raw").arg_list(options or {}).args(refname, "--").call().stdout ) end diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index d7415285b..b629ea2e3 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -1,7 +1,6 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local config = require("neogit.config") local record = require("neogit.lib.record") -local repo = require("neogit.lib.git.repository") local util = require("neogit.lib.util") local M = {} @@ -12,7 +11,7 @@ local M = {} ---@return table function M.list(namespaces, format, sortby) return util.filter_map( - cli["for-each-ref"] + git.cli["for-each-ref"] .format(format or "%(refname)") .sort(sortby or config.values.sort_branches) .call({ hidden = true }).stdout, @@ -67,7 +66,7 @@ local record_template = record.encode({ }, "ref") function M.list_parsed() - local refs = cli["for-each-ref"].format(record_template).show_popup(false).call({ hidden = true }).stdout + local refs = git.cli["for-each-ref"].format(record_template).show_popup(false).call({ hidden = true }).stdout local result = record.decode(refs) local output = { @@ -106,7 +105,7 @@ function M.heads() local heads = { "HEAD", "ORIG_HEAD", "FETCH_HEAD", "MERGE_HEAD", "CHERRY_PICK_HEAD" } local present = {} for _, head in ipairs(heads) do - if repo:git_path(head):exists() then + if git.repo:git_path(head):exists() then table.insert(present, head) end end diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index c895834f5..9806277f2 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -1,12 +1,10 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local M = {} -- https://github.com/magit/magit/blob/main/lisp/magit-remote.el#LL141C32-L141C32 local function cleanup_push_variables(remote, new_name) - local git = require("neogit.lib.git") - if remote == git.config.get("remote.pushDefault").value then git.config.set("remote.pushDefault", new_name) end @@ -23,11 +21,11 @@ local function cleanup_push_variables(remote, new_name) end function M.add(name, url, args) - return cli.remote.add.arg_list(args).args(name, url).call().code == 0 + return git.cli.remote.add.arg_list(args).args(name, url).call().code == 0 end function M.rename(from, to) - local result = cli.remote.rename.arg_list({ from, to }).call_sync() + local result = git.cli.remote.rename.arg_list({ from, to }).call_sync() if result.code == 0 then cleanup_push_variables(from, to) end @@ -36,7 +34,7 @@ function M.rename(from, to) end function M.remove(name) - local result = cli.remote.rm.args(name).call_sync() + local result = git.cli.remote.rm.args(name).call_sync() if result.code == 0 then cleanup_push_variables(name) end @@ -45,15 +43,15 @@ function M.remove(name) end function M.prune(name) - return cli.remote.prune.args(name).call().code == 0 + return git.cli.remote.prune.args(name).call().code == 0 end M.list = util.memoize(function() - return cli.remote.call_sync({ hidden = false }).stdout + return git.cli.remote.call_sync({ hidden = false }).stdout end) function M.get_url(name) - return cli.remote.get_url(name).call({ hidden = true }).stdout + return git.cli.remote.get_url(name).call({ hidden = true }).stdout end ---@class RemoteInfo diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 6fc499ab0..ce2e24b6d 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -1,13 +1,13 @@ local a = require("plenary.async") local logger = require("neogit.logger") local Path = require("plenary.path") -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") ---@return NeogitRepo local function empty_state() ---@class NeogitRepo return { - git_root = cli.git_root_of_cwd(), + git_root = git.cli.git_root_of_cwd(), head = { branch = nil, oid = nil, @@ -80,7 +80,7 @@ function M.refresh(self, opts) -- Needed until Process doesn't use vim.fn.* a.util.scheduler() - self.state.git_root = cli.git_root_of_cwd() + self.state.git_root = git.cli.git_root_of_cwd() -- This needs to be run before all others, because libs like Pull and Push depend on it setting some state. logger.debug("[REPO]: Refreshing 'update_status'") @@ -109,6 +109,10 @@ function M.git_path(self, ...) return Path.new(self.state.git_root):joinpath(".git", ...) end +function M.instance() + return M +end + if not M.initialized then logger.debug("[REPO]: Initializing Repository") M.initialized = true diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index 5c923910b..1abe653b2 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -1,5 +1,5 @@ -local cli = require("neogit.lib.git.cli") local notification = require("neogit.lib.notification") +local git = require("neogit.lib.git") local a = require("plenary.async") local M = {} @@ -11,7 +11,7 @@ end function M.mixed(commit) a.util.scheduler() - local result = cli.reset.mixed.args(commit).call() + local result = git.cli.reset.mixed.args(commit).call() if result.code ~= 0 then notification.error("Reset Failed") else @@ -23,7 +23,7 @@ end function M.soft(commit) a.util.scheduler() - local result = cli.reset.soft.args(commit).call() + local result = git.cli.reset.soft.args(commit).call() if result.code ~= 0 then notification.error("Reset Failed") else @@ -35,7 +35,7 @@ end function M.hard(commit) a.util.scheduler() - local result = cli.reset.hard.args(commit).call() + local result = git.cli.reset.hard.args(commit).call() if result.code ~= 0 then notification.error("Reset Failed") else @@ -47,7 +47,7 @@ end function M.keep(commit) a.util.scheduler() - local result = cli.reset.keep.args(commit).call() + local result = git.cli.reset.keep.args(commit).call() if result.code ~= 0 then notification.error("Reset Failed") else @@ -59,7 +59,7 @@ end function M.index(commit) a.util.scheduler() - local result = cli.reset.args(commit).files(".").call() + local result = git.cli.reset.args(commit).files(".").call() if result.code ~= 0 then notification.error("Reset Failed") else @@ -79,7 +79,7 @@ end -- end function M.file(commit, files) - local result = cli.checkout.rev(commit).files(unpack(files)).call_sync() + local result = git.cli.checkout.rev(commit).files(unpack(files)).call_sync() if result.code ~= 0 then notification.error("Reset Failed") else diff --git a/lua/neogit/lib/git/rev_parse.lua b/lua/neogit/lib/git/rev_parse.lua index 23484639e..3d3e9d3d5 100644 --- a/lua/neogit/lib/git/rev_parse.lua +++ b/lua/neogit/lib/git/rev_parse.lua @@ -1,6 +1,6 @@ local M = {} -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") ---@param oid string @@ -12,7 +12,7 @@ M.abbreviate_commit = util.memoize(function(oid) if oid == "(initial)" then return "(initial)" else - return cli["rev-parse"].short.args(oid).call({ hidden = true, ignore_error = true }).stdout[1] + return git.cli["rev-parse"].short.args(oid).call({ hidden = true, ignore_error = true }).stdout[1] end end, { timeout = math.huge }) @@ -20,7 +20,7 @@ end, { timeout = math.huge }) ---@return string ---@async function M.oid(rev) - return cli["rev-parse"].args(rev).call_sync({ hidden = true, ignore_error = true }).stdout[1] + return git.cli["rev-parse"].args(rev).call_sync({ hidden = true, ignore_error = true }).stdout[1] end return M diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index 4a573dfe7..adbde5b01 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -1,22 +1,22 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local M = {} function M.commits(commits, args) - return cli.revert.no_commit.arg_list(util.merge(args, commits)).call().code == 0 + return git.cli.revert.no_commit.arg_list(util.merge(args, commits)).call().code == 0 end function M.continue() - cli.revert.continue.call_sync() + git.cli.revert.continue.call_sync() end function M.skip() - cli.revert.skip.call_sync() + git.cli.revert.skip.call_sync() end function M.abort() - cli.revert.abort.call_sync() + git.cli.revert.abort.call_sync() end return M diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index 2663ec415..282ebbcbd 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -1,6 +1,5 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local input = require("neogit.lib.input") -local rev_parse = require("neogit.lib.git.rev_parse") local util = require("neogit.lib.util") local M = {} @@ -11,19 +10,19 @@ local function perform_stash(include) end local index = - cli["commit-tree"].no_gpg_sign.parent("HEAD").tree(cli["write-tree"].call().stdout).call().stdout + git.cli["commit-tree"].no_gpg_sign.parent("HEAD").tree(git.cli["write-tree"].call().stdout).call().stdout - cli["read-tree"].merge.index_output(".git/NEOGIT_TMP_INDEX").args(index).call() + git.cli["read-tree"].merge.index_output(".git/NEOGIT_TMP_INDEX").args(index).call() if include.worktree then - local files = cli.diff.no_ext_diff.name_only + local files = git.cli.diff.no_ext_diff.name_only .args("HEAD") .env({ GIT_INDEX_FILE = ".git/NEOGIT_TMP_INDEX", }) .call() - cli["update-index"].add.remove + git.cli["update-index"].add.remove .files(unpack(files)) .env({ GIT_INDEX_FILE = ".git/NEOGIT_TMP_INDEX", @@ -31,15 +30,15 @@ local function perform_stash(include) .call() end - local tree = cli["commit-tree"].no_gpg_sign + local tree = git.cli["commit-tree"].no_gpg_sign .parents("HEAD", index) - .tree(cli["write-tree"].call()) + .tree(git.cli["write-tree"].call()) .env({ GIT_INDEX_FILE = ".git/NEOGIT_TMP_INDEX", }) .call() - cli["update-ref"].create_reflog.args("refs/stash", tree).call() + git.cli["update-ref"].create_reflog.args("refs/stash", tree).call() -- selene: allow(empty_if) if include.worktree and include.index then @@ -52,16 +51,15 @@ local function perform_stash(include) --.commit('HEAD') --.call() elseif include.index then - local diff = cli.diff.no_ext_diff.cached.call().stdout[1] .. "\n" + local diff = git.cli.diff.no_ext_diff.cached.call().stdout[1] .. "\n" - cli.apply.reverse.cached.input(diff).call() - - cli.apply.reverse.input(diff).call() + git.cli.apply.reverse.cached.input(diff).call() + git.cli.apply.reverse.input(diff).call() end end function M.list_refs() - local result = cli.reflog.show.format("%h").args("stash").call { ignore_error = true } + local result = git.cli.reflog.show.format("%h").args("stash").call { ignore_error = true } if result.code > 0 then return {} else @@ -70,7 +68,7 @@ function M.list_refs() end function M.stash_all(args) - cli.stash.arg_list(args).call() + git.cli.stash.arg_list(args).call() -- this should work, but for some reason doesn't. --return perform_stash({ worktree = true, index = true }) end @@ -80,44 +78,42 @@ function M.stash_index() end function M.push(args, files) - cli.stash.push.arg_list(args).files(unpack(files)).call() + git.cli.stash.push.arg_list(args).files(unpack(files)).call() end function M.pop(stash) - local result = cli.stash.apply.index.args(stash).show_popup(false).call() + local result = git.cli.stash.apply.index.args(stash).show_popup(false).call() if result.code == 0 then - cli.stash.drop.args(stash).call() + git.cli.stash.drop.args(stash).call() else - cli.stash.apply.args(stash).call() + git.cli.stash.apply.args(stash).call() end end function M.apply(stash) - local result = cli.stash.apply.index.args(stash).show_popup(false).call() + local result = git.cli.stash.apply.index.args(stash).show_popup(false).call() if result.code ~= 0 then - cli.stash.apply.args(stash).call() + git.cli.stash.apply.args(stash).call() end end function M.drop(stash) - cli.stash.drop.args(stash).call() + git.cli.stash.drop.args(stash).call() end function M.list() - return cli.stash.args("list").call({ hidden = true }).stdout + return git.cli.stash.args("list").call({ hidden = true }).stdout end function M.rename(stash) local message = input.get_user_input("New name") - if message == nil then - -- User pressed ESC or entered empty message, dont drop stash - return + if message then + local oid = git.rev_parse.abbreviate_commit(stash) + git.cli.stash.drop.args(stash).call() + git.cli.stash.store.message(message).args(oid).call() end - local oid = rev_parse.abbreviate_commit(stash) - cli.stash.drop.args(stash).call() - cli.stash.store.message(message).args(oid).call() end function M.register(meta) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 09cc56cd5..dad1a6b79 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -1,4 +1,5 @@ local Path = require("plenary.path") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local Collection = require("neogit.lib.collection") @@ -183,7 +184,6 @@ local function update_status(state) state.staged.items = staged_files end -local git = { cli = require("neogit.lib.git.cli") } local status = { stage = function(files) git.cli.add.files(unpack(files)).call() @@ -192,8 +192,7 @@ local status = { git.cli.add.update.call() end, stage_untracked = function() - local repo = require("neogit.lib.git.repository") - local paths = util.map(repo.untracked.items, function(item) + local paths = util.map(git.repo.untracked.items, function(item) return item.escaped_path end) @@ -209,14 +208,13 @@ local status = { git.cli.reset.call() end, is_dirty = function() - local repo = require("neogit.lib.git.repository") - return #repo.staged.items > 0 or #repo.unstaged.items > 0 + return #git.repo.staged.items > 0 or #git.repo.unstaged.items > 0 end, anything_staged = function() - return #require("neogit.lib.git.repository").staged.items > 0 + return #git.repo.staged.items > 0 end, anything_unstaged = function() - return #require("neogit.lib.git.repository").unstaged.items > 0 + return #git.repo.unstaged.items > 0 end, } diff --git a/lua/neogit/lib/git/tag.lua b/lua/neogit/lib/git/tag.lua index e098e7a6f..18d7b5007 100644 --- a/lua/neogit/lib/git/tag.lua +++ b/lua/neogit/lib/git/tag.lua @@ -1,18 +1,18 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local M = {} --- Outputs a list of tags locally ---@return table List of tags. function M.list() - return cli.tag.list.call().stdout + return git.cli.tag.list.call().stdout end --- Deletes a list of tags ---@param tags table List of tags ---@return boolean Successfully deleted function M.delete(tags) - local result = cli.tag.delete.arg_list(tags).call() + local result = git.cli.tag.delete.arg_list(tags).call() return result.code == 0 end @@ -20,7 +20,7 @@ end ---@param remote string ---@return table function M.list_remote(remote) - return cli["ls-remote"].tags.args(remote).call().stdout + return git.cli["ls-remote"].tags.args(remote).call().stdout end return M diff --git a/lua/neogit/lib/git/worktree.lua b/lua/neogit/lib/git/worktree.lua index d5ea23c52..a9f79724d 100644 --- a/lua/neogit/lib/git/worktree.lua +++ b/lua/neogit/lib/git/worktree.lua @@ -1,4 +1,4 @@ -local cli = require("neogit.lib.git.cli") +local git = require("neogit.lib.git") local util = require("neogit.lib.util") local Path = require("plenary.path") @@ -9,7 +9,7 @@ local M = {} ---@param path string absolute path ---@return boolean function M.add(ref, path, params) - local result = cli.worktree.add.arg_list(params or {}).args(path, ref).call_sync() + local result = git.cli.worktree.add.arg_list(params or {}).args(path, ref).call_sync() return result.code == 0 end @@ -18,7 +18,7 @@ end ---@param destination string absolute path for where to move worktree ---@return boolean function M.move(worktree, destination) - local result = cli.worktree.move.args(worktree, destination).call() + local result = git.cli.worktree.move.args(worktree, destination).call() return result.code == 0 end @@ -27,7 +27,7 @@ end ---@param args? table ---@return boolean function M.remove(worktree, args) - local result = cli.worktree.remove.args(worktree).arg_list(args or {}).call { ignore_error = true } + local result = git.cli.worktree.remove.args(worktree).arg_list(args or {}).call { ignore_error = true } return result.code == 0 end @@ -43,7 +43,7 @@ end ---@return Worktree[] function M.list(opts) opts = opts or { include_main = true } - local list = vim.split(cli.worktree.list.args("--porcelain", "-z").call().stdout_raw[1], "\n\n") + local list = vim.split(git.cli.worktree.list.args("--porcelain", "-z").call().stdout_raw[1], "\n\n") return util.filter_map(list, function(w) local path, head, type, ref = w:match("^worktree (.-)\nHEAD (.-)\n([^ ]+) (.+)$") diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 588e9929f..e06cf8eb9 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -1,4 +1,5 @@ local a = require("plenary.async") +local git = require("neogit.lib.git") local state = require("neogit.lib.state") local config = require("neogit.lib.git.config") local util = require("neogit.lib.util") @@ -315,7 +316,7 @@ end ---@param options.passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI ---@return self function M:config(key, name, options) - local entry = config.get(name) + local entry = git.config.get(name) ---@type PopupConfig local variable = { diff --git a/lua/neogit/popups/cherry_pick/init.lua b/lua/neogit/popups/cherry_pick/init.lua index b7a904ca6..fbef4d2a1 100644 --- a/lua/neogit/popups/cherry_pick/init.lua +++ b/lua/neogit/popups/cherry_pick/init.lua @@ -1,10 +1,11 @@ local popup = require("neogit.lib.popup") local actions = require("neogit.popups.cherry_pick.actions") +local git = require("neogit.lib.git") local M = {} function M.create(env) - local in_progress = require("neogit.lib.git.sequencer").pick_or_revert_in_progress() + local in_progress = git.sequencer.pick_or_revert_in_progress() -- TODO -- :switch("x", "x", "Reference cherry in commit message", { cli_prefix = "-" }) diff --git a/lua/neogit/popups/ignore/init.lua b/lua/neogit/popups/ignore/init.lua index f6799be56..7b624a461 100644 --- a/lua/neogit/popups/ignore/init.lua +++ b/lua/neogit/popups/ignore/init.lua @@ -1,5 +1,6 @@ local actions = require("neogit.popups.ignore.actions") local Path = require("plenary.path") +local git = require("neogit.lib.git") local popup = require("neogit.lib.popup") local M = {} @@ -7,7 +8,7 @@ local M = {} ---@class IgnoreEnv ---@field files string[] Absolute paths function M.create(env) - local excludesFile = require("neogit.lib.git.config").get_global("core.excludesfile") + local excludesFile = git.config.get_global("core.excludesfile") local p = popup .builder() diff --git a/lua/neogit/popups/revert/init.lua b/lua/neogit/popups/revert/init.lua index e52568935..092f16596 100644 --- a/lua/neogit/popups/revert/init.lua +++ b/lua/neogit/popups/revert/init.lua @@ -1,10 +1,11 @@ local actions = require("neogit.popups.revert.actions") +local git = require("neogit.lib.git") local popup = require("neogit.lib.popup") local M = {} function M.create(env) - local in_progress = require("neogit.lib.git.sequencer").pick_or_revert_in_progress() + local in_progress = git.sequencer.pick_or_revert_in_progress() -- TODO: enabled = true needs to check if incompatible switch is toggled in internal state, and not apply. -- if you enable 'no edit', and revert, next time you load the popup both will be enabled -- From 17e2356233a0c205e8921a9766e296a96c5fc85b Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 23:31:24 +0200 Subject: [PATCH 299/443] Refactor watcher to accept the status buffer as a parameter. Removes the need for looking up the buffer by instance resolution --- lua/neogit/buffers/status/init.lua | 4 +- lua/neogit/lib/popup/builder.lua | 3 +- lua/neogit/watcher.lua | 105 ++++++++++------------------- 3 files changed, 37 insertions(+), 75 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ff1b63895..a7679727c 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -14,7 +14,7 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.status.ui") local popups = require("neogit.popups") local git = require("neogit.lib.git") -local watcher = require("neogit.watcher") +local Watcher = require("neogit.watcher") local a = require("plenary.async") local input = require("neogit.lib.input") local logger = require("neogit.logger") -- TODO: Add logging @@ -1120,7 +1120,7 @@ function M:open(kind) if config.values.filewatcher.enabled then logger.debug("[STATUS] Starting file watcher") - self.watcher = watcher.new(git.repo:git_path():absolute()):start() + self.watcher = Watcher.new(self, git.repo):start() end buffer:move_cursor(buffer.ui:first_section().first) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index e06cf8eb9..d60e2e910 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -5,7 +5,6 @@ local config = require("neogit.lib.git.config") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") local logger = require("neogit.logger") -local watcher = require("neogit.watcher") local M = {} @@ -375,7 +374,7 @@ function M:action(keys, description, callback) local permit = action_lock:acquire() logger.debug(string.format("[ACTION] Running action from %s", self.state.name)) - watcher.suspend(callback, { ... }) + callback(...) permit:forget() local status = require("neogit.buffers.status").instance diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 9d98eee22..3f6cdb535 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -1,25 +1,48 @@ -- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/watcher.lua#L103 -local uv = vim.loop local logger = require("neogit.logger") ---@class Watcher ----@field gitdir string ----@field paused boolean ----@field started boolean +---@field git_root string ---@field status_buffer StatusBuffer +---@field running boolean ---@field fs_event_handler uv_fs_event_t local Watcher = {} Watcher.__index = Watcher -function Watcher:fs_event_callback() - local status = require("neogit.buffers.status") +function Watcher.new(status_buffer, repo) + local instance = { + status_buffer = status_buffer, + git_root = repo:git_path():absolute(), + running = false, + fs_event_handler = assert(vim.loop.new_fs_event()), + } - return function(err, filename, events) - if self.paused then - return - end + setmetatable(instance, Watcher) + + return instance +end + +function Watcher:start() + if not self.running then + self.running = true + + logger.debug("[WATCHER] Watching git dir: " .. self.git_root) + self.fs_event_handler:start(self.git_root, {}, self:fs_event_callback()) + end +end + +function Watcher:stop() + if self.running then + self.running = false + + logger.debug("[WATCHER] Stopped watching git dir: " .. self.git_root) + self.fs_event_handler:stop() + end +end +function Watcher:fs_event_callback() + return function(err, filename, events) if err then logger.error(string.format("[WATCHER] Git dir update error: %s", err)) return @@ -44,67 +67,7 @@ function Watcher:fs_event_callback() end logger.debug(info) - if status.instance then - status.instance:dispatch_refresh(nil, "watcher") - end - end -end - -function Watcher:pause() - logger.debug("[WATCHER] Paused") - self.paused = true -end - -function Watcher:resume() - logger.debug("[WATCHER] Resumed") - self.paused = false -end - -function Watcher:start() - if not self.running then - logger.debug("[WATCHER] Watching git dir: " .. self.gitdir) - self.running = true - self.fs_event_handler:start(self.gitdir, {}, self:fs_event_callback()) - end -end - -function Watcher:stop() - if self.running then - logger.debug("[WATCHER] Stopped watching git dir: " .. self.gitdir) - self.running = false - self.fs_event_handler:stop() - end -end - -function Watcher.new(gitdir) - if Watcher.instance then - Watcher.instance:stop() - Watcher.instance = nil - end - - local instance = { - gitdir = gitdir, - paused = false, - running = false, - fs_event_handler = assert(uv.new_fs_event()), - } - - setmetatable(instance, Watcher) - - Watcher.instance = instance - return instance -end - -function Watcher.suspend(callback, args) - local watcher = Watcher.instance - if watcher then - watcher:pause() - end - - callback(unpack(args)) - - if watcher then - watcher:resume() + self.status_buffer:dispatch_refresh(nil, "watcher") end end From aa5bf897b07470996ce4878682ab518a84418149 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 23:32:49 +0200 Subject: [PATCH 300/443] squash! Cleanup: All calls to internal git lib should go through "neogit.lib.git"'s new dynamic module resolution. This fixes the circular import issue of linking to it directly from modules. It also sets up the possibility to use the cwd to get the "right" repo. --- lua/neogit/lib/popup/builder.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index d60e2e910..ada9e01bf 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -1,7 +1,6 @@ local a = require("plenary.async") local git = require("neogit.lib.git") local state = require("neogit.lib.state") -local config = require("neogit.lib.git.config") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") local logger = require("neogit.logger") From 26a94ba51a65e74799ce613bdfa23ebef21bd483 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 23:33:57 +0200 Subject: [PATCH 301/443] Remove echo popup - only used for testing --- lua/neogit/popups/echo/actions.lua | 9 --------- lua/neogit/popups/echo/init.lua | 24 ------------------------ 2 files changed, 33 deletions(-) delete mode 100644 lua/neogit/popups/echo/actions.lua delete mode 100644 lua/neogit/popups/echo/init.lua diff --git a/lua/neogit/popups/echo/actions.lua b/lua/neogit/popups/echo/actions.lua deleted file mode 100644 index a8284bc1c..000000000 --- a/lua/neogit/popups/echo/actions.lua +++ /dev/null @@ -1,9 +0,0 @@ ---- This popup is for unit testing purposes and is not associated to any git command. -local notification = require("neogit.lib.notification") -local M = {} - -function M.echo(value) - notification.info("Echo: " .. value) -end - -return M diff --git a/lua/neogit/popups/echo/init.lua b/lua/neogit/popups/echo/init.lua deleted file mode 100644 index 8ebce3f7d..000000000 --- a/lua/neogit/popups/echo/init.lua +++ /dev/null @@ -1,24 +0,0 @@ ---- This popup is for unit testing purposes and is not associated to any git command. - -local popup = require("neogit.lib.popup") -local actions = require("neogit.popups.echo.actions") - -local M = {} - -function M.create(...) - local args = { ... } - local p = popup.builder():name("NeogitEchoPopup") - for k, v in ipairs(args) do - p:action(tostring(k), tostring(v), function() - actions.echo(v) - end) - end - - local p = p:build() - - p:show() - - return p -end - -return M From 7cb65355f509e2404ffe2e7d7865125da3503a87 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 23:36:58 +0200 Subject: [PATCH 302/443] Improve Ex command autocomplete --- lua/neogit.lua | 59 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index dc90a6cb3..494201972 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -121,8 +121,29 @@ local function open_status_buffer(opts) end) end ----@alias Popup "cherry_pick" | "commit" | "branch" | "diff" | "fetch" | "log" | "merge" | "remote" | "pull" | "push" | "rebase" | "revert" | "reset" | "stash" ---- +---@alias Popup +---| "bisect" +---| "branch" +---| "branch_config" +---| "cherry_pick" +---| "commit" +---| "diff" +---| "fetch" +---| "help" +---| "ignore" +---| "log" +---| "merge" +---| "pull" +---| "push" +---| "rebase" +---| "remote" +---| "remote_config" +---| "reset" +---| "revert" +---| "stash" +---| "tag" +---| "worktree" + ---@class OpenOpts ---@field cwd string|nil ---@field [1] Popup|nil @@ -230,10 +251,40 @@ function M.complete(arglead) "kind=auto", } end - -- Only complete arguments that start with arglead + + if arglead:find("^cwd=") then + return { + "cwd=" .. vim.fn.getcwd(), + } + end + return vim.tbl_filter(function(arg) return arg:match("^" .. arglead) - end, { "kind=", "cwd=", "commit" }) + end, { + "kind=", + "cwd=", + "bisect", + "branch", + "branch_config", + "cherry_pick", + "commit", + "diff", + "fetch", + "help", + "ignore", + "log", + "merge", + "pull", + "push", + "rebase", + "remote", + "remote_config", + "reset", + "revert", + "stash", + "tag", + "worktree", + }) end function M.get_log_file_path() From fffb448615f45db90b59461a537075d6966e9eda Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 2 Apr 2024 23:39:07 +0200 Subject: [PATCH 303/443] Fix: Don't crash when the first section has no start. Applies to a buffer for a brand new repo. --- lua/neogit/lib/ui/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index dd8c96e1a..6eae0050b 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -391,7 +391,7 @@ function Ui:get_cursor_location(line) first, last = loc.first, loc.last break - elseif line >= loc.first and line <= loc.last then + elseif loc.first and line >= loc.first and line <= loc.last then section_loc = { index = li, name = loc.name } if #loc.items > 0 then From ff2df2de4d7c0c2cdb8f1a7c2b665eb4b5a2d096 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 15:56:01 +0200 Subject: [PATCH 304/443] Refactor: Better management of repositories with respect to multiple git repos in the same neovim process. Rewrites callers of repo to use the state instead of using metatable to access it. --- lua/neogit.lua | 2 +- lua/neogit/buffers/status/init.lua | 6 +- lua/neogit/integrations/diffview.lua | 8 +- lua/neogit/lib/git/bisect.lua | 67 ++++---- lua/neogit/lib/git/branch.lua | 13 +- lua/neogit/lib/git/cli.lua | 7 +- lua/neogit/lib/git/config.lua | 2 +- lua/neogit/lib/git/index.lua | 2 +- lua/neogit/lib/git/log.lua | 31 ++-- lua/neogit/lib/git/merge.lua | 41 +++-- lua/neogit/lib/git/rebase.lua | 29 +++- lua/neogit/lib/git/repository.lua | 245 +++++++++++++++++++-------- lua/neogit/lib/git/sequencer.lua | 13 +- lua/neogit/lib/git/stash.lua | 5 + lua/neogit/lib/git/status.lua | 11 +- lua/neogit/lib/popup/init.lua | 2 +- lua/neogit/lib/util.lua | 2 +- lua/neogit/popups/commit/actions.lua | 2 +- lua/neogit/popups/merge/actions.lua | 2 +- lua/neogit/popups/pull/actions.lua | 4 +- lua/neogit/popups/rebase/actions.lua | 4 +- lua/neogit/popups/rebase/init.lua | 4 +- 22 files changed, 315 insertions(+), 187 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 494201972..7a50a158f 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -83,7 +83,7 @@ local function construct_opts(opts) if not opts.cwd then local git = require("neogit.lib.git") - opts.cwd = git.cli.git_root_of_cwd() + opts.cwd = git.cli.git_root(".") if opts.cwd == "" then opts.cwd = vim.fn.getcwd() diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index a7679727c..d0b8e8665 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1111,7 +1111,11 @@ function M:open(kind) vim.o.autochdir = false end, render = function() - return ui.Status(self.state, self.config) + if self.state.initialized then + return ui.Status(self.state, self.config) + else + return {} + end end, ---@param buffer Buffer ---@param _win any diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 9bdb1a8bb..158a4a94a 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -42,10 +42,10 @@ local function get_local_diff_view(section_name, item_name, opts) conflicting = { items = vim.tbl_filter(function(o) return o.mode and o.mode:sub(2, 2) == "U" - end, git.repo.untracked.items), + end, git.repo.state.untracked.items), }, - working = git.repo.unstaged, - staged = git.repo.staged, + working = git.repo.state.unstaged, + staged = git.repo.state.staged, } for kind, section in pairs(sections) do @@ -102,7 +102,7 @@ local function get_local_diff_view(section_name, item_name, opts) view:on_files_staged(a.void(function(_) if status.is_open() then - status.instance:dispatch_refresh({ update_diffs = true }, "on_files_staged") + status.instance():dispatch_refresh({ update_diffs = true }, "on_files_staged") end view:update_files() diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index 424151319..58ced7a1c 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -21,7 +21,7 @@ function M.in_progress() end function M.is_finished() - return git.repo.bisect.finished + return git.repo.state.bisect.finished end ---@param bad_revision string @@ -61,51 +61,46 @@ end ---@field oid string ---@field subject string ---@field abbreviated_commit string +---@field finished boolean -local function update_bisect_information(state) - logger.debug("[GIT BISECT] Starting") - state.bisect = { items = {}, finished = false, current = {} } +M.register = function(meta) + meta.update_bisect_information = function(state) + state.bisect = { items = {}, finished = false, current = {} } - if not M.in_progress() then - logger.debug("[GIT BISECT] Not in progress") - return - end + if not M.in_progress() then + return + end - logger.debug("[GIT BISECT] In progress") - local finished - local git = require("neogit.lib.git") + local finished - for line in git.repo:git_path("BISECT_LOG"):iter() do - if line:match("^#") and line ~= "" then - local action, oid, subject = line:match("^# ([^:]+): %[(.+)%] (.+)") + for line in git.repo:git_path("BISECT_LOG"):iter() do + if line:match("^#") and line ~= "" then + local action, oid, subject = line:match("^# ([^:]+): %[(.+)%] (.+)") - finished = action == "first bad commit" - if finished then - fire_bisect_event { type = "finished", oid = oid } - end + finished = action == "first bad commit" + if finished then + fire_bisect_event { type = "finished", oid = oid } + end - ---@type BisectItem - local item = { - finished = finished, - action = action, - subject = subject, - oid = oid, - abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), - } + ---@type BisectItem + local item = { + finished = finished, + action = action, + subject = subject, + oid = oid, + abbreviated_commit = oid:sub(1, git.log.abbreviated_size()), + } - table.insert(state.bisect.items, item) + table.insert(state.bisect.items, item) + end end - end - - local expected = vim.trim(git.repo:git_path("BISECT_EXPECTED_REV"):read()) - state.bisect.current = - git.log.parse(git.cli.show.format("fuller").args(expected).call_sync({ trim = false }).stdout)[1] - state.bisect.finished = finished -end + local expected = vim.trim(git.repo:git_path("BISECT_EXPECTED_REV"):read()) + state.bisect.current = + git.log.parse(git.cli.show.format("fuller").args(expected).call_sync({ trim = false }).stdout)[1] -M.register = function(meta) - meta.update_bisect_information = update_bisect_information + state.bisect.finished = finished + end end return M diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 3f3a66956..a9b4be175 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -160,7 +160,7 @@ end ---Returns current branch name, or nil if detached HEAD ---@return string|nil function M.current() - local head = require("neogit.lib.git").repo.head.branch + local head = git.repo.state.head.branch if head and head ~= "(detached)" then return head else @@ -209,12 +209,12 @@ function M.pushRemote_remote_label() end function M.is_detached() - return require("neogit.lib.git").repo.head.branch == "(detached)" + return git.repo.state.head.branch == "(detached)" end function M.set_pushRemote() - local remotes = require("neogit.lib.git").remote.list() - local pushDefault = require("neogit.lib.git").config.get("remote.pushDefault") + local remotes = git.remote.list() + local pushDefault = git.config.get("remote.pushDefault") local pushRemote if #remotes == 1 then @@ -247,7 +247,7 @@ function M.upstream(name) return result.stdout[1] end else - return require("neogit.lib.git").repo.upstream.ref + return git.repo.state.upstream.ref end end @@ -260,8 +260,7 @@ function M.upstream_remote_label() end function M.upstream_remote() - local git = require("neogit.lib.git") - local remote = git.repo.upstream.remote + local remote = git.repo.state.upstream.remote if not remote then local remotes = git.remote.list() diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index d0ba0b9f0..955130bba 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -589,10 +589,11 @@ local configurations = { -- git_root_of_cwd() returns the git repo of the cwd, which can change anytime -- after git_root_of_cwd() has been called. ---@return string -local function git_root_of_cwd() +local function git_root(dir) local process = process .new({ - cmd = { "git", "rev-parse", "--show-toplevel" }, + cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel" }, + verbose = false, on_error = function() return false end, @@ -1046,7 +1047,7 @@ local meta = { local cli = setmetatable({ history = history, insert = handle_new_cmd, - git_root_of_cwd = git_root_of_cwd, + git_root = git_root, is_inside_worktree = is_inside_worktree, }, meta) diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index 01d0ec448..470b7f271 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -72,7 +72,7 @@ local config_cache = {} local cache_key = nil local function make_cache_key() - local stat = vim.loop.fs_stat(git.repo.git_root .. "/.git/config") + local stat = vim.loop.fs_stat(git.repo:git_path("config"):absolute()) if stat then return stat.mtime.sec end diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index d1adb3699..171be1161 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -118,7 +118,7 @@ function M.reset_HEAD(...) end function M.checkout_unstaged() - local items = util.map(git.repo.unstaged.items, function(item) + local items = util.map(git.repo.state.unstaged.items, function(item) return item.escaped_path end) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 06f4ed10b..8597f0cf7 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -400,21 +400,24 @@ end ---@param commit string ---@return string|nil function M.parent(commit) - return vim.split(git.cli["rev-list"].max_count(1).parents.args(commit).call({ hidden = true }).stdout[1], " ")[2] + return vim.split( + git.cli["rev-list"].max_count(1).parents.args(commit).call({ hidden = true }).stdout[1], + " " + )[2] end -local function update_recent(state) - local count = config.values.status.recent_commit_count - if count < 1 then - return - end +function M.register(meta) + meta.update_recent = function(state) + state.recent = { items = {} } - state.recent.items = - util.filter_map(M.list({ "--max-count=" .. tostring(count) }, nil, {}, true), M.present_commit) -end + local count = config.values.status.recent_commit_count + if count < 1 then + return + end -function M.register(meta) - meta.update_recent = update_recent + local commits = M.list({ "--max-count=" .. tostring(count) }, nil, {}, true) + state.recent.items = util.filter_map(commits, M.present_commit) + end end function M.update_ref(from, to) @@ -429,6 +432,12 @@ function M.full_message(commit) return git.cli.log.max_count(1).format("%B").args(commit).call({ hidden = true }).stdout end +---@class CommitItem +---@field name string +---@field oid string +---@field commit CommitLogEntry[] + +---@return nil|CommitItem function M.present_commit(commit) if not commit.oid then return diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index da49c7331..bca39d482 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -35,30 +35,27 @@ function M.abort() return merge_command(git.cli.merge.abort) end -function M.update_merge_status(state) - if git.repo.git_root == "" then - return - end - - state.merge = { head = nil, msg = "", items = {} } - - local merge_head = git.repo:git_path("MERGE_HEAD") - if not merge_head:exists() then - return - end - - state.merge.head = merge_head:read():match("([^\r\n]+)") - state.merge.subject = git.log.message(state.merge.head) - - local message = git.repo:git_path("MERGE_MSG") - if message:exists() then - state.merge.msg = message:read():match("([^\r\n]+)") -- we need \r? to support windows - state.merge.branch = state.merge.msg:match("Merge branch '(.*)'$") - end -end +---@class MergeItem +---Not used, just for a consistent interface M.register = function(meta) - meta.update_merge_status = M.update_merge_status + meta.update_merge_status = function(state) + state.merge = { head = nil, branch = nil, msg = "", items = {} } + + local merge_head = git.repo:git_path("MERGE_HEAD") + if not merge_head:exists() then + return + end + + state.merge.head = merge_head:read():match("([^\r\n]+)") + state.merge.subject = git.log.message(state.merge.head) + + local message = git.repo:git_path("MERGE_MSG") + if message:exists() then + state.merge.msg = message:read():match("([^\r\n]+)") -- we need \r? to support windows + state.merge.branch = state.merge.msg:match("Merge branch '(.*)'$") + end + end end return M diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 173c2b6b4..91f07100b 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -82,13 +82,15 @@ end ---@return ProcessResult|nil function M.reword(commit) local message = table.concat(git.log.full_message(commit), "\n") - local status = - client.wrap(git.cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), { + local status = client.wrap( + git.cli.commit.only.allow_empty.edit.with_message(("amend! %s\n\n%s"):format(commit, message)), + { autocmd = "NeogitCommitComplete", msg = { success = "Commit Updated", }, - }) + } + ) if status == 0 then return M.instantly(commit) @@ -138,17 +140,28 @@ end ---Find the merge base for HEAD and it's upstream ---@return string|nil function M.merge_base_HEAD() - local result = git.cli["merge-base"].args("HEAD", "HEAD@{upstream}").call { ignore_error = true, hidden = true } + local result = + git.cli["merge-base"].args("HEAD", "HEAD@{upstream}").call { ignore_error = true, hidden = true } if result.code == 0 then return result.stdout[1] end end -function M.update_rebase_status(state) - if git.repo.git_root == "" then - return - end +---@class RebaseItem +---@field action string +---@field oid string +---@field abbreviated_commit string +---@field subject string +---@field done boolean +---@field stopped boolean +---@class RebaseOnto +---@field oid string +---@field subject string +---@field ref string +---@field is_remote boolean + +function M.update_rebase_status(state) state.rebase = { items = {}, onto = {}, head = nil, current = nil } local rebase_file diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index ce2e24b6d..7dd74362b 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -3,11 +3,94 @@ local logger = require("neogit.logger") local Path = require("plenary.path") local git = require("neogit.lib.git") +local modules = { + "status", + "branch", + "diff", + "stash", + "pull", + "push", + "log", + "rebase", + "sequencer", + "merge", + "bisect", +} + +---@class NeogitRepo +---@field initialized boolean +---@field git_root string +---@field head NeogitRepoHead +---@field upstream NeogitRepoRemote +---@field pushRemote NeogitRepoRemote +---@field untracked NeogitRepoIndex +---@field unstaged NeogitRepoIndex +---@field staged NeogitRepoIndex +---@field stashes NeogitRepoStash +---@field recent NeogitRepoRecent +---@field sequencer NeogitRepoSequencer +---@field rebase NeogitRepoRebase +---@field merge NeogitRepoMerge +---@field bisect NeogitRepoBisect +--- +---@class NeogitRepoHead +---@field branch string|nil +---@field oid string|nil +---@field commit_message string|nil +---@field tag NeogitRepoHeadTag +--- +---@class NeogitRepoHeadTag +---@field name string|nil +---@field oid string|nil +---@field distance number|nil +--- +---@class NeogitRepoRemote +---@field branch string|nil +---@field commit_message string|nil +---@field remote string|nil +---@field ref string|nil +---@field oid string|nil +---@field unmerged NeogitRepoIndex +---@field unpulled NeogitRepoIndex +--- +---@class NeogitRepoIndex +---@field items StatusItem[] +--- +---@class NeogitRepoStash +---@field items StashItem[] +--- +---@class NeogitRepoRecent +---@field items CommitItem[] +--- +---@class NeogitRepoSequencer +---@field items SequencerItem[] +---@field head string|nil +---@field head_oid string|nil +---@field revert boolean +---@field cherry_pick boolean +--- +---@class NeogitRepoRebase +---@field items RebaseItem[] +---@field onto RebaseOnto +---@field head string|nil +---@field current string|nil +--- +---@class NeogitRepoMerge +---@field items MergeItem[] +---@field head string|nil +---@field msg string +---@field branch string|nil +--- +---@class NeogitRepoBisect +---@field items BisectItem[] +---@field finished boolean +---@field current CommitLogEntry + ---@return NeogitRepo local function empty_state() - ---@class NeogitRepo return { - git_root = git.cli.git_root_of_cwd(), + initialized = false, + git_root = "", head = { branch = nil, oid = nil, @@ -41,110 +124,128 @@ local function empty_state() staged = { items = {} }, stashes = { items = {} }, recent = { items = {} }, - rebase = { items = {}, head = nil }, - sequencer = { items = {}, head = nil }, - merge = { items = {}, head = nil, msg = nil }, - bisect = { items = {}, finished = false, current = {} }, + rebase = { + items = {}, + onto = {}, + head = nil, + current = nil, + }, + sequencer = { + items = {}, + head = nil, + head_oid = nil, + revert = false, + cherry_pick = false, + }, + merge = { + items = {}, + head = nil, + msg = "", + branch = nil, + }, + bisect = { + items = {}, + finished = false, + current = {}, + }, } end -local meta = { - __index = function(self, method) - return self.state[method] - end, -} +local Repo = {} +Repo.__index = Repo -local M = {} +local instances = {} -M.state = empty_state() -M.lib = {} -M.updates = {} +function Repo.instance(dir) + local cwd = dir or vim.loop.cwd() + if cwd and not instances[cwd] then + instances[cwd] = Repo.new(cwd) + end -function M.reset(self) - self.state = empty_state() + return instances[cwd] end -function M.refresh(self, opts) - opts = opts or {} - logger.fmt_info("[REPO]: Refreshing START (source: %s)", opts.source or "UNKNOWN") +-- Use Repo.instance when calling directly to ensure it's registered +function Repo.new(dir) + logger.debug("[REPO]: Initializing Repository") - local cleanup = function() - logger.debug("[REPO]: Refreshes complete") + local instance = { + lib = {}, + updates = {}, + state = empty_state(), + git_root = git.cli.git_root(dir), + } - if opts.callback then - logger.debug("[REPO]: Running refresh callback") - opts.callback() + instance.state.git_root = instance.git_root + + setmetatable(instance, Repo) + + for _, m in ipairs(modules) do + require("neogit.lib.git." .. m).register(instance.lib) + end + + for name, fn in pairs(instance.lib) do + if name ~= "update_status" then + table.insert(instance.updates, function() + logger.fmt_debug("[REPO]: Refreshing %s", name) + fn(instance.state) + end) end end + return instance +end + +function Repo:reset() + self.state = empty_state() +end + +function Repo:git_path(...) + return Path.new(self.git_root):joinpath(".git", ...) +end + +function Repo:refresh(opts) + self.state.initialized = true + + if self.git_root == "" then + logger.debug("[REPO] No git root found - skipping refresh") + return + end + + opts = opts or {} + logger.fmt_info("[REPO]: Refreshing START (source: %s)", opts.source or "UNKNOWN") + -- Needed until Process doesn't use vim.fn.* a.util.scheduler() - self.state.git_root = git.cli.git_root_of_cwd() - -- This needs to be run before all others, because libs like Pull and Push depend on it setting some state. logger.debug("[REPO]: Refreshing 'update_status'") self.lib.update_status(self.state) local tasks = {} if opts.partial then - for name, fn in pairs(M.lib) do + for name, fn in pairs(self.lib) do if opts.partial[name] then local filter = type(opts.partial[name]) == "table" and opts.partial[name] table.insert(tasks, function() logger.fmt_debug("[REPO]: Refreshing %s", name) - fn(M.state, filter) + fn(self.state, filter) end) end end else - tasks = M.updates + tasks = self.updates end - a.util.run_all(tasks, cleanup) -end - -function M.git_path(self, ...) - return Path.new(self.state.git_root):joinpath(".git", ...) -end - -function M.instance() - return M -end - -if not M.initialized then - logger.debug("[REPO]: Initializing Repository") - M.initialized = true - - setmetatable(M, meta) - - local modules = { - "status", - "branch", - "diff", - "stash", - "pull", - "push", - "log", - "rebase", - "sequencer", - "merge", - "bisect", - } - - for _, m in ipairs(modules) do - require("neogit.lib.git." .. m).register(M.lib) - end + a.util.run_all(tasks, function() + logger.debug("[REPO]: Refreshes complete") - for name, fn in pairs(M.lib) do - if name ~= "update_status" then - table.insert(M.updates, function() - logger.fmt_debug("[REPO]: Refreshing %s", name) - fn(M.state) - end) + if opts.callback then + logger.debug("[REPO]: Running refresh callback") + opts.callback() end - end + end) end -return M +return Repo diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 830abe7ea..63b0adf6d 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -1,3 +1,4 @@ +local git = require("neogit.lib.git") local M = {} -- .git/sequencer/todo does not exist when there is only one commit left. @@ -6,21 +7,25 @@ local M = {} -- And REVERT_HEAD does not exist when a conflict happens while reverting a series of commits with --no-commit. -- function M.pick_or_revert_in_progress() - local git = require("neogit.lib.git") local pick_or_revert_todo = false - for _, item in ipairs(git.repo.sequencer.items) do + for _, item in ipairs(git.repo.state.sequencer.items) do if item.action == "pick" or item.action == "revert" then pick_or_revert_todo = true break end end - return git.repo.sequencer.head or pick_or_revert_todo + return git.repo.state.sequencer.head or pick_or_revert_todo end +---@class SequencerItem +---@field action string +---@field oid string +---@field abbreviated_commit string +---@field subject string + function M.update_sequencer_status(state) - local git = require("neogit.lib.git") state.sequencer = { items = {}, head = nil, head_oid = nil, revert = false, cherry_pick = false } local revert_head = git.repo:git_path("REVERT_HEAD") diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index 282ebbcbd..a6d5fe9bb 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -116,6 +116,11 @@ function M.rename(stash) end end +---@class StashItem +---@field idx number +---@field name string +---@field message string + function M.register(meta) meta.update_stashes = function(state) state.stashes.items = util.map(M.list(), function(line) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index dad1a6b79..5a281c4fd 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -9,7 +9,7 @@ local Collection = require("neogit.lib.collection") ---@field diff string[] ---@field absolute_path string ---@field escaped_path string ----@field original_nae string|nil +---@field original_name string|nil ---@return StatusItem local function update_file(cwd, file, mode, name, original_name) @@ -46,7 +46,6 @@ local match_1 = "(.)(.) (....) (%d+) (%d+) (%d+) (%w+) (%w+) (.+)" local match_2 = "(.)(.) (....) (%d+) (%d+) (%d+) (%w+) (%w+) (%a%d+) ([^\t]+)\t?(.+)" local function update_status(state) - local git = require("neogit.lib.git") local cwd = state.git_root local head = {} @@ -192,7 +191,7 @@ local status = { git.cli.add.update.call() end, stage_untracked = function() - local paths = util.map(git.repo.untracked.items, function(item) + local paths = util.map(git.repo.state.untracked.items, function(item) return item.escaped_path end) @@ -208,13 +207,13 @@ local status = { git.cli.reset.call() end, is_dirty = function() - return #git.repo.staged.items > 0 or #git.repo.unstaged.items > 0 + return #git.repo.state.staged.items > 0 or #git.repo.state.unstaged.items > 0 end, anything_staged = function() - return #git.repo.staged.items > 0 + return #git.repo.state.staged.items > 0 end, anything_unstaged = function() - return #git.repo.unstaged.items > 0 + return #git.repo.state.unstaged.items > 0 end, } diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 0d294e2fa..490b65a7c 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -363,7 +363,7 @@ function M:show() vim.fn.matchadd("NeogitPopupBranchName", self.state.env.highlight[i], 100) end else - vim.fn.matchadd("NeogitPopupBranchName", git.repo.head.branch, 100) + vim.fn.matchadd("NeogitPopupBranchName", git.repo.state.head.branch, 100) end if self.state.env.bold then diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 1819029aa..4923d54d2 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -471,7 +471,7 @@ function M.memoize(f, opts) local timer = {} return function(...) - local key = vim.inspect { ... } + local key = vim.inspect { vim.loop.cwd(), ... } if cache[key] == nil then cache[key] = f(...) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 0909f689d..51fa33182 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -10,7 +10,7 @@ local a = require("plenary.async") local function confirm_modifications() if git.branch.upstream() - and #git.repo.upstream.unmerged.items < 1 + and #git.repo.state.upstream.unmerged.items < 1 and not input.get_permission( string.format( "This commit has already been published to %s, do you really want to modify it?", diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index cdd46fa58..f6c1d70f6 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -6,7 +6,7 @@ local input = require("neogit.lib.input") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") function M.in_merge() - return git.repo.merge.head + return git.repo.state.merge.head end function M.commit() diff --git a/lua/neogit/popups/pull/actions.lua b/lua/neogit/popups/pull/actions.lua index dfe1b76f3..f87702545 100644 --- a/lua/neogit/popups/pull/actions.lua +++ b/lua/neogit/popups/pull/actions.lua @@ -38,12 +38,12 @@ function M.from_pushremote(popup) end if pushRemote then - pull_from(popup:get_arguments(), pushRemote, git.repo.head.branch) + pull_from(popup:get_arguments(), pushRemote, git.repo.state.head.branch) end end function M.from_upstream(popup) - local upstream = git.repo.upstream.ref + local upstream = git.repo.state.upstream.ref local set_upstream if not upstream then diff --git a/lua/neogit/popups/rebase/actions.lua b/lua/neogit/popups/rebase/actions.lua index e3fbddf67..f9baf6eba 100644 --- a/lua/neogit/popups/rebase/actions.lua +++ b/lua/neogit/popups/rebase/actions.lua @@ -33,8 +33,8 @@ end function M.onto_upstream(popup) local upstream - if git.repo.upstream.ref then - upstream = string.format("refs/remotes/%s", git.repo.upstream.ref) + if git.repo.state.upstream.ref then + upstream = string.format("refs/remotes/%s", git.repo.state.upstream.ref) else local target = FuzzyFinderBuffer.new(git.refs.list_remote_branches()):open_async() if not target then diff --git a/lua/neogit/popups/rebase/init.lua b/lua/neogit/popups/rebase/init.lua index adb2069a2..19d6a45e6 100644 --- a/lua/neogit/popups/rebase/init.lua +++ b/lua/neogit/popups/rebase/init.lua @@ -6,7 +6,7 @@ local M = {} function M.create(env) local branch = git.branch.current() - local in_rebase = git.repo.rebase.head + local in_rebase = git.repo.state.rebase.head local base_branch = git.branch.base_branch() local show_base_branch = branch ~= base_branch and base_branch ~= nil @@ -46,7 +46,7 @@ function M.create(env) :action_if(not in_rebase, "f", "to autosquash", actions.autosquash) :env({ commit = env.commit, - highlight = { branch, git.repo.upstream.ref, base_branch }, + highlight = { branch, git.repo.state.upstream.ref, base_branch }, bold = { "@{upstream}", "pushRemote" }, }) :build() From d62fd23ec7e7f38ce438aad76daf4284a6e6a0b5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 16:07:09 +0200 Subject: [PATCH 305/443] Refactor: Better status buffer instance management. Should allow for multiple instances in the same nvim process. Also fixes a bug where we were changing the working directory of the window via fn.lcd() _before_ opening the neogit window, which would effect the user buffer by mistake. --- lua/neogit.lua | 30 ++++++---------- lua/neogit/buffers/status/init.lua | 49 +++++++++++++++++--------- lua/neogit/lib/finder.lua | 6 ++-- lua/neogit/lib/git/init.lua | 4 +-- lua/neogit/lib/popup/builder.lua | 6 ++-- lua/neogit/popups/help/actions.lua | 2 +- lua/neogit/popups/worktree/actions.lua | 10 +++--- lua/neogit/watcher.lua | 4 +-- 8 files changed, 60 insertions(+), 51 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 7a50a158f..9b5eaa896 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -23,35 +23,35 @@ function M.setup(opts) M.status = require("neogit.buffers.status") M.dispatch_reset = function() - local instance = M.status.instance + local instance = M.status.instance() if instance then instance:dispatch_reset() end end M.refresh = function() - local instance = M.status.instance + local instance = M.status.instance() if instance then instance:refresh() end end M.reset = function() - local instance = M.status.instance + local instance = M.status.instance() if instance then instance:reset() end end M.dispatch_refresh = function() - local instance = M.status.instance + local instance = M.status.instance() if instance then instance:dispatch_refresh() end end M.close = function() - local instance = M.status.instance + local instance = M.status.instance() if instance then instance:close() end @@ -95,30 +95,22 @@ end local function open_popup(name) local has_pop, popup = pcall(require, "neogit.popups." .. name) - if not has_pop then - vim.api.nvim_err_writeln("Invalid popup '" .. name .. "'") + M.notification.error(("Invalid popup %q"):format(name)) else popup.create {} end end local function open_status_buffer(opts) - local a = require("plenary.async") local status = require("neogit.buffers.status") local config = require("neogit.config") - local git = require("neogit.lib.git") - vim.cmd.lcd(opts.cwd) - - a.run(function() - git.repo:refresh { - source = "initialize", - callback = function() - status.new(git.repo, config.values):open(opts.kind) - end, - } - end) + -- We need to construct the repo instance manually here since the actual CWD may not be the directory neogit is + -- going to open into. We will use vim.fn.lcd() in the status buffer constructor, so this will eventually be + -- correct. + local repo = require("neogit.lib.git.repository").instance(opts.cwd) + status.new(repo.state, config.values, repo.git_root):open(opts.kind, opts.cwd) end ---@alias Popup diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index d0b8e8665..bc8a8ec14 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -37,14 +37,30 @@ local fn = vim.fn local M = {} M.__index = M +local instances = {} + +function M.register(instance, dir) + instances[dir] = instance +end + +function M.unregister() + instances[vim.uv.cwd()] = nil +end + +function M.instance() + return instances[vim.uv.cwd()] +end + ---@param state NeogitRepo ---@param config NeogitConfig +---@param root string ---@return StatusBuffer -function M.new(state, config) +function M.new(state, config, root) local instance = { -- frozen = false, state = state, config = config, + root = root, buffer = nil, watcher = nil, refresh_lock = a.control.Semaphore.new(1), @@ -57,18 +73,18 @@ end ---@return boolean function M.is_open() - return (M.instance and M.instance.buffer and M.instance.buffer:is_visible()) == true + return (M.instance() and M.instance().buffer and M.instance().buffer:is_visible()) == true end -function M:open(kind) +---@param kind string<"floating" | "split" | "tab" | "split" | "vsplit">|nil +---@param cwd string +function M:open(kind, cwd) if M.is_open() then logger.debug("[STATUS] An Instance is already open - closing it") - M.instance:close() + M.instance():close() end - M.instance = self - kind = kind or config.values.kind - logger.debug("[STATUS] Opening kind: " .. kind) + M.register(self, cwd) local mappings = config.get_reversed_status_maps() @@ -76,16 +92,16 @@ function M:open(kind) name = "NeogitStatus", filetype = "NeogitStatus", context_highlight = true, - kind = kind, + kind = kind or config.values.kind, disable_line_numbers = config.values.disable_line_numbers, status_column = " ", on_detach = function() - logger.debug("[STATUS] Running on_detach") if self.watcher then self.watcher:stop() end vim.o.autochdir = self.prev_autochdir + M.unregister() end, autocmds = { ["BufEnter"] = function() @@ -372,7 +388,8 @@ function M:open(kind) if fold then -- Do not allow folding on the last (empty) line of a section. It should be considered "not part of either -- section" from a UX perspective. Only applies to unfolded sections. - if fold.options.tag == "Section" + if + fold.options.tag == "Section" and not fold.options.folded and self.buffer:get_current_line()[1] == "" then @@ -1106,7 +1123,9 @@ function M:open(kind) }, }, initialize = function() - logger.debug("[STATUS] Initializing") + vim.cmd.lcd(cwd) + self:dispatch_refresh(nil, "initialize") + self.prev_autochdir = vim.o.autochdir vim.o.autochdir = false end, @@ -1120,11 +1139,9 @@ function M:open(kind) ---@param buffer Buffer ---@param _win any after = function(buffer, _win) - vim.cmd([[setlocal nowrap]]) - if config.values.filewatcher.enabled then logger.debug("[STATUS] Starting file watcher") - self.watcher = Watcher.new(self, git.repo):start() + self.watcher = Watcher.new(self, self.root):start() end buffer:move_cursor(buffer.ui:first_section().first) @@ -1148,7 +1165,7 @@ function M:close() vim.o.autochdir = self.prev_autochdir end - M.instance = nil + M.unregister() end function M:chdir(dir) @@ -1194,7 +1211,7 @@ function M:refresh(partial, reason) end logger.debug("[STATUS][Refresh Callback] Rendering UI") - self.buffer.ui:render(unpack(ui.Status(git.repo, self.config))) + self.buffer.ui:render(unpack(ui.Status(self.state, self.config))) if cursor and view then self.buffer:restore_view(view, self.buffer.ui:resolve_cursor_location(cursor)) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 6532f7840..b4d684e40 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -3,9 +3,9 @@ local a = require("plenary.async") local function refocus_status_buffer() local status = require("neogit.buffers.status") - if status.instance then - status.instance:focus() - status.instance:dispatch_refresh(nil, "finder.refocus") + if status.instance() then + status.instance():focus() + status.instance():dispatch_refresh(nil, "finder.refocus") end end diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 84585d733..46f2200be 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -30,7 +30,7 @@ M.init_repo = function() end local status = require("neogit.buffers.status") if status.is_open() then - status.instance:chdir(directory) + status.instance():chdir(directory) end if git.cli.is_inside_worktree() then @@ -41,7 +41,7 @@ M.init_repo = function() M.create(directory) if status.is_open() then - status.instance:dispatch_refresh(nil, "InitRepo") + status.instance():dispatch_refresh(nil, "InitRepo") end end diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index ada9e01bf..e51c7919b 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -4,6 +4,7 @@ local state = require("neogit.lib.state") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") local logger = require("neogit.logger") +local status = require("neogit.buffers.status") local M = {} @@ -376,10 +377,9 @@ function M:action(keys, description, callback) callback(...) permit:forget() - local status = require("neogit.buffers.status").instance - if status then + if status.instance() then logger.debug("[ACTION] Dispatching Refresh") - status:dispatch_refresh(nil, "action") + status.instance():dispatch_refresh(nil, "action") end end) end diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index f49a7d89c..318ddfa7e 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -126,7 +126,7 @@ M.essential = function() function() local status = require("neogit.buffers.status") if status.is_open() then - status.instance:dispatch_refresh(nil, "user_refresh") + status.instance():dispatch_refresh(nil, "user_refresh") end end, }, diff --git a/lua/neogit/popups/worktree/actions.lua b/lua/neogit/popups/worktree/actions.lua index f96633ad5..d0f43f108 100644 --- a/lua/neogit/popups/worktree/actions.lua +++ b/lua/neogit/popups/worktree/actions.lua @@ -51,7 +51,7 @@ M.checkout_worktree = operations("checkout_worktree", function() if git.worktree.add(selected, path) then notification.info("Added worktree") if status.is_open() then - status.instance:chdir(path) + status.instance():chdir(path) end end end) @@ -77,7 +77,7 @@ M.create_worktree = operations("create_worktree", function() if git.worktree.add(selected, path, { "-b", name }) then notification.info("Added worktree") if status.is_open() then - status.instance:chdir(path) + status.instance():chdir(path) end end end) @@ -108,7 +108,7 @@ M.move = operations("move_worktree", function() notification.info(("Moved worktree to %s"):format(path)) if change_dir and status.is_open() then - status.instance:chdir(path) + status.instance():chdir(path) end end end) @@ -133,7 +133,7 @@ M.delete = operations("delete_worktree", function() if input.get_permission("Remove worktree?") then if change_dir and status.is_open() then - status.instance:chdir(git.worktree.main().path) + status.instance():chdir(git.worktree.main().path) end -- This might produce some error messages that need to get suppressed @@ -165,7 +165,7 @@ M.visit = operations("visit_worktree", function() local selected = FuzzyFinderBuffer.new(options):open_async { prompt_prefix = "visit worktree" } if selected and status.is_open() then - status.instance:chdir(selected) + status.instance():chdir(selected) end end) diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 3f6cdb535..0f34585e5 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -10,10 +10,10 @@ local logger = require("neogit.logger") local Watcher = {} Watcher.__index = Watcher -function Watcher.new(status_buffer, repo) +function Watcher.new(status_buffer, root) local instance = { status_buffer = status_buffer, - git_root = repo:git_path():absolute(), + git_root = root, running = false, fs_event_handler = assert(vim.loop.new_fs_event()), } From fa6144b0e5a285be2f713b6af76a7106dec56fba Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 16:09:52 +0200 Subject: [PATCH 306/443] fix type --- lua/neogit/lib/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 4923d54d2..e3f48f5da 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -497,7 +497,7 @@ end --- @param hash? integer|fun(...): any Function that determines id from arguments to fn --- @return F Debounced function. function M.debounce_trailing(ms, fn, hash) - local running = {} --- @type table + local running = {} --- @type table if type(hash) == "number" then local hash_i = hash From 2f8d330ba97f3fbc30d495220f82e7f6caedceb1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 16:10:12 +0200 Subject: [PATCH 307/443] Fix for new repos that have no commits. Use a default value. --- lua/neogit/lib/git/log.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 8597f0cf7..bdd316d9d 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -529,7 +529,12 @@ function M.reflog_message(skip) end M.abbreviated_size = util.memoize(function() - return string.len(M.list({ "HEAD", "--max-count=1" })[1].abbreviated_commit) + local commits = M.list { "HEAD", "--max-count=1" } + if vim.tbl_isempty(commits) then + return 7 + else + return string.len(commits[1].abbreviated_commit) + end end, { timeout = math.huge }) return M From 677246ece0652389c59454462136e3849daca3ed Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 16:10:52 +0200 Subject: [PATCH 308/443] Use initialized values instead of requiring them directly --- lua/neogit.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 9b5eaa896..48311fc4f 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -187,7 +187,6 @@ end ---@param args table? CLI arguments to pass to git command ---@return function function M.action(popup, action, args) - local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") local a = require("plenary.async") @@ -215,7 +214,7 @@ function M.action(popup, action, args) end, } else - notification.error( + M.notification.error( string.format( "Invalid action %s for %s popup\nValid actions are: %s", action, @@ -225,7 +224,7 @@ function M.action(popup, action, args) ) end else - notification.error("Invalid popup: " .. popup) + M.notification.error("Invalid popup: " .. popup) end end) end @@ -284,7 +283,7 @@ function M.get_log_file_path() end function M.get_config() - return require("neogit.config").values + return M.config.values end return M From be605ac0ede3efe309176555b81e1cbcc9707edd Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 19:27:43 +0200 Subject: [PATCH 309/443] Fix: Use absolute path instead of escaped relative path when opening a file. This wouldn't work if the CWD is not the git root. --- lua/neogit/buffers/status/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index bc8a8ec14..ecd8e0e0a 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -888,7 +888,7 @@ function M:open(kind, cwd) local item = self.buffer.ui:get_item_under_cursor() -- Goto FILE - if item and item.escaped_path then + if item and item.absolute_path then local cursor -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. if rawget(item, "diff") then @@ -915,7 +915,7 @@ function M:open(kind, cwd) self:close() vim.schedule(function() - vim.cmd("edit! " .. item.escaped_path) + vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) if cursor then api.nvim_win_set_cursor(0, cursor) From f3eaff35e02149da5842ab1b84a4081d4422811b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 19:49:48 +0200 Subject: [PATCH 310/443] Fix: Use a safer means to get the section name, which works when the cursor is in the header. Also, handle when there is no section name, but a commit hash, passed to diff view --- lua/neogit/buffers/status/init.lua | 6 +++--- lua/neogit/integrations/diffview.lua | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ecd8e0e0a..f77cb97ee 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -365,7 +365,7 @@ function M:open(kind, cwd) p { name = stash and stash:match("^stash@{%d+}") } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local section = self.buffer.ui:get_current_section().options.section + local section = self.buffer.ui:get_selection().sections[1] local item = self.buffer.ui:get_yankable_under_cursor() p { section = { name = section }, item = { name = item } } end), @@ -1065,7 +1065,7 @@ function M:open(kind, cwd) p { name = stash and stash:match("^stash@{%d+}") } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local section = self.buffer.ui:get_current_section().options.section + local section = self.buffer.ui:get_selection().sections[1] local item = self.buffer.ui:get_yankable_under_cursor() p { section = { name = section }, @@ -1082,7 +1082,7 @@ function M:open(kind, cwd) [popups.mapping_for("HelpPopup")] = popups.open("help", function(p) -- Since any other popup can be launched from help, build an ENV for any of them. local path = self.buffer.ui:get_hunk_or_filename_under_cursor() - local section = self.buffer.ui:get_current_section().options.section + local section = self.buffer.ui:get_selection().sections[1] local item = self.buffer.ui:get_yankable_under_cursor() local stash = self.buffer.ui:get_yankable_under_cursor() local commit = self.buffer.ui:get_commit_under_cursor() diff --git a/lua/neogit/integrations/diffview.lua b/lua/neogit/integrations/diffview.lua index 158a4a94a..3de0ccd16 100644 --- a/lua/neogit/integrations/diffview.lua +++ b/lua/neogit/integrations/diffview.lua @@ -142,8 +142,10 @@ function M.open(section_name, item_name, opts) local range if type(item_name) == "table" then range = string.format("%s..%s", item_name[1], item_name[#item_name]) - else + elseif item_name ~= nil then range = string.format("%s^!", item_name:match("[a-f0-9]+")) + else + return end view = dv_lib.diffview_open(dv_utils.tbl_pack(range)) @@ -156,8 +158,10 @@ function M.open(section_name, item_name, opts) view = dv_lib.diffview_open(dv_utils.tbl_pack(stash_id .. "^!")) elseif section_name == "commit" then view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) - else + elseif section_name ~= nil then view = get_local_diff_view(section_name, item_name, opts) + else + view = dv_lib.diffview_open(dv_utils.tbl_pack(item_name .. "^!")) end if view then From 3590b9d14d7505cf9ef07a304454ceca0b8bac18 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 19:58:24 +0200 Subject: [PATCH 311/443] Fix: When there is already a neogit instance for the DIR open, focus it and bail. --- lua/neogit/buffers/status/init.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index f77cb97ee..94e942882 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -43,10 +43,6 @@ function M.register(instance, dir) instances[dir] = instance end -function M.unregister() - instances[vim.uv.cwd()] = nil -end - function M.instance() return instances[vim.uv.cwd()] end @@ -80,8 +76,9 @@ end ---@param cwd string function M:open(kind, cwd) if M.is_open() then - logger.debug("[STATUS] An Instance is already open - closing it") - M.instance():close() + logger.debug("[STATUS] An Instance is already open - focusing it") + M.instance():focus() + return end M.register(self, cwd) @@ -101,7 +98,6 @@ function M:open(kind, cwd) end vim.o.autochdir = self.prev_autochdir - M.unregister() end, autocmds = { ["BufEnter"] = function() @@ -1164,8 +1160,6 @@ function M:close() if self.prev_autochdir then vim.o.autochdir = self.prev_autochdir end - - M.unregister() end function M:chdir(dir) From d870d7a8f66ad81859098be732ae4b789b62c9f6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 20:01:45 +0200 Subject: [PATCH 312/443] Fix: When neogit is the last tab page, and the user attempts to close it, vim will throw an error. Instead, if this happens, catch the error, make a _new_ tabpage, and then close neogit. --- lua/neogit/lib/buffer.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b2609f69e..a8534c4a5 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -232,7 +232,12 @@ function Buffer:close(force) end if self.kind == "tab" then - vim.cmd("tabclose") + local ok, _ = pcall(vim.cmd, "tabclose") + if not ok then + vim.cmd("tabnew") + vim.cmd("tabclose #") + end + return end @@ -689,7 +694,7 @@ function Buffer.create(config) on_detach = function() logger.debug("[BUFFER:" .. buffer.handle .. "] Running on_detach") config.on_detach(buffer) - end + end, }) end From 7eb3d9c80f0a61ca391c808c75751281ac4bdb8f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 20:19:30 +0200 Subject: [PATCH 313/443] Fix: When the cursor is located _between_ sections, don't change the depth. --- lua/neogit/buffers/status/init.lua | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 94e942882..2f92e7066 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -424,7 +424,11 @@ function M:open(kind, cwd) [mappings["Depth1"]] = function() local section = self.buffer.ui:get_current_section() if section then - local start, _ = section:row_range_abs() + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + self.buffer:move_cursor(start) section:close_all_folds(self.buffer.ui) @@ -436,7 +440,11 @@ function M:open(kind, cwd) local row = self.buffer.ui:get_component_under_cursor() if section then - local start, _ = section:row_range_abs() + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + self.buffer:move_cursor(start) section:close_all_folds(self.buffer.ui) @@ -455,7 +463,11 @@ function M:open(kind, cwd) local context = self.buffer.ui:get_cursor_context() if section then - local start, _ = section:row_range_abs() + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + self.buffer:move_cursor(start) section:close_all_folds(self.buffer.ui) @@ -476,7 +488,11 @@ function M:open(kind, cwd) local context = self.buffer.ui:get_cursor_context() if section then - local start, _ = section:row_range_abs() + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + self.buffer:move_cursor(start) section:close_all_folds(self.buffer.ui) section:open_all_folds(self.buffer.ui, 3) From 6f9906bc1968dfb41c6184e427507803605dd8bf Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 3 Apr 2024 20:24:22 +0200 Subject: [PATCH 314/443] Fix: When discarding multiple unstaged files in visual mode, don't raise an error, but just discard the files instead. --- lua/neogit/buffers/status/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2f92e7066..8c5ec8072 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -154,7 +154,7 @@ function M:open(kind, cwd) if section.name == "untracked" then table.insert(untracked_files, item.escaped_path) elseif section.name == "unstaged" then - if selection.item.mode == "A" then + if item.mode == "A" then table.insert(new_files, item.escaped_path) else table.insert(unstaged_files, item.escaped_path) From 3ffb3e67b111c12830cc61137f0ca8112cb8f0a2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 5 Apr 2024 23:58:01 +0200 Subject: [PATCH 315/443] Better usage of diff invalidation --- lua/neogit/buffers/status/init.lua | 36 ++++++++++++++++++++---------- lua/neogit/lib/git/diff.lua | 4 +--- lua/neogit/lib/git/repository.lua | 3 +-- lua/neogit/lib/git/status.lua | 7 +----- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8c5ec8072..8d6653163 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -308,7 +308,7 @@ function M:open(kind, cwd) end if #files > 0 or #patches > 0 then - self:refresh() + self:refresh({ update_diffs = { "staged:*" } }) end end), [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) @@ -538,6 +538,7 @@ function M:open(kind, cwd) local section = selection.section.name local action, message, choices + local refresh = {} if selection.item and selection.item.first == fn.line(".") then -- Discard File if section == "untracked" then @@ -552,6 +553,7 @@ function M:open(kind, cwd) fn.delete(selection.item.escaped_path) end + refresh = { update_diffs = { "untracked:" .. selection.item.name } } elseif section == "unstaged" then if selection.item.mode:match("^[UA][UA]") then choices = { "&ours", "&theirs", "&conflict", "&abort" } @@ -572,6 +574,7 @@ function M:open(kind, cwd) git.status.stage { selection.item.name } end end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } else message = ("Discard %q?"):format(selection.item.name) action = function() @@ -591,6 +594,7 @@ function M:open(kind, cwd) end end end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } elseif section == "staged" then message = ("Discard %q?"):format(selection.item.name) action = function() @@ -621,11 +625,13 @@ function M:open(kind, cwd) ) end end + refresh = { update_diffs = { "staged:" .. selection.item.name } } elseif section == "stashes" then message = ("Discard %q?"):format(selection.item.name) action = function() git.stash.drop(selection.item.name:match("(stash@{%d+})")) end + refresh = {} end elseif selection.item then -- Discard Hunk if selection.item.mode == "UU" then @@ -651,16 +657,19 @@ function M:open(kind, cwd) git.index.apply(patch, { reverse = true }) git.index.apply(patch, { reverse = true }) end + refresh = { update_diffs = { "untracked:" .. selection.item.name } } elseif section == "unstaged" then message = "Discard hunk?" action = function() git.index.apply(patch, { reverse = true }) end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } elseif section == "staged" then message = "Discard hunk?" action = function() git.index.apply(patch, { index = true, reverse = true }) end + refresh = { update_diffs = { "staged:" .. selection.item.name } } end else -- Discard Section if section == "untracked" then @@ -677,6 +686,7 @@ function M:open(kind, cwd) fn.delete(file.escaped_path) end end + refresh = { update_diffs = { "untracked:*" } } elseif section == "unstaged" then local conflict = false for _, item in ipairs(selection.section.items) do @@ -695,6 +705,7 @@ function M:open(kind, cwd) action = function() git.index.checkout_unstaged() end + refresh = { update_diffs = { "unstaged:*" } } end elseif section == "staged" then message = ("Discard %s files?"):format(#selection.section.items) @@ -752,6 +763,7 @@ function M:open(kind, cwd) git.index.checkout(staged_files_deleted) end end + refresh = { update_diffs = { "staged:*" } } elseif section == "stashes" then message = ("Discard %s stashes?"):format(#selection.section.items) action = function() @@ -764,7 +776,7 @@ function M:open(kind, cwd) if action and (choices or input.get_permission(message)) then action() - self:refresh() + self:refresh(refresh) end end), [mappings["GoToNextHunkHeader"]] = function() @@ -834,23 +846,23 @@ function M:open(kind, cwd) git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) - self:refresh() + self:refresh({ update_diffs = { "*:" .. item.escaped_path } }) elseif stagable.filename then if section.options.section == "unstaged" then git.status.stage { stagable.filename } - self:refresh() + self:refresh({ update_diffs = { "unstaged:" .. stagable.filename } }) elseif section.options.section == "untracked" then git.index.add { stagable.filename } - self:refresh() + self:refresh({ update_diffs = { "untracked:" .. stagable.filename } }) end end elseif section then if section.options.section == "untracked" then git.status.stage_untracked() - self:refresh() + self:refresh({ update_diffs = { "untracked:*" } }) elseif section.options.section == "unstaged" then git.status.stage_modified() - self:refresh() + self:refresh({ update_diffs = { "unstaged:*" } }) end end end), @@ -860,7 +872,7 @@ function M:open(kind, cwd) end), [mappings["StageUnstaged"]] = a.void(function() git.status.stage_modified() - self:refresh() + self:refresh({ update_diffs = { "unstaged:*" } }) end), [mappings["Unstage"]] = a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -882,19 +894,19 @@ function M:open(kind, cwd) ) git.index.apply(patch, { cached = true, reverse = true }) - self:refresh() + self:refresh({ update_diffs = { "*:" .. item.escaped_path } }) elseif unstagable.filename then git.status.unstage { unstagable.filename } - self:refresh() + self:refresh({ update_diffs = { "*:" .. unstagable.filename } }) end elseif section then git.status.unstage_all() - self:refresh() + self:refresh({ update_diffs = { "staged:*" } }) end end), [mappings["UnstageStaged"]] = a.void(function() git.status.unstage_all() - self:refresh() + self:refresh({ update_diffs = { "staged:*" } }) end), [mappings["GoToFile"]] = function() local item = self.buffer.ui:get_item_under_cursor() diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 62c81238b..abef86d96 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -204,8 +204,6 @@ local function build_metatable(f, raw_output_fn) end end, }) - - f.has_diff = true end -- Doing a git-diff with untracked files will exit(1) if a difference is observed, which we can ignore. @@ -249,7 +247,7 @@ end local function invalidate_diff(filter, section, item) if not filter or filter:accepts(section, item.name) then - logger.fmt_debug("[DIFF] Invalidating cached diff for: %s", item.name) + logger.debug(("[DIFF] Invalidating cached diff for: %s"):format(item.name)) item.diff = nil end end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index 7dd74362b..d8ed48110 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -205,13 +205,12 @@ function Repo:git_path(...) end function Repo:refresh(opts) - self.state.initialized = true - if self.git_root == "" then logger.debug("[REPO] No git root found - skipping refresh") return end + self.state.initialized = true opts = opts or {} logger.fmt_info("[REPO]: Refreshing START (source: %s)", opts.source or "UNKNOWN") diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 5a281c4fd..4c862a1f0 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -5,7 +5,6 @@ local Collection = require("neogit.lib.collection") ---@class StatusItem ---@field mode string ----@field has_diff boolean ---@field diff string[] ---@field absolute_path string ---@field escaped_path string @@ -13,15 +12,12 @@ local Collection = require("neogit.lib.collection") ---@return StatusItem local function update_file(cwd, file, mode, name, original_name) - local mt, diff, has_diff - local absolute_path = Path:new(cwd, name):absolute() local escaped_path = vim.fn.fnameescape(vim.fn.fnamemodify(absolute_path, ":~:.")) + local mt, diff if file then mt = getmetatable(file) - has_diff = file.has_diff - if rawget(file, "diff") then diff = file.diff end @@ -31,7 +27,6 @@ local function update_file(cwd, file, mode, name, original_name) mode = mode, name = name, original_name = original_name, - has_diff = has_diff, diff = diff, absolute_path = absolute_path, escaped_path = escaped_path, From 9b7abbf6d2a6a81dd8b80396ca2077fa0bed5b94 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 5 Apr 2024 23:59:08 +0200 Subject: [PATCH 316/443] Ensure foldmethod is set to "manual" when setting folds. Fixes a bug report from a user --- lua/neogit/lib/buffer.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index a8534c4a5..831860bfc 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -194,6 +194,8 @@ function Buffer:flush_fold_buffer() end function Buffer:set_folds(folds) + self:set_window_option("foldmethod", "manual") + for _, fold in ipairs(folds) do self:create_fold(unpack(fold)) self:set_fold_state(unpack(fold)) From 814cff7b6ffd04de8264219ddb091c67e616df76 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 6 Apr 2024 00:00:51 +0200 Subject: [PATCH 317/443] Copy fold status over better, and set up internal data structure for statuscolumn --- lua/neogit/buffers/status/ui.lua | 11 +++--- lua/neogit/lib/ui/init.lua | 59 +++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 47a6ac4e1..33e5803da 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -213,8 +213,8 @@ local SectionItemFile = function(section) -- Set fold state when called from ui:update() if prefix then local key = ("%s--%s"):format(prefix, hunk.hash) - if ui._old_node_attributes and ui._old_node_attributes[key] then - hunk._folded = ui._old_node_attributes[key].folded + if ui._node_fold_state and ui._node_fold_state[key] then + hunk._folded = ui._node_fold_state[key].folded end end end @@ -251,10 +251,13 @@ local SectionItemFile = function(section) mode_text = util.pad_right(mode, 11) end + local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name + local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) + return col.tag("File")({ row { - text.highlight(("NeogitChange%s"):format(mode:gsub(" ", "")))(mode_text), - text(item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name), + text.highlight(highlight)(mode_text), + text(name), }, }, { foldable = true, diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 6eae0050b..dd6624609 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -543,7 +543,7 @@ local function node_prefix(node, prefix) end end -local function gather_nodes(node, node_table, prefix) +local function folded_node_state(node, node_table, prefix) if not node_table then node_table = {} end @@ -558,14 +558,33 @@ local function gather_nodes(node, node_table, prefix) if node.children and not base then for _, child in ipairs(node.children) do - gather_nodes(child, node_table, prefix) + folded_node_state(child, node_table, prefix) end end return node_table end -function Ui:_update_attributes(node, attributes, prefix) +function Ui:_update_fold_state(node, attributes, prefix) + prefix = prefix or "" + + local key, base = node_prefix(node, prefix) + if key then + prefix = key + + if attributes[prefix] then + node.options.folded = attributes[prefix].folded + end + end + + if node.children and not base then + for _, child in ipairs(node.children) do + self:_update_fold_state(child, attributes, prefix) + end + end +end + +function Ui:_update_on_open(node, attributes, prefix) prefix = prefix or "" local key, base = node_prefix(node, prefix) @@ -577,14 +596,12 @@ function Ui:_update_attributes(node, attributes, prefix) if node.options.on_open and not attributes[prefix].folded then node.options.on_open(node, self, prefix) end - - node.options.folded = attributes[prefix].folded end end if node.children and not base then for _, child in ipairs(node.children) do - self:_update_attributes(child, attributes, prefix) + self:_update_on_open(child, attributes, prefix) end end end @@ -596,7 +613,7 @@ function Ui:render(...) end)() if not vim.tbl_isempty(self.layout) then - self._old_node_attributes = gather_nodes(self.layout) + self._node_fold_state = folded_node_state(self.layout) end self.layout = root @@ -609,6 +626,11 @@ function Ui:update() return end + -- Copy over the old fold state _before_ buffer is rendered so the output of the fold buffer is correct + if self._node_fold_state then + self:_update_fold_state(self.layout, self._node_fold_state) + end + local renderer = Renderer:new(self.layout, self.buf):render() self.node_index = renderer:node_index() self.item_index = renderer:item_index() @@ -624,13 +646,26 @@ function Ui:update() self.buf:set_extmarks(renderer.buffer.extmark) self.buf:set_line_highlights(renderer.buffer.line_highlight) self.buf:set_folds(renderer.buffer.fold) - self.buf:lock() - self.buf:move_cursor(math.min(cursor_line, #renderer.buffer.line)) - if self._old_node_attributes then - self:_update_attributes(self.layout, self._old_node_attributes) - self._old_node_attributes = nil + self.statuscolumn = {} + self.statuscolumn.foldmarkers = {} + + for i = 1, #renderer.buffer.line do + self.statuscolumn.foldmarkers[i] = false end + + for _, fold in ipairs(renderer.buffer.fold) do + self.statuscolumn.foldmarkers[fold[1]] = true + end + + -- Run on_open callbacks for hunks once buffer is rendered + if self._node_fold_state then + self:_update_on_open(self.layout, self._node_fold_state) + self._node_fold_state = nil + end + + self.buf:lock() + self.buf:move_cursor(math.min(cursor_line, #renderer.buffer.line)) end Ui.col = Component.new(function(children, options) From 5ffe26bf6524cc33761605da695574b3da2a6d18 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 6 Apr 2024 00:01:44 +0200 Subject: [PATCH 318/443] Use absolute paths when opening files so we can handle non-cwd instances --- lua/neogit/buffers/status/init.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 8d6653163..f2666341b 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -33,6 +33,7 @@ local fn = vim.fn ---@field state NeogitRepo ---@field config NeogitConfig ---@field frozen boolean +---@field root string ---@field refresh_lock Semaphore local M = {} M.__index = M @@ -958,7 +959,7 @@ function M:open(kind, cwd) [mappings["TabOpen"]] = function() local item = self.buffer.ui:get_item_under_cursor() - if item and item.escaped_path then + if item and item.absolute_path then local cursor -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. if rawget(item, "diff") then @@ -982,7 +983,7 @@ function M:open(kind, cwd) end end - vim.cmd.tabedit(item.escaped_path) + vim.cmd.tabedit(fn.fnameescape(item.absolute_path)) if cursor then api.nvim_win_set_cursor(0, cursor) end @@ -991,7 +992,7 @@ function M:open(kind, cwd) [mappings["SplitOpen"]] = function() local item = self.buffer.ui:get_item_under_cursor() - if item and item.escaped_path then + if item and item.absolute_path then local cursor -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. if rawget(item, "diff") then @@ -1015,7 +1016,7 @@ function M:open(kind, cwd) end end - vim.cmd.split(item.escaped_path) + vim.cmd.split(fn.fnameescape(item.absolute_path)) if cursor then api.nvim_win_set_cursor(0, cursor) end @@ -1024,7 +1025,7 @@ function M:open(kind, cwd) [mappings["VSplitOpen"]] = function() local item = self.buffer.ui:get_item_under_cursor() - if item and item.escaped_path then + if item and item.absolute_path then local cursor -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. if rawget(item, "diff") then @@ -1048,7 +1049,7 @@ function M:open(kind, cwd) end end - vim.cmd.vsplit(item.escaped_path) + vim.cmd.vsplit(fn.fnameescape(item.absolute_path)) if cursor then api.nvim_win_set_cursor(0, cursor) end From eecfc95f309a75a1abb7c2c73e66f866d6eebb95 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 6 Apr 2024 00:02:25 +0200 Subject: [PATCH 319/443] Feat: when using j/k, skip over "blank" lines between sections --- lua/neogit/buffers/status/init.lua | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index f2666341b..4a365a471 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -380,6 +380,33 @@ function M:open(kind, cwd) [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), }, n = { + ["j"] = function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "j") + else + vim.cmd("norm! j") + end + + if self.buffer:get_current_line()[1] == "" then + vim.cmd("norm! j") + end + + -- TODO: The renderer should trim the last empty line instead of this + if self.buffer:cursor_line() == fn.line("$") then + vim.cmd("norm! k") + end + end, + ["k"] = function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "k") + else + vim.cmd("norm! k") + end + + if self.buffer:get_current_line()[1] == "" then + vim.cmd("norm! k") + end + end, [mappings["Toggle"]] = function() local fold = self.buffer.ui:get_fold_under_cursor() if fold then From 9f07dea109bf23b882174b3ac36cc0aede97c7ce Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 6 Apr 2024 23:07:59 +0200 Subject: [PATCH 320/443] Reimplement fold markers in the status buffer --- lua/neogit/buffers/status/init.lua | 39 +++++++++++++++++++++++++++--- lua/neogit/buffers/status/ui.lua | 2 +- lua/neogit/lib/ui/init.lua | 2 +- lua/neogit/lib/ui/renderer.lua | 1 + 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 4a365a471..fbcafe723 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -92,7 +92,7 @@ function M:open(kind, cwd) context_highlight = true, kind = kind or config.values.kind, disable_line_numbers = config.values.disable_line_numbers, - status_column = " ", + status_column = "%{%v:lua.require('neogit.buffers.status').eval_statuscolumn()%}", on_detach = function() if self.watcher then self.watcher:stop() @@ -809,7 +809,7 @@ function M:open(kind, cwd) end), [mappings["GoToNextHunkHeader"]] = function() local c = self.buffer.ui:get_component_under_cursor(function(c) - return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "File" + return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "Item" end) local section = self.buffer.ui:get_current_section() @@ -819,7 +819,7 @@ function M:open(kind, cwd) if c.options.tag == "Diff" then next_location = fn.line(".") + 1 - elseif c.options.tag == "File" then + elseif c.options.tag == "Item" then vim.cmd("normal! zo") next_location = fn.line(".") + 1 elseif c.options.tag == "Hunk" then @@ -837,7 +837,7 @@ function M:open(kind, cwd) [mappings["GoToPreviousHunkHeader"]] = function() local function previous_hunk_header(self, line) local c = self.buffer.ui:get_component_on_line(line, function(c) - return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "File" + return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "Item" end) if c then @@ -1317,4 +1317,35 @@ function M:_get_refresh_lock(reason) return permit end +local function fold_opened() + return vim.fn.foldclosed(vim.v.lnum) == -1 +end + +function M.eval_statuscolumn() + if config.values.disable_signs and config.values.disable_line_numbers then + return " " + elseif config.values.disable_signs and not config.values.disable_line_numbers then + return "%l%r " + end + + local foldmarkers = M.instance().buffer.ui.statuscolumn.foldmarkers + + local fold + if foldmarkers[vim.v.lnum] then + if fold_opened() then + fold = config.values.signs[string.lower(foldmarkers[vim.v.lnum])][2] + else + fold = config.values.signs[string.lower(foldmarkers[vim.v.lnum])][1] + end + else + fold = " " + end + + if config.values.disable_line_numbers then + return ("%s "):format(fold) + else + return ("%s%s "):format("%l%r ", fold) + end +end + return M diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 33e5803da..2e997d197 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -254,7 +254,7 @@ local SectionItemFile = function(section) local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) - return col.tag("File")({ + return col.tag("Item")({ row { text.highlight(highlight)(mode_text), text(name), diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index dd6624609..4a9d9ef35 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -655,7 +655,7 @@ function Ui:update() end for _, fold in ipairs(renderer.buffer.fold) do - self.statuscolumn.foldmarkers[fold[1]] = true + self.statuscolumn.foldmarkers[fold[1]] = fold[4] end -- Run on_open callbacks for hunks once buffer is rendered diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 09ba9771d..1812ad645 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -227,6 +227,7 @@ function Renderer:_render_child(child) #self.buffer.line - (child.position.row_end - child.position.row_start), #self.buffer.line, not child.options.folded, + child.options.tag }) end end From 1f6ab9dc8c4e676ffdc4f649fe7e4074c9bfabce Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 6 Apr 2024 23:16:12 +0200 Subject: [PATCH 321/443] Reimplement the commit HASH in the head of the status buffer, configurable by user --- lua/neogit/buffers/status/ui.lua | 5 +++++ lua/neogit/config.lua | 3 +++ 2 files changed, 8 insertions(+) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 2e997d197..609392d28 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -85,6 +85,8 @@ local HEAD = Component.new(function(props) return row({ text(util.pad_right(props.name .. ":", 10)), + text.highlight("Comment")(props.show_oid and props.yankable:sub(1, 7) or ""), + text(props.show_oid and " " or ""), text.highlight(highlight)(ref), text(" "), text(props.msg or "(no commits)"), @@ -448,6 +450,7 @@ function M.Status(state, config) oid = state.head.abbrev, msg = state.head.commit_message, yankable = state.head.oid, + show_oid = config.show_head_commit_hash, }, show_upstream and HEAD { name = "Merge", @@ -455,6 +458,7 @@ function M.Status(state, config) remote = state.upstream.remote, msg = state.upstream.commit_message, yankable = state.upstream.oid, + show_oid = config.show_head_commit_hash, }, show_pushRemote and HEAD { name = "Push", @@ -462,6 +466,7 @@ function M.Status(state, config) remote = state.pushRemote.remote, msg = state.pushRemote.commit_message, yankable = state.pushRemote.oid, + show_oid = config.show_head_commit_hash, }, show_tag and Tag { name = state.head.tag.name, diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 5a41fb926..7e7a9f558 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -234,6 +234,7 @@ end ---@field sort_branches? string Value used for `--sort` for the `git branch` command ---@field kind? WindowKind The default type of window neogit should open in ---@field disable_line_numbers? boolean Whether to disable line numbers +---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout ---@field status? { recent_commit_count: integer } Status buffer options @@ -285,6 +286,7 @@ function M.get_default_values() }, disable_insert_on_commit = "auto", use_per_project_settings = true, + show_head_commit_hash = true, remember_settings = true, fetch_after_checkout = false, auto_refresh = true, @@ -941,6 +943,7 @@ function M.validate_config() if validate_type(config, "base config", "table") then validate_type(config.disable_hint, "disable_hint", "boolean") validate_type(config.disable_context_highlighting, "disable_context_highlighting", "boolean") + validate_type(config.show_head_commit_hash, "show_head_commit_hash", "boolean") validate_type(config.disable_signs, "disable_signs", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") From a49eabeea85dfc06feaec97c662ca818fdd8e59b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 7 Apr 2024 21:30:39 +0200 Subject: [PATCH 322/443] More robust ignoring of exit(1) when aborting an edit --- lua/neogit/client.lua | 1 + lua/neogit/lib/git/cli.lua | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 8167fdce7..1b5d86f80 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -153,6 +153,7 @@ function M.wrap(cmd, opts) notification.warn(opts.msg.fail, { dismiss = true }) end end + return result.code end diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 955130bba..d80dd9304 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -918,7 +918,14 @@ local function new_builder(subcommand) local handle_line = opts.handle_line or handle_interactive_password_questions local p = to_process { verbose = opts.verbose, - on_error = function(_res) + on_error = function(res) + -- When aborting, don't alert the user. exit(1) is expected. + for _, line in ipairs(res.stdout) do + if line:match("^hint: Waiting for your editor to close the file...") then + return false + end + end + return true end, } @@ -959,10 +966,11 @@ local function new_builder(subcommand) local p = to_process { verbose = opts.verbose, on_error = function(res) - local commit_aborted_msg = "hint: Waiting for your editor to close the file..." - - if vim.startswith(res.stdout[1], commit_aborted_msg) then - return false + -- When aborting, don't alert the user. exit(1) is expected. + for _, line in ipairs(res.stdout) do + if line:match("^hint: Waiting for your editor to close the file...") then + return false + end end return not opts.ignore_error From 7a60374f341b87923bc11b46bd4ffb9c673ccd59 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 7 Apr 2024 22:09:46 +0200 Subject: [PATCH 323/443] Fix: When opening status buffer in a brand new repo, it'll encounter an error with this call and alert the user, but it should be ignored. --- lua/neogit/lib/git/cli.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index d80dd9304..17adf3fb8 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -973,6 +973,11 @@ local function new_builder(subcommand) end end + -- When opening in a brand new repo, HEAD will cause an error. + if res.stderr[1] == "fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree." then + return false + end + return not opts.ignore_error end, } From 07042679c3ec3b90e16904b56cd7954df6f88658 Mon Sep 17 00:00:00 2001 From: AThePeanut4 <49614525+AThePeanut4@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:26:04 +0200 Subject: [PATCH 324/443] Fix props.yankable error, display (detached) again --- lua/neogit/buffers/status/ui.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 609392d28..d5252cbf7 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -71,10 +71,12 @@ local HINT = Component.new(function(props) end) local HEAD = Component.new(function(props) + local show_oid = props.show_oid local highlight, ref if props.branch == "(detached)" then - highlight = "Comment" - ref = props.oid + highlight = "NeogitBranch" + ref = props.branch + show_oid = true elseif props.remote then highlight = "NeogitRemote" ref = ("%s/%s"):format(props.remote, props.branch) @@ -83,10 +85,17 @@ local HEAD = Component.new(function(props) ref = props.branch end + local oid = props.yankable + if not oid or oid == "(initial)" then + oid = "0000000" + else + oid = oid:sub(1, 7) + end + return row({ text(util.pad_right(props.name .. ":", 10)), - text.highlight("Comment")(props.show_oid and props.yankable:sub(1, 7) or ""), - text(props.show_oid and " " or ""), + text.highlight("Comment")(show_oid and oid or ""), + text(show_oid and " " or ""), text.highlight(highlight)(ref), text(" "), text(props.msg or "(no commits)"), From e79a70e473d9516cb51136a7b99874ab31c8b593 Mon Sep 17 00:00:00 2001 From: AThePeanut4 <49614525+AThePeanut4@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:50:39 +0200 Subject: [PATCH 325/443] Fix EmptyLine component --- lua/neogit/buffers/common.lua | 4 ++++ lua/neogit/buffers/status/init.lua | 5 ----- lua/neogit/buffers/status/ui.lua | 16 ++++++++-------- lua/neogit/lib/popup/ui.lua | 9 ++++----- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 7a0c7360d..d9254ed27 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -14,6 +14,10 @@ local range = util.range local M = {} +M.EmptyLine = Component.new(function() + return col { row { text("") } } +end) + local diff_add_start = "+" local diff_delete_start = "-" diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index fbcafe723..f232b58de 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -390,11 +390,6 @@ function M:open(kind, cwd) if self.buffer:get_current_line()[1] == "" then vim.cmd("norm! j") end - - -- TODO: The renderer should trim the last empty line instead of this - if self.buffer:cursor_line() == fn.line("$") then - vim.cmd("norm! k") - end end, ["k"] = function() if vim.v.count > 0 then diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index d5252cbf7..1e59447d7 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -10,9 +10,9 @@ local text = Ui.text local map = util.map +local EmptyLine = common.EmptyLine local List = common.List local DiffHunks = common.DiffHunks -local EmptyLine = col { row { text("") } } local M = {} @@ -161,7 +161,7 @@ local Section = Component.new(function(props) return col.tag("Section")({ row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), col(map(props.items, props.render)), - EmptyLine, + EmptyLine(), }, { foldable = true, folded = props.folded, @@ -174,7 +174,7 @@ local SequencerSection = Component.new(function(props) return col.tag("Section")({ row(util.merge(props.title)), col(map(props.items, props.render)), - EmptyLine, + EmptyLine(), }, { foldable = true, folded = props.folded, @@ -193,7 +193,7 @@ local RebaseSection = Component.new(function(props) text(")"), })), col(map(props.items, props.render)), - EmptyLine, + EmptyLine(), }, { foldable = true, folded = props.folded, @@ -373,12 +373,12 @@ local BisectDetailsSection = Component.new(function(props) text((props.commit.committer_name or "") .. " <" .. (props.commit.committer_email or "") .. ">"), }, row { text.highlight("Comment")("CommitDate: "), text(props.commit.committer_date) }, - EmptyLine, + EmptyLine(), col( map(props.commit.description, text), { highlight = "NeogitCommitViewDescription", tag = "Description" } ), - EmptyLine, + EmptyLine(), }, { foldable = true, folded = props.folded, @@ -452,7 +452,7 @@ function M.Status(state, config) List { items = { show_hint and HINT { config = config }, - show_hint and EmptyLine, + show_hint and EmptyLine(), HEAD { name = "Head", branch = state.head.branch, @@ -482,7 +482,7 @@ function M.Status(state, config) distance = show_tag_distance and state.head.tag.distance, yankable = state.head.tag.oid, }, - EmptyLine, + EmptyLine(), show_merge and SequencerSection { title = SectionTitleMerge { title = "Merging", branch = state.merge.branch }, render = SectionItemSequencer, diff --git a/lua/neogit/lib/popup/ui.lua b/lua/neogit/lib/popup/ui.lua index e2da5ecfb..29f758bf6 100644 --- a/lua/neogit/lib/popup/ui.lua +++ b/lua/neogit/lib/popup/ui.lua @@ -4,6 +4,7 @@ local common = require("neogit.buffers.common") local Ui = require("neogit.lib.ui") local util = require("neogit.lib.util") +local EmptyLine = common.EmptyLine local List = common.List local Grid = common.Grid local col = Ui.col @@ -15,8 +16,6 @@ local intersperse = util.intersperse local filter_map = util.filter_map local map = util.map -local EmptyLine = col { row { text("") } } - -- Builds config component to be rendered ---@return table local function construct_config_options(config, prefix, suffix) @@ -237,7 +236,7 @@ function M.items(state) if state.config[1] then table.insert(items, Config { state = state.config }) - table.insert(items, EmptyLine) + table.insert(items, EmptyLine()) end if state.args[1] then @@ -251,7 +250,7 @@ function M.items(state) elseif item.type == "heading" then if section[1] then -- If there are items in the section, flush to items table with current name table.insert(items, Section(name, section)) - table.insert(items, EmptyLine) + table.insert(items, EmptyLine()) section = {} end @@ -260,7 +259,7 @@ function M.items(state) end table.insert(items, Section(name, section)) - table.insert(items, EmptyLine) + table.insert(items, EmptyLine()) end if state.actions[1] then From 4ddc5dbbe1b1c3c051aed7829ff6acd3b3388955 Mon Sep 17 00:00:00 2001 From: AThePeanut4 <49614525+AThePeanut4@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:51:00 +0200 Subject: [PATCH 326/443] Allow folding on empty line again --- lua/neogit/buffers/status/init.lua | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index f232b58de..5f07c6285 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -405,17 +405,6 @@ function M:open(kind, cwd) [mappings["Toggle"]] = function() local fold = self.buffer.ui:get_fold_under_cursor() if fold then - -- Do not allow folding on the last (empty) line of a section. It should be considered "not part of either - -- section" from a UX perspective. Only applies to unfolded sections. - if - fold.options.tag == "Section" - and not fold.options.folded - and self.buffer:get_current_line()[1] == "" - then - logger.info("Toggle early return") - return - end - if fold.options.on_open then fold.options.on_open(fold, self.buffer.ui) else From ce364161121cd0786f3c82e81a9ced91993385b1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 7 Apr 2024 22:46:53 +0200 Subject: [PATCH 327/443] Fix: Due to this issue: https://github.com/NeogitOrg/neogit/issues/1236 Move the `lcd()` call to _after_ all buffer:call() callbacks. --- lua/neogit.lua | 11 ++++++++++- lua/neogit/buffers/status/init.lua | 19 +++++++++++++------ lua/neogit/lib/buffer.lua | 6 ++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index 48311fc4f..e41f899c7 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -105,12 +105,21 @@ end local function open_status_buffer(opts) local status = require("neogit.buffers.status") local config = require("neogit.config") + local a = require("plenary.async") -- We need to construct the repo instance manually here since the actual CWD may not be the directory neogit is -- going to open into. We will use vim.fn.lcd() in the status buffer constructor, so this will eventually be -- correct. local repo = require("neogit.lib.git.repository").instance(opts.cwd) - status.new(repo.state, config.values, repo.git_root):open(opts.kind, opts.cwd) + + local instance = status.new(repo.state, config.values, repo.git_root) + :open(opts.kind, opts.cwd) + + a.run(function() + repo:refresh({ callback = function() + instance:dispatch_refresh() + end }) + end) end ---@alias Popup diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index fbcafe723..2948ed19b 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -44,8 +44,10 @@ function M.register(instance, dir) instances[dir] = instance end -function M.instance() - return instances[vim.uv.cwd()] +---@param dir? string +---@return StatusBuffer +function M.instance(dir) + return instances[dir or vim.uv.cwd()] end ---@param state NeogitRepo @@ -75,11 +77,12 @@ end ---@param kind string<"floating" | "split" | "tab" | "split" | "vsplit">|nil ---@param cwd string +---@return StatusBuffer function M:open(kind, cwd) if M.is_open() then logger.debug("[STATUS] An Instance is already open - focusing it") M.instance():focus() - return + return M.instance() end M.register(self, cwd) @@ -89,6 +92,7 @@ function M:open(kind, cwd) self.buffer = Buffer.create { name = "NeogitStatus", filetype = "NeogitStatus", + cwd = cwd, context_highlight = true, kind = kind or config.values.kind, disable_line_numbers = config.values.disable_line_numbers, @@ -1175,9 +1179,6 @@ function M:open(kind, cwd) }, }, initialize = function() - vim.cmd.lcd(cwd) - self:dispatch_refresh(nil, "initialize") - self.prev_autochdir = vim.o.autochdir vim.o.autochdir = false end, @@ -1199,6 +1200,8 @@ function M:open(kind, cwd) buffer:move_cursor(buffer.ui:first_section().first) end, } + + return self end function M:close() @@ -1328,6 +1331,10 @@ function M.eval_statuscolumn() return "%l%r " end + if not M.is_open() then + return " " + end + local foldmarkers = M.instance().buffer.ui.statuscolumn.foldmarkers local fold diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 831860bfc..b355290ba 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -567,6 +567,7 @@ end ---@field bufhidden string|nil ---@field header string|nil ---@field buftype string|nil +---@field cwd string|nil ---@field status_column string|nil ---@field load boolean|nil ---@field context_highlight boolean|nil @@ -750,6 +751,11 @@ function Buffer.create(config) buffer:set_header(config.header) end + if config.cwd then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting CWD to: " .. config.cwd) + vim.cmd.lcd(config.cwd) + end + return buffer end From 18938e2bda1ff9db8a1bdab573a05fbfbb136b9c Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 7 Apr 2024 23:07:34 +0200 Subject: [PATCH 328/443] Nicer formatting for statuscolumn --- lua/neogit/buffers/status/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index c44fc7b62..227b4979a 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1333,9 +1333,9 @@ function M.eval_statuscolumn() end if config.values.disable_line_numbers then - return ("%s "):format(fold) + return ("%s"):format(fold) else - return ("%s%s "):format("%l%r ", fold) + return ("%s %s"):format("%l%r", fold) end end From 9df7b92b8034716fc658a42671a0967da9d2d699 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 21:25:41 +0200 Subject: [PATCH 329/443] Fix: Watch ./.git/ of repo --- lua/neogit/watcher.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/watcher.lua b/lua/neogit/watcher.lua index 0f34585e5..8301f5d97 100644 --- a/lua/neogit/watcher.lua +++ b/lua/neogit/watcher.lua @@ -1,6 +1,7 @@ -- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/watcher.lua#L103 local logger = require("neogit.logger") +local Path = require("plenary.path") ---@class Watcher ---@field git_root string @@ -13,7 +14,7 @@ Watcher.__index = Watcher function Watcher.new(status_buffer, root) local instance = { status_buffer = status_buffer, - git_root = root, + git_root = Path.new(root):joinpath(".git"):absolute(), running = false, fs_event_handler = assert(vim.loop.new_fs_event()), } From edbb87c32f4a1c48d87b5bc5a60fa25b552ff003 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 21:27:06 +0200 Subject: [PATCH 330/443] lint --- lua/neogit.lua | 11 ++++++----- lua/neogit/buffers/commit_view/init.lua | 5 +---- lua/neogit/buffers/editor/init.lua | 1 - lua/neogit/buffers/rebase_editor/init.lua | 9 ++++++--- lua/neogit/buffers/status/init.lua | 22 +++++++++++----------- lua/neogit/lib/git/cli.lua | 5 ++++- lua/neogit/lib/git/config.lua | 6 ++++-- lua/neogit/lib/git/diff.lua | 3 ++- lua/neogit/lib/git/files.lua | 4 +++- lua/neogit/lib/git/pull.lua | 6 ++++-- lua/neogit/lib/git/refs.lua | 3 ++- lua/neogit/lib/ui/renderer.lua | 2 +- tests/util/git_harness.lua | 2 +- 13 files changed, 45 insertions(+), 34 deletions(-) diff --git a/lua/neogit.lua b/lua/neogit.lua index e41f899c7..0530601a4 100644 --- a/lua/neogit.lua +++ b/lua/neogit.lua @@ -112,13 +112,14 @@ local function open_status_buffer(opts) -- correct. local repo = require("neogit.lib.git.repository").instance(opts.cwd) - local instance = status.new(repo.state, config.values, repo.git_root) - :open(opts.kind, opts.cwd) + local instance = status.new(repo.state, config.values, repo.git_root):open(opts.kind, opts.cwd) a.run(function() - repo:refresh({ callback = function() - instance:dispatch_refresh() - end }) + repo:refresh { + callback = function() + instance:dispatch_refresh() + end, + } end) end diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 976dee1a8..3c37648e7 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -86,10 +86,7 @@ end function M.open_or_run_in_window(commit_id, filter, cmd) assert(commit_id, "commit id cannot be nil") - if - M.is_open() - and M.instance.commit_info.commit_arg == commit_id - then + if M.is_open() and M.instance.commit_info.commit_arg == commit_id then M.instance.buffer:win_exec(cmd) else local cw = api.nvim_get_current_win() diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 05852160e..4dbd890ec 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -80,7 +80,6 @@ function M:open(kind) end require("neogit.process").defer_show_preview_buffers() - end, after = function(buffer) -- Populate help lines with mappings for buffer diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 8e933b32f..4d8a1d305 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -205,19 +205,22 @@ function M:open(kind) vim.cmd("move +1") end, [mapping["OpenCommit"]] = function() - local oid = vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") + local oid = + vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") if oid then CommitViewBuffer.new(oid):open("tab") end end, [mapping["OpenOrScrollDown"]] = function() - local oid = vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") + local oid = + vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") if oid then CommitViewBuffer.open_or_scroll_down(oid) end end, [mapping["OpenOrScrollUp"]] = function() - local oid = vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") + local oid = + vim.api.nvim_get_current_line():match("(" .. string.rep("%x", git.log.abbreviated_size()) .. ")") if oid then CommitViewBuffer.open_or_scroll_up(oid) end diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 227b4979a..af6802366 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -313,7 +313,7 @@ function M:open(kind, cwd) end if #files > 0 or #patches > 0 then - self:refresh({ update_diffs = { "staged:*" } }) + self:refresh { update_diffs = { "staged:*" } } end end), [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) @@ -862,23 +862,23 @@ function M:open(kind, cwd) git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) - self:refresh({ update_diffs = { "*:" .. item.escaped_path } }) + self:refresh { update_diffs = { "*:" .. item.escaped_path } } elseif stagable.filename then if section.options.section == "unstaged" then git.status.stage { stagable.filename } - self:refresh({ update_diffs = { "unstaged:" .. stagable.filename } }) + self:refresh { update_diffs = { "unstaged:" .. stagable.filename } } elseif section.options.section == "untracked" then git.index.add { stagable.filename } - self:refresh({ update_diffs = { "untracked:" .. stagable.filename } }) + self:refresh { update_diffs = { "untracked:" .. stagable.filename } } end end elseif section then if section.options.section == "untracked" then git.status.stage_untracked() - self:refresh({ update_diffs = { "untracked:*" } }) + self:refresh { update_diffs = { "untracked:*" } } elseif section.options.section == "unstaged" then git.status.stage_modified() - self:refresh({ update_diffs = { "unstaged:*" } }) + self:refresh { update_diffs = { "unstaged:*" } } end end end), @@ -888,7 +888,7 @@ function M:open(kind, cwd) end), [mappings["StageUnstaged"]] = a.void(function() git.status.stage_modified() - self:refresh({ update_diffs = { "unstaged:*" } }) + self:refresh { update_diffs = { "unstaged:*" } } end), [mappings["Unstage"]] = a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -910,19 +910,19 @@ function M:open(kind, cwd) ) git.index.apply(patch, { cached = true, reverse = true }) - self:refresh({ update_diffs = { "*:" .. item.escaped_path } }) + self:refresh { update_diffs = { "*:" .. item.escaped_path } } elseif unstagable.filename then git.status.unstage { unstagable.filename } - self:refresh({ update_diffs = { "*:" .. unstagable.filename } }) + self:refresh { update_diffs = { "*:" .. unstagable.filename } } end elseif section then git.status.unstage_all() - self:refresh({ update_diffs = { "staged:*" } }) + self:refresh { update_diffs = { "staged:*" } } end end), [mappings["UnstageStaged"]] = a.void(function() git.status.unstage_all() - self:refresh({ update_diffs = { "staged:*" } }) + self:refresh { update_diffs = { "staged:*" } } end), [mappings["GoToFile"]] = function() local item = self.buffer.ui:get_item_under_cursor() diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 17adf3fb8..9bff8819f 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -974,7 +974,10 @@ local function new_builder(subcommand) end -- When opening in a brand new repo, HEAD will cause an error. - if res.stderr[1] == "fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree." then + if + res.stderr[1] + == "fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree." + then return false end diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index 470b7f271..a85a2136e 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -81,8 +81,10 @@ end local function build_config() local result = {} - local out = - vim.split(table.concat(git.cli.config.list.null._local.call_sync({ hidden = true }).stdout_raw, "\0"), "\n") + local out = vim.split( + table.concat(git.cli.config.list.null._local.call_sync({ hidden = true }).stdout_raw, "\0"), + "\n" + ) for _, option in ipairs(out) do local key, value = unpack(vim.split(option, "\0")) diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index abef86d96..e37b0c48a 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -239,7 +239,8 @@ end local function raw_staged_renamed(name, original) return function() local diff = git.cli.diff.no_ext_diff.cached.files(name, original).call({ hidden = true }).stdout - local stats = git.cli.diff.no_ext_diff.cached.shortstat.files(name, original).call({ hidden = true }).stdout + local stats = + git.cli.diff.no_ext_diff.cached.shortstat.files(name, original).call({ hidden = true }).stdout return { diff, stats } end diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index ae3e9f75a..f9793b1e3 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -3,7 +3,9 @@ local git = require("neogit.lib.git") local M = {} function M.all() - return git.cli["ls-files"].full_name.deleted.modified.exclude_standard.deduplicate.call_sync({ hidden = true }).stdout + return git.cli["ls-files"].full_name.deleted.modified.exclude_standard.deduplicate.call_sync({ + hidden = true, + }).stdout end function M.untracked() diff --git a/lua/neogit/lib/git/pull.lua b/lua/neogit/lib/git/pull.lua index 9b8108c03..6e3bf9481 100644 --- a/lua/neogit/lib/git/pull.lua +++ b/lua/neogit/lib/git/pull.lua @@ -24,8 +24,10 @@ local function update_unpulled(state) local pushRemote = require("neogit.lib.git").branch.pushRemote_ref() if pushRemote then - state.pushRemote.unpulled.items = - util.filter_map(git.log.list({ string.format("..%s", pushRemote) }, nil, {}, true), git.log.present_commit) + state.pushRemote.unpulled.items = util.filter_map( + git.log.list({ string.format("..%s", pushRemote) }, nil, {}, true), + git.log.present_commit + ) end end diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index b629ea2e3..ded4fde49 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -66,7 +66,8 @@ local record_template = record.encode({ }, "ref") function M.list_parsed() - local refs = git.cli["for-each-ref"].format(record_template).show_popup(false).call({ hidden = true }).stdout + local refs = + git.cli["for-each-ref"].format(record_template).show_popup(false).call({ hidden = true }).stdout local result = record.decode(refs) local output = { diff --git a/lua/neogit/lib/ui/renderer.lua b/lua/neogit/lib/ui/renderer.lua index 1812ad645..e09e9eb84 100644 --- a/lua/neogit/lib/ui/renderer.lua +++ b/lua/neogit/lib/ui/renderer.lua @@ -227,7 +227,7 @@ function Renderer:_render_child(child) #self.buffer.line - (child.position.row_end - child.position.row_start), #self.buffer.line, not child.options.folded, - child.options.tag + child.options.tag, }) end end diff --git a/tests/util/git_harness.lua b/tests/util/git_harness.lua index 388328a43..1077f6dd3 100644 --- a/tests/util/git_harness.lua +++ b/tests/util/git_harness.lua @@ -54,7 +54,7 @@ end function M.in_prepared_repo(cb) return function() local dir = M.prepare_repository() - require("neogit").setup({}) + require("neogit").setup {} local status = require("neogit.buffers.status") vim.cmd("Neogit") From 9964072769b202c80791685c32a46ab03ba08ee7 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 21:41:34 +0200 Subject: [PATCH 331/443] Remove BufEnter autocmd - watcher should take care of refreshing --- lua/neogit/buffers/status/init.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index af6802366..c29efc5d3 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -104,11 +104,6 @@ function M:open(kind, cwd) vim.o.autochdir = self.prev_autochdir end, - autocmds = { - ["BufEnter"] = function() - self:dispatch_refresh() - end, - }, mappings = { v = { [mappings["Discard"]] = a.void(function() From 5c7c46693915fd81c0e6069a432f163dac8a6d75 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 22:23:16 +0200 Subject: [PATCH 332/443] Add git absorb support --- lua/neogit/popups/commit/actions.lua | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 51fa33182..c0d147117 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -149,4 +149,34 @@ function M.instant_squash(popup) commit_special(popup, "squash", { rebase = true, edit = false }) end +function M.absorb(popup) + if vim.fn.executable("git-absorb") == 0 then + notification.info("Absorb requires `https://github.com/tummychow/git-absorb` to be installed.") + return + end + + if not git.status.anything_staged() then + if git.status.anything_unstaged() then + if input.get_permission("Nothing is staged. Absorb all unstaged changed?") then + git.status.stage_modified() + else + return + end + else + notification.warn("There are no changes that could be absorbed") + return + end + end + + local commit = popup.state.env.commit or CommitSelectViewBuffer.new( + git.log.list { "HEAD" }, + "Select a base commit for the absorb stack with , or to abort" + ):open_async()[1] + if not commit then + return + end + + -- git.cli.absorb.verbose.base(commit).call() +end + return M From 14207ff0a030cb7e7282c82d16185bd3c8649cc3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 22:30:41 +0200 Subject: [PATCH 333/443] Add git-absorb to commit popup --- lua/neogit/popups/commit/actions.lua | 2 +- lua/neogit/popups/commit/init.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index c0d147117..95eb916ee 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -176,7 +176,7 @@ function M.absorb(popup) return end - -- git.cli.absorb.verbose.base(commit).call() + git.cli.absorb.verbose.base(commit).and_rebase.call() end return M diff --git a/lua/neogit/popups/commit/init.lua b/lua/neogit/popups/commit/init.lua index 7f0fc37da..6b9223519 100644 --- a/lua/neogit/popups/commit/init.lua +++ b/lua/neogit/popups/commit/init.lua @@ -18,6 +18,7 @@ function M.create(env) :option("C", "reuse-message", "", "Reuse commit message", { key_prefix = "-" }) :group_heading("Create") :action("c", "Commit", actions.commit) + :action("x", "Absorb", actions.absorb) :new_action_group("Edit HEAD") :action("e", "Extend", actions.extend) :action("w", "Reword", actions.reword) From c094fa124b8689e8045a39a6fd2c8c9da93d0d39 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 22:30:57 +0200 Subject: [PATCH 334/443] Add git-absorb to cli --- lua/neogit/lib/git/cli.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 9bff8819f..9bb284bc1 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -310,6 +310,20 @@ local configurations = { }, }, + absorb = config { + flags = { + verbose = "--verbose", + and_rebase = "--and-rebase" + }, + aliases = { + base = function(tbl) + return function(commit) + return tbl.args("--base", commit) + end + end + } + }, + commit = config { flags = { all = "--all", From 876f14b67496bd7b780b752cd49494b03f2fcb90 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Apr 2024 22:34:44 +0200 Subject: [PATCH 335/443] Add documentation for git absorb --- doc/neogit.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 2a5d38620..8a7737661 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -842,6 +842,16 @@ Actions: *neogit_commit_popup_actions* Creates a new commit on the current branch from the currently staged changes. + • Absorb *neogit_commit_absorb* + (Requires `https://github.com/tummychow/git-absorb`) + + `git absorb` will automatically identify which commits are safe to modify, + and which staged changes belong to each of those commits. It will then + write fixup! commits for each of those changes. + + The `--with-rebase` flag is passed, meaning these fixup commits will be + automatically integrated into the corresponding ones via rebase. + • Extend *neogit_commit_extend* Amends the last commit without editing the commit message. From 8edd1b7468af206936a03169c8b323d70211599b Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 9 Apr 2024 21:32:31 +0200 Subject: [PATCH 336/443] Add highlight groups for + an - signs with no background color --- lua/neogit/buffers/commit_view/ui.lua | 4 ++-- lua/neogit/lib/hl.lua | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index be2cf7671..76d8b58fc 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -16,8 +16,8 @@ function M.OverviewFile(file) text(" | "), text.highlight("Number")(util.pad_left(file.changes, 5)), text(" "), - text.highlight("NeogitDiffAdd")(file.insertions), - text.highlight("NeogitDiffDelete")(file.deletions), + text.highlight("NeogitDiffAdditions")(file.insertions), + text.highlight("NeogitDiffDeletetions")(file.deletions), } end diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 8dea970bc..19a4513d4 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -146,9 +146,11 @@ function M.setup() NeogitDiffContext = { bg = palette.bg1 }, NeogitDiffContextHighlight = { bg = palette.bg2 }, NeogitDiffContextCursor = { bg = palette.bg1 }, + NeogitDiffAdditions = { fg = palette.bg_green }, NeogitDiffAdd = { bg = palette.line_green, fg = palette.bg_green }, NeogitDiffAddHighlight = { bg = palette.line_green, fg = palette.green }, NeogitDiffAddCursor = { bg = palette.bg1, fg = palette.green }, + NeogitDiffDeletions = { fg = palette.bg_red }, NeogitDiffDelete = { bg = palette.line_red, fg = palette.bg_red }, NeogitDiffDeleteHighlight = { bg = palette.line_red, fg = palette.red }, NeogitDiffDeleteCursor = { bg = palette.bg1, fg = palette.red }, From 8e489a043981e59fb93f9630cb1bf35d1bc635a8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 9 Apr 2024 21:56:59 +0200 Subject: [PATCH 337/443] Add buffer for diffing staged files --- lua/neogit/buffers/diff/init.lua | 112 +++++++++++++++++++++++++++++++ lua/neogit/buffers/diff/ui.lua | 37 ++++++++++ lua/neogit/lib/buffer.lua | 10 +-- lua/neogit/lib/git/diff.lua | 38 +++++++++++ 4 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 lua/neogit/buffers/diff/init.lua create mode 100644 lua/neogit/buffers/diff/ui.lua diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua new file mode 100644 index 000000000..ab6b6d904 --- /dev/null +++ b/lua/neogit/buffers/diff/init.lua @@ -0,0 +1,112 @@ +local Buffer = require("neogit.lib.buffer") +local ui = require("neogit.buffers.diff.ui") +local git = require("neogit.lib.git") +local status_maps = require("neogit.config").get_reversed_status_maps() + +local api = vim.api + +--- @class DiffBuffer +--- @field buffer Buffer +--- @field open fun(self, kind: string) +--- @field close fun() +--- @see Buffer +--- @see Ui +local M = { + instance = nil, +} + +M.__index = M + +function M:new() + local instance = { + buffer = nil, + } + + setmetatable(instance, self) + return instance +end + +--- Closes the Diff +function M:close() + if self.buffer then + self.buffer:close() + self.buffer = nil + end + + M.instance = nil +end + +---Opens the DiffBuffer +---If already open will close the buffer +function M:open() + M.instance = self + + self.buffer = Buffer.create { + name = "NeogitDiffView", + filetype = "NeogitDiffView", + kind = "split", + context_highlight = true, + mappings = { + n = { + ["{"] = function() -- Goto Previous + local function previous_hunk_header(self, line) + local c = self.buffer.ui:get_component_on_line(line, function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" + end) + + if c then + local first, _ = c:row_range_abs() + if vim.fn.line(".") == first then + first = previous_hunk_header(self, line - 1) + end + + return first + end + end + + local previous_header = previous_hunk_header(self, vim.fn.line(".")) + if previous_header then + api.nvim_win_set_cursor(0, { previous_header, 0 }) + vim.cmd("normal! zt") + end + end, + ["}"] = function() -- Goto next + local c = self.buffer.ui:get_component_under_cursor(function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" + end) + + if c then + if c.options.tag == "Diff" then + self.buffer:move_cursor(vim.fn.line(".") + 1) + else + local _, last = c:row_range_abs() + if last == vim.fn.line("$") then + self.buffer:move_cursor(last) + else + self.buffer:move_cursor(last + 1) + end + end + vim.cmd("normal! zt") + end + end, + [status_maps["Toggle"]] = function() + pcall(vim.cmd, "normal! za") + end, + }, + }, + render = function() + local stats = git.diff.staged_stats() + + local diffs = vim.tbl_map(function(item) + return item.diff + end, git.repo.state.staged.items) + + return ui.DiffView(stats, diffs) + end, + after = function() + vim.cmd("normal! zR") + end, + } +end + +return M diff --git a/lua/neogit/buffers/diff/ui.lua b/lua/neogit/buffers/diff/ui.lua new file mode 100644 index 000000000..b08e994be --- /dev/null +++ b/lua/neogit/buffers/diff/ui.lua @@ -0,0 +1,37 @@ +local M = {} + +local Ui = require("neogit.lib.ui") +local util = require("neogit.lib.util") +local common_ui = require("neogit.buffers.common") + +local Diff = common_ui.Diff +local EmptyLine = common_ui.EmptyLine +local text = Ui.text +local col = Ui.col +local row = Ui.row +local map = util.map + +function M.OverviewFile(file_padding) + return function(file) + return row.tag("OverviewFile") { + text.highlight("NeogitFilePath")(util.pad_right(file.path, file_padding)), + text(" | "), + text.highlight("Number")(util.pad_left(file.changes or "0", 5)), + text(" "), + text.highlight("NeogitDiffAdditions")(file.insertions), + text.highlight("NeogitDiffDeletions")(file.deletions), + } + end +end + +function M.DiffView(stats, diffs) + local file_padding = util.max_length(map(diffs, function(diff) return diff.file end)) + + return { + text(stats.summary), + col(map(stats.files, M.OverviewFile(file_padding)), { tag = "OverviewFileList" }), + EmptyLine(), + col(map(diffs, Diff), { tag = "DiffList" }), + } +end +return M diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b355290ba..8cfb7ce46 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -705,12 +705,12 @@ function Buffer.create(config) logger.debug("[BUFFER:" .. buffer.handle .. "] Running buffer:call") -- Set fold styling for Neogit windows while preserving user styling vim.opt_local.winhl:append("Folded:NeogitFold") - vim.opt_local.fillchars:append("fold: ") + -- vim.opt_local.fillchars:append("fold: ") - -- Set signcolumn unless disabled by user settings - if not config.disable_signs then - vim.opt_local.signcolumn = "auto" - end + -- -- Set signcolumn unless disabled by user settings + -- if not config.disable_signs then + -- vim.opt_local.signcolumn = "auto" + -- end end) if config.context_highlight then diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index e37b0c48a..068cf9662 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -255,6 +255,44 @@ end return { parse = parse_diff, + staged_stats = function() + -- TODO: Use call instead of call_sync + local raw = git.cli.diff.no_ext_diff.cached.args("--stat").call_sync({ hidden = true }).stdout + local files = {} + local summary + + local idx = 1 + local function advance() + idx = idx + 1 + end + + local function peek() + return raw[idx] + end + + while true do + local line = peek() + + if line:match("^ %d+ file[s ]+changed,") then + summary = vim.trim(line) + break + else + table.insert(files, { + path = vim.trim(line:match("^ ([^ ]+)")), + changes = line:match("|%s+(%d+)"), + insertions = line:match("|%s+%d+ (%+*)"), + deletions = line:match("|%s+%d+ %+*(%-*)$"), + }) + + advance() + end + end + + return { + summary = summary, + files = files, + } + end, register = function(meta) meta.update_diffs = function(repo, filter) filter = filter or false From e4390a9cc650fc7a855ada20b579f9a0e36a91c6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 9 Apr 2024 22:09:38 +0200 Subject: [PATCH 338/443] Breaking: (kinda) Change default for commit editor from "auto" to "tab". Also, open diff view below/beside commit view --- lua/neogit/buffers/diff/init.lua | 2 +- lua/neogit/buffers/editor/init.lua | 15 ++++++++++++++- lua/neogit/config.lua | 2 +- lua/neogit/lib/git/cli.lua | 1 + lua/neogit/lib/git/diff.lua | 3 +-- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index ab6b6d904..5ae19dede 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -44,7 +44,7 @@ function M:open() self.buffer = Buffer.create { name = "NeogitDiffView", filetype = "NeogitDiffView", - kind = "split", + kind = "auto", context_highlight = true, mappings = { n = { diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 4dbd890ec..6915fdba2 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -63,9 +63,12 @@ function M:open(kind) return message end + local filetype = filetypes[self.filename:match("[%u_]+$")] or "NeogitEditor" + local diff_view + self.buffer = Buffer.create { name = self.filename, - filetype = filetypes[self.filename:match("[%u_]+$")] or "NeogitEditor", + filetype = filetype, load = true, buftype = "", kind = kind, @@ -80,6 +83,10 @@ function M:open(kind) end require("neogit.process").defer_show_preview_buffers() + + if diff_view then + diff_view:close() + end end, after = function(buffer) -- Populate help lines with mappings for buffer @@ -143,6 +150,12 @@ function M:open(kind) else vim.cmd.source("$VIMRUNTIME/syntax/gitcommit.vim") end + + if filetype == "NeogitCommitMessage" then + diff_view = require("neogit.buffers.diff"):new() + diff_view:open() + buffer:focus() + end end, mappings = { i = { diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 7e7a9f558..ba62430c7 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -302,7 +302,7 @@ function M.get_default_values() recent_commit_count = 10, }, commit_editor = { - kind = "auto", + kind = "tab", }, commit_select_view = { kind = "tab", diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 9bb284bc1..604d5dff4 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -135,6 +135,7 @@ local configurations = { diff = config { flags = { cached = "--cached", + stat = "--stat", shortstat = "--shortstat", patch = "--patch", name_only = "--name-only", diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 068cf9662..dced4a53d 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -256,8 +256,7 @@ end return { parse = parse_diff, staged_stats = function() - -- TODO: Use call instead of call_sync - local raw = git.cli.diff.no_ext_diff.cached.args("--stat").call_sync({ hidden = true }).stdout + local raw = git.cli.diff.no_ext_diff.cached.stat.call_sync({ hidden = true }).stdout local files = {} local summary From b34f0eb4c28cd0568156f8d632150a46f989de9f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 9 Apr 2024 22:18:38 +0200 Subject: [PATCH 339/443] Hide abbreviated_size call from user --- lua/neogit/lib/git/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index bdd316d9d..3f771d5aa 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -529,7 +529,7 @@ function M.reflog_message(skip) end M.abbreviated_size = util.memoize(function() - local commits = M.list { "HEAD", "--max-count=1" } + local commits = M.list({ "HEAD", "--max-count=1" }, {}, {}, true) if vim.tbl_isempty(commits) then return 7 else From e1dc674055d11d3d304298c4a57492cf6140ea74 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 09:19:53 +0200 Subject: [PATCH 340/443] Add a header to the diff buffer, showing its staged changes --- lua/neogit/buffers/diff/init.lua | 23 +++++++++++------------ lua/neogit/buffers/diff/ui.lua | 3 ++- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index 5ae19dede..51f408f47 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -11,40 +11,37 @@ local api = vim.api --- @field close fun() --- @see Buffer --- @see Ui -local M = { - instance = nil, -} - +local M = {} M.__index = M -function M:new() +---@param header string +---@return DiffBuffer +function M:new(header) local instance = { buffer = nil, + header = header, } setmetatable(instance, self) return instance end ---- Closes the Diff +--- Closes the DiffBuffer function M:close() if self.buffer then self.buffer:close() self.buffer = nil end - - M.instance = nil end ---Opens the DiffBuffer ---If already open will close the buffer +---@return DiffBuffer function M:open() - M.instance = self - self.buffer = Buffer.create { name = "NeogitDiffView", filetype = "NeogitDiffView", - kind = "auto", + kind = "split", context_highlight = true, mappings = { n = { @@ -101,12 +98,14 @@ function M:open() return item.diff end, git.repo.state.staged.items) - return ui.DiffView(stats, diffs) + return ui.DiffView(self.header, stats, diffs) end, after = function() vim.cmd("normal! zR") end, } + + return self end return M diff --git a/lua/neogit/buffers/diff/ui.lua b/lua/neogit/buffers/diff/ui.lua index b08e994be..bd151ae7e 100644 --- a/lua/neogit/buffers/diff/ui.lua +++ b/lua/neogit/buffers/diff/ui.lua @@ -24,10 +24,11 @@ function M.OverviewFile(file_padding) end end -function M.DiffView(stats, diffs) +function M.DiffView(header, stats, diffs) local file_padding = util.max_length(map(diffs, function(diff) return diff.file end)) return { + text.highlight("NeogitFloatHeaderHighlight")(header), text(stats.summary), col(map(stats.files, M.OverviewFile(file_padding)), { tag = "OverviewFileList" }), EmptyLine(), From 189407cad5a9fffce8f0a32a9b780d701dfbe50b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 09:22:31 +0200 Subject: [PATCH 341/443] cleanup --- lua/neogit/buffers/editor/init.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 6915fdba2..6a0e2b6f0 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -4,6 +4,8 @@ local input = require("neogit.lib.input") local util = require("neogit.lib.util") local git = require("neogit.lib.git") +local DiffViewBuffer = require("neogit.buffers.diff") + local pad = util.pad_right local M = {} @@ -46,7 +48,7 @@ function M:open(kind) local message_index = 1 local message_buffer = { { "" } } - local amend_header, footer + local amend_header, footer, diff_view local function reflog_message(index) return git.log.reflog_message(index - 2) @@ -64,7 +66,6 @@ function M:open(kind) end local filetype = filetypes[self.filename:match("[%u_]+$")] or "NeogitEditor" - local diff_view self.buffer = Buffer.create { name = self.filename, @@ -152,9 +153,7 @@ function M:open(kind) end if filetype == "NeogitCommitMessage" then - diff_view = require("neogit.buffers.diff"):new() - diff_view:open() - buffer:focus() + diff_view = DiffViewBuffer:new("Staged Changes"):open() end end, mappings = { From abbb35a48f47ebd786ef44174b080c61e7fb7cf0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 09:29:49 +0200 Subject: [PATCH 342/443] Add highlights to branch/remote in editor --- lua/neogit/buffers/editor/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 6a0e2b6f0..f93e24af2 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -152,6 +152,9 @@ function M:open(kind) vim.cmd.source("$VIMRUNTIME/syntax/gitcommit.vim") end + vim.fn.matchadd("NeogitBranch", git.branch.current(), 100) + vim.fn.matchadd("NeogitRemote", git.branch.upstream(), 100) + if filetype == "NeogitCommitMessage" then diff_view = DiffViewBuffer:new("Staged Changes"):open() end From 9ecd60fd31accb28ac65829da191da203f808c7f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 22:17:30 +0200 Subject: [PATCH 343/443] Clean up docs --- doc/neogit.txt | 147 +++++++++++++++++-------------------------------- 1 file changed, 49 insertions(+), 98 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 8a7737661..8b8976b8c 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -4,7 +4,8 @@ Author: TimUntersberger Maintainer: CKolkey License: MIT license {{{ - Copyright (c) 2020 Karl Yngve Lervåg + Copyright (c) 2020-2023 TimUntersberger + Copyright (c) 2024-2025 CKolkey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to @@ -28,39 +29,43 @@ License: MIT license {{{ ============================================================================== CONTENTS *neogit_contents* - 1. Intro |neogit_intro| - 2. Commands |neogit_commands| - 3. Mappings |neogit_mappings| - 4. Highlights |neogit_highlights| - 5. API |neogit_api| - 6. Popups |neogit_popups| - • Branch |neogit_branch_popup| - • Branch Config |neogit_branch_config_popup| - • Cherry Pick |neogit_cherry_pick_popup| - • Commit |neogit_commit_popup| - • Diff |neogit_diff_popup| - • Fetch |neogit_fetch_popup| - • Ignore |neogit_ignore_popup| - • Log |neogit_log_popup| - • Merge |neogit_merge_popup| - • Pull |neogit_pull_popup| - • Push |neogit_push_popup| - • Rebase |neogit_rebase_popup| - • Remote |neogit_remote_popup| - • Remote Config |neogit_remote_config_popup| - • Reset |neogit_reset_popup| - • Revert |neogit_revert_popup| - • Stash |neogit_stash_popup| - • Tag |neogit_tag_popup| - • Worktree |neogit_worktree_popup| - - 7. Buffers |neogit_buffers| - • Status |neogit_status_buffer| - • Editor |neogit_editor_buffer| - • Log |neogit_log_buffer| - • Reflog |neogit_reflog_buffer| - • Commit |neogit_commit_buffer| - • Rebase Todo |neogit_rebase_todo_buffer| + 1. Intro |neogit_intro| + 2. Commands |neogit_commands| + 3. Mappings |neogit_mappings| + 4. Highlights |neogit_highlights| + 5. API |neogit_api| + 6. Usage |neogit_usage| + + 7. Popups *neogit_popups* + • Bisect |neogit_bisect_popup| + • Branch |neogit_branch_popup| + • Branch Config |neogit_branch_config_popup| + • Cherry Pick |neogit_cherry_pick_popup| + • Commit |neogit_commit_popup| + • Diff |neogit_diff_popup| + • Fetch |neogit_fetch_popup| + • Ignore |neogit_ignore_popup| + • Log |neogit_log_popup| + • Merge |neogit_merge_popup| + • Pull |neogit_pull_popup| + • Push |neogit_push_popup| + • Rebase |neogit_rebase_popup| + • Remote |neogit_remote_popup| + • Remote Config |neogit_remote_config_popup| + • Reset |neogit_reset_popup| + • Revert |neogit_revert_popup| + • Stash |neogit_stash_popup| + • Tag |neogit_tag_popup| + • Worktree |neogit_worktree_popup| + + 8. Buffers *neogit_buffers* + • Status |neogit_status_buffer| + • Editor |neogit_editor_buffer| + • Log |neogit_log_buffer| + • Reflog |neogit_reflog_buffer| + • Refs |neogit_refs_buffer| + • Commit |neogit_commit_buffer| + • Rebase Todo |neogit_rebase_todo_buffer| ============================================================================== 1. Intro *neogit_intro* @@ -402,62 +407,14 @@ neogit.action({popup}, {action}, {args}) *neogit.action()* • {args} (table|nil) CLI arguments to pass to git command -============================================================================== -6. Popups *neogit_popups* - -A Neogit popup is a client interface over a git subcommand or other -git mechanism. - - *neogit_branch* -"branch" see :Man git-branch or |neogit_branch_popup| - - *neogit_branch_config* -"branch_config" Common configuration for branch/repo - - *neogit_cherry_pick* -"cherry_pick" see :Man git-cherry-pick - - *neogit_commit* -"commit" see :Man git-commit - - *neogit_diff* -"diff" see :Man git-diff - - *neogit_fetch* -"fetch" see :Man git-fetch - - *neogit_help* -"help" Overview of different popups - - *neogit_log* -"log" see :Man git-log - - *neogit_merge* -"merge" see :Man git-merge - - *neogit_pull* -"pull" see :Man git-pull - - *neogit_push* -"push" see :Man git-push - - *neogit_rebase* -"rebase" see :Man git-rebase - *neogit_remote* -"remote" see :Man git-remote - - *neogit_remote_config* -"remote_config" Common configuration for remote - - *neogit_reset* -"reset" see :Man git-reset - - *neogit_revert* -"revert" see :Man git-revert +============================================================================== +5. Usage *neogit_usage* +(TODO) - *neogit_stash* -"stash" see :Man git-stash +============================================================================== +Bisect Popup *neogit_bisect_popup* +(TODO) ============================================================================== Cherry-Pick Popup *neogit_cherry_pick_popup* @@ -1447,16 +1404,6 @@ Actions: *neogit_worktree_popup_actions* is the CWD, then CWD will be changed to the main worktree. -============================================================================== -7. Buffers *neogit_buffers* - -• Status |neogit_status_buffer| -• Editor |neogit_editor_buffer| -• Log |neogit_log_buffer| -• Reflog |neogit_reflog_buffer| -• Commit |neogit_commit_buffer| -• Rebase Todo |neogit_rebase_todo_buffer| - ============================================================================== Status Buffer *neogit_status_buffer* (TODO) @@ -1519,6 +1466,10 @@ Reflog Buffer *neogit_reflog_buffer* Commit Buffer *neogit_commit_buffer* (TODO) +============================================================================== +Refs Buffer *neogit_refs_buffer* +(TODO) + ============================================================================== Rebase Todo Buffer *neogit_rebase_todo_buffer* From a31973d2b75882cc85223225f2bf289588ef913b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:06:16 +0200 Subject: [PATCH 344/443] add some space --- lua/neogit/lib/git/cli.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 604d5dff4..c23caf683 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -803,7 +803,9 @@ local mt_builder = { ---@param line string local function handle_interactive_password_questions(p, line) process.hide_preview_buffers() + logger.debug(string.format("Matching interactive cmd output: '%s'", line)) + if vim.startswith(line, "Are you sure you want to continue connecting ") then logger.debug("[CLI]: Confirming whether to continue with unauthenticated host") local prompt = line From f6aff3e082888b66c13b20d33e4e7bb0f057873b Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:15:54 +0200 Subject: [PATCH 345/443] Fix: Replace signcolumn and fillchars settings. These were not optional and I shouldn't have removed them --- lua/neogit/lib/buffer.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 8cfb7ce46..b355290ba 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -705,12 +705,12 @@ function Buffer.create(config) logger.debug("[BUFFER:" .. buffer.handle .. "] Running buffer:call") -- Set fold styling for Neogit windows while preserving user styling vim.opt_local.winhl:append("Folded:NeogitFold") - -- vim.opt_local.fillchars:append("fold: ") + vim.opt_local.fillchars:append("fold: ") - -- -- Set signcolumn unless disabled by user settings - -- if not config.disable_signs then - -- vim.opt_local.signcolumn = "auto" - -- end + -- Set signcolumn unless disabled by user settings + if not config.disable_signs then + vim.opt_local.signcolumn = "auto" + end end) if config.context_highlight then From 40aea384c87f6305f66c5a2729d722712bef6896 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:19:37 +0200 Subject: [PATCH 346/443] Add a nice space after the fold arrow --- lua/neogit/buffers/status/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index c29efc5d3..e05f5b810 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1328,9 +1328,9 @@ function M.eval_statuscolumn() end if config.values.disable_line_numbers then - return ("%s"):format(fold) + return ("%s "):format(fold) else - return ("%s %s"):format("%l%r", fold) + return ("%s %s "):format("%l%r", fold) end end From 7c8e48a0eee9e5b0bcf7192cfa9fd0313a79d5ae Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:20:59 +0200 Subject: [PATCH 347/443] Add better logging to editor --- lua/neogit/buffers/editor/init.lua | 35 +++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index f93e24af2..5291f4ccf 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -3,6 +3,8 @@ local config = require("neogit.config") local input = require("neogit.lib.input") local util = require("neogit.lib.util") local git = require("neogit.lib.git") +local logger = require("neogit.logger") +local process = require("neogit.process") local DiffViewBuffer = require("neogit.buffers.diff") @@ -41,6 +43,7 @@ end function M:open(kind) assert(kind, "Editor must specify a kind") + logger.debug("[EDITOR] Opening editor as " .. kind) local mapping = config.get_reversed_commit_editor_maps() local mapping_I = config.get_reversed_commit_editor_maps_I() @@ -66,6 +69,7 @@ function M:open(kind) end local filetype = filetypes[self.filename:match("[%u_]+$")] or "NeogitEditor" + logger.debug("[EDITOR] Filetype " .. filetype) self.buffer = Buffer.create { name = self.filename, @@ -77,17 +81,22 @@ function M:open(kind) status_column = " ", readonly = false, on_detach = function(buffer) + logger.debug("[EDITOR] Cleaning Up") pcall(vim.treesitter.stop, buffer.handle) if self.on_unload then + logger.debug("[EDITOR] Running on_unload callback") self.on_unload(aborted and 1 or 0) end - require("neogit.process").defer_show_preview_buffers() + process.defer_show_preview_buffers() if diff_view then + logger.debug("[EDITOR] Closing diff view") diff_view:close() end + + logger.debug("[EDITOR] Done cleaning up") end, after = function(buffer) -- Populate help lines with mappings for buffer @@ -100,6 +109,8 @@ function M:open(kind) or git.config.get_global("core.commentChar"):read() or "#" + logger.debug("[EDITOR] Using comment character '" .. comment_char .. "'") + -- stylua: ignore local help_lines = { ("%s"):format(comment_char), @@ -125,6 +136,8 @@ function M:open(kind) amend_header = buffer:get_lines(0, 2) if amend_header[1]:match("^amend! %x+$") then + logger.debug("[EDITOR] Found 'amend!' header") + buffer:set_lines(0, 2, false, {}) -- remove captured header from buffer else amend_header = nil @@ -147,21 +160,30 @@ function M:open(kind) -- Apply syntax highlighting local ok, _ = pcall(vim.treesitter.language.inspect, "gitcommit") if ok then + logger.debug("[EDITOR] Loading treesitter for gitcommit") vim.treesitter.start(buffer.handle, "gitcommit") else + logger.debug("[EDITOR] Loading syntax for gitcommit") vim.cmd.source("$VIMRUNTIME/syntax/gitcommit.vim") end - vim.fn.matchadd("NeogitBranch", git.branch.current(), 100) - vim.fn.matchadd("NeogitRemote", git.branch.upstream(), 100) + if git.branch.current() then + vim.fn.matchadd("NeogitBranch", git.branch.current(), 100) + end + + if git.branch.upstream() then + vim.fn.matchadd("NeogitRemote", git.branch.upstream(), 100) + end if filetype == "NeogitCommitMessage" then + logger.debug("[EDITOR] Opening Diffview for staged changes") diff_view = DiffViewBuffer:new("Staged Changes"):open() end end, mappings = { i = { [mapping_I["Submit"]] = function(buffer) + logger.debug("[EDITOR] Action I: Submit") vim.cmd.stopinsert() if amend_header then buffer:set_lines(0, 0, false, amend_header) @@ -171,6 +193,7 @@ function M:open(kind) buffer:close(true) end, [mapping_I["Abort"]] = function(buffer) + logger.debug("[EDITOR] Action I: Abort") vim.cmd.stopinsert() aborted = true buffer:write() @@ -179,6 +202,7 @@ function M:open(kind) }, n = { [mapping["Close"]] = function(buffer) + logger.debug("[EDITOR] Action N: Close") if amend_header then buffer:set_lines(0, 0, false, amend_header) end @@ -191,6 +215,7 @@ function M:open(kind) buffer:close(true) end, [mapping["Submit"]] = function(buffer) + logger.debug("[EDITOR] Action N: Submit") if amend_header then buffer:set_lines(0, 0, false, amend_header) end @@ -199,11 +224,13 @@ function M:open(kind) buffer:close(true) end, [mapping["Abort"]] = function(buffer) + logger.debug("[EDITOR] Action N: Abort") aborted = true buffer:write() buffer:close(true) end, [mapping["PrevMessage"]] = function(buffer) + logger.debug("[EDITOR] Action N: PrevMessage") local message = current_message(buffer) message_buffer[message_index] = message @@ -213,6 +240,7 @@ function M:open(kind) buffer:move_cursor(1) end, [mapping["NextMessage"]] = function(buffer) + logger.debug("[EDITOR] Action N: NextMessage") local message = current_message(buffer) if message_index > 1 then @@ -224,6 +252,7 @@ function M:open(kind) buffer:move_cursor(1) end, [mapping["ResetMessage"]] = function(buffer) + logger.debug("[EDITOR] Action N: ResetMessage") local message = current_message(buffer) buffer:set_lines(0, #message, false, reflog_message(message_index)) buffer:move_cursor(1) From ea64a1ffc4cfcd11f397d551f057b53388df15c2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:24:01 +0200 Subject: [PATCH 348/443] Add "kind" to git history buffer --- lua/neogit/buffers/git_command_history.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neogit/buffers/git_command_history.lua b/lua/neogit/buffers/git_command_history.lua index 356fd74f1..001e74b01 100644 --- a/lua/neogit/buffers/git_command_history.lua +++ b/lua/neogit/buffers/git_command_history.lua @@ -50,6 +50,7 @@ function M:show() M.instance = self self.buffer = Buffer.create { + kind = "split", name = "NeogitGitCommandHistory", filetype = "NeogitGitCommandHistory", mappings = { From 5af0ef09247f25c78ecf73ae6b32ed5f39213b1c Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:24:33 +0200 Subject: [PATCH 349/443] When debugging, pass debug ENV vars to client --- lua/neogit/client.lua | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 1b5d86f80..04ccbd745 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -34,10 +34,19 @@ end function M.get_envs_git_editor() local nvim_cmd = M.get_nvim_remote_editor() - return { + + local env = { GIT_SEQUENCE_EDITOR = nvim_cmd, GIT_EDITOR = nvim_cmd, } + + if os.getenv("NEOGIT_DEBUG") then + env.NEOGIT_LOG_LEVEL = "debug" + env.NEOGIT_LOG_FILE = "true" + env.NEOGIT_DEBUG = true + end + + return env end --- Entry point for the headless client. @@ -139,9 +148,11 @@ function M.wrap(cmd, opts) call_cmd = c.call_interactive end + logger.debug("[CLIENT] Calling editor command") local result = call_cmd { verbose = true } a.util.scheduler() + logger.debug("[CLIENT] DONE editor command") if result.code == 0 then if opts.msg.success then From a6f632ebaca7103893684bd2331789cce0bd5323 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 10 Apr 2024 23:32:18 +0200 Subject: [PATCH 350/443] Fix: Diffview should take a string here --- lua/neogit/buffers/status/init.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index e05f5b810..27c71913e 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -361,9 +361,9 @@ function M:open(kind, cwd) p { name = stash and stash:match("^stash@{%d+}") } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local section = self.buffer.ui:get_selection().sections[1] + local section = self.buffer.ui:get_selection().section local item = self.buffer.ui:get_yankable_under_cursor() - p { section = { name = section }, item = { name = item } } + p { section = { name = section and section.name }, item = { name = item } } end), [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) p { paths = self.buffer.ui:get_filepaths_in_selection(), git_root = git.repo.git_root } @@ -1100,10 +1100,10 @@ function M:open(kind, cwd) p { name = stash and stash:match("^stash@{%d+}") } end), [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local section = self.buffer.ui:get_selection().sections[1] + local section = self.buffer.ui:get_selection().section local item = self.buffer.ui:get_yankable_under_cursor() p { - section = { name = section }, + section = { name = section and section.name }, item = { name = item }, } end), @@ -1117,7 +1117,11 @@ function M:open(kind, cwd) [popups.mapping_for("HelpPopup")] = popups.open("help", function(p) -- Since any other popup can be launched from help, build an ENV for any of them. local path = self.buffer.ui:get_hunk_or_filename_under_cursor() - local section = self.buffer.ui:get_selection().sections[1] + local section = self.buffer.ui:get_selection().section + if section then + section = section.name + end + local item = self.buffer.ui:get_yankable_under_cursor() local stash = self.buffer.ui:get_yankable_under_cursor() local commit = self.buffer.ui:get_commit_under_cursor() From 18a46b1ffd379f8d618ab9a6103a3411cd3e362f Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 11 Apr 2024 22:28:55 +0200 Subject: [PATCH 351/443] Improve docs --- doc/neogit.txt | 237 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 193 insertions(+), 44 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 8b8976b8c..343b8a2d0 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1,42 +1,25 @@ *neogit.txt* A Magit inspired Git porcelain for Neovim *neogit* -Author: TimUntersberger -Maintainer: CKolkey -License: MIT license {{{ - - Copyright (c) 2020-2023 TimUntersberger - Copyright (c) 2024-2025 CKolkey - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to - deal in the Software without restriction, including without limitation the - rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. -}}} + +Original Author: TimUntersberger +Current Author: Cameron Kolkey +Homepage: +License: MIT license ============================================================================== CONTENTS *neogit_contents* 1. Intro |neogit_intro| - 2. Commands |neogit_commands| - 3. Mappings |neogit_mappings| - 4. Highlights |neogit_highlights| - 5. API |neogit_api| - 6. Usage |neogit_usage| - - 7. Popups *neogit_popups* + 2. Setup *neogit_setup* + • Plugin Setup |neogit_setup_plugin| + • Mappings |neogit_setup_mappings| + • GPG Integration |neogit_setup_gpg| + 3. Commands |neogit_commands| + 4. Events |neogit_events| + 5. Highlights |neogit_highlights| + 6. API |neogit_api| + 7. Usage |neogit_usage| + 8. Popups *neogit_popups* • Bisect |neogit_bisect_popup| • Branch |neogit_branch_popup| • Branch Config |neogit_branch_config_popup| @@ -57,8 +40,7 @@ CONTENTS *neogit_contents* • Stash |neogit_stash_popup| • Tag |neogit_tag_popup| • Worktree |neogit_worktree_popup| - - 8. Buffers *neogit_buffers* + 9. Buffers *neogit_buffers* • Status |neogit_status_buffer| • Editor |neogit_editor_buffer| • Log |neogit_log_buffer| @@ -98,16 +80,172 @@ Though not yet feature complete, our goal is to bring the Magit git experience to Neovim users. ============================================================================== -2. Commands *neogit_commands* +2. Plugin Setup *neogit_setup_plugin* + +TODO: Detail what these do + + use_default_keymaps = true, + disable_hint = false, + disable_context_highlighting = false, + disable_signs = false, + graph_style = "ascii", + filewatcher = { + enabled = true, + }, + telescope_sorter = function() + return nil + end, + git_services = { + ["github.com"] = "https://github.com/${owner}/${repository}/compare/${branch_name}?expand=1", + ["bitbucket.org"] = "https://bitbucket.org/${owner}/${repository}/pull-requests/new?source=${branch_name}&t=1", + ["gitlab.com"] = "https://gitlab.com/${owner}/${repository}/merge_requests/new?merge_request[source_branch]=${branch_name}", + }, + highlight = { + italic = true, + bold = true, + underline = true, + }, + disable_insert_on_commit = "auto", + use_per_project_settings = true, + show_head_commit_hash = true, + remember_settings = true, + fetch_after_checkout = false, + auto_refresh = true, + sort_branches = "-committerdate", + kind = "tab", + disable_line_numbers = true, + -- The time after which an output console is shown for slow running commands + console_timeout = 2000, + -- Automatically show console if a command takes more than console_timeout milliseconds + auto_show_console = true, + notification_icon = "󰊢", + status = { + recent_commit_count = 10, + }, + commit_editor = { + kind = "tab", + }, + commit_select_view = { + kind = "tab", + }, + commit_view = { + kind = "vsplit", + verify_commit = vim.fn.executable("gpg") == 1, + }, + log_view = { + kind = "tab", + }, + rebase_editor = { + kind = "auto", + }, + reflog_view = { + kind = "tab", + }, + merge_editor = { + kind = "auto", + }, + description_editor = { + kind = "auto", + }, + tag_editor = { + kind = "auto", + }, + preview_buffer = { + kind = "split", + }, + popup = { + kind = "split", + }, + refs_view = { + kind = "tab", + }, + signs = { + hunk = { "", "" }, + item = { ">", "v" }, + section = { ">", "v" }, + }, + integrations = { + telescope = nil, + diffview = nil, + fzf_lua = nil, + }, + sections = { + sequencer = { + folded = false, + hidden = false, + }, + bisect = { + folded = false, + hidden = false, + }, + untracked = { + folded = false, + hidden = false, + }, + unstaged = { + folded = false, + hidden = false, + }, + staged = { + folded = false, + hidden = false, + }, + stashes = { + folded = true, + hidden = false, + }, + unpulled_upstream = { + folded = true, + hidden = false, + }, + unmerged_upstream = { + folded = false, + hidden = false, + }, + unpulled_pushRemote = { + folded = true, + hidden = false, + }, + unmerged_pushRemote = { + folded = false, + hidden = false, + }, + recent = { + folded = true, + hidden = false, + }, + rebase = { + folded = true, + hidden = false, + }, + }, + ignored_settings = { + "NeogitPushPopup--force-with-lease", + "NeogitPushPopup--force", + "NeogitPullPopup--rebase", + "NeogitCommitPopup--allow-empty", + } - *:Neogit* -:Neogit In a Git repository, opens a new NeogitStatus tab. +============================================================================== +Commit Signing / GPG Integration *neogit_setup_gpg* + +If you sign commits using gnugpg, there are a few steps that need to be taken +to properly integrate the password authentication with Neogit: + >gpg + # ~/.gnupg/gpg-agent.conf + pinentry-program /opt/homebrew/bin/pinentry-tty + allow-loopback-pinentry +< + >gpg + # ~/.gnupg/gpg.conf + pinentry-mode loopback +< - *:NeogitResetState* -:NeogitResetState Performs a full reset of saved flags for all popups. + Note: If you are not using Homebrew you may need to change the path for + `pinentry-program ============================================================================== -3. Mappings *neogit_mappings* +Mappings *neogit_setup_mappings* The following mappings can all be customized via the setup function. >lua @@ -211,7 +349,16 @@ The following mappings can all be customized via the setup function. } < ============================================================================== -4. Highlights *neogit_highlights* +3. Commands *neogit_commands* + + *:Neogit* +:Neogit In a Git repository, opens a new NeogitStatus tab. + + *:NeogitResetState* +:NeogitResetState Performs a full reset of saved flags for all popups. + +============================================================================== +5. Highlights *neogit_highlights* The following highlight groups are defined by this plugin. If you set any of these yourself before the plugin loads, that will be respected. If they do not @@ -346,7 +493,7 @@ COMMIT SELECT BUFFER NeogitFloatHeader Foreground/Background for header text at top of win NeogitFloatHeaderHighlight Emphasized text in header ============================================================================== -5. Lua API *neogit_api* *neogit-lua* +6. Lua API *neogit_api* *neogit-lua* neogit.open({*opts}) *neogit.open()* Open Neogit. Alternative to `:Neogit` >lua @@ -409,7 +556,7 @@ neogit.action({popup}, {action}, {args}) *neogit.action()* ============================================================================== -5. Usage *neogit_usage* +7. Usage *neogit_usage* (TODO) ============================================================================== @@ -1486,4 +1633,6 @@ The following keys, in normal mode, will act on the commit under the cursor: • `b` Insert breakpoint • `` Open current commit in Commit Buffer -vim:tw=78:ts=8:ft=help +------------------------------------------------------------------------------ +vim:tw=78:ts=8:ft=help:norl: + From d2e32fb8a5447a71fdff393ac50f382225fbf753 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 11 Apr 2024 22:30:16 +0200 Subject: [PATCH 352/443] add events tag to docs --- doc/neogit.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 343b8a2d0..137b4f86b 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -357,6 +357,9 @@ The following mappings can all be customized via the setup function. *:NeogitResetState* :NeogitResetState Performs a full reset of saved flags for all popups. +============================================================================== +3. Events *neogit_events* +(TODO) ============================================================================== 5. Highlights *neogit_highlights* From 162e9c7a31e7e9a74a9a6a4b55c053820078a933 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 11 Apr 2024 22:30:41 +0200 Subject: [PATCH 353/443] formatting --- doc/neogit.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 137b4f86b..bef855f96 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -358,8 +358,9 @@ The following mappings can all be customized via the setup function. :NeogitResetState Performs a full reset of saved flags for all popups. ============================================================================== -3. Events *neogit_events* +4. Events *neogit_events* (TODO) + ============================================================================== 5. Highlights *neogit_highlights* From 475a4749d16193c542a9502200c884bde92f3fed Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 12 Apr 2024 23:07:52 +0200 Subject: [PATCH 354/443] Add better mouse support to telescope picker - fixes closing picker by clicking off it --- lua/neogit/config.lua | 6 ++++++ lua/neogit/lib/finder.lua | 16 +++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index ba62430c7..9210cd74d 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -450,6 +450,12 @@ function M.get_default_values() [""] = "MultiselectToggleNext", [""] = "MultiselectTogglePrevious", [""] = "NOP", + [""] = "ScrollWheelDown", + [""] = "ScrollWheelUp", + [""] = "NOP", + [""] = "NOP", + [""] = "MouseClick", + ["<2-LeftMouse>"] = "NOP", }, popup = { ["?"] = "HelpPopup", diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index b4d684e40..49240f326 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -14,6 +14,9 @@ local function telescope_mappings(on_select, allow_multi, refocus_status) local actions = require("telescope.actions") local function close_action(prompt_bufnr) + -- Make sure to notify the caller that we aborted to avoid hanging on the async task forever + on_select(nil) + actions.close(prompt_bufnr) if refocus_status then @@ -59,24 +62,23 @@ local function telescope_mappings(on_select, allow_multi, refocus_status) return function(_, map) local commands = { ["Select"] = select_action, - ["Close"] = function(...) - -- Make sure to notify the caller that we aborted to avoid hanging on the async task forever - on_select(nil) - close_action(...) - end, + ["Close"] = close_action, ["Next"] = actions.move_selection_next, ["Previous"] = actions.move_selection_previous, ["NOP"] = actions.nop, ["MultiselectToggleNext"] = actions.toggle_selection + actions.move_selection_worse, ["MultiselectTogglePrevious"] = actions.toggle_selection + actions.move_selection_better, + ["ScrollWheelDown"] = actions.move_selection_next, + ["ScrollWheelUp"] = actions.move_selection_previous, + ["MouseClick"] = actions.mouse_click, } for mapping, command in pairs(config.values.mappings.finder) do - if command:match("^Multiselect") then + if command and command:match("^Multiselect") then if allow_multi then map({ "i" }, mapping, commands[command]) end - else + elseif command then map({ "i" }, mapping, commands[command]) end end From ca815d165edfa450a9ef3f875e937513d68693d9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 12 Apr 2024 23:22:46 +0200 Subject: [PATCH 355/443] Don't write to log if the file doesn't exist --- lua/neogit/logger.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/logger.lua b/lua/neogit/logger.lua index a00dff1dd..e02626261 100644 --- a/lua/neogit/logger.lua +++ b/lua/neogit/logger.lua @@ -107,8 +107,10 @@ log.new = function(config, standalone) if config.use_file then local fp = io.open(outfile, "a") local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) - fp:write(str) - fp:close() + if fp then + fp:write(str) + fp:close() + end end end From 95d5975bdca23d87471c561c1a0ab84f42d30e34 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 12 Apr 2024 23:41:40 +0200 Subject: [PATCH 356/443] Ensure all list chars are turned off for neogit buffer, and clamp highlight to viewport --- lua/neogit/lib/buffer.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index b355290ba..e5188ccc1 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -631,6 +631,8 @@ function Buffer.create(config) buffer:set_window_option("foldlevel", 99) buffer:set_window_option("foldminlines", 0) buffer:set_window_option("foldtext", "") + buffer:set_window_option("listchars", "") + buffer:set_window_option("list", false) if vim.fn.has("nvim-0.10") == 1 then buffer:set_window_option("spell", false) @@ -707,7 +709,6 @@ function Buffer.create(config) vim.opt_local.winhl:append("Folded:NeogitFold") vim.opt_local.fillchars:append("fold: ") - -- Set signcolumn unless disabled by user settings if not config.disable_signs then vim.opt_local.signcolumn = "auto" end @@ -729,7 +730,9 @@ function Buffer.create(config) end local cursor = vim.fn.line(".") - for line = context.position.row_start, context.position.row_end do + local start = math.max(context.position.row_start, vim.fn.line("w0")) + local stop = math.min(context.position.row_end, vim.fn.line("w$")) + for line = start, stop do local line_hl = ("%s%s"):format( buffer.ui:get_line_highlight(line) or "NeogitDiffContext", line == cursor and "Cursor" or "Highlight" From 717dd95853582d4852a5886cd21d0c41167a1b29 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 12 Apr 2024 23:56:19 +0200 Subject: [PATCH 357/443] Add fast j/k navigation to refs and log view, skipping blank lines --- lua/neogit/buffers/log_view/init.lua | 30 +++++++++++++++++++++++++++ lua/neogit/buffers/refs_view/init.lua | 22 ++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index bc634c08e..4eb708cdc 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -205,6 +205,36 @@ function M:open() [""] = function() pcall(vim.cmd, "normal! za") end, + ["j"] = function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "j") + else + vim.cmd("norm! j") + end + + while self.buffer:get_current_line()[1]:sub(1, 1) == " " do + if vim.fn.line(".") == vim.fn.line("$") then + break + end + + vim.cmd("norm! j") + end + end, + ["k"] = function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "k") + else + vim.cmd("norm! k") + end + + while self.buffer:get_current_line()[1]:sub(1, 1) == " " do + if vim.fn.line(".") == 1 then + break + end + + vim.cmd("norm! k") + end + end, }, }, render = function() diff --git a/lua/neogit/buffers/refs_view/init.lua b/lua/neogit/buffers/refs_view/init.lua index 0947e258b..3a0617085 100644 --- a/lua/neogit/buffers/refs_view/init.lua +++ b/lua/neogit/buffers/refs_view/init.lua @@ -139,6 +139,28 @@ function M:open() item = { name = item }, } end), + ["j"] = function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "j") + else + vim.cmd("norm! j") + end + + if self.buffer:get_current_line()[1] == " " then + vim.cmd("norm! j") + end + end, + ["k"] = function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "k") + else + vim.cmd("norm! k") + end + + if self.buffer:get_current_line()[1] == " " then + vim.cmd("norm! k") + end + end, [""] = require("neogit.lib.ui.helpers").close_topmost(self), [status_maps["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), [status_maps["GoToFile"]] = function() From 387df88a5a63344a6d619cee0bdc48b4d8cffcd8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 12 Apr 2024 23:59:15 +0200 Subject: [PATCH 358/443] When dropping the stash that was under cursor, ask for confirmation from user in case they didn't mean to use the one selected. --- lua/neogit/popups/stash/actions.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lua/neogit/popups/stash/actions.lua b/lua/neogit/popups/stash/actions.lua index e2fcf4471..7e3d7077e 100644 --- a/lua/neogit/popups/stash/actions.lua +++ b/lua/neogit/popups/stash/actions.lua @@ -1,5 +1,6 @@ local git = require("neogit.lib.git") local operation = require("neogit.operations") +local input = require("neogit.lib.input") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") @@ -22,10 +23,12 @@ function M.push(popup) git.stash.push(popup:get_arguments(), files) end -local function use(action, stash) - local name +local function use(action, stash, opts) + opts = opts or {} + local name, get_permission if stash and stash.name then + get_permission = true name = stash.name else name = FuzzyFinderBuffer.new(git.stash.list()):open_async() @@ -37,6 +40,14 @@ local function use(action, stash) end if name then + if + get_permission + and opts.confirm + and not input.get_permission(("%s%s '%s'?"):format(action:upper():sub(1, 1), action:sub(2, -1), name)) + then + return + end + git.stash[action](name) end end @@ -50,7 +61,7 @@ function M.apply(popup) end function M.drop(popup) - use("drop", popup.state.env.stash) + use("drop", popup.state.env.stash, { confirm = true }) end M.rename = operation("stash_rename", function(popup) From 89e08829a6ec95f1239ff258dbc95b706073f81e Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 14:31:03 +0200 Subject: [PATCH 359/443] Fix: The structure of the on_select and close_action requires this, or it will error out. --- lua/neogit/lib/finder.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 49240f326..4afc7a106 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -14,9 +14,6 @@ local function telescope_mappings(on_select, allow_multi, refocus_status) local actions = require("telescope.actions") local function close_action(prompt_bufnr) - -- Make sure to notify the caller that we aborted to avoid hanging on the async task forever - on_select(nil) - actions.close(prompt_bufnr) if refocus_status then @@ -62,7 +59,11 @@ local function telescope_mappings(on_select, allow_multi, refocus_status) return function(_, map) local commands = { ["Select"] = select_action, - ["Close"] = close_action, + ["Close"] = function(...) + -- Make sure to notify the caller that we aborted to avoid hanging on the async task forever + on_select(nil) + close_action(...) + end, ["Next"] = actions.move_selection_next, ["Previous"] = actions.move_selection_previous, ["NOP"] = actions.nop, From 631659eb99b2836acf0e4c4345bc218e567811ca Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 22:13:52 +0200 Subject: [PATCH 360/443] Show decoration in Recent Commits section of status buffer --- lua/neogit/buffers/common.lua | 10 +++++-- lua/neogit/buffers/status/ui.lua | 51 ++++++++++++++++++++++++++++---- lua/neogit/lib/git/log.lua | 10 +++---- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index d9254ed27..919b786a5 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -122,6 +122,7 @@ local highlight_for_signature = { M.CommitEntry = Component.new(function(commit, args) local ref = {} + local ref_last = {} local info = git.log.branch_info(commit.ref_name, git.remote.list()) @@ -129,7 +130,10 @@ M.CommitEntry = Component.new(function(commit, args) if args.decorate and commit.ref_name ~= "" then -- Render local only branches first for name, _ in pairs(info.locals) do - if info.remotes[name] == nil then + if name:match("^refs/") then + table.insert(ref_last, text(name, { highlight = "NeogitGraphGray" })) + table.insert(ref_last, text(" ")) + elseif info.remotes[name] == nil then local branch_highlight = info.head == name and "NeogitBranchHead" or "NeogitBranch" table.insert(ref, text(name, { highlight = branch_highlight })) table.insert(ref, text(" ")) @@ -149,6 +153,8 @@ M.CommitEntry = Component.new(function(commit, args) table.insert(ref, text(name, { highlight = locally and branch_highlight or "NeogitRemote" })) table.insert(ref, text(" ")) end + + -- Render tags for _, tag in pairs(info.tags) do table.insert(ref, text(tag, { highlight = "NeogitTagName" })) table.insert(ref, text(" ")) @@ -228,7 +234,7 @@ M.CommitEntry = Component.new(function(commit, args) or "Comment", }), text(" "), - }, graph, { text(" ") }, ref, { text(commit.subject) }), + }, graph, { text(" ") }, ref, ref_last, { text(commit.subject) }), { virtual_text = { { " ", "Constant" }, diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 1e59447d7..a29d4d6c8 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -293,11 +293,52 @@ local SectionItemStash = Component.new(function(item) end) local SectionItemCommit = Component.new(function(item) - return row({ - text.highlight("Comment")(item.commit.abbreviated_commit), - text(" "), - text(item.commit.subject), - }, { oid = item.commit.oid, yankable = item.commit.oid, item = item }) + local ref = {} + local ref_last = {} + + if item.commit.ref_name ~= "" then + -- Render local only branches first + for name, _ in pairs(item.decoration.locals) do + if name:match("^refs/") then + table.insert(ref_last, text(name, { highlight = "NeogitGraphGray" })) + table.insert(ref_last, text(" ")) + elseif item.decoration.remotes[name] == nil then + local branch_highlight = item.decoration.head == name and "NeogitBranchHead" or "NeogitBranch" + table.insert(ref, text(name, { highlight = branch_highlight })) + table.insert(ref, text(" ")) + end + end + + -- Render tracked (local+remote) branches next + for name, remotes in pairs(item.decoration.remotes) do + if #remotes == 1 then + table.insert(ref, text(remotes[1] .. "/", { highlight = "NeogitRemote" })) + end + + if #remotes > 1 then + table.insert(ref, text("{" .. table.concat(remotes, ",") .. "}/", { highlight = "NeogitRemote" })) + end + + local branch_highlight = item.decoration.head == name and "NeogitBranchHead" or "NeogitBranch" + local locally = item.decoration.locals[name] ~= nil + table.insert(ref, text(name, { highlight = locally and branch_highlight or "NeogitRemote" })) + table.insert(ref, text(" ")) + end + + -- Render tags + for _, tag in pairs(item.decoration.tags) do + table.insert(ref, text(tag, { highlight = "NeogitTagName" })) + table.insert(ref, text(" ")) + end + end + + return row(util.merge( + { text.highlight("Comment")(item.commit.abbreviated_commit) }, + { text(" ") }, + ref, + ref_last, + { text(item.commit.subject) } + ), { oid = item.commit.oid, yankable = item.commit.oid, item = item }) end) local SectionItemRebase = Component.new(function(item) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 3f771d5aa..55e2f67fb 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -411,12 +411,10 @@ function M.register(meta) state.recent = { items = {} } local count = config.values.status.recent_commit_count - if count < 1 then - return + if count > 0 then + state.recent.items = + util.filter_map(M.list({ "--max-count=" .. tostring(count) }, nil, {}, true), M.present_commit) end - - local commits = M.list({ "--max-count=" .. tostring(count) }, nil, {}, true) - state.recent.items = util.filter_map(commits, M.present_commit) end end @@ -435,6 +433,7 @@ end ---@class CommitItem ---@field name string ---@field oid string +---@field decoration CommitBranchInfo ---@field commit CommitLogEntry[] ---@return nil|CommitItem @@ -445,6 +444,7 @@ function M.present_commit(commit) return { name = string.format("%s %s", commit.abbreviated_commit, commit.subject or ""), + decoration = M.branch_info(commit.ref_name, git.remote.list()), oid = commit.oid, commit = commit, } From 857c761b1008411c7f1dd0957c1400e705f696e9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 22:14:16 +0200 Subject: [PATCH 361/443] Decoration: Show "HEAD" as "@" --- lua/neogit/lib/git/log.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 55e2f67fb..768c90bb5 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -512,7 +512,11 @@ M.branch_info = util.memoize(function(ref, remotes) end table.insert(result.remotes[name], remote) else - result.locals[name] = true + if name == "HEAD" then + result.locals["@"] = true + elseif name ~= "" then + result.locals[name] = true + end end end end From c6f5b6070c248b65b5e368110fb4848413aabaab Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 22:18:11 +0200 Subject: [PATCH 362/443] Conditionally show count of sections (basically, don't show it for the recent commits). When there are unmerged commits to the branch's upstream, hide recent commits section. --- lua/neogit/buffers/status/ui.lua | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index a29d4d6c8..8a9e19fb8 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -158,8 +158,13 @@ local SectionTitleMerge = Component.new(function(props) end) local Section = Component.new(function(props) + local count + if props.count then + count = { text(" ("), text(#props.items), text(")") } + end + return col.tag("Section")({ - row(util.merge(props.title, { text(" ("), text(#props.items), text(")") })), + row(util.merge(props.title, count or {})), col(map(props.items, props.render)), EmptyLine(), }, { @@ -574,6 +579,7 @@ function M.Status(state, config) }, show_untracked and Section { title = SectionTitle { title = "Untracked files" }, + count = true, render = SectionItemFile("untracked"), items = state.untracked.items, folded = config.sections.untracked.folded, @@ -581,6 +587,7 @@ function M.Status(state, config) }, show_unstaged and Section { title = SectionTitle { title = "Unstaged changes" }, + count = true, render = SectionItemFile("unstaged"), items = state.unstaged.items, folded = config.sections.unstaged.folded, @@ -588,13 +595,23 @@ function M.Status(state, config) }, show_staged and Section { title = SectionTitle { title = "Staged changes" }, + count = true, render = SectionItemFile("staged"), items = state.staged.items, folded = config.sections.staged.folded, name = "staged", }, + show_stashes and Section { + title = SectionTitle { title = "Stashes" }, + count = true, + render = SectionItemStash, + items = state.stashes.items, + folded = config.sections.stashes.folded, + name = "stashes", + }, show_upstream_unpulled and Section { title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref }, + count = true, render = SectionItemCommit, items = state.upstream.unpulled.items, folded = config.sections.unpulled_upstream.folded, @@ -602,6 +619,7 @@ function M.Status(state, config) }, show_pushRemote_unpulled and Section { title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, + count = true, render = SectionItemCommit, items = state.pushRemote.unpulled.items, folded = config.sections.unpulled_pushRemote.folded, @@ -609,6 +627,7 @@ function M.Status(state, config) }, show_upstream_unmerged and Section { title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref }, + count = true, render = SectionItemCommit, items = state.upstream.unmerged.items, folded = config.sections.unmerged_upstream.folded, @@ -616,20 +635,15 @@ function M.Status(state, config) }, show_pushRemote_unmerged and Section { title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref }, + count = true, render = SectionItemCommit, items = state.pushRemote.unmerged.items, folded = config.sections.unmerged_pushRemote.folded, name = "pushRemote_unmerged", }, - show_stashes and Section { - title = SectionTitle { title = "Stashes" }, - render = SectionItemStash, - items = state.stashes.items, - folded = config.sections.stashes.folded, - name = "stashes", - }, - show_recent and Section { + not show_upstream_unmerged and show_recent and Section { title = SectionTitle { title = "Recent Commits" }, + count = false, render = SectionItemCommit, items = state.recent.items, folded = config.sections.recent.folded, From d59719ab6bf88b03f26ec9d44ad253a2d2009557 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 22:20:17 +0200 Subject: [PATCH 363/443] Lowercase all file statuses in status buffer --- lua/neogit/buffers/status/ui.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 8a9e19fb8..78d8fca6a 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -241,20 +241,20 @@ local SectionItemFile = function(section) end local mode_to_text = { - M = "Modified", - N = "New File", - A = "Added", - D = "Deleted", - C = "Copied", - U = "Updated", - R = "Renamed", - DD = "Unmerged", - AU = "Unmerged", - UD = "Unmerged", - UA = "Unmerged", - DU = "Unmerged", - AA = "Unmerged", - UU = "Unmerged", + M = "modified", + N = "new file", + A = "added", + D = "deleted", + C = "copied", + U = "updated", + R = "renamed", + DD = "unmerged", + AU = "unmerged", + UD = "unmerged", + UA = "unmerged", + DU = "unmerged", + AA = "unmerged", + UU = "unmerged", ["?"] = "", -- Untracked } From 09e77316d2264b011963a628a8695f97e51eaa02 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 22:21:44 +0200 Subject: [PATCH 364/443] Schedule this so calling :wq in commit buffer doesn't error --- lua/neogit/lib/buffer.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index e5188ccc1..1ea31188f 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -249,7 +249,9 @@ function Buffer:close(force) local winid = fn.win_getid(winnr) local ok, _ = pcall(api.nvim_win_close, winid, force) if not ok then - vim.cmd("b#") + vim.schedule(function() + vim.cmd("b#") + end) end else api.nvim_buf_delete(self.handle, { force = force }) From 1ecb91e4b9416605f713e7706fa1d03a6a9e2d0d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 22:33:40 +0200 Subject: [PATCH 365/443] Use autocmd to close diff view when commit editor was closed via ":wq" --- lua/neogit/buffers/editor/init.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 5291f4ccf..99d2c222b 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -80,6 +80,14 @@ function M:open(kind) modifiable = true, status_column = " ", readonly = false, + autocmds = { + ["BufWinLeave"] = function() + if diff_view then + diff_view:close() + diff_view = nil + end + end + }, on_detach = function(buffer) logger.debug("[EDITOR] Cleaning Up") pcall(vim.treesitter.stop, buffer.handle) @@ -94,6 +102,7 @@ function M:open(kind) if diff_view then logger.debug("[EDITOR] Closing diff view") diff_view:close() + diff_view = nil end logger.debug("[EDITOR] Done cleaning up") From c97d8796f6ed85dfbcf72f7829bcb9570a8ac908 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 23:03:19 +0200 Subject: [PATCH 366/443] Fix: can't use fmt_* logging... --- lua/neogit/buffers/status/init.lua | 6 +++--- lua/neogit/client.lua | 6 +++--- lua/neogit/config.lua | 4 ++-- lua/neogit/lib/git/repository.lua | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 27c71913e..57cead9f7 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -128,7 +128,7 @@ function M:open(kind, cwd) local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) if #hunks > 0 then - logger.fmt_debug("Discarding %d hunks from %q", #hunks, item.name) + logger.debug(("Discarding %d hunks from %q"):format(#hunks, item.name)) hunk_count = hunk_count + #hunks if hunk_count > 1 then @@ -139,7 +139,7 @@ function M:open(kind, cwd) table.insert(patches, function() local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) - logger.fmt_debug("Discarding Patch: %s", patch) + logger.debug(("Discarding Patch: %s"):format(patch)) git.index.apply(patch, { index = section.name == "staged", @@ -149,7 +149,7 @@ function M:open(kind, cwd) end else discard_message = ("Discard %s files?"):format(file_count) - logger.fmt_debug("Discarding in section %s %s", section.name, item.name) + logger.debug(("Discarding in section %s %s"):format(section.name, item.name)) if section.name == "untracked" then table.insert(untracked_files, item.escaped_path) diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 04ccbd745..62f54c689 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -58,10 +58,10 @@ function M.client() end local file_target = fn.fnamemodify(fn.argv()[1], ":p") - logger.fmt_debug("[CLIENT] File target: %s", file_target) + logger.debug(("[CLIENT] File target: %s"):format(file_target)) local client = fn.serverstart() - logger.fmt_debug("[CLIENT] Client address: %s", client) + logger.debug(("[CLIENT] Client address: %s"):format(client)) local lua_cmd = fmt('lua require("neogit.client").editor("%s", "%s")', file_target, client) @@ -77,7 +77,7 @@ end ---@param target string Filename to open ---@param client string Address returned from vim.fn.serverstart() function M.editor(target, client) - logger.fmt_debug("[CLIENT] Invoked editor with target: %s, from: %s", target, client) + logger.debug(("[CLIENT] Invoked editor with target: %s, from: %s"):format(target, client)) require("neogit.process").hide_preview_buffers() local rpc_client = RPC.create_connection(client) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 9210cd74d..7f5f3a1fa 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -1024,11 +1024,11 @@ function M.check_integration(name) if enabled == nil or enabled == "auto" then local success, _ = pcall(require, name) - logger.fmt_info("[CONFIG] Found auto integration '%s = %s'", name, success) + logger.info(("[CONFIG] Found auto integration '%s = %s'"):format(name, success)) return success end - logger.fmt_info("[CONFIG] Found explicit integration '%s' = %s", name, enabled) + logger.info(("[CONFIG] Found explicit integration '%s' = %s"):format(name, enabled)) return enabled end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index d8ed48110..a3189c29a 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -187,7 +187,7 @@ function Repo.new(dir) for name, fn in pairs(instance.lib) do if name ~= "update_status" then table.insert(instance.updates, function() - logger.fmt_debug("[REPO]: Refreshing %s", name) + logger.debug(("[REPO]: Refreshing %s"):format(name)) fn(instance.state) end) end @@ -212,7 +212,7 @@ function Repo:refresh(opts) self.state.initialized = true opts = opts or {} - logger.fmt_info("[REPO]: Refreshing START (source: %s)", opts.source or "UNKNOWN") + logger.info(("[REPO]: Refreshing START (source: %s)"):format(opts.source or "UNKNOWN")) -- Needed until Process doesn't use vim.fn.* a.util.scheduler() @@ -228,7 +228,7 @@ function Repo:refresh(opts) local filter = type(opts.partial[name]) == "table" and opts.partial[name] table.insert(tasks, function() - logger.fmt_debug("[REPO]: Refreshing %s", name) + logger.debug(("[REPO]: Refreshing %s"):format(name)) fn(self.state, filter) end) end From c722b28292e69bb616d9566a0642e326c5ee4cd6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 23:04:18 +0200 Subject: [PATCH 367/443] Only show diff for staged changes --- lua/neogit/buffers/editor/init.lua | 6 ++++-- lua/neogit/client.lua | 20 +++++++++++--------- lua/neogit/popups/commit/actions.lua | 1 + 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 99d2c222b..f7c5f8e9b 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -22,6 +22,7 @@ local filetypes = { ---@class EditorBuffer ---@field filename string filename of buffer ---@field on_unload function callback invoked when buffer is unloaded +---@field show_diff boolean show the diff view or not ---@field buffer Buffer ---@see Buffer @@ -29,8 +30,9 @@ local filetypes = { ---@param filename string the filename of buffer ---@param on_unload function the event dispatched on buffer unload ---@return EditorBuffer -function M.new(filename, on_unload) +function M.new(filename, on_unload, show_diff) local instance = { + show_diff = show_diff, filename = filename, on_unload = on_unload, buffer = nil, @@ -184,7 +186,7 @@ function M:open(kind) vim.fn.matchadd("NeogitRemote", git.branch.upstream(), 100) end - if filetype == "NeogitCommitMessage" then + if self.show_diff then logger.debug("[EDITOR] Opening Diffview for staged changes") diff_view = DiffViewBuffer:new("Staged Changes"):open() end diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index 62f54c689..f2adf264c 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -7,14 +7,14 @@ local fmt = string.format local M = {} -function M.get_nvim_remote_editor() +function M.get_nvim_remote_editor(show_diff) local neogit_path = debug.getinfo(1, "S").source:sub(2, -#"lua/neogit/client.lua" - 2) local nvim_path = fn.shellescape(vim.v.progpath) logger.debug("[CLIENT] Neogit path: " .. neogit_path) logger.debug("[CLIENT] Neovim path: " .. nvim_path) local runtimepath_cmd = fn.shellescape(fmt("set runtimepath^=%s", fn.fnameescape(tostring(neogit_path)))) - local lua_cmd = fn.shellescape("lua require('neogit.client').client()") + local lua_cmd = fn.shellescape("lua require('neogit.client').client({ show_diff = " .. tostring(show_diff) .. " })") local shell_cmd = { nvim_path, @@ -32,8 +32,8 @@ function M.get_nvim_remote_editor() return table.concat(shell_cmd, " ") end -function M.get_envs_git_editor() - local nvim_cmd = M.get_nvim_remote_editor() +function M.get_envs_git_editor(show_diff) + local nvim_cmd = M.get_nvim_remote_editor(show_diff) local env = { GIT_SEQUENCE_EDITOR = nvim_cmd, @@ -51,7 +51,7 @@ end --- Entry point for the headless client. --- Starts a server and connects to the parent process rpc, opening an editor -function M.client() +function M.client(opts) local nvim_server = vim.env.NVIM if not nvim_server then error("NVIM server address not set") @@ -63,7 +63,7 @@ function M.client() local client = fn.serverstart() logger.debug(("[CLIENT] Client address: %s"):format(client)) - local lua_cmd = fmt('lua require("neogit.client").editor("%s", "%s")', file_target, client) + local lua_cmd = fmt('lua require("neogit.client").editor("%s", "%s", %s)', file_target, client, opts.show_diff) if vim.loop.os_uname().sysname == "Windows_NT" then lua_cmd = lua_cmd:gsub("\\", "/") @@ -76,7 +76,8 @@ end --- Invoked by the `client` and starts the appropriate file editor ---@param target string Filename to open ---@param client string Address returned from vim.fn.serverstart() -function M.editor(target, client) +---@param show_diff boolean +function M.editor(target, client, show_diff) logger.debug(("[CLIENT] Invoked editor with target: %s, from: %s"):format(target, client)) require("neogit.process").hide_preview_buffers() @@ -116,7 +117,7 @@ function M.editor(target, client) editor = require("neogit.buffers.editor") end - editor.new(target, send_client_quit):open(kind) + editor.new(target, send_client_quit, show_diff):open(kind) end ---@class NotifyMsg @@ -127,6 +128,7 @@ end ---@class WrapOpts ---@field autocmd string ---@field msg NotifyMsg +---@field show_diff boolean? ---@field interactive boolean? ---@param cmd any @@ -142,7 +144,7 @@ function M.wrap(cmd, opts) notification.info(opts.msg.setup) end - local c = cmd.env(M.get_envs_git_editor()):in_pty(true) + local c = cmd.env(M.get_envs_git_editor(opts.show_diff)):in_pty(true) local call_cmd = c.call if opts.interactive then call_cmd = c.call_interactive diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 95eb916ee..16622fe50 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -31,6 +31,7 @@ local function do_commit(popup, cmd) success = "Committed", }, interactive = true, + show_diff = true, }) end From 61ab7b5b581bd5c3d8b7e5857727b6efd6006b28 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 23:05:05 +0200 Subject: [PATCH 368/443] Notes --- lua/neogit/buffers/editor/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index f7c5f8e9b..61fef41cb 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -83,7 +83,7 @@ function M:open(kind) status_column = " ", readonly = false, autocmds = { - ["BufWinLeave"] = function() + ["BufWinLeave"] = function() -- For :wq compatibility if diff_view then diff_view:close() diff_view = nil From f7f5f4ea6832f8048e311693434382e9e5b355be Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 13 Apr 2024 23:27:51 +0200 Subject: [PATCH 369/443] Use QuitPre, since it's triggered explicitly on :wq --- lua/neogit/buffers/editor/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index 61fef41cb..a7dbf6262 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -83,7 +83,7 @@ function M:open(kind) status_column = " ", readonly = false, autocmds = { - ["BufWinLeave"] = function() -- For :wq compatibility + ["QuitPre"] = function() -- For :wq compatibility if diff_view then diff_view:close() diff_view = nil From 0a7a6f4be89d2cea325bf81c2c909ce26eea679b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Apr 2024 14:33:07 +0200 Subject: [PATCH 370/443] Swap the order of unmerged and unpulled --- lua/neogit/buffers/status/ui.lua | 49 +++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 78d8fca6a..78f5e9f46 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -337,13 +337,16 @@ local SectionItemCommit = Component.new(function(item) end end - return row(util.merge( - { text.highlight("Comment")(item.commit.abbreviated_commit) }, - { text(" ") }, - ref, - ref_last, - { text(item.commit.subject) } - ), { oid = item.commit.oid, yankable = item.commit.oid, item = item }) + return row( + util.merge( + { text.highlight("Comment")(item.commit.abbreviated_commit) }, + { text(" ") }, + ref, + ref_last, + { text(item.commit.subject) } + ), + { oid = item.commit.oid, yankable = item.commit.oid, item = item } + ) end) local SectionItemRebase = Component.new(function(item) @@ -609,22 +612,6 @@ function M.Status(state, config) folded = config.sections.stashes.folded, name = "stashes", }, - show_upstream_unpulled and Section { - title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref }, - count = true, - render = SectionItemCommit, - items = state.upstream.unpulled.items, - folded = config.sections.unpulled_upstream.folded, - name = "upstream_unpulled", - }, - show_pushRemote_unpulled and Section { - title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, - count = true, - render = SectionItemCommit, - items = state.pushRemote.unpulled.items, - folded = config.sections.unpulled_pushRemote.folded, - name = "pushRemote_unpulled", - }, show_upstream_unmerged and Section { title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref }, count = true, @@ -649,6 +636,22 @@ function M.Status(state, config) folded = config.sections.recent.folded, name = "recent", }, + show_upstream_unpulled and Section { + title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref }, + count = true, + render = SectionItemCommit, + items = state.upstream.unpulled.items, + folded = config.sections.unpulled_upstream.folded, + name = "upstream_unpulled", + }, + show_pushRemote_unpulled and Section { + title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, + count = true, + render = SectionItemCommit, + items = state.pushRemote.unpulled.items, + folded = config.sections.unpulled_pushRemote.folded, + name = "pushRemote_unpulled", + }, }, }, } From 3aa9d60a7b0266c36b39c96827a94f57fbefcf08 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Apr 2024 23:26:40 +0200 Subject: [PATCH 371/443] Move async logic from popup builder into the caller of the actions to be more similar to the other invocations - like configs --- lua/neogit/lib/popup/builder.lua | 23 ++--------------------- lua/neogit/lib/popup/init.lua | 30 +++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index e51c7919b..97645d3b5 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -4,7 +4,6 @@ local state = require("neogit.lib.state") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") local logger = require("neogit.logger") -local status = require("neogit.buffers.status") local M = {} @@ -348,9 +347,6 @@ function M:config_if(cond, key, name, options) return self end --- Allow user actions to be queued -local action_lock = a.control.Semaphore.new(1) - ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ---@param callback function Function that gets run in async context @@ -365,29 +361,14 @@ function M:action(keys, description, callback) notification.error(string.format("[POPUP] Duplicate key mapping %q", key)) return self end - self.state.keys[key] = true - end - local callback_fn - if callback then - callback_fn = a.void(function(...) - local permit = action_lock:acquire() - logger.debug(string.format("[ACTION] Running action from %s", self.state.name)) - - callback(...) - permit:forget() - - if status.instance() then - logger.debug("[ACTION] Dispatching Refresh") - status.instance():dispatch_refresh(nil, "action") - end - end) + self.state.keys[key] = true end table.insert(self.state.actions[#self.state.actions], { keys = keys, description = description, - callback = callback_fn, + callback = callback, }) return self diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 490b65a7c..feea659da 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -1,4 +1,5 @@ local PopupBuilder = require("neogit.lib.popup.builder") +local status = require("neogit.buffers.status") local Buffer = require("neogit.lib.buffer") local logger = require("neogit.logger") local util = require("neogit.lib.util") @@ -231,6 +232,9 @@ function M:set_config(config) end end +-- Allow user actions to be queued +local action_lock = a.control.Semaphore.new(1) + function M:mappings() local mappings = { n = { @@ -303,11 +307,27 @@ function M:mappings() -- nothing elseif action.callback then for _, key in ipairs(action.keys) do - mappings.n[key] = function() - logger.debug(string.format("[POPUP]: Invoking action '%s' of %s", key, self.state.name)) - action.callback(self) - self:close() - end + mappings.n[key] = a.void(function() + local permit = action_lock:acquire() + logger.debug(string.format("[POPUP]: Invoking action %q of %s", key, self.state.name)) + + local act = function() + action.callback(self) + end + + local callback = function() + self:close() + + if status.instance() then + logger.debug("[ACTION] Dispatching Refresh to Status Buffer") + status.instance():dispatch_refresh(nil, "action") + end + + permit:forget() + end + + a.run(act, callback) + end) end else for _, key in ipairs(action.keys) do From a439dfaac9ccfc42c2ed5547f32224462a1d9d5d Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 14 Apr 2024 23:59:24 +0200 Subject: [PATCH 372/443] Cleanup: Rewrite last vim.fn.input calls that didn't go through input lib. --- lua/neogit/lib/input.lua | 16 +++++++++++++--- lua/neogit/lib/popup/init.lua | 23 ++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index 7e263572f..b247da59c 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -43,6 +43,7 @@ end ---@field default any? Default value ---@field completion string? ---@field separator string? +---@field cancel string? ---@param prompt string Prompt to use for user input ---@param opts GetUserInputOpts? Options table @@ -56,6 +57,7 @@ function M.get_user_input(prompt, opts) prompt = ("%s%s"):format(prompt, opts.separator), default = opts.default, completion = opts.completion, + cancelreturn = opts.cancel, }) vim.fn.inputrestore() @@ -74,11 +76,19 @@ function M.get_user_input(prompt, opts) return result end -function M.get_secret_user_input(prompt) +---@param prompt string +---@param opts? table +---@return string|nil +function M.get_secret_user_input(prompt, opts) + opts = vim.tbl_extend("keep", opts or {}, { separator = ": " }) + vim.fn.inputsave() - local status, result = pcall(vim.fn.inputsecret, prompt) - vim.fn.inputrestore() + local status, result = pcall(vim.fn.inputsecret, { + prompt = ("%s%s"):format(prompt, opts.separator), + cancelreturn = opts.cancel, + }) + vim.fn.inputrestore() if not status then return nil end diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index feea659da..d88e68a2a 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -170,12 +170,14 @@ function M:set_option(option) elseif option.fn then option.value = option.fn(self, option) else - -- ...Otherwise get the value via input. - local input = vim.fn.input { - prompt = option.cli .. "=", - default = option.value, - cancelreturn = option.value, - } + local input = input.get_user_input( + option.cli, + { + separator = "=", + default = option.value, + cancel = option.value, + } + ) -- If the option specifies a default value, and the user set the value to be empty, defer to default value. -- This is handy to prevent the user from accidentally loading thousands of log entries by accident. @@ -208,11 +210,10 @@ function M:set_config(config) elseif config.fn then config.value = config.fn(self, config) else - local result = vim.fn.input { - prompt = config.name .. " > ", - default = config.value, - cancelreturn = config.value, - } + local result = input.get_user_input( + config.name, + { default = config.value, cancel = config.value } + ) config.value = result git.config.set(config.name, config.value) From ee94bb2d542d1762656fecd497c73079cb503780 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 15 Apr 2024 00:00:14 +0200 Subject: [PATCH 373/443] Cleanup interactive CLI input handler --- lua/neogit/lib/git/cli.lua | 97 +++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index c23caf683..7966310f1 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -3,6 +3,7 @@ local git = require("neogit.lib.git") local process = require("neogit.process") local util = require("neogit.lib.util") local Path = require("plenary.path") +local input = require("neogit.lib.input") local function config(setup) setup = setup or {} @@ -799,62 +800,70 @@ local mt_builder = { end, } ----@param p Process ---@param line string -local function handle_interactive_password_questions(p, line) - process.hide_preview_buffers() +---@return string +local function handle_interactive_authenticity(line) + logger.debug("[CLI]: Confirming whether to continue with unauthenticated host") + + local prompt = line + return input.get_user_input( + "The authenticity of the host can't be established." .. prompt .. "", + { cancel = "__CANCEL__" } + ) or "__CANCEL__" +end +---@param line string +---@return string +local function handle_interactive_username(line) + logger.debug("[CLI]: Asking for username") + + local prompt = line:match("(.*:?):.*") + return input.get_user_input(prompt, { cancel = "__CANCEL__" }) or "__CANCEL__" +end + +---@param line string +---@return string +local function handle_interactive_password(line) + logger.debug("[CLI]: Asking for password") + + local prompt = line:match("(.*:?):.*") + return input.get_secret_user_input(prompt, { cancel = "__CANCEL__" }) or "__CANCEL__" +end + +---@param p Process +---@param line string +---@return boolean +local function handle_line_interactive(p, line) logger.debug(string.format("Matching interactive cmd output: '%s'", line)) - if vim.startswith(line, "Are you sure you want to continue connecting ") then - logger.debug("[CLI]: Confirming whether to continue with unauthenticated host") - local prompt = line - local value = vim.fn.input { - prompt = "The authenticity of the host can't be established. " .. prompt .. " ", - cancelreturn = "__CANCEL__", - } - if value ~= "__CANCEL__" then - logger.debug("[CLI]: Received answer") - p:send(value .. "\r\n") - else + local handler + if line:match("^Are you sure you want to continue connecting ") then + handler = handle_interactive_authenticity + elseif line:match("^Username for ") then + handler = handle_interactive_username + elseif line:match("^Enter passphrase") + or line:match("^Password for") then + handler = handle_interactive_password + end + + if handler then + process.hide_preview_buffers() + + local value = handler(line) + if value == "__CANCEL__" then logger.debug("[CLI]: Cancelling the interactive cmd") p:stop() - end - elseif vim.startswith(line, "Username for ") then - logger.debug("[CLI]: Asking for username") - local prompt = line:match("(.*:?):.*") - local value = vim.fn.input { - prompt = prompt .. " ", - cancelreturn = "__CANCEL__", - } - if value ~= "__CANCEL__" then - logger.debug("[CLI]: Received username") - p:send(value .. "\r\n") else - logger.debug("[CLI]: Cancelling the interactive cmd") - p:stop() - end - elseif vim.startswith(line, "Enter passphrase") or vim.startswith(line, "Password for") then - logger.debug("[CLI]: Asking for password") - local prompt = line:match("(.*:?):.*") - local value = vim.fn.inputsecret { - prompt = prompt .. " ", - cancelreturn = "__CANCEL__", - } - if value ~= "__CANCEL__" then - logger.debug("[CLI]: Received password") + logger.debug("[CLI]: Sending user input") p:send(value .. "\r\n") - else - logger.debug("[CLI]: Cancelling the interactive cmd") - p:stop() end + + process.defer_show_preview_buffers() + return true else process.defer_show_preview_buffers() return false end - - process.defer_show_preview_buffers() - return true end local function new_builder(subcommand) @@ -932,7 +941,7 @@ local function new_builder(subcommand) call_interactive = function(options) local opts = options or {} - local handle_line = opts.handle_line or handle_interactive_password_questions + local handle_line = opts.handle_line or handle_line_interactive local p = to_process { verbose = opts.verbose, on_error = function(res) From 7318212aec4791f0b6f6a913a495b59c55c5c9ae Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 15 Apr 2024 00:02:51 +0200 Subject: [PATCH 374/443] Rework action invocation so the popup closes quickly instead of waiting for longer processes like "fetch" to complete. --- lua/neogit/lib/popup/init.lua | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index d88e68a2a..2013cf93c 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -312,22 +312,15 @@ function M:mappings() local permit = action_lock:acquire() logger.debug(string.format("[POPUP]: Invoking action %q of %s", key, self.state.name)) - local act = function() - action.callback(self) - end - - local callback = function() - self:close() - - if status.instance() then - logger.debug("[ACTION] Dispatching Refresh to Status Buffer") - status.instance():dispatch_refresh(nil, "action") - end + self:close() + action.callback(self) - permit:forget() + if status.instance() then + logger.debug("[ACTION] Dispatching Refresh to Status Buffer") + status.instance():dispatch_refresh(nil, "action") end - a.run(act, callback) + permit:forget() end) end else From 7dde905d4e67d18190dd8158ec3f6586fa978500 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 15 Apr 2024 22:35:29 +0200 Subject: [PATCH 375/443] Add better annotations to popup builder --- lua/neogit/lib/popup/builder.lua | 62 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 97645d3b5..5036697e2 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -1,9 +1,7 @@ -local a = require("plenary.async") local git = require("neogit.lib.git") local state = require("neogit.lib.state") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") -local logger = require("neogit.logger") local M = {} @@ -67,6 +65,29 @@ local M = {} ---@field description string ---@field callback function +---@class PopupSwitchOpts +---@field enabled boolean Controls if the switch should default to 'on' state +---@field internal boolean Whether the switch is internal to neogit or should be included in the cli command. If `true` we don't include it in the cli command. +---@field incompatible table A table of strings that represent other cli flags that this one cannot be used with +---@field key_prefix string Allows overwriting the default '-' to toggle switch +---@field cli_prefix string Allows overwriting the default '--' thats used to create the cli flag. Sometimes you may want to use '++' or '-'. +---@field cli_suffix string +---@field options table +---@field value string Allows for pre-building cli flags that can be customised by user input +---@field user_input boolean If true, allows user to customise the value of the cli flag +---@field dependant string[] other switches with a state dependency on this one + +---@class PopupOptionsOpts +---@field key_prefix string Allows overwriting the default '=' to set option +---@field cli_prefix string Allows overwriting the default '--' cli prefix +---@field choices table Table of predefined choices that a user can select for option +---@field default string|integer|boolean Default value for option, if the user attempts to unset value + +---@class PopupConfigOpts +---@field options { display: string, value: string, config: function? } +---@field passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI +-- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. + function M.new(builder_fn) local instance = { state = { @@ -95,17 +116,17 @@ function M:env(x) return self end --- Adds new column to actions section of popup ----@param heading string|nil +---Adds new column to actions section of popup +---@param heading string? ---@return self function M:new_action_group(heading) table.insert(self.state.actions, { { heading = heading or "" } }) return self end --- Conditionally adds new column to actions section of popup +---Conditionally adds new column to actions section of popup ---@param cond boolean ----@param heading string|nil +---@param heading string? ---@return self function M:new_action_group_if(cond, heading) if cond then @@ -115,7 +136,7 @@ function M:new_action_group_if(cond, heading) return self end --- Adds new heading to current column within actions section of popup +---Adds new heading to current column within actions section of popup ---@param heading string ---@return self function M:group_heading(heading) @@ -138,16 +159,7 @@ end ---@param key string Which key triggers switch ---@param cli string Git cli flag to use ---@param description string Description text to show user ----@param opts table|nil A table of options for the switch ----@param opts.enabled boolean Controls if the switch should default to 'on' state ----@param opts.internal boolean Whether the switch is internal to neogit or should be included in the cli command. --- If `true` we don't include it in the cli command. ----@param opts.incompatible table A table of strings that represent other cli flags that this one cannot be used with ----@param opts.key_prefix string Allows overwriting the default '-' to toggle switch ----@param opts.cli_prefix string Allows overwriting the default '--' thats used to create the cli flag. Sometimes you may want --- to use '++' or '-'. ----@param opts.value string Allows for pre-building cli flags that can be customised by user input ----@param opts.user_input boolean If true, allows user to customise the value of the cli flag +---@param opts PopupSwitchOpts? ---@return self function M:switch(key, cli, description, opts) opts = opts or {} @@ -222,6 +234,10 @@ end -- Conditionally adds a switch. ---@see M:switch ---@param cond boolean +---@param key string Which key triggers switch +---@param cli string Git cli flag to use +---@param description string Description text to show user +---@param opts PopupSwitchOpts? ---@return self function M:switch_if(cond, key, cli, description, opts) if cond then @@ -235,11 +251,6 @@ end ---@param cli string CLI value used ---@param value string Current value of option ---@param description string Description of option, presented to user ----@param opts table|nil ----@param opts.key_prefix string Allows overwriting the default '=' to set option ----@param opts.cli_prefix string Allows overwriting the default '--' cli prefix ----@param opts.choices table Table of predefined choices that a user can select for option ----@param opts.default string|integer|boolean Default value for option, if the user attempts to unset value function M:option(key, cli, value, description, opts) opts = opts or {} @@ -282,7 +293,6 @@ end ---@param heading string Heading to show ---@return self function M:arg_heading(heading) - ---@type PopupHeading table.insert(self.state.args, { type = "heading", heading = heading }) return self end @@ -307,11 +317,7 @@ end ---@param key string Key for user to use that engages config ---@param name string Name of config ----@param options table|nil ----@param options.options table Table of tables, each consisting of `{ display = "", value = "" }` --- where 'display' is what is shown to the user, and 'value' is what gets used by the cli. --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. ----@param options.passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI +---@param options PopupConfigOpts? ---@return self function M:config(key, name, options) local entry = git.config.get(name) From 15688d8533b20759ed032db3d0da43a06757b8a2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Apr 2024 09:15:46 +0200 Subject: [PATCH 376/443] Guard mouse support for telescope behind check since current latest tag (0.1.6) doesn't have these functions. They exist on HEAD though. --- lua/neogit/lib/finder.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/finder.lua b/lua/neogit/lib/finder.lua index 4afc7a106..df3ddd4f3 100644 --- a/lua/neogit/lib/finder.lua +++ b/lua/neogit/lib/finder.lua @@ -69,11 +69,17 @@ local function telescope_mappings(on_select, allow_multi, refocus_status) ["NOP"] = actions.nop, ["MultiselectToggleNext"] = actions.toggle_selection + actions.move_selection_worse, ["MultiselectTogglePrevious"] = actions.toggle_selection + actions.move_selection_better, - ["ScrollWheelDown"] = actions.move_selection_next, - ["ScrollWheelUp"] = actions.move_selection_previous, - ["MouseClick"] = actions.mouse_click, } + -- Telescope HEAD has mouse click support, but not the latest tag. Need to check if the user has + -- support for mouse click, while avoiding the error that the metatable raises. + -- stylua: ignore + if pcall(function() return actions.mouse_click and true end) then + commands.ScrollWheelDown = actions.move_selection_next + commands.ScrollWheelUp = actions.move_selection_previous + commands.MouseClick = actions.mouse_click + end + for mapping, command in pairs(config.values.mappings.finder) do if command and command:match("^Multiselect") then if allow_multi then From 345f4443365451edb96643773634c9d396ae41bc Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Apr 2024 22:44:28 +0200 Subject: [PATCH 377/443] Feature: Add "+" mapping to log buffer, that will append an additional n commits to the current history, where n is the original value used to create the buffer. --- lua/neogit/buffers/log_view/init.lua | 26 +++++++++++++- lua/neogit/buffers/log_view/ui.lua | 20 ++++++++++- lua/neogit/popups/log/actions.lua | 51 ++++++++++++++++++++++------ 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 4eb708cdc..02aa15da3 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -4,11 +4,16 @@ local config = require("neogit.config") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") +local util = require("neogit.lib.util") +local a = require("plenary.async") ---@class LogViewBuffer ---@field commits CommitLogEntry[] ---@field internal_args table ---@field files string[] +---@field buffer Buffer +---@field fetch_func fun(offset: number): CommitLogEntry[] +---@field refresh_lock Semaphore local M = {} M.__index = M @@ -16,13 +21,16 @@ M.__index = M ---@param commits CommitLogEntry[]|nil ---@param internal_args table|nil ---@param files string[]|nil list of files to filter by +---@param fetch_func fun(offset: number): CommitLogEntry[] ---@return LogViewBuffer -function M.new(commits, internal_args, files) +function M.new(commits, internal_args, files, fetch_func) local instance = { files = files, commits = commits, internal_args = internal_args, + fetch_func = fetch_func, buffer = nil, + refresh_lock = a.control.Semaphore.new(1), } setmetatable(instance, M) @@ -30,6 +38,14 @@ function M.new(commits, internal_args, files) return instance end +function M:commit_count() + return #util.filter_map(self.commits, function(commit) + if commit.oid then + return 1 + end + end) +end + function M:close() if self.buffer then self.buffer:close() @@ -202,6 +218,14 @@ function M:open() pcall(vim.cmd, "normal! zo") vim.cmd("normal! zz") end, + ["+"] = a.void(function() + local permit = self.refresh_lock:acquire() + + self.commits = util.merge(self.commits, self.fetch_func(self:commit_count())) + self.buffer.ui:render(unpack(ui.View(self.commits, self.internal_args))) + + permit:forget() + end), [""] = function() pcall(vim.cmd, "normal! za") end, diff --git a/lua/neogit/buffers/log_view/ui.lua b/lua/neogit/buffers/log_view/ui.lua index 6907ad702..9b1426534 100644 --- a/lua/neogit/buffers/log_view/ui.lua +++ b/lua/neogit/buffers/log_view/ui.lua @@ -3,6 +3,11 @@ local util = require("neogit.lib.util") local Commit = require("neogit.buffers.common").CommitEntry local Graph = require("neogit.buffers.common").CommitGraph +local Ui = require("neogit.lib.ui") +local text = Ui.text +local col = Ui.col +local row = Ui.row + local M = {} ---@param commits CommitLogEntry[] @@ -11,13 +16,26 @@ local M = {} function M.View(commits, args) args.details = true - return util.filter_map(commits, function(commit) + local graph = util.filter_map(commits, function(commit) if commit.oid then return Commit(commit, args) elseif args.graph then return Graph(commit) end end) + + table.insert( + graph, + col { + row { + text.highlight("NeogitGraphBoldBlue")("Type"), + text.highlight("NeogitGraphBoldCyan")(" + "), + text.highlight("NeogitGraphBoldBlue")("to show more history"), + }, + } + ) + + return graph end return M diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 8768700ba..5f97b4f01 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -24,44 +24,75 @@ local function commits(popup, flags) ) end +---@param popup table +---@param flags table +---@return fun(offset: number): CommitLogEntry[] +local function fetch_more_commits(popup, flags) + return function(offset) + return commits(popup, util.merge(flags, { ("--skip=%s"):format(offset) })) + end +end + -- TODO: Handle when head is detached M.log_current = operation("log_current", function(popup) - LogViewBuffer.new(commits(popup, {}), popup:get_internal_arguments(), popup.state.env.files):open() + LogViewBuffer.new( + commits(popup, {}), + popup:get_internal_arguments(), + popup.state.env.files, + fetch_more_commits(popup, {}) + ):open() end) function M.log_head(popup) - LogViewBuffer.new(commits(popup, { "HEAD" }), popup:get_internal_arguments(), popup.state.env.files):open() + local flags = { "HEAD" } + LogViewBuffer.new( + commits(popup, flags), + popup:get_internal_arguments(), + popup.state.env.files, + fetch_more_commits(popup, flags) + ):open() end function M.log_local_branches(popup) + local flags = { git.branch.is_detached() and "" or "HEAD", "--branches" } LogViewBuffer.new( - commits(popup, { git.branch.is_detached() and "" or "HEAD", "--branches" }), + commits(popup, flags), popup:get_internal_arguments(), - popup.state.env.files + popup.state.env.files, + fetch_more_commits(popup, flags) ):open() end function M.log_other(popup) local branch = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() if branch then - LogViewBuffer.new(commits(popup, { branch }), popup:get_internal_arguments(), popup.state.env.files) - :open() + local flags = { branch } + LogViewBuffer.new( + commits(popup, flags), + popup:get_internal_arguments(), + popup.state.env.files, + fetch_more_commits(popup, flags) + ):open() end end function M.log_all_branches(popup) + local flags = { git.branch.is_detached() and "" or "HEAD", "--branches", "--remotes" } LogViewBuffer.new( - commits(popup, { git.branch.is_detached() and "" or "HEAD", "--branches", "--remotes" }), + commits(popup, flags), popup:get_internal_arguments(), - popup.state.env.files + popup.state.env.files, + fetch_more_commits(popup, flags) ):open() end function M.log_all_references(popup) + local flags = { git.branch.is_detached() and "" or "HEAD", "--all" } LogViewBuffer.new( - commits(popup, { git.branch.is_detached() and "" or "HEAD", "--all" }), + commits(popup, flags), popup:get_internal_arguments(), - popup.state.env.files + popup.state.env.files, + fetch_more_commits(popup, flags) ):open() end From bb0dc2e09a4a1b0cf52fbb9722b64db2ca110d8e Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 16 Apr 2024 22:50:06 +0200 Subject: [PATCH 378/443] Fix: When arg.value is nil, you cant get it's length --- lua/neogit/lib/popup/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 2013cf93c..e80ddf5a8 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -55,7 +55,7 @@ function M:get_arguments() table.insert(flags, arg.cli_prefix .. arg.cli .. arg.cli_suffix) end - if arg.type == "option" and arg.cli ~= "" and #arg.value ~= 0 and not arg.internal then + if arg.type == "option" and arg.cli ~= "" and (arg.value and #arg.value ~= 0) and not arg.internal then table.insert(flags, arg.cli_prefix .. arg.cli .. "=" .. arg.value) end end From 68d7c3dfd043c8f3ca74fb4827eb88f270bdc066 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 10:49:09 +0200 Subject: [PATCH 379/443] Feature: Add configuration for status.reset_style - this allows the user to set either "commit" (the default, and current behaviour) or "parent". When "parent" is set, the reset popup will receive the commit hash suffixed with a "^" when a commit is selected. --- README.md | 5 +++++ lua/neogit/buffers/status/init.lua | 11 ++++++++++- lua/neogit/config.lua | 4 +++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06288adc9..84b36d291 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,12 @@ neogit.setup { -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, status = { + -- Number of commits to show in recent commit section recent_commit_count = 10, + -- Can be "commit" or "parent". When "parent" is set and the reset popup is invoked with a commit selected/under + -- the cursor, the argument used will be suffixed with "^", resetting the index to the _parent_ commit of the target. + -- When left as "commit", it will perform like normal, resetting to the given commit. + reset_style = "commit", }, commit_editor = { kind = "auto", diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 57cead9f7..3e404d346 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -347,6 +347,10 @@ function M:open(kind, cwd) [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then + if config.values.status.reset_style == "parent" then + commits[1] = commits[1] .. "^" + end + p { commit = commits[1] } end end), @@ -1090,7 +1094,12 @@ function M:open(kind, cwd) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } + local commit = self.buffer.ui:get_commit_under_cursor() + if commit and config.values.status.reset_style == "parent" then + commit = commit .. "^" + end + + p { commit = commit } end), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 7f5f3a1fa..9f39d5754 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -237,7 +237,7 @@ end ---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout ----@field status? { recent_commit_count: integer } Status buffer options +---@field status? { recent_commit_count?: integer, reset_style?: "parent" | "commit" } Status buffer options ---@field commit_editor? NeogitConfigPopup Commit editor options ---@field commit_select_view? NeogitConfigPopup Commit select view options ---@field commit_view? NeogitCommitBufferConfig Commit buffer options @@ -300,6 +300,7 @@ function M.get_default_values() notification_icon = "󰊢", status = { recent_commit_count = 10, + reset_style = "commit", }, commit_editor = { kind = "tab", @@ -963,6 +964,7 @@ function M.validate_config() validate_type(config.auto_show_console, "auto_show_console", "boolean") if validate_type(config.status, "status", "table") then validate_type(config.status.recent_commit_count, "status.recent_commit_count", "number") + validate_type(config.status.reset_style, "status.reset_style", "string") end validate_signs() validate_trinary_auto(config.disable_insert_on_commit, "disable_insert_on_commit") From 619bdc41eede817cce589308676b7d5dfdd0a2ab Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 11:19:32 +0200 Subject: [PATCH 380/443] formatting --- lua/neogit/config.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 9f39d5754..06b1b300d 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -207,6 +207,10 @@ end ---| false ---| fun() +---@alias NeogitGraphStyle +---| "ascii" +---| "unicode" + ---@class NeogitConfigMappings Consult the config file or documentation for values ---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds ---@field status? { [string]: NeogitConfigMappingsStatus } A dictionary that uses status commands to set a single keybind @@ -216,8 +220,6 @@ end ---@field commit_editor? { [string]: NeogitConfigMappingsCommitEditor } A dictionary that uses Commit editor commands to set a single keybind ---@field commit_editor_I? { [string]: NeogitConfigMappingsCommitEditor_I } A dictionary that uses Commit editor commands to set a single keybind ----@alias NeogitGraphStyle "ascii" | "unicode" - ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher ---@field graph_style? NeogitGraphStyle Style for graph From c1b8d296603c0bcc7ea1cd85ee0b8d6078a5dd42 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 11:20:53 +0200 Subject: [PATCH 381/443] Revert 1 commits 68d7c3d 'Feature: Add configuration for status.reset_style - this allows the user to set either "commit" (the default, and current behaviour) or "parent".' --- README.md | 5 ----- lua/neogit/buffers/status/init.lua | 11 +---------- lua/neogit/config.lua | 4 +--- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 84b36d291..06288adc9 100644 --- a/README.md +++ b/README.md @@ -137,12 +137,7 @@ neogit.setup { -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, status = { - -- Number of commits to show in recent commit section recent_commit_count = 10, - -- Can be "commit" or "parent". When "parent" is set and the reset popup is invoked with a commit selected/under - -- the cursor, the argument used will be suffixed with "^", resetting the index to the _parent_ commit of the target. - -- When left as "commit", it will perform like normal, resetting to the given commit. - reset_style = "commit", }, commit_editor = { kind = "auto", diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 3e404d346..57cead9f7 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -347,10 +347,6 @@ function M:open(kind, cwd) [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) local commits = self.buffer.ui:get_commits_in_selection() if #commits == 1 then - if config.values.status.reset_style == "parent" then - commits[1] = commits[1] .. "^" - end - p { commit = commits[1] } end end), @@ -1094,12 +1090,7 @@ function M:open(kind, cwd) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) - local commit = self.buffer.ui:get_commit_under_cursor() - if commit and config.values.status.reset_style == "parent" then - commit = commit .. "^" - end - - p { commit = commit } + p { commit = self.buffer.ui:get_commit_under_cursor() } end), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 06b1b300d..680d97b80 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -239,7 +239,7 @@ end ---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout ----@field status? { recent_commit_count?: integer, reset_style?: "parent" | "commit" } Status buffer options +---@field status? { recent_commit_count: integer } Status buffer options ---@field commit_editor? NeogitConfigPopup Commit editor options ---@field commit_select_view? NeogitConfigPopup Commit select view options ---@field commit_view? NeogitCommitBufferConfig Commit buffer options @@ -302,7 +302,6 @@ function M.get_default_values() notification_icon = "󰊢", status = { recent_commit_count = 10, - reset_style = "commit", }, commit_editor = { kind = "tab", @@ -966,7 +965,6 @@ function M.validate_config() validate_type(config.auto_show_console, "auto_show_console", "boolean") if validate_type(config.status, "status", "table") then validate_type(config.status.recent_commit_count, "status.recent_commit_count", "number") - validate_type(config.status.reset_style, "status.reset_style", "string") end validate_signs() validate_trinary_auto(config.disable_insert_on_commit, "disable_insert_on_commit") From 711b02fdd4a5726ca944e24bc22f19691382b73f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 11:33:53 +0200 Subject: [PATCH 382/443] Replace static config option with a variable that can be set on the fly. --- doc/neogit.txt | 8 ++++++++ lua/neogit/popups/reset/actions.lua | 4 ++++ lua/neogit/popups/reset/init.lua | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index bef855f96..754ca0dd4 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1443,6 +1443,14 @@ Reset Popup *neogit_reset_popup* All actions will default to the commit under cursor if there is one, or prompt the user to select one if there is not. +Variables: *neogit_reset_popup_variables* + • neogit.resetThisTo *neogit_reset_this_to* + When "Commit", will pass the commit hash to `git reset` unaltered. This has + the effect of resetting "to" the commit. + + When "Parent", will append "^" to the commit hash when calling `git reset`. + This has the effect of resetting the commit itself. + Actions: *neogit_reset_popup_actions* • Mixed *neogit_reset_mixed* Resets the index but not the working tree (i.e., the changed files are diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 37035c8ff..14422d500 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -19,6 +19,10 @@ local function commit(popup, prompt) end end + if git.config.get("neogit.resetThisTo"):read() then + commit = commit .. "^" + end + return commit end diff --git a/lua/neogit/popups/reset/init.lua b/lua/neogit/popups/reset/init.lua index d723c3a3f..e0aa3d84c 100644 --- a/lua/neogit/popups/reset/init.lua +++ b/lua/neogit/popups/reset/init.lua @@ -8,6 +8,12 @@ function M.create(env) local p = popup .builder() :name("NeogitResetPopup") + :config("R", "neogit.resetThisTo", { + options = { + { display = "Commit", value = "" }, + { display = "Parent", value = "true" }, + }, + }) :group_heading("Reset") :action("f", "file", actions.a_file) :action("b", "branch", branch_actions.reset_branch) From 9fb89364062401570f82f324d3a212d4658d4ce3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 11:39:21 +0200 Subject: [PATCH 383/443] Only show/use neogit.resetThisTo when selecting a commit before launching the popup. Commits chosen via finder will be used unaltered. --- doc/neogit.txt | 3 +++ lua/neogit/popups/reset/actions.lua | 8 ++++---- lua/neogit/popups/reset/init.lua | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 754ca0dd4..024648513 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1451,6 +1451,9 @@ Variables: *neogit_reset_popup_variables* When "Parent", will append "^" to the commit hash when calling `git reset`. This has the effect of resetting the commit itself. + Only applies when a commit is selected before launching the popup. Commits + chosen via the finder will be used unmodified, ignoring this setting. + Actions: *neogit_reset_popup_actions* • Mixed *neogit_reset_mixed* Resets the index but not the working tree (i.e., the changed files are diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index 14422d500..ba284fdf6 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -11,6 +11,10 @@ local function commit(popup, prompt) local commit if popup.state.env.commit then commit = popup.state.env.commit + + if git.config.get("neogit.resetThisTo"):read() then + commit = commit .. "^" + end else local commits = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) commit = FuzzyFinderBuffer.new(commits):open_async { prompt_prefix = prompt } @@ -19,10 +23,6 @@ local function commit(popup, prompt) end end - if git.config.get("neogit.resetThisTo"):read() then - commit = commit .. "^" - end - return commit end diff --git a/lua/neogit/popups/reset/init.lua b/lua/neogit/popups/reset/init.lua index e0aa3d84c..655ad4175 100644 --- a/lua/neogit/popups/reset/init.lua +++ b/lua/neogit/popups/reset/init.lua @@ -8,7 +8,7 @@ function M.create(env) local p = popup .builder() :name("NeogitResetPopup") - :config("R", "neogit.resetThisTo", { + :config_if(env.commit ~= nil, "R", "neogit.resetThisTo", { options = { { display = "Commit", value = "" }, { display = "Parent", value = "true" }, From 1adf0b9d3aa344528a9af92a46a95a6d8b3e7306 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 12:17:31 +0200 Subject: [PATCH 384/443] Bugfix: when user cancelled a choice, use the default --- lua/neogit/lib/input.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/neogit/lib/input.lua b/lua/neogit/lib/input.lua index b247da59c..923e82a44 100644 --- a/lua/neogit/lib/input.lua +++ b/lua/neogit/lib/input.lua @@ -35,6 +35,11 @@ end function M.get_choice(msg, options) local choice = vim.fn.confirm(msg, table.concat(options.values, "\n"), options.default) vim.cmd("redraw") + + if choice == 0 then -- User cancelled + choice = options.default + end + return options.values[choice]:match("&(.)") end From 8c69b8780e0ec918bf33ee0689a8033d29ab3261 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 17 Apr 2024 23:54:02 +0200 Subject: [PATCH 385/443] Capture and restore user statuscolumn when opening split from neogit status buffer. --- lua/neogit/buffers/status/init.lua | 40 ++++++++++++++++++++----- lua/neogit/lib/buffer.lua | 48 ++++++++++++++++-------------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 57cead9f7..2586e22a2 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -952,8 +952,13 @@ function M:open(kind, cwd) vim.schedule(function() vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) - if cursor then - api.nvim_win_set_cursor(0, cursor) + local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) + if self.user_statuscolumn then + buf:set_window_option("statuscolumn", self.user_statuscolumn) + end + + if buf:is_focused() and cursor then + buf:move_cursor(cursor) end end) @@ -994,8 +999,13 @@ function M:open(kind, cwd) end vim.cmd.tabedit(fn.fnameescape(item.absolute_path)) - if cursor then - api.nvim_win_set_cursor(0, cursor) + local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) + if self.user_statuscolumn then + buf:set_window_option("statuscolumn", self.user_statuscolumn) + end + + if buf:is_focused() and cursor then + buf:move_cursor(cursor) end end end, @@ -1027,8 +1037,13 @@ function M:open(kind, cwd) end vim.cmd.split(fn.fnameescape(item.absolute_path)) - if cursor then - api.nvim_win_set_cursor(0, cursor) + local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) + if self.user_statuscolumn then + buf:set_window_option("statuscolumn", self.user_statuscolumn) + end + + if buf:is_focused() and cursor then + buf:move_cursor(cursor) end end end, @@ -1060,8 +1075,13 @@ function M:open(kind, cwd) end vim.cmd.vsplit(fn.fnameescape(item.absolute_path)) - if cursor then - api.nvim_win_set_cursor(0, cursor) + local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) + if self.user_statuscolumn then + buf:set_window_option("statuscolumn", self.user_statuscolumn) + end + + if buf:is_focused() and cursor then + buf:move_cursor(cursor) end end end, @@ -1162,6 +1182,10 @@ function M:open(kind, cwd) }, }, initialize = function() + if vim.wo.statuscolumn ~= "" then + self.user_statuscolumn = vim.wo.statuscolumn + end + self.prev_autochdir = vim.o.autochdir vim.o.autochdir = false end, diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 1ea31188f..a451e0ee1 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -23,10 +23,12 @@ local Buffer = { Buffer.__index = Buffer ---@param handle number +---@param win_handle number ---@return Buffer -function Buffer:new(handle) +function Buffer:new(handle, win_handle) local this = { handle = handle, + win_handle = win_handle, border = nil, mmanager = mappings_manager.new(handle), kind = nil, -- how the buffer was opened. For more information look at the create function @@ -625,26 +627,6 @@ function Buffer.create(config) buffer:set_buffer_option("foldtext", "v:lua._G.NeogitFoldText()") end - if win then - logger.debug("[BUFFER:" .. buffer.handle .. "] Setting window options") - - buffer:set_window_option("statuscolumn", config.status_column or "") - buffer:set_window_option("foldenable", true) - buffer:set_window_option("foldlevel", 99) - buffer:set_window_option("foldminlines", 0) - buffer:set_window_option("foldtext", "") - buffer:set_window_option("listchars", "") - buffer:set_window_option("list", false) - - if vim.fn.has("nvim-0.10") == 1 then - buffer:set_window_option("spell", false) - buffer:set_window_option("wrap", false) - buffer:set_window_option("foldmethod", "manual") - -- TODO: Need to find a way to turn this off properly when unloading plugin - -- buffer:set_window_option("winfixbuf", true) - end - end - if config.filetype then logger.debug("[BUFFER:" .. buffer.handle .. "] Setting filetype: " .. config.filetype) buffer:set_filetype(config.filetype) @@ -674,6 +656,26 @@ function Buffer.create(config) config.initialize(buffer, win) end + if win then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting window options") + + buffer:set_window_option("statuscolumn", config.status_column or "") + buffer:set_window_option("foldenable", true) + buffer:set_window_option("foldlevel", 99) + buffer:set_window_option("foldminlines", 0) + buffer:set_window_option("foldtext", "") + buffer:set_window_option("listchars", "") + buffer:set_window_option("list", false) + + if vim.fn.has("nvim-0.10") == 1 then + buffer:set_window_option("spell", false) + buffer:set_window_option("wrap", false) + buffer:set_window_option("foldmethod", "manual") + -- TODO: Need to find a way to turn this off properly when unloading plugin + -- buffer:set_window_option("winfixbuf", true) + end + end + if config.render then logger.debug("[BUFFER:" .. buffer.handle .. "] Rendering buffer") buffer.ui:render(unpack(config.render(buffer))) @@ -773,7 +775,9 @@ function Buffer.from_name(name) api.nvim_buf_set_name(buffer_handle, name) end - return Buffer:new(buffer_handle) + local window_handle = fn.win_findbuf(buffer_handle) + + return Buffer:new(buffer_handle, window_handle[1]) end return Buffer From 50ab2773a10f9a6af2d54de51efcbcad9075117c Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 18 Apr 2024 09:23:26 +0200 Subject: [PATCH 386/443] Add individual highlight groups for each status section header, linking to NeogitSectionHeader, to allow user to color them individually if desired. --- doc/neogit.txt | 3 +++ lua/neogit/buffers/status/ui.lua | 39 ++++++++++++++++---------------- lua/neogit/lib/hl.lua | 3 +++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 024648513..3a82c5ff3 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -388,6 +388,7 @@ NeogitUnpulledFrom ^ NeogitUntrackedfiles ^ NeogitUnstagedchanges ^ NeogitUnmergedchanges ^ +NeogitUnpushedchanges ^ NeogitUnpulledchanges ^ NeogitRecentcommits ^ NeogitStagedchanges ^ @@ -395,6 +396,8 @@ NeogitStashes ^ NeogitRebasing ^ NeogitReverting ^ NeogitPicking ^ +NeogitMerging ^ +NeogitBisecting ^ STATUS BUFFER FILE Applied to the label on the left of filenames. diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 78f5e9f46..b6417365f 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -120,12 +120,12 @@ local Tag = Component.new(function(props) end) local SectionTitle = Component.new(function(props) - return { text.highlight("NeogitSectionHeader")(props.title) } + return { text.highlight(props.highlight or "NeogitSectionHeader")(props.title) } end) local SectionTitleRemote = Component.new(function(props) return { - text.highlight("NeogitSectionHeader")(props.title), + text.highlight(props.highlight or "NeogitSectionHeader")(props.title), text(" "), text.highlight("NeogitRemote")(props.ref), } @@ -134,7 +134,7 @@ end) local SectionTitleRebase = Component.new(function(props) if props.onto then return { - text.highlight("NeogitSectionHeader")(props.title), + text.highlight(props.highlight or "NeogitSectionHeader")(props.title), text(" "), text.highlight("NeogitBranch")(props.head), text.highlight("NeogitSectionHeader")(" onto "), @@ -142,7 +142,7 @@ local SectionTitleRebase = Component.new(function(props) } else return { - text.highlight("NeogitSectionHeader")(props.title), + text.highlight(props.highlight or "NeogitSectionHeader")(props.title), text(" "), text.highlight("NeogitBranch")(props.head), } @@ -151,7 +151,7 @@ end) local SectionTitleMerge = Component.new(function(props) return { - text.highlight("NeogitSectionHeader")(props.title), + text.highlight(props.highlight or "NeogitSectionHeader")(props.title), text(" "), text.highlight("NeogitBranch")(props.branch), } @@ -533,7 +533,7 @@ function M.Status(state, config) }, EmptyLine(), show_merge and SequencerSection { - title = SectionTitleMerge { title = "Merging", branch = state.merge.branch }, + title = SectionTitleMerge { title = "Merging", branch = state.merge.branch, highlight = "NeogitMerging" }, render = SectionItemSequencer, items = { { action = "", oid = state.merge.head, subject = state.merge.subject } }, folded = config.sections.sequencer.folded, @@ -546,6 +546,7 @@ function M.Status(state, config) onto = state.rebase.onto.ref, oid = state.rebase.onto.oid, is_remote_ref = state.rebase.onto.is_remote, + highlight = "NeogitRebasing" }, render = SectionItemRebase, current = state.rebase.current, @@ -554,34 +555,34 @@ function M.Status(state, config) name = "rebase", }, show_cherry_pick and SequencerSection { - title = SectionTitle { title = "Cherry Picking" }, + 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 { - title = SectionTitle { title = "Reverting" }, + 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 { + title = SectionTitle { title = "Bisecting at", highlight = "NeogitBisecting" }, commit = state.bisect.current, - title = SectionTitle { title = "Bisecting at" }, folded = config.sections.bisect.folded, name = "bisect_details", }, show_bisect and SequencerSection { - title = SectionTitle { title = "Bisecting Log" }, + title = SectionTitle { title = "Bisecting Log", highlight = "NeogitBisecting" }, render = SectionItemBisect, items = state.bisect.items, folded = config.sections.bisect.folded, name = "bisect", }, show_untracked and Section { - title = SectionTitle { title = "Untracked files" }, + title = SectionTitle { title = "Untracked files", highlight = "NeogitUntrackedfiles" }, count = true, render = SectionItemFile("untracked"), items = state.untracked.items, @@ -589,7 +590,7 @@ function M.Status(state, config) name = "untracked", }, show_unstaged and Section { - title = SectionTitle { title = "Unstaged changes" }, + title = SectionTitle { title = "Unstaged changes", highlight = "NeogitUnstagedchanges" }, count = true, render = SectionItemFile("unstaged"), items = state.unstaged.items, @@ -597,7 +598,7 @@ function M.Status(state, config) name = "unstaged", }, show_staged and Section { - title = SectionTitle { title = "Staged changes" }, + title = SectionTitle { title = "Staged changes", highlight = "NeogitStagedchanges" }, count = true, render = SectionItemFile("staged"), items = state.staged.items, @@ -605,7 +606,7 @@ function M.Status(state, config) name = "staged", }, show_stashes and Section { - title = SectionTitle { title = "Stashes" }, + title = SectionTitle { title = "Stashes", highlight = "NeogitStashes" }, count = true, render = SectionItemStash, items = state.stashes.items, @@ -613,7 +614,7 @@ function M.Status(state, config) name = "stashes", }, show_upstream_unmerged and Section { - title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref }, + title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref, highlight = "NeogitUnmergedchanges" }, count = true, render = SectionItemCommit, items = state.upstream.unmerged.items, @@ -621,7 +622,7 @@ function M.Status(state, config) name = "upstream_unmerged", }, show_pushRemote_unmerged and Section { - title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref }, + title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref, highlight = "NeogitUnpushedchanges" }, count = true, render = SectionItemCommit, items = state.pushRemote.unmerged.items, @@ -629,7 +630,7 @@ function M.Status(state, config) name = "pushRemote_unmerged", }, not show_upstream_unmerged and show_recent and Section { - title = SectionTitle { title = "Recent Commits" }, + title = SectionTitle { title = "Recent Commits", highlight = "NeogitRecentcommits" }, count = false, render = SectionItemCommit, items = state.recent.items, @@ -637,7 +638,7 @@ function M.Status(state, config) name = "recent", }, show_upstream_unpulled and Section { - title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref }, + title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref, highlight = "NeogitUnpulledchanges" }, count = true, render = SectionItemCommit, items = state.upstream.unpulled.items, @@ -645,7 +646,7 @@ function M.Status(state, config) name = "upstream_unpulled", }, show_pushRemote_unpulled and Section { - title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref }, + title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref, highlight = "NeogitUnpulledchanges" }, count = true, render = SectionItemCommit, items = state.pushRemote.unpulled.items, diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 19a4513d4..9bcd36d5c 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -201,9 +201,12 @@ function M.setup() NeogitUnstagedchanges = { link = "NeogitSectionHeader" }, NeogitUnmergedchanges = { link = "NeogitSectionHeader" }, NeogitUnpulledchanges = { link = "NeogitSectionHeader" }, + NeogitUnpushedchanges = { link = "NeogitSectionHeader" }, NeogitRecentcommits = { link = "NeogitSectionHeader" }, NeogitStagedchanges = { link = "NeogitSectionHeader" }, NeogitStashes = { link = "NeogitSectionHeader" }, + NeogitMerging = { link = "NeogitSectionHeader" }, + NeogitBisecting = { link = "NeogitSectionHeader" }, NeogitRebasing = { link = "NeogitSectionHeader" }, NeogitPicking = { link = "NeogitSectionHeader" }, NeogitReverting = { link = "NeogitSectionHeader" }, From fd63c19287cb275ea38d2c85f41ce712a816c160 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 18 Apr 2024 09:43:17 +0200 Subject: [PATCH 387/443] Allow mode text and mode padding to be user configurable on the status buffer --- README.md | 18 ++++++++++++++++++ lua/neogit/buffers/status/ui.lua | 31 ++++++------------------------- lua/neogit/config.lua | 27 ++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 06288adc9..f0143fcaa 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,24 @@ neogit.setup { auto_show_console = true, status = { recent_commit_count = 10, + mode_padding = 3, + mode_text = { + M = "modified", + N = "new file", + A = "added", + D = "deleted", + C = "copied", + U = "updated", + R = "renamed", + DD = "unmerged", + AU = "unmerged", + UD = "unmerged", + UA = "unmerged", + DU = "unmerged", + AA = "unmerged", + UU = "unmerged", + ["?"] = "", + }, }, commit_editor = { kind = "auto", diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index b6417365f..258f07297 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -207,7 +207,7 @@ local RebaseSection = Component.new(function(props) }) end) -local SectionItemFile = function(section) +local SectionItemFile = function(section, config) return Component.new(function(item) local load_diff = function(item) ---@param this Component @@ -240,31 +240,12 @@ local SectionItemFile = function(section) end) end - local mode_to_text = { - M = "modified", - N = "new file", - A = "added", - D = "deleted", - C = "copied", - U = "updated", - R = "renamed", - DD = "unmerged", - AU = "unmerged", - UD = "unmerged", - UA = "unmerged", - DU = "unmerged", - AA = "unmerged", - UU = "unmerged", - ["?"] = "", -- Untracked - } - - local mode = mode_to_text[item.mode] - + local mode = config.status.mode_text[item.mode] local mode_text if mode == "" then mode_text = "" else - mode_text = util.pad_right(mode, 11) + mode_text = util.pad_right(mode, util.max_length(vim.tbl_values(config.status.mode_text)) + config.status.mode_padding) end local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name @@ -584,7 +565,7 @@ function M.Status(state, config) show_untracked and Section { title = SectionTitle { title = "Untracked files", highlight = "NeogitUntrackedfiles" }, count = true, - render = SectionItemFile("untracked"), + render = SectionItemFile("untracked", config), items = state.untracked.items, folded = config.sections.untracked.folded, name = "untracked", @@ -592,7 +573,7 @@ function M.Status(state, config) show_unstaged and Section { title = SectionTitle { title = "Unstaged changes", highlight = "NeogitUnstagedchanges" }, count = true, - render = SectionItemFile("unstaged"), + render = SectionItemFile("unstaged", config), items = state.unstaged.items, folded = config.sections.unstaged.folded, name = "unstaged", @@ -600,7 +581,7 @@ function M.Status(state, config) show_staged and Section { title = SectionTitle { title = "Staged changes", highlight = "NeogitStagedchanges" }, count = true, - render = SectionItemFile("staged"), + render = SectionItemFile("staged", config), items = state.staged.items, folded = config.sections.staged.folded, name = "staged", diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 680d97b80..e74eee925 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -211,6 +211,11 @@ end ---| "ascii" ---| "unicode" +---@class NeogitConfigStatusOptions +---@field recent_commit_count? integer The number of recent commits to display +---@field mode_padding? integer The amount of padding to add to the right of the mode column +---@field mode_text? { [string]: string } The text to display for each mode + ---@class NeogitConfigMappings Consult the config file or documentation for values ---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds ---@field status? { [string]: NeogitConfigMappingsStatus } A dictionary that uses status commands to set a single keybind @@ -239,7 +244,7 @@ end ---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout ----@field status? { recent_commit_count: integer } Status buffer options +---@field status? NeogitConfigStatusOptions Status buffer options ---@field commit_editor? NeogitConfigPopup Commit editor options ---@field commit_select_view? NeogitConfigPopup Commit select view options ---@field commit_view? NeogitCommitBufferConfig Commit buffer options @@ -302,6 +307,24 @@ function M.get_default_values() notification_icon = "󰊢", status = { recent_commit_count = 10, + mode_padding = 3, + mode_text = { + M = "modified", + N = "new file", + A = "added", + D = "deleted", + C = "copied", + U = "updated", + R = "renamed", + DD = "unmerged", + AU = "unmerged", + UD = "unmerged", + UA = "unmerged", + DU = "unmerged", + AA = "unmerged", + UU = "unmerged", + ["?"] = "", + }, }, commit_editor = { kind = "tab", @@ -965,6 +988,8 @@ function M.validate_config() validate_type(config.auto_show_console, "auto_show_console", "boolean") if validate_type(config.status, "status", "table") then validate_type(config.status.recent_commit_count, "status.recent_commit_count", "number") + validate_type(config.status.mode_padding, "status.mode_padding", "number") + validate_type(config.status.mode_text, "status.mode_text", "table") end validate_signs() validate_trinary_auto(config.disable_insert_on_commit, "disable_insert_on_commit") From 9e9b9f2abe24e077aba3e2a763f4e6718d8960d4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 18 Apr 2024 21:55:51 +0200 Subject: [PATCH 388/443] Handle committing with an empty commit --- lua/neogit/buffers/diff/init.lua | 31 ++++++++++++++++++------------- lua/neogit/lib/git/diff.lua | 3 +++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index 51f408f47..1a1189bb3 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -5,12 +5,15 @@ local status_maps = require("neogit.config").get_reversed_status_maps() local api = vim.api ---- @class DiffBuffer ---- @field buffer Buffer ---- @field open fun(self, kind: string) ---- @field close fun() ---- @see Buffer ---- @see Ui +---@class DiffBuffer +---@field buffer Buffer +---@field open fun(self, kind: string) +---@field close fun() +---@field stats table +---@field diffs table +---@field header string +---@see Buffer +---@see Ui local M = {} M.__index = M @@ -20,6 +23,10 @@ function M:new(header) local instance = { buffer = nil, header = header, + stats = git.diff.staged_stats(), + diffs = vim.tbl_map(function(item) + return item.diff + end, git.repo.state.staged.items) } setmetatable(instance, self) @@ -38,6 +45,10 @@ end ---If already open will close the buffer ---@return DiffBuffer function M:open() + if vim.tbl_isempty(self.stats.files) then + return self + end + self.buffer = Buffer.create { name = "NeogitDiffView", filetype = "NeogitDiffView", @@ -92,13 +103,7 @@ function M:open() }, }, render = function() - local stats = git.diff.staged_stats() - - local diffs = vim.tbl_map(function(item) - return item.diff - end, git.repo.state.staged.items) - - return ui.DiffView(self.header, stats, diffs) + return ui.DiffView(self.header, self.stats, self.diffs) end, after = function() vim.cmd("normal! zR") diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index dced4a53d..fb1b5e62d 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -271,6 +271,9 @@ return { while true do local line = peek() + if not line then + break + end if line:match("^ %d+ file[s ]+changed,") then summary = vim.trim(line) From 5d24c8cc8198b252afc913993c62551d341193e0 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 18 Apr 2024 21:57:44 +0200 Subject: [PATCH 389/443] Fix: Remote parsing when the url doesn't end in .git should be handled. --- lua/neogit/lib/git/remote.lua | 2 +- tests/specs/neogit/lib/git/remote_spec.lua | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index 9806277f2..fca4cbea0 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -100,7 +100,7 @@ function M.parse(url) owner = path -- Strictly for backwards compatibility. end - repository = url:match([[/([^/]+)%.git]]) + repository = url:match([[/([^/]+)%.git]]) or url:match([[/([^/]+)$]]) end return { diff --git a/tests/specs/neogit/lib/git/remote_spec.lua b/tests/specs/neogit/lib/git/remote_spec.lua index 2ce9b3944..03e377c2b 100644 --- a/tests/specs/neogit/lib/git/remote_spec.lua +++ b/tests/specs/neogit/lib/git/remote_spec.lua @@ -161,5 +161,19 @@ describe("lib.git.remote", function() user = "git", }) end) + + it("can parse 'https://github.com/my-org/myrepo' - no .git suffix", function() + local url = "https://github.com/my-org/myrepo" + + assert.are.same(git.remote.parse(url), { + host = "github.com", + owner = "my-org", + protocol = "https", + repo = "myrepo", + repository = "myrepo", + path = "my-org", + url = "https://github.com/my-org/myrepo", + }) + end) end) end) From 62ff78c2242aaf3d2214520ecae8ff921bd8125e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 18 Apr 2024 22:21:51 +0200 Subject: [PATCH 390/443] Fix: When user has changed status buffer labels, highlight groups should continue to work --- lua/neogit/buffers/status/ui.lua | 4 ++-- lua/neogit/lib/hl.lua | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 258f07297..30aa668e9 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -244,12 +244,12 @@ local SectionItemFile = function(section, config) local mode_text if mode == "" then mode_text = "" - else + elseif config.status.mode_padding > 0 then mode_text = util.pad_right(mode, util.max_length(vim.tbl_values(config.status.mode_text)) + config.status.mode_padding) end local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name - local highlight = ("NeogitChange%s"):format(mode:gsub(" ", "")) + local highlight = ("NeogitChange%s"):format(item.mode:gsub("%?", "Untracked")) return col.tag("Item")({ row { diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 9bcd36d5c..bf3fb2938 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -188,6 +188,20 @@ function M.setup() NeogitRebaseDone = { link = "Comment" }, NeogitCursorLine = { bg = palette.bg1 }, NeogitFold = { fg = "None", bg = "None" }, + NeogitChangeM = { link = "NeogitChangeModified" }, + NeogitChangeA = { link = "NeogitChangeNewFile" }, + NeogitChangeD = { link = "NeogitChangeDeleted" }, + NeogitChangeC = { link = "NeogitChangeCopied" }, + NeogitChangeU = { link = "NeogitChangeUpdated" }, + NeogitChangeR = { link = "NeogitChangeRenamed" }, + NeogitChangeDD = { link = "NeogitChangeUnmerged" }, + NeogitChangeUU = { link = "NeogitChangeUnmerged" }, + NeogitChangeAA = { link = "NeogitChangeUnmerged" }, + NeogitChangeDU = { link = "NeogitChangeUnmerged" }, + NeogitChangeUD = { link = "NeogitChangeUnmerged" }, + NeogitChangeAU = { link = "NeogitChangeUnmerged" }, + NeogitChangeUA = { link = "NeogitChangeUnmerged" }, + NeogitChangeUntracked = { fg = "None" }, NeogitChangeModified = { fg = palette.bg_blue, bold = palette.bold, italic = palette.italic }, NeogitChangeAdded = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, NeogitChangeDeleted = { fg = palette.bg_red, bold = palette.bold, italic = palette.italic }, From 5748a61f05e55838a362de5ce134440656e0d7bd Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 19 Apr 2024 09:38:49 +0200 Subject: [PATCH 391/443] Fix up passing commits into reset popup. ths switch idea was dumb, just pass two strings into picker --- lua/neogit/popups/reset/actions.lua | 36 +++++++++++++++-------------- lua/neogit/popups/reset/init.lua | 6 ----- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lua/neogit/popups/reset/actions.lua b/lua/neogit/popups/reset/actions.lua index ba284fdf6..c3ffec77d 100644 --- a/lua/neogit/popups/reset/actions.lua +++ b/lua/neogit/popups/reset/actions.lua @@ -6,55 +6,56 @@ local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") local M = {} ---@param popup PopupData +---@param prompt string ---@return string|nil -local function commit(popup, prompt) - local commit +local function target(popup, prompt) + local commit = {} if popup.state.env.commit then - commit = popup.state.env.commit - - if git.config.get("neogit.resetThisTo"):read() then - commit = commit .. "^" - end - else - local commits = util.merge(git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) - commit = FuzzyFinderBuffer.new(commits):open_async { prompt_prefix = prompt } - if not commit then - return - end + commit = { popup.state.env.commit, popup.state.env.commit .. "^" } end - return commit + local refs = util.merge(commit, git.refs.list_branches(), git.refs.list_tags(), git.refs.heads()) + return FuzzyFinderBuffer.new(refs):open_async { prompt_prefix = prompt } end +---@param type string +---@param popup PopupData +---@param prompt string local function reset(type, popup, prompt) - local target = commit(popup, prompt) + local target = target(popup, prompt) if target then git.reset[type](target) end end +---@param popup PopupData function M.mixed(popup) reset("mixed", popup, ("Reset %s to"):format(git.branch.current())) end +---@param popup PopupData function M.soft(popup) reset("soft", popup, ("Soft reset %s to"):format(git.branch.current())) end +---@param popup PopupData function M.hard(popup) reset("hard", popup, ("Hard reset %s to"):format(git.branch.current())) end +---@param popup PopupData function M.keep(popup) reset("keep", popup, ("Reset %s to"):format(git.branch.current())) end +---@param popup PopupData function M.index(popup) reset("index", popup, "Reset index to") end +---@param popup PopupData function M.worktree(popup) - local target = commit(popup, "Reset worktree to") + local target = target(popup, "Reset worktree to") if target then git.index.with_temp_index(target, function(index) git.cli["checkout-index"].all.force.env({ GIT_INDEX_FILE = index }).call() @@ -63,8 +64,9 @@ function M.worktree(popup) end end +---@param popup PopupData function M.a_file(popup) - local target = commit(popup, "Checkout from revision") + local target = target(popup, "Checkout from revision") if not target then return end diff --git a/lua/neogit/popups/reset/init.lua b/lua/neogit/popups/reset/init.lua index 655ad4175..d723c3a3f 100644 --- a/lua/neogit/popups/reset/init.lua +++ b/lua/neogit/popups/reset/init.lua @@ -8,12 +8,6 @@ function M.create(env) local p = popup .builder() :name("NeogitResetPopup") - :config_if(env.commit ~= nil, "R", "neogit.resetThisTo", { - options = { - { display = "Commit", value = "" }, - { display = "Parent", value = "true" }, - }, - }) :group_heading("Reset") :action("f", "file", actions.a_file) :action("b", "branch", branch_actions.reset_branch) From 5b162854e775dd6a1e98073a7b8b837a7e911f8b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 19 Apr 2024 15:40:32 +0200 Subject: [PATCH 392/443] fix: new file hl group on status buffer wasn't properly applied --- lua/neogit/buffers/status/ui.lua | 39 +++++++++++++++++++++++++------- lua/neogit/lib/hl.lua | 3 ++- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 30aa668e9..358c60787 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -245,7 +245,10 @@ local SectionItemFile = function(section, config) if mode == "" then mode_text = "" elseif config.status.mode_padding > 0 then - mode_text = util.pad_right(mode, util.max_length(vim.tbl_values(config.status.mode_text)) + config.status.mode_padding) + mode_text = util.pad_right( + mode, + util.max_length(vim.tbl_values(config.status.mode_text)) + config.status.mode_padding + ) end local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name @@ -476,7 +479,6 @@ function M.Status(state, config) local show_recent = #state.recent.items > 0 and not config.sections.recent.hidden - -- stylua: ignore end return { List { @@ -514,7 +516,11 @@ function M.Status(state, config) }, EmptyLine(), show_merge and SequencerSection { - title = SectionTitleMerge { title = "Merging", branch = state.merge.branch, highlight = "NeogitMerging" }, + title = SectionTitleMerge { + title = "Merging", + branch = state.merge.branch, + highlight = "NeogitMerging", + }, render = SectionItemSequencer, items = { { action = "", oid = state.merge.head, subject = state.merge.subject } }, folded = config.sections.sequencer.folded, @@ -527,7 +533,7 @@ function M.Status(state, config) onto = state.rebase.onto.ref, oid = state.rebase.onto.oid, is_remote_ref = state.rebase.onto.is_remote, - highlight = "NeogitRebasing" + highlight = "NeogitRebasing", }, render = SectionItemRebase, current = state.rebase.current, @@ -595,7 +601,11 @@ function M.Status(state, config) name = "stashes", }, show_upstream_unmerged and Section { - title = SectionTitleRemote { title = "Unmerged into", ref = state.upstream.ref, highlight = "NeogitUnmergedchanges" }, + title = SectionTitleRemote { + title = "Unmerged into", + ref = state.upstream.ref, + highlight = "NeogitUnmergedchanges", + }, count = true, render = SectionItemCommit, items = state.upstream.unmerged.items, @@ -603,7 +613,11 @@ function M.Status(state, config) name = "upstream_unmerged", }, show_pushRemote_unmerged and Section { - title = SectionTitleRemote { title = "Unpushed to", ref = state.pushRemote.ref, highlight = "NeogitUnpushedchanges" }, + title = SectionTitleRemote { + title = "Unpushed to", + ref = state.pushRemote.ref, + highlight = "NeogitUnpushedchanges", + }, count = true, render = SectionItemCommit, items = state.pushRemote.unmerged.items, @@ -619,7 +633,11 @@ function M.Status(state, config) name = "recent", }, show_upstream_unpulled and Section { - title = SectionTitleRemote { title = "Unpulled from", ref = state.upstream.ref, highlight = "NeogitUnpulledchanges" }, + title = SectionTitleRemote { + title = "Unpulled from", + ref = state.upstream.ref, + highlight = "NeogitUnpulledchanges", + }, count = true, render = SectionItemCommit, items = state.upstream.unpulled.items, @@ -627,7 +645,11 @@ function M.Status(state, config) name = "upstream_unpulled", }, show_pushRemote_unpulled and Section { - title = SectionTitleRemote { title = "Unpulled from", ref = state.pushRemote.ref, highlight = "NeogitUnpulledchanges" }, + title = SectionTitleRemote { + title = "Unpulled from", + ref = state.pushRemote.ref, + highlight = "NeogitUnpulledchanges", + }, count = true, render = SectionItemCommit, items = state.pushRemote.unpulled.items, @@ -638,5 +660,6 @@ function M.Status(state, config) }, } end +-- stylua: ignore end return M diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index bf3fb2938..3522659f4 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -189,7 +189,8 @@ function M.setup() NeogitCursorLine = { bg = palette.bg1 }, NeogitFold = { fg = "None", bg = "None" }, NeogitChangeM = { link = "NeogitChangeModified" }, - NeogitChangeA = { link = "NeogitChangeNewFile" }, + NeogitChangeA = { link = "NeogitChangeAdded" }, + NeogitChangeN = { link = "NeogitChangeNewFile" }, NeogitChangeD = { link = "NeogitChangeDeleted" }, NeogitChangeC = { link = "NeogitChangeCopied" }, NeogitChangeU = { link = "NeogitChangeUpdated" }, From 80b805770bdaa7cd9bb12a0a5e9754302c7a2e6a Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 20 Apr 2024 23:27:04 +0200 Subject: [PATCH 393/443] Perf: Prevent string concatonations, memoize refs for 1000ms, and pass filter to CLI to offload filtering to C --- lua/neogit/lib/git/refs.lua | 50 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index ded4fde49..9621170a9 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -5,30 +5,30 @@ local util = require("neogit.lib.util") local M = {} ----@param namespaces? string[] ----@param format? string format string passed to `git for-each-ref` ----@param sortby? string sort by string passed to `git for-each-ref` ----@return table +---@return fun(format?: string, sortby?: string, filter?: table): string[] +local refs = util.memoize(function(format, sortby, filter) + return git.cli["for-each-ref"] + .format(format or "%(refname)") + .sort(sortby or config.values.sort_branches) + .arg_list(filter or {}) + .call({ hidden = true }).stdout +end) + +---@return string[] function M.list(namespaces, format, sortby) - return util.filter_map( - git.cli["for-each-ref"] - .format(format or "%(refname)") - .sort(sortby or config.values.sort_branches) - .call({ hidden = true }).stdout, - function(revision) - for _, namespace in ipairs(namespaces or { "refs/heads", "refs/remotes", "refs/tags" }) do - if revision:match("^" .. namespace) then - local name, _ = revision:gsub("^" .. namespace .. "/", "") - return name - end - end - end - ) + local filter = util.map(namespaces or {}, function(namespace) + return namespace:sub(2, -1) + end) + + return util.map(refs(format, sortby, filter), function(revision) + local name, _ = revision:gsub("^refs/[^/]*/", "") + return name + end) end ---@return string[] function M.list_tags() - return M.list { "refs/tags" } + return M.list { "^refs/tags/" } end ---@return string[] @@ -38,13 +38,13 @@ end ---@return string[] function M.list_local_branches() - return M.list { "refs/heads" } + return M.list { "^refs/heads/" } end ---@param remote? string Filter branches by remote ---@return string[] function M.list_remote_branches(remote) - local remote_branches = M.list { "refs/remotes" } + local remote_branches = M.list { "^refs/remotes/" } if remote then return vim.tbl_filter(function(ref) @@ -66,9 +66,7 @@ local record_template = record.encode({ }, "ref") function M.list_parsed() - local refs = - git.cli["for-each-ref"].format(record_template).show_popup(false).call({ hidden = true }).stdout - local result = record.decode(refs) + local result = record.decode(refs(record_template)) local output = { local_branch = {}, @@ -102,7 +100,7 @@ end -- TODO: Use in more places --- Determines what HEAD's exist in repo, and enumerates them -function M.heads() +M.heads = util.memoize(function() local heads = { "HEAD", "ORIG_HEAD", "FETCH_HEAD", "MERGE_HEAD", "CHERRY_PICK_HEAD" } local present = {} for _, head in ipairs(heads) do @@ -112,6 +110,6 @@ function M.heads() end return present -end +end) return M From 5e0c9107d9a8d9ccb22ba6990e30601cdda97a29 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 20 Apr 2024 23:28:02 +0200 Subject: [PATCH 394/443] Perf: Prevent extra allocations when filtering ansi codes from stdout --- lua/neogit/process.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 9d5732363..fb59a58f4 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -6,8 +6,10 @@ local config = require("neogit.config") local logger = require("neogit.logger") -- from: https://stackoverflow.com/questions/48948630/lua-ansi-escapes-pattern +local pattern_1 = "[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]" +local pattern_2 = "[\r\n\04\08]" local function remove_escape_codes(s) - return s:gsub("[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]", ""):gsub("[\r\n\04\08]", "") + return s:gsub(pattern_1, ""):gsub(pattern_2, "") end local command_mask = From 023a515fa33904e140f3f20a83e6fb1c7b9ffffe Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 20 Apr 2024 23:36:08 +0200 Subject: [PATCH 395/443] Allow closing diff buffer --- lua/neogit/buffers/diff/init.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index 1a1189bb3..c2ee5b866 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -26,7 +26,7 @@ function M:new(header) stats = git.diff.staged_stats(), diffs = vim.tbl_map(function(item) return item.diff - end, git.repo.state.staged.items) + end, git.repo.state.staged.items), } setmetatable(instance, self) @@ -100,6 +100,9 @@ function M:open() [status_maps["Toggle"]] = function() pcall(vim.cmd, "normal! za") end, + [status_maps["Close"]] = function() + self:close() + end, }, }, render = function() From 95e31adb8196843f2ad8a52cf360e8a2e2f65382 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 12:33:43 +0200 Subject: [PATCH 396/443] Use 'NeogitObjectId' hl group instead of "comment" - but link that to Comment --- lua/neogit/buffers/commit_view/ui.lua | 2 +- lua/neogit/buffers/common.lua | 2 +- lua/neogit/buffers/reflog_view/ui.lua | 2 +- lua/neogit/buffers/refs_view/ui.lua | 2 +- lua/neogit/buffers/status/ui.lua | 10 +++++----- lua/neogit/lib/buffer.lua | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index 76d8b58fc..6f04c57ce 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -23,7 +23,7 @@ end local function commit_header_arg(info) if info.oid ~= info.commit_arg then - return row { text(info.commit_arg .. " "), text.highlight("Comment")(info.oid) } + return row { text(info.commit_arg .. " "), text.highlight("NeogitObjectId")(info.oid) } else return row {} end diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index 919b786a5..d818e1404 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -231,7 +231,7 @@ M.CommitEntry = Component.new(function(commit, args) util.merge({ text(commit.abbreviated_commit, { highlight = commit.verification_flag and highlight_for_signature[commit.verification_flag] - or "Comment", + or "NeogitObjectId", }), text(" "), }, graph, { text(" ") }, ref, ref_last, { text(commit.subject) }), diff --git a/lua/neogit/buffers/reflog_view/ui.lua b/lua/neogit/buffers/reflog_view/ui.lua index ab342d375..cefd97a3e 100644 --- a/lua/neogit/buffers/reflog_view/ui.lua +++ b/lua/neogit/buffers/reflog_view/ui.lua @@ -29,7 +29,7 @@ M.Entry = Component.new(function(entry, total) return col({ row({ - text(entry.oid:sub(1, 7), { highlight = "Comment" }), + text(entry.oid:sub(1, 7), { highlight = "NeogitObjectId" }), text(" "), text(tostring(entry.index), { align_right = #tostring(total) + 1 }), text(entry.type, { highlight = highlight_for_type(entry.type), align_right = 16 }), diff --git a/lua/neogit/buffers/refs_view/ui.lua b/lua/neogit/buffers/refs_view/ui.lua index 51cffffb1..902f45dfc 100644 --- a/lua/neogit/buffers/refs_view/ui.lua +++ b/lua/neogit/buffers/refs_view/ui.lua @@ -27,7 +27,7 @@ local function Cherries(ref, head) return row({ text.highlight(highlights[cherry.status])(cherry.status), text(" "), - text.highlight("Comment")(cherry.oid:sub(1, git.log.abbreviated_size())), + text.highlight("NeogitObjectId")(cherry.oid:sub(1, git.log.abbreviated_size())), text(" "), text.highlight("NeogitGraphWhite")(cherry.subject), }, { oid = cherry.oid }) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 358c60787..433be1cd0 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -94,7 +94,7 @@ local HEAD = Component.new(function(props) return row({ text(util.pad_right(props.name .. ":", 10)), - text.highlight("Comment")(show_oid and oid or ""), + text.highlight("NeogitObjectId")(show_oid and oid or ""), text(show_oid and " " or ""), text.highlight(highlight)(ref), text(" "), @@ -323,7 +323,7 @@ local SectionItemCommit = Component.new(function(item) return row( util.merge( - { text.highlight("Comment")(item.commit.abbreviated_commit) }, + { text.highlight("NeogitObjectId")(item.commit.abbreviated_commit) }, { text(" ") }, ref, ref_last, @@ -367,7 +367,7 @@ local SectionItemSequencer = Component.new(function(item) return row({ text.highlight(action_hl)(action), text(show_action and " " or ""), - text.highlight("Comment")(item.abbreviated_commit), + text.highlight("NeogitObjectId")(item.abbreviated_commit), text(" "), text(item.subject), }, { yankable = item.oid, oid = item.oid }) @@ -387,7 +387,7 @@ local SectionItemBisect = Component.new(function(item) text(item.finished and "> " or " "), text.highlight(highlight)(util.pad_right(item.action, 5)), text(" "), - text.highlight("Comment")(item.abbreviated_commit), + text.highlight("NeogitObjectId")(item.abbreviated_commit), text(" "), text(item.subject), }, { yankable = item.oid, oid = item.oid }) @@ -395,7 +395,7 @@ end) local BisectDetailsSection = Component.new(function(props) return col.tag("Section")({ - row(util.merge(props.title, { text(" "), text.highlight("Comment")(props.commit.oid) })), + row(util.merge(props.title, { text(" "), text.highlight("NeogitObjectId")(props.commit.oid) })), row { text.highlight("Comment")("Author: "), text((props.commit.author_name or "") .. " <" .. (props.commit.author_email or "") .. ">"), diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index a451e0ee1..ea6b43012 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -531,7 +531,7 @@ function Buffer:set_header(text) -- Create a blank line at the top of the buffer so our floating window doesn't -- hide any content self:set_extmark(self:get_namespace_id("default"), 0, 0, { - virt_lines = { { { "", "Comment" } } }, + virt_lines = { { { "", "NeogitObjectId" } } }, virt_lines_above = true, }) From e76dbc556986426f5161748eb77e8c4780667a95 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 12:34:11 +0200 Subject: [PATCH 397/443] Internally, link all hl groups to NeogitSubtleText instead of Comment. But, link NeogitSubtleText to comment. --- lua/neogit/lib/hl.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 3522659f4..e86675cad 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -132,10 +132,11 @@ function M.setup() NeogitGraphBoldBlue = { fg = palette.blue, bold = palette.bold }, NeogitGraphBoldPurple = { fg = palette.purple, bold = palette.bold }, NeogitGraphBoldGray = { fg = palette.grey, bold = palette.bold }, + NeogitSubtleText = { link = "Comment" }, NeogitSignatureGood = { link = "NeogitGraphGreen" }, NeogitSignatureBad = { link = "NeogitGraphBoldRed" }, NeogitSignatureMissing = { link = "NeogitGraphPurple" }, - NeogitSignatureNone = { link = "Comment" }, + NeogitSignatureNone = { link = "NeogitSubtleText" }, NeogitSignatureGoodUnknown = { link = "NeogitGraphBlue" }, NeogitSignatureGoodExpired = { link = "NeogitGraphOrange" }, NeogitSignatureGoodExpiredKey = { link = "NeogitGraphYellow" }, @@ -159,22 +160,22 @@ function M.setup() NeogitPopupBold = { bold = palette.bold }, NeogitPopupSwitchKey = { fg = palette.purple }, NeogitPopupSwitchEnabled = { link = "SpecialChar" }, - NeogitPopupSwitchDisabled = { link = "Comment" }, + NeogitPopupSwitchDisabled = { link = "NeogitSubtleText" }, NeogitPopupOptionKey = { fg = palette.purple }, NeogitPopupOptionEnabled = { link = "SpecialChar" }, - NeogitPopupOptionDisabled = { link = "Comment" }, + NeogitPopupOptionDisabled = { link = "NeogitSubtleText" }, NeogitPopupConfigKey = { fg = palette.purple }, NeogitPopupConfigEnabled = { link = "SpecialChar" }, - NeogitPopupConfigDisabled = { link = "Comment" }, + NeogitPopupConfigDisabled = { link = "NeogitSubtleText" }, NeogitPopupActionKey = { fg = palette.purple }, - NeogitPopupActionDisabled = { link = "Comment" }, + NeogitPopupActionDisabled = { link = "NeogitSubtleText" }, NeogitFilePath = { fg = palette.blue, italic = palette.italic }, NeogitCommitViewHeader = { bg = palette.bg_cyan, fg = palette.bg0 }, NeogitCommitViewDescription = { link = "String" }, NeogitDiffHeader = { bg = palette.bg3, fg = palette.blue, bold = palette.bold }, NeogitDiffHeaderHighlight = { bg = palette.bg3, fg = palette.orange, bold = palette.bold }, - NeogitCommandText = { link = "Comment" }, - NeogitCommandTime = { link = "Comment" }, + NeogitCommandText = { link = "NeogitSubtleText" }, + NeogitCommandTime = { link = "NeogitSubtleText" }, NeogitCommandCodeNormal = { link = "String" }, NeogitCommandCodeError = { link = "Error" }, NeogitBranch = { fg = palette.blue, bold = palette.bold }, @@ -183,9 +184,9 @@ function M.setup() NeogitUnmergedInto = { fg = palette.bg_purple, bold = palette.bold }, NeogitUnpushedTo = { fg = palette.bg_purple, bold = palette.bold }, NeogitUnpulledFrom = { fg = palette.bg_purple, bold = palette.bold }, - NeogitObjectId = { link = "Comment" }, - NeogitStash = { link = "Comment" }, - NeogitRebaseDone = { link = "Comment" }, + NeogitObjectId = { link = "NeogitSubtleText" }, + NeogitStash = { link = "NeogitSubtleText" }, + NeogitRebaseDone = { link = "NeogitSubtleText" }, NeogitCursorLine = { bg = palette.bg1 }, NeogitFold = { fg = "None", bg = "None" }, NeogitChangeM = { link = "NeogitChangeModified" }, From 3c83d1b8cd4397eb230e810583d117cfec45b3cd Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 12:40:03 +0200 Subject: [PATCH 398/443] Highlight 'Head', 'Merge', 'Tags', with "NeogitStatusHEAD" --- doc/neogit.txt | 2 +- lua/neogit/buffers/status/ui.lua | 2 +- lua/neogit/lib/hl.lua | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 3a82c5ff3..666ab1fd2 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -378,10 +378,10 @@ NeogitFold Folded text highlight NeogitRebaseDone Current position within rebase NeogitTagName Closest Tag name NeogitTagDistance Number of commits between the tag and HEAD +NeogitStatusHEAD The left text in the HEAD section STATUS BUFFER SECTION HEADERS NeogitSectionHeader - NeogitUnpushedTo Linked to NeogitSectionHeader NeogitUnmergedInto ^ NeogitUnpulledFrom ^ diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 433be1cd0..d74e50bf9 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -93,7 +93,7 @@ local HEAD = Component.new(function(props) end return row({ - text(util.pad_right(props.name .. ":", 10)), + text.highlight("NeogitStatusHEAD")(util.pad_right(props.name .. ":", 10)), text.highlight("NeogitObjectId")(show_oid and oid or ""), text(show_oid and " " or ""), text.highlight(highlight)(ref), diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index e86675cad..5f80bfd29 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -184,6 +184,7 @@ function M.setup() NeogitUnmergedInto = { fg = palette.bg_purple, bold = palette.bold }, NeogitUnpushedTo = { fg = palette.bg_purple, bold = palette.bold }, NeogitUnpulledFrom = { fg = palette.bg_purple, bold = palette.bold }, + NeogitStatusHEAD = {}, NeogitObjectId = { link = "NeogitSubtleText" }, NeogitStash = { link = "NeogitSubtleText" }, NeogitRebaseDone = { link = "NeogitSubtleText" }, From 34e5296ff90d6f9331b78d54e328accb23306217 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 12:45:41 +0200 Subject: [PATCH 399/443] Correction to docs: hl group name --- doc/neogit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 666ab1fd2..1af1937ef 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -408,8 +408,8 @@ NeogitChangeDeleted NeogitChangeRenamed NeogitChangeUpdated NeogitChangeCopied -NeogitChangeBothModified NeogitChangeNewFile +NeogitChangeUnmerged SIGNS FOR LINE HIGHLIGHTING Used to highlight different sections of the status buffer or commit buffer. From d581789a9158d494d0ea45113dbb77e13228cb00 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 12:45:54 +0200 Subject: [PATCH 400/443] Correction to docs: No longer using NeogitCursorLine hl group --- doc/neogit.txt | 11 +++++++---- lua/neogit/lib/hl.lua | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 1af1937ef..d7313660a 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -424,15 +424,18 @@ SIGNS FOR LINE HIGHLIGHTING CURRENT CONTEXT These are essentially an accented version of the above highlight groups. Only applies to the current context the cursor is within. +The "Cursor" suffix applies only to the Cursor line + NeogitHunkHeaderHighlight NeogitDiffContextHighlight NeogitDiffAddHighlight NeogitDiffDeleteHighlight NeogitDiffHeaderHighlight - -NeogitCursorLine Applies a "fake" cursorline highlight because - signs will otherwise take precedence over normal - CursorLine HL group +NeogitHunkHeaderCursor +NeogitDiffContextCursor +NeogitDiffAddCursor +NeogitDiffDeleteCursor +NeogitDiffHeaderCursor COMMIT BUFFER NeogitFilePath Applied to filepath diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 5f80bfd29..e9e85efda 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -188,7 +188,6 @@ function M.setup() NeogitObjectId = { link = "NeogitSubtleText" }, NeogitStash = { link = "NeogitSubtleText" }, NeogitRebaseDone = { link = "NeogitSubtleText" }, - NeogitCursorLine = { bg = palette.bg1 }, NeogitFold = { fg = "None", bg = "None" }, NeogitChangeM = { link = "NeogitChangeModified" }, NeogitChangeA = { link = "NeogitChangeAdded" }, From 0f871d0da7ee27d6c4593807247ec2af588704d2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 12:59:37 +0200 Subject: [PATCH 401/443] Formatting and docs. --- lua/neogit/lib/hl.lua | 237 ++++++++++++++++++++++-------------------- 1 file changed, 122 insertions(+), 115 deletions(-) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index e9e85efda..f997dd5b4 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -18,6 +18,8 @@ local Color = require("neogit.lib.color").Color local hl_store local M = {} +---@param dec number +---@return string local function to_hex(dec) local hex = string.format("%x", dec) if #hex < 6 then @@ -28,6 +30,7 @@ local function to_hex(dec) end ---@param name string Syntax group name. +---@return string|nil local function get_fg(name) local color = vim.api.nvim_get_hl(0, { name = name }) if color["link"] then @@ -40,6 +43,7 @@ local function get_fg(name) end ---@param name string Syntax group name. +---@return string|nil local function get_bg(name) local color = vim.api.nvim_get_hl(0, { name = name }) if color["link"] then @@ -55,6 +59,7 @@ end -- stylua: ignore start local function make_palette() local bg = Color.from_hex(get_bg("Normal") or (vim.o.bg == "dark" and "#22252A" or "#eeeeee")) + local fg = Color.from_hex((vim.o.bg == "dark" and "#fcfcfc" or "#22252A")) local red = Color.from_hex(get_fg("Error") or "#E06C75") local orange = Color.from_hex(get_fg("SpecialChar") or "#ffcb6b") local yellow = Color.from_hex(get_fg("PreProc") or "#FFE082") @@ -73,6 +78,7 @@ local function make_palette() bg2 = bg:shade(bg_factor * 0.065):to_css(), bg3 = bg:shade(bg_factor * 0.11):to_css(), grey = bg:shade(bg_factor * 0.4):to_css(), + white = fg:to_css(), red = red:to_css(), bg_red = red:shade(bg_factor * -0.18):to_css(), line_red = get_bg("DiffDelete") or red:shade(bg_factor * -0.6):set_saturation(0.4):to_css(), @@ -112,124 +118,125 @@ end function M.setup() local palette = make_palette() + -- stylua: ignore hl_store = { - NeogitGraphAuthor = { fg = palette.orange }, - NeogitGraphRed = { fg = palette.red }, - NeogitGraphWhite = { fg = palette.white }, - NeogitGraphYellow = { fg = palette.yellow }, - NeogitGraphGreen = { fg = palette.green }, - NeogitGraphCyan = { fg = palette.cyan }, - NeogitGraphBlue = { fg = palette.blue }, - NeogitGraphPurple = { fg = palette.purple }, - NeogitGraphGray = { fg = palette.grey }, - NeogitGraphOrange = { fg = palette.orange }, - NeogitGraphBoldOrange = { fg = palette.orange, bold = palette.bold }, - NeogitGraphBoldRed = { fg = palette.red, bold = palette.bold }, - NeogitGraphBoldWhite = { fg = palette.white, bold = palette.bold }, - NeogitGraphBoldYellow = { fg = palette.yellow, bold = palette.bold }, - NeogitGraphBoldGreen = { fg = palette.green, bold = palette.bold }, - NeogitGraphBoldCyan = { fg = palette.cyan, bold = palette.bold }, - NeogitGraphBoldBlue = { fg = palette.blue, bold = palette.bold }, - NeogitGraphBoldPurple = { fg = palette.purple, bold = palette.bold }, - NeogitGraphBoldGray = { fg = palette.grey, bold = palette.bold }, - NeogitSubtleText = { link = "Comment" }, - NeogitSignatureGood = { link = "NeogitGraphGreen" }, - NeogitSignatureBad = { link = "NeogitGraphBoldRed" }, - NeogitSignatureMissing = { link = "NeogitGraphPurple" }, - NeogitSignatureNone = { link = "NeogitSubtleText" }, - NeogitSignatureGoodUnknown = { link = "NeogitGraphBlue" }, - NeogitSignatureGoodExpired = { link = "NeogitGraphOrange" }, + NeogitGraphAuthor = { fg = palette.orange }, + NeogitGraphRed = { fg = palette.red }, + NeogitGraphWhite = { fg = palette.white }, + NeogitGraphYellow = { fg = palette.yellow }, + NeogitGraphGreen = { fg = palette.green }, + NeogitGraphCyan = { fg = palette.cyan }, + NeogitGraphBlue = { fg = palette.blue }, + NeogitGraphPurple = { fg = palette.purple }, + NeogitGraphGray = { fg = palette.grey }, + NeogitGraphOrange = { fg = palette.orange }, + NeogitGraphBoldOrange = { fg = palette.orange, bold = palette.bold }, + NeogitGraphBoldRed = { fg = palette.red, bold = palette.bold }, + NeogitGraphBoldWhite = { fg = palette.white, bold = palette.bold }, + NeogitGraphBoldYellow = { fg = palette.yellow, bold = palette.bold }, + NeogitGraphBoldGreen = { fg = palette.green, bold = palette.bold }, + NeogitGraphBoldCyan = { fg = palette.cyan, bold = palette.bold }, + NeogitGraphBoldBlue = { fg = palette.blue, bold = palette.bold }, + NeogitGraphBoldPurple = { fg = palette.purple, bold = palette.bold }, + NeogitGraphBoldGray = { fg = palette.grey, bold = palette.bold }, + NeogitSubtleText = { link = "Comment" }, + NeogitSignatureGood = { link = "NeogitGraphGreen" }, + NeogitSignatureBad = { link = "NeogitGraphBoldRed" }, + NeogitSignatureMissing = { link = "NeogitGraphPurple" }, + NeogitSignatureNone = { link = "NeogitSubtleText" }, + NeogitSignatureGoodUnknown = { link = "NeogitGraphBlue" }, + NeogitSignatureGoodExpired = { link = "NeogitGraphOrange" }, NeogitSignatureGoodExpiredKey = { link = "NeogitGraphYellow" }, NeogitSignatureGoodRevokedKey = { link = "NeogitGraphRed" }, - NeogitHunkHeader = { fg = palette.bg0, bg = palette.grey, bold = palette.bold }, - NeogitHunkHeaderHighlight = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, - NeogitHunkHeaderCursor = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, - NeogitDiffContext = { bg = palette.bg1 }, - NeogitDiffContextHighlight = { bg = palette.bg2 }, - NeogitDiffContextCursor = { bg = palette.bg1 }, - NeogitDiffAdditions = { fg = palette.bg_green }, - NeogitDiffAdd = { bg = palette.line_green, fg = palette.bg_green }, - NeogitDiffAddHighlight = { bg = palette.line_green, fg = palette.green }, - NeogitDiffAddCursor = { bg = palette.bg1, fg = palette.green }, - NeogitDiffDeletions = { fg = palette.bg_red }, - NeogitDiffDelete = { bg = palette.line_red, fg = palette.bg_red }, - NeogitDiffDeleteHighlight = { bg = palette.line_red, fg = palette.red }, - NeogitDiffDeleteCursor = { bg = palette.bg1, fg = palette.red }, - NeogitPopupSectionTitle = { link = "Function" }, - NeogitPopupBranchName = { link = "String" }, - NeogitPopupBold = { bold = palette.bold }, - NeogitPopupSwitchKey = { fg = palette.purple }, - NeogitPopupSwitchEnabled = { link = "SpecialChar" }, - NeogitPopupSwitchDisabled = { link = "NeogitSubtleText" }, - NeogitPopupOptionKey = { fg = palette.purple }, - NeogitPopupOptionEnabled = { link = "SpecialChar" }, - NeogitPopupOptionDisabled = { link = "NeogitSubtleText" }, - NeogitPopupConfigKey = { fg = palette.purple }, - NeogitPopupConfigEnabled = { link = "SpecialChar" }, - NeogitPopupConfigDisabled = { link = "NeogitSubtleText" }, - NeogitPopupActionKey = { fg = palette.purple }, - NeogitPopupActionDisabled = { link = "NeogitSubtleText" }, - NeogitFilePath = { fg = palette.blue, italic = palette.italic }, - NeogitCommitViewHeader = { bg = palette.bg_cyan, fg = palette.bg0 }, - NeogitCommitViewDescription = { link = "String" }, - NeogitDiffHeader = { bg = palette.bg3, fg = palette.blue, bold = palette.bold }, - NeogitDiffHeaderHighlight = { bg = palette.bg3, fg = palette.orange, bold = palette.bold }, - NeogitCommandText = { link = "NeogitSubtleText" }, - NeogitCommandTime = { link = "NeogitSubtleText" }, - NeogitCommandCodeNormal = { link = "String" }, - NeogitCommandCodeError = { link = "Error" }, - NeogitBranch = { fg = palette.blue, bold = palette.bold }, - NeogitBranchHead = { fg = palette.blue, bold = palette.bold, underline = palette.underline }, - NeogitRemote = { fg = palette.green, bold = palette.bold }, - NeogitUnmergedInto = { fg = palette.bg_purple, bold = palette.bold }, - NeogitUnpushedTo = { fg = palette.bg_purple, bold = palette.bold }, - NeogitUnpulledFrom = { fg = palette.bg_purple, bold = palette.bold }, - NeogitStatusHEAD = {}, - NeogitObjectId = { link = "NeogitSubtleText" }, - NeogitStash = { link = "NeogitSubtleText" }, - NeogitRebaseDone = { link = "NeogitSubtleText" }, - NeogitFold = { fg = "None", bg = "None" }, - NeogitChangeM = { link = "NeogitChangeModified" }, - NeogitChangeA = { link = "NeogitChangeAdded" }, - NeogitChangeN = { link = "NeogitChangeNewFile" }, - NeogitChangeD = { link = "NeogitChangeDeleted" }, - NeogitChangeC = { link = "NeogitChangeCopied" }, - NeogitChangeU = { link = "NeogitChangeUpdated" }, - NeogitChangeR = { link = "NeogitChangeRenamed" }, - NeogitChangeDD = { link = "NeogitChangeUnmerged" }, - NeogitChangeUU = { link = "NeogitChangeUnmerged" }, - NeogitChangeAA = { link = "NeogitChangeUnmerged" }, - NeogitChangeDU = { link = "NeogitChangeUnmerged" }, - NeogitChangeUD = { link = "NeogitChangeUnmerged" }, - NeogitChangeAU = { link = "NeogitChangeUnmerged" }, - NeogitChangeUA = { link = "NeogitChangeUnmerged" }, - NeogitChangeUntracked = { fg = "None" }, - NeogitChangeModified = { fg = palette.bg_blue, bold = palette.bold, italic = palette.italic }, - NeogitChangeAdded = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, - NeogitChangeDeleted = { fg = palette.bg_red, bold = palette.bold, italic = palette.italic }, - NeogitChangeRenamed = { fg = palette.bg_purple, bold = palette.bold, italic = palette.italic }, - NeogitChangeUpdated = { fg = palette.bg_orange, bold = palette.bold, italic = palette.italic }, - NeogitChangeCopied = { fg = palette.bg_cyan, bold = palette.bold, italic = palette.italic }, - NeogitChangeUnmerged = { fg = palette.bg_yellow, bold = palette.bold, italic = palette.italic }, - NeogitChangeNewFile = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, - NeogitSectionHeader = { fg = palette.bg_purple, bold = palette.bold }, - NeogitUntrackedfiles = { link = "NeogitSectionHeader" }, - NeogitUnstagedchanges = { link = "NeogitSectionHeader" }, - NeogitUnmergedchanges = { link = "NeogitSectionHeader" }, - NeogitUnpulledchanges = { link = "NeogitSectionHeader" }, - NeogitUnpushedchanges = { link = "NeogitSectionHeader" }, - NeogitRecentcommits = { link = "NeogitSectionHeader" }, - NeogitStagedchanges = { link = "NeogitSectionHeader" }, - NeogitStashes = { link = "NeogitSectionHeader" }, - NeogitMerging = { link = "NeogitSectionHeader" }, - NeogitBisecting = { link = "NeogitSectionHeader" }, - NeogitRebasing = { link = "NeogitSectionHeader" }, - NeogitPicking = { link = "NeogitSectionHeader" }, - NeogitReverting = { link = "NeogitSectionHeader" }, - NeogitTagName = { fg = palette.yellow }, - NeogitTagDistance = { fg = palette.cyan }, - NeogitFloatHeader = { bg = palette.bg0, bold = palette.bold }, - NeogitFloatHeaderHighlight = { bg = palette.bg2, fg = palette.cyan, bold = palette.bold }, + NeogitHunkHeader = { fg = palette.bg0, bg = palette.grey, bold = palette.bold }, + NeogitHunkHeaderHighlight = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, + NeogitHunkHeaderCursor = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, + NeogitDiffContext = { bg = palette.bg1 }, + NeogitDiffContextHighlight = { bg = palette.bg2 }, + NeogitDiffContextCursor = { bg = palette.bg1 }, + NeogitDiffAdditions = { fg = palette.bg_green }, + NeogitDiffAdd = { bg = palette.line_green, fg = palette.bg_green }, + NeogitDiffAddHighlight = { bg = palette.line_green, fg = palette.green }, + NeogitDiffAddCursor = { bg = palette.bg1, fg = palette.green }, + NeogitDiffDeletions = { fg = palette.bg_red }, + NeogitDiffDelete = { bg = palette.line_red, fg = palette.bg_red }, + NeogitDiffDeleteHighlight = { bg = palette.line_red, fg = palette.red }, + NeogitDiffDeleteCursor = { bg = palette.bg1, fg = palette.red }, + NeogitPopupSectionTitle = { link = "Function" }, + NeogitPopupBranchName = { link = "String" }, + NeogitPopupBold = { bold = palette.bold }, + NeogitPopupSwitchKey = { fg = palette.purple }, + NeogitPopupSwitchEnabled = { link = "SpecialChar" }, + NeogitPopupSwitchDisabled = { link = "NeogitSubtleText" }, + NeogitPopupOptionKey = { fg = palette.purple }, + NeogitPopupOptionEnabled = { link = "SpecialChar" }, + NeogitPopupOptionDisabled = { link = "NeogitSubtleText" }, + NeogitPopupConfigKey = { fg = palette.purple }, + NeogitPopupConfigEnabled = { link = "SpecialChar" }, + NeogitPopupConfigDisabled = { link = "NeogitSubtleText" }, + NeogitPopupActionKey = { fg = palette.purple }, + NeogitPopupActionDisabled = { link = "NeogitSubtleText" }, + NeogitFilePath = { fg = palette.blue, italic = palette.italic }, + NeogitCommitViewHeader = { bg = palette.bg_cyan, fg = palette.bg0 }, + NeogitCommitViewDescription = { link = "String" }, + NeogitDiffHeader = { bg = palette.bg3, fg = palette.blue, bold = palette.bold }, + NeogitDiffHeaderHighlight = { bg = palette.bg3, fg = palette.orange, bold = palette.bold }, + NeogitCommandText = { link = "NeogitSubtleText" }, + NeogitCommandTime = { link = "NeogitSubtleText" }, + NeogitCommandCodeNormal = { link = "String" }, + NeogitCommandCodeError = { link = "Error" }, + NeogitBranch = { fg = palette.blue, bold = palette.bold }, + NeogitBranchHead = { fg = palette.blue, bold = palette.bold, underline = palette.underline }, + NeogitRemote = { fg = palette.green, bold = palette.bold }, + NeogitUnmergedInto = { fg = palette.bg_purple, bold = palette.bold }, + NeogitUnpushedTo = { fg = palette.bg_purple, bold = palette.bold }, + NeogitUnpulledFrom = { fg = palette.bg_purple, bold = palette.bold }, + NeogitStatusHEAD = {}, + NeogitObjectId = { link = "NeogitSubtleText" }, + NeogitStash = { link = "NeogitSubtleText" }, + NeogitRebaseDone = { link = "NeogitSubtleText" }, + NeogitFold = { fg = "None", bg = "None" }, + NeogitChangeM = { link = "NeogitChangeModified" }, + NeogitChangeA = { link = "NeogitChangeAdded" }, + NeogitChangeN = { link = "NeogitChangeNewFile" }, + NeogitChangeD = { link = "NeogitChangeDeleted" }, + NeogitChangeC = { link = "NeogitChangeCopied" }, + NeogitChangeU = { link = "NeogitChangeUpdated" }, + NeogitChangeR = { link = "NeogitChangeRenamed" }, + NeogitChangeDD = { link = "NeogitChangeUnmerged" }, + NeogitChangeUU = { link = "NeogitChangeUnmerged" }, + NeogitChangeAA = { link = "NeogitChangeUnmerged" }, + NeogitChangeDU = { link = "NeogitChangeUnmerged" }, + NeogitChangeUD = { link = "NeogitChangeUnmerged" }, + NeogitChangeAU = { link = "NeogitChangeUnmerged" }, + NeogitChangeUA = { link = "NeogitChangeUnmerged" }, + NeogitChangeUntracked = { fg = "None" }, + NeogitChangeModified = { fg = palette.bg_blue, bold = palette.bold, italic = palette.italic }, + NeogitChangeAdded = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, + NeogitChangeDeleted = { fg = palette.bg_red, bold = palette.bold, italic = palette.italic }, + NeogitChangeRenamed = { fg = palette.bg_purple, bold = palette.bold, italic = palette.italic }, + NeogitChangeUpdated = { fg = palette.bg_orange, bold = palette.bold, italic = palette.italic }, + NeogitChangeCopied = { fg = palette.bg_cyan, bold = palette.bold, italic = palette.italic }, + NeogitChangeUnmerged = { fg = palette.bg_yellow, bold = palette.bold, italic = palette.italic }, + NeogitChangeNewFile = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, + NeogitSectionHeader = { fg = palette.bg_purple, bold = palette.bold }, + NeogitUntrackedfiles = { link = "NeogitSectionHeader" }, + NeogitUnstagedchanges = { link = "NeogitSectionHeader" }, + NeogitUnmergedchanges = { link = "NeogitSectionHeader" }, + NeogitUnpulledchanges = { link = "NeogitSectionHeader" }, + NeogitUnpushedchanges = { link = "NeogitSectionHeader" }, + NeogitRecentcommits = { link = "NeogitSectionHeader" }, + NeogitStagedchanges = { link = "NeogitSectionHeader" }, + NeogitStashes = { link = "NeogitSectionHeader" }, + NeogitMerging = { link = "NeogitSectionHeader" }, + NeogitBisecting = { link = "NeogitSectionHeader" }, + NeogitRebasing = { link = "NeogitSectionHeader" }, + NeogitPicking = { link = "NeogitSectionHeader" }, + NeogitReverting = { link = "NeogitSectionHeader" }, + NeogitTagName = { fg = palette.yellow }, + NeogitTagDistance = { fg = palette.cyan }, + NeogitFloatHeader = { bg = palette.bg0, bold = palette.bold }, + NeogitFloatHeaderHighlight = { bg = palette.bg2, fg = palette.cyan, bold = palette.bold }, } for group, hl in pairs(hl_store) do From c31acc55404a04dca04742049eadf0895c9c5444 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 13:04:58 +0200 Subject: [PATCH 402/443] Add user configurable HEAD padding to status buffer --- lua/neogit/buffers/status/ui.lua | 10 +++++++--- lua/neogit/config.lua | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index d74e50bf9..4b58e8df2 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -93,7 +93,7 @@ local HEAD = Component.new(function(props) end return row({ - text.highlight("NeogitStatusHEAD")(util.pad_right(props.name .. ":", 10)), + text.highlight("NeogitStatusHEAD")(util.pad_right(props.name .. ": ", props.HEAD_padding)), text.highlight("NeogitObjectId")(show_oid and oid or ""), text(show_oid and " " or ""), text.highlight(highlight)(ref), @@ -105,7 +105,7 @@ end) local Tag = Component.new(function(props) if props.distance then return row({ - text(util.pad_right("Tag:", 10)), + text.highlight("NeogitStatusHEAD")(util.pad_right("Tag: ", props.HEAD_padding)), text.highlight("NeogitTagName")(props.name), text(" ("), text.highlight("NeogitTagDistance")(props.distance), @@ -113,7 +113,7 @@ local Tag = Component.new(function(props) }, { yankable = props.yankable }) else return row({ - text(util.pad_right("Tag:", 10)), + text(util.pad_right("Tag: ", props.HEAD_padding)), text.highlight("NeogitTagName")(props.name), }, { yankable = props.yankable }) end @@ -492,6 +492,7 @@ function M.Status(state, config) msg = state.head.commit_message, yankable = state.head.oid, show_oid = config.show_head_commit_hash, + HEAD_padding = config.status.HEAD_padding, }, show_upstream and HEAD { name = "Merge", @@ -500,6 +501,7 @@ function M.Status(state, config) msg = state.upstream.commit_message, yankable = state.upstream.oid, show_oid = config.show_head_commit_hash, + HEAD_padding = config.status.HEAD_padding, }, show_pushRemote and HEAD { name = "Push", @@ -508,11 +510,13 @@ function M.Status(state, config) msg = state.pushRemote.commit_message, yankable = state.pushRemote.oid, show_oid = config.show_head_commit_hash, + HEAD_padding = config.status.HEAD_padding, }, show_tag and Tag { name = state.head.tag.name, distance = show_tag_distance and state.head.tag.distance, yankable = state.head.tag.oid, + HEAD_padding = config.status.HEAD_padding, }, EmptyLine(), show_merge and SequencerSection { diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index e74eee925..4c976fb37 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -214,6 +214,7 @@ end ---@class NeogitConfigStatusOptions ---@field recent_commit_count? integer The number of recent commits to display ---@field mode_padding? integer The amount of padding to add to the right of the mode column +---@field HEAD_padding? integer The amount of padding to add to the right of the HEAD label ---@field mode_text? { [string]: string } The text to display for each mode ---@class NeogitConfigMappings Consult the config file or documentation for values @@ -307,6 +308,7 @@ function M.get_default_values() notification_icon = "󰊢", status = { recent_commit_count = 10, + HEAD_padding = 10, mode_padding = 3, mode_text = { M = "modified", @@ -989,6 +991,7 @@ function M.validate_config() if validate_type(config.status, "status", "table") then validate_type(config.status.recent_commit_count, "status.recent_commit_count", "number") validate_type(config.status.mode_padding, "status.mode_padding", "number") + validate_type(config.status.HEAD_padding, "status.HEAD_padding", "number") validate_type(config.status.mode_text, "status.mode_text", "table") end validate_signs() From fbf4619b98e7c5848a96f157d4a1c7ebee9ddfeb Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 21 Apr 2024 13:06:24 +0200 Subject: [PATCH 403/443] Update config in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f0143fcaa..079d49cc6 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ neogit.setup { auto_show_console = true, status = { recent_commit_count = 10, + HEAD_padding = 10, mode_padding = 3, mode_text = { M = "modified", From 614cbd908f234f0bb9cc9db086f62b2ca6d6188f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Apr 2024 21:38:46 +0200 Subject: [PATCH 404/443] Move config.show_head_commit_hash to config.status.show_head_commit_hash and add documentation for it. --- README.md | 1 + lua/neogit/buffers/status/ui.lua | 6 +++--- lua/neogit/config.lua | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 079d49cc6..ae67e1f4f 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ neogit.setup { -- Automatically show console if a command takes more than console_timeout milliseconds auto_show_console = true, status = { + show_head_commit_hash = true, recent_commit_count = 10, HEAD_padding = 10, mode_padding = 3, diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 4b58e8df2..dedb3f39c 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -491,7 +491,7 @@ function M.Status(state, config) oid = state.head.abbrev, msg = state.head.commit_message, yankable = state.head.oid, - show_oid = config.show_head_commit_hash, + show_oid = config.status.show_head_commit_hash, HEAD_padding = config.status.HEAD_padding, }, show_upstream and HEAD { @@ -500,7 +500,7 @@ function M.Status(state, config) remote = state.upstream.remote, msg = state.upstream.commit_message, yankable = state.upstream.oid, - show_oid = config.show_head_commit_hash, + show_oid = config.status.show_head_commit_hash, HEAD_padding = config.status.HEAD_padding, }, show_pushRemote and HEAD { @@ -509,7 +509,7 @@ function M.Status(state, config) remote = state.pushRemote.remote, msg = state.pushRemote.commit_message, yankable = state.pushRemote.oid, - show_oid = config.show_head_commit_hash, + show_oid = config.status.show_head_commit_hash, HEAD_padding = config.status.HEAD_padding, }, show_tag and Tag { diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 4c976fb37..791c5ff7f 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -216,6 +216,7 @@ end ---@field mode_padding? integer The amount of padding to add to the right of the mode column ---@field HEAD_padding? integer The amount of padding to add to the right of the HEAD label ---@field mode_text? { [string]: string } The text to display for each mode +---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer ---@class NeogitConfigMappings Consult the config file or documentation for values ---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds @@ -242,7 +243,6 @@ end ---@field sort_branches? string Value used for `--sort` for the `git branch` command ---@field kind? WindowKind The default type of window neogit should open in ---@field disable_line_numbers? boolean Whether to disable line numbers ----@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout ---@field status? NeogitConfigStatusOptions Status buffer options @@ -294,7 +294,6 @@ function M.get_default_values() }, disable_insert_on_commit = "auto", use_per_project_settings = true, - show_head_commit_hash = true, remember_settings = true, fetch_after_checkout = false, auto_refresh = true, @@ -307,6 +306,7 @@ function M.get_default_values() auto_show_console = true, notification_icon = "󰊢", status = { + show_head_commit_hash = true, recent_commit_count = 10, HEAD_padding = 10, mode_padding = 3, @@ -976,7 +976,6 @@ function M.validate_config() if validate_type(config, "base config", "table") then validate_type(config.disable_hint, "disable_hint", "boolean") validate_type(config.disable_context_highlighting, "disable_context_highlighting", "boolean") - validate_type(config.show_head_commit_hash, "show_head_commit_hash", "boolean") validate_type(config.disable_signs, "disable_signs", "boolean") validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") @@ -989,6 +988,7 @@ function M.validate_config() validate_type(config.disable_line_numbers, "disable_line_numbers", "boolean") validate_type(config.auto_show_console, "auto_show_console", "boolean") if validate_type(config.status, "status", "table") then + validate_type(config.status.show_head_commit_hash, "status.show_head_commit_hash", "boolean") validate_type(config.status.recent_commit_count, "status.recent_commit_count", "number") validate_type(config.status.mode_padding, "status.mode_padding", "number") validate_type(config.status.HEAD_padding, "status.HEAD_padding", "number") From 0a513ca1b0034d9c205c360c0e5866e4b0e4f10c Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Apr 2024 22:04:23 +0200 Subject: [PATCH 405/443] Add highlight group for the number of changes in a section of the status buffer --- doc/neogit.txt | 1 + lua/neogit/buffers/status/ui.lua | 2 +- lua/neogit/lib/hl.lua | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index d7313660a..f1bdfb70e 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -398,6 +398,7 @@ NeogitReverting ^ NeogitPicking ^ NeogitMerging ^ NeogitBisecting ^ +NeogitSectionHeaderCount The number, for sections with a number. STATUS BUFFER FILE Applied to the label on the left of filenames. diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index dedb3f39c..e59b511fe 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -160,7 +160,7 @@ end) local Section = Component.new(function(props) local count if props.count then - count = { text(" ("), text(#props.items), text(")") } + count = { text(" ("), text.highlight("NeogitSectionHeaderCount")(#props.items), text(")") } end return col.tag("Section")({ diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index f997dd5b4..e8884a0c2 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -220,6 +220,7 @@ function M.setup() NeogitChangeUnmerged = { fg = palette.bg_yellow, bold = palette.bold, italic = palette.italic }, NeogitChangeNewFile = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, NeogitSectionHeader = { fg = palette.bg_purple, bold = palette.bold }, + NeogitSectionHeaderCount = {}, NeogitUntrackedfiles = { link = "NeogitSectionHeader" }, NeogitUnstagedchanges = { link = "NeogitSectionHeader" }, NeogitUnmergedchanges = { link = "NeogitSectionHeader" }, From 2c150da831ac1d1130d4173dfb870203aea1619e Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Apr 2024 22:05:28 +0200 Subject: [PATCH 406/443] Use decoration provider to set fold icons in status column --- lua/neogit/buffers/status/init.lua | 57 +----------------------------- lua/neogit/lib/buffer.lua | 53 +++++++++++++++++++++++---- lua/neogit/lib/hl.lua | 1 + lua/neogit/lib/signs.lua | 9 +++-- 4 files changed, 56 insertions(+), 64 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 2586e22a2..cc3b310aa 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -96,7 +96,7 @@ function M:open(kind, cwd) context_highlight = true, kind = kind or config.values.kind, disable_line_numbers = config.values.disable_line_numbers, - status_column = "%{%v:lua.require('neogit.buffers.status').eval_statuscolumn()%}", + foldmarkers = not config.values.disable_signs, on_detach = function() if self.watcher then self.watcher:stop() @@ -953,10 +953,6 @@ function M:open(kind, cwd) vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if self.user_statuscolumn then - buf:set_window_option("statuscolumn", self.user_statuscolumn) - end - if buf:is_focused() and cursor then buf:move_cursor(cursor) end @@ -1000,10 +996,6 @@ function M:open(kind, cwd) vim.cmd.tabedit(fn.fnameescape(item.absolute_path)) local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if self.user_statuscolumn then - buf:set_window_option("statuscolumn", self.user_statuscolumn) - end - if buf:is_focused() and cursor then buf:move_cursor(cursor) end @@ -1038,10 +1030,6 @@ function M:open(kind, cwd) vim.cmd.split(fn.fnameescape(item.absolute_path)) local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if self.user_statuscolumn then - buf:set_window_option("statuscolumn", self.user_statuscolumn) - end - if buf:is_focused() and cursor then buf:move_cursor(cursor) end @@ -1076,10 +1064,6 @@ function M:open(kind, cwd) vim.cmd.vsplit(fn.fnameescape(item.absolute_path)) local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if self.user_statuscolumn then - buf:set_window_option("statuscolumn", self.user_statuscolumn) - end - if buf:is_focused() and cursor then buf:move_cursor(cursor) end @@ -1182,10 +1166,6 @@ function M:open(kind, cwd) }, }, initialize = function() - if vim.wo.statuscolumn ~= "" then - self.user_statuscolumn = vim.wo.statuscolumn - end - self.prev_autochdir = vim.o.autochdir vim.o.autochdir = false end, @@ -1327,39 +1307,4 @@ function M:_get_refresh_lock(reason) return permit end -local function fold_opened() - return vim.fn.foldclosed(vim.v.lnum) == -1 -end - -function M.eval_statuscolumn() - if config.values.disable_signs and config.values.disable_line_numbers then - return " " - elseif config.values.disable_signs and not config.values.disable_line_numbers then - return "%l%r " - end - - if not M.is_open() then - return " " - end - - local foldmarkers = M.instance().buffer.ui.statuscolumn.foldmarkers - - local fold - if foldmarkers[vim.v.lnum] then - if fold_opened() then - fold = config.values.signs[string.lower(foldmarkers[vim.v.lnum])][2] - else - fold = config.values.signs[string.lower(foldmarkers[vim.v.lnum])][1] - end - else - fold = " " - end - - if config.values.disable_line_numbers then - return ("%s "):format(fold) - else - return ("%s %s "):format("%l%r", fold) - end -end - return M diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index ea6b43012..162fb8acd 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -444,7 +444,11 @@ function Buffer:place_sign(line, name, opts) local ns_id = self:get_namespace_id(opts.namespace) if ns_id then - api.nvim_buf_set_extmark(self.handle, ns_id, line - 1, 0, { sign_text = signs.get(name) }) + api.nvim_buf_set_extmark(self.handle, ns_id, line - 1, 0, { + sign_text = signs.get(name), + sign_hl_group = opts.highlight, + cursorline_hl_group = opts.cursor_hl, + }) end end @@ -587,6 +591,7 @@ end ---@field after function|nil ---@field on_detach function|nil ---@field render function|nil +---@field foldmarkers boolean|nil ---@param config BufferConfig ---@return Buffer @@ -659,7 +664,6 @@ function Buffer.create(config) if win then logger.debug("[BUFFER:" .. buffer.handle .. "] Setting window options") - buffer:set_window_option("statuscolumn", config.status_column or "") buffer:set_window_option("foldenable", true) buffer:set_window_option("foldlevel", 99) buffer:set_window_option("foldminlines", 0) @@ -723,7 +727,7 @@ function Buffer.create(config) buffer:create_namespace("ViewContext") buffer:set_decorations("ViewContext", { on_start = function() - return buffer:exists() and buffer:is_focused() + return buffer:exists() and buffer:is_valid() and buffer:is_focused() end, on_win = function() buffer:clear_namespace("ViewContext") @@ -733,9 +737,10 @@ function Buffer.create(config) return end - local cursor = vim.fn.line(".") - local start = math.max(context.position.row_start, vim.fn.line("w0")) - local stop = math.min(context.position.row_end, vim.fn.line("w$")) + local cursor = fn.line(".") + local start = math.max(context.position.row_start, fn.line("w0")) + local stop = math.min(context.position.row_end, fn.line("w$")) + for line = start, stop do local line_hl = ("%s%s"):format( buffer.ui:get_line_highlight(line) or "NeogitDiffContext", @@ -753,6 +758,42 @@ function Buffer.create(config) }) end + if config.foldmarkers then + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up foldmarkers") + buffer:create_namespace("FoldSigns") + buffer:set_decorations("FoldSigns", { + on_start = function() + return buffer:exists() and buffer:is_valid() and buffer:is_focused() + end, + on_win = function() + buffer:clear_namespace("FoldSigns") + local foldmarkers = buffer.ui.statuscolumn.foldmarkers + for line = fn.line("w0"), fn.line("w$") do + if foldmarkers[line] then + local fold + + if fn.foldclosed(line) == -1 then + fold = "NeogitOpen" + else + fold = "NeogitClosed" + end + + buffer:place_sign(line, fold .. string.lower(foldmarkers[line]), { + namespace = "FoldSigns", + highlight = "NeogitSubtleText", + cursor_hl = "NeogitCursorLine", + }) + else + buffer:place_sign(line, "NeogitBlank", { + namespace = "FoldSigns", + cursor_hl = "NeogitCursorLine", + }) + end + end + end, + }) + end + if config.header then logger.debug("[BUFFER:" .. buffer.handle .. "] Setting header") buffer:set_header(config.header) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index e8884a0c2..3c50d48bc 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -148,6 +148,7 @@ function M.setup() NeogitSignatureGoodExpired = { link = "NeogitGraphOrange" }, NeogitSignatureGoodExpiredKey = { link = "NeogitGraphYellow" }, NeogitSignatureGoodRevokedKey = { link = "NeogitGraphRed" }, + NeogitCursorLine = { link = "CursorLine" }, NeogitHunkHeader = { fg = palette.bg0, bg = palette.grey, bold = palette.bold }, NeogitHunkHeaderHighlight = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, NeogitHunkHeaderCursor = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, diff --git a/lua/neogit/lib/signs.lua b/lua/neogit/lib/signs.lua index 3d6aee182..167f1ef05 100644 --- a/lua/neogit/lib/signs.lua +++ b/lua/neogit/lib/signs.lua @@ -1,10 +1,15 @@ local config = require("neogit.config") local M = {} -local signs = {} +local signs = { NeogitBlank = " " } function M.get(name) - return signs[name] + local sign = signs[name] + if sign == "" then + return " " + else + return sign + end end function M.setup() From 656241f64d0eb2cb7179d2435b6657ba3b24357d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Apr 2024 22:05:44 +0200 Subject: [PATCH 407/443] formatting --- lua/neogit/lib/buffer.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 162fb8acd..672f2a00f 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -687,8 +687,12 @@ function Buffer.create(config) local neogit_augroup = require("neogit").autocmd_group for event, callback in pairs(config.autocmds or {}) do - logger.debug("[BUFFER:" .. buffer.handle .. "] Setting autocmd: " .. event) - api.nvim_create_autocmd(event, { callback = callback, buffer = buffer.handle, group = neogit_augroup }) + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting autocmd for: " .. event) + api.nvim_create_autocmd(event, { + callback = callback, + buffer = buffer.handle, + group = neogit_augroup, + }) end logger.debug("[BUFFER:" .. buffer.handle .. "] Mappings Registered") From 88e11ef783f7b0f9db54f721c9f1b4dbe9c74b23 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Apr 2024 22:09:16 +0200 Subject: [PATCH 408/443] breaking: remove "auto_refresh" setting - this is now managed by the file watcher. Also this setting didn't do anything. --- lua/neogit/config.lua | 3 --- tests/specs/neogit/config_spec.lua | 5 ----- 2 files changed, 8 deletions(-) diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 791c5ff7f..2a2460be4 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -239,7 +239,6 @@ end ---@field disable_insert_on_commit? boolean|"auto" Disable automatically entering insert mode in commit dialogues ---@field use_per_project_settings? boolean Scope persisted settings on a per-project basis ---@field remember_settings? boolean Whether neogit should persist flags from popups, e.g. git push flags ----@field auto_refresh? boolean Automatically refresh to detect git modifications without manual intervention ---@field sort_branches? string Value used for `--sort` for the `git branch` command ---@field kind? WindowKind The default type of window neogit should open in ---@field disable_line_numbers? boolean Whether to disable line numbers @@ -296,7 +295,6 @@ function M.get_default_values() use_per_project_settings = true, remember_settings = true, fetch_after_checkout = false, - auto_refresh = true, sort_branches = "-committerdate", kind = "tab", disable_line_numbers = true, @@ -980,7 +978,6 @@ function M.validate_config() validate_type(config.telescope_sorter, "telescope_sorter", "function") validate_type(config.use_per_project_settings, "use_per_project_settings", "boolean") validate_type(config.remember_settings, "remember_settings", "boolean") - validate_type(config.auto_refresh, "auto_refresh", "boolean") validate_type(config.sort_branches, "sort_branches", "string") validate_type(config.notification_icon, "notification_icon", "string") validate_type(config.console_timeout, "console_timeout", "number") diff --git a/tests/specs/neogit/config_spec.lua b/tests/specs/neogit/config_spec.lua index a999976b3..3a51eb951 100644 --- a/tests/specs/neogit/config_spec.lua +++ b/tests/specs/neogit/config_spec.lua @@ -51,11 +51,6 @@ describe("Neogit config", function() assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) end) - it("should return invalid when auto_refresh isn't a boolean", function() - config.values.auto_refresh = "not a boolean" - assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) - end) - it("should return invalid when sort_branches isn't a string", function() config.values.sort_branches = false assert.True(vim.tbl_count(require("neogit.config").validate_config()) ~= 0) From 45dcbb677d3933b8671be13df872aca51fa9d0df Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 22 Apr 2024 23:07:52 +0200 Subject: [PATCH 409/443] Add back statuscolumn for some buffers --- lua/neogit/buffers/commit_select_view/init.lua | 2 +- lua/neogit/buffers/commit_view/init.lua | 1 + lua/neogit/buffers/diff/init.lua | 1 + lua/neogit/buffers/editor/init.lua | 2 +- lua/neogit/buffers/log_view/init.lua | 2 +- lua/neogit/buffers/rebase_editor/init.lua | 2 +- lua/neogit/buffers/reflog_view/init.lua | 2 +- lua/neogit/lib/buffer.lua | 13 ++++++++----- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lua/neogit/buffers/commit_select_view/init.lua b/lua/neogit/buffers/commit_select_view/init.lua index b48c55e62..135b385c6 100644 --- a/lua/neogit/buffers/commit_select_view/init.lua +++ b/lua/neogit/buffers/commit_select_view/init.lua @@ -56,7 +56,7 @@ function M:open(action) self.buffer = Buffer.create { name = "NeogitCommitSelectView", filetype = "NeogitCommitSelectView", - status_column = " ", + status_column = "", kind = config.values.commit_select_view.kind, header = self.header or "Select a commit with , or to abort", mappings = { diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 3c37648e7..8da73d253 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -128,6 +128,7 @@ function M:open(kind) name = "NeogitCommitView", filetype = "NeogitCommitView", kind = kind, + status_column = "", context_highlight = true, mappings = { n = { diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index c2ee5b866..d0b214556 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -52,6 +52,7 @@ function M:open() self.buffer = Buffer.create { name = "NeogitDiffView", filetype = "NeogitDiffView", + status_column = "", kind = "split", context_highlight = true, mappings = { diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index a7dbf6262..a5f891314 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -80,7 +80,7 @@ function M:open(kind) buftype = "", kind = kind, modifiable = true, - status_column = " ", + status_column = "", readonly = false, autocmds = { ["QuitPre"] = function() -- For :wq compatibility diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 02aa15da3..9d5ac16dc 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -73,7 +73,7 @@ function M:open() filetype = "NeogitLogView", kind = config.values.log_view.kind, context_highlight = false, - status_column = " ", + status_column = "", mappings = { v = { [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) diff --git a/lua/neogit/buffers/rebase_editor/init.lua b/lua/neogit/buffers/rebase_editor/init.lua index 4d8a1d305..c410624e9 100644 --- a/lua/neogit/buffers/rebase_editor/init.lua +++ b/lua/neogit/buffers/rebase_editor/init.lua @@ -74,7 +74,7 @@ function M:open(kind) load = true, filetype = "NeogitRebaseTodo", buftype = "", - status_column = " ", + status_column = "", kind = kind, modifiable = true, disable_line_numbers = config.values.disable_line_numbers, diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 1530e503f..85958b82f 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -49,7 +49,7 @@ function M:open(_) name = "NeogitReflogView", filetype = "NeogitReflogView", kind = config.values.reflog_view.kind, - status_column = " ", + status_column = "", context_highlight = true, mappings = { v = { diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 672f2a00f..58e158de0 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -720,10 +720,6 @@ function Buffer.create(config) -- Set fold styling for Neogit windows while preserving user styling vim.opt_local.winhl:append("Folded:NeogitFold") vim.opt_local.fillchars:append("fold: ") - - if not config.disable_signs then - vim.opt_local.signcolumn = "auto" - end end) if config.context_highlight then @@ -762,7 +758,14 @@ function Buffer.create(config) }) end - if config.foldmarkers then + if config.status_column then + vim.opt_local.statuscolumn = config.status_column + vim.opt_local.signcolumn = "no" + end + + if config.foldmarkers and not config.disable_signs then + vim.opt_local.signcolumn = "auto" + logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up foldmarkers") buffer:create_namespace("FoldSigns") buffer:set_decorations("FoldSigns", { From 98ae1662e8efd58d09b22d1a71c965bec5009e82 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 25 Apr 2024 21:54:34 +0200 Subject: [PATCH 410/443] Add more fine-grained highlighting to labels in status buffer. By default these just link back to a common base, however. --- doc/neogit.txt | 32 ++++++++++++++++- lua/neogit/buffers/status/ui.lua | 2 +- lua/neogit/lib/hl.lua | 60 ++++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index f1bdfb70e..645b3940a 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -401,7 +401,8 @@ NeogitBisecting ^ NeogitSectionHeaderCount The number, for sections with a number. STATUS BUFFER FILE -Applied to the label on the left of filenames. +Applied to the label on the left of filenames. These highlight groups are not +used directly, but linked to by other groups: NeogitChangeModified NeogitChangeAdded @@ -412,6 +413,35 @@ NeogitChangeCopied NeogitChangeNewFile NeogitChangeUnmerged +Styling one of the above groups will apply to all sections, and is generally +whats recommended. However, if you want to control the style on a per-section +basis, the _actual_ highlight groups on the labels follow this pattern: + `NeogitChange
` + +Where `` is one of: (corrospinding to the git mode) + M + A + N + D + C + U + R + DD + UU + AA + DU + UD + AU + UA + +And `
` is one of: + untracked + unstaged + staged + +So, a modified and staged change would use the `NeogitChangeMstaged` highlight +group, which is linked to `NeogitChangeModified` by default. + SIGNS FOR LINE HIGHLIGHTING Used to highlight different sections of the status buffer or commit buffer. diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index e59b511fe..62ed3c26b 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -252,7 +252,7 @@ local SectionItemFile = function(section, config) end local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name - local highlight = ("NeogitChange%s"):format(item.mode:gsub("%?", "Untracked")) + local highlight = ("NeogitChange%s%s"):format(item.mode:gsub("%?", "Untracked"), section) return col.tag("Item")({ row { diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index 3c50d48bc..c9c8fdf0e 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -197,21 +197,51 @@ function M.setup() NeogitStash = { link = "NeogitSubtleText" }, NeogitRebaseDone = { link = "NeogitSubtleText" }, NeogitFold = { fg = "None", bg = "None" }, - NeogitChangeM = { link = "NeogitChangeModified" }, - NeogitChangeA = { link = "NeogitChangeAdded" }, - NeogitChangeN = { link = "NeogitChangeNewFile" }, - NeogitChangeD = { link = "NeogitChangeDeleted" }, - NeogitChangeC = { link = "NeogitChangeCopied" }, - NeogitChangeU = { link = "NeogitChangeUpdated" }, - NeogitChangeR = { link = "NeogitChangeRenamed" }, - NeogitChangeDD = { link = "NeogitChangeUnmerged" }, - NeogitChangeUU = { link = "NeogitChangeUnmerged" }, - NeogitChangeAA = { link = "NeogitChangeUnmerged" }, - NeogitChangeDU = { link = "NeogitChangeUnmerged" }, - NeogitChangeUD = { link = "NeogitChangeUnmerged" }, - NeogitChangeAU = { link = "NeogitChangeUnmerged" }, - NeogitChangeUA = { link = "NeogitChangeUnmerged" }, - NeogitChangeUntracked = { fg = "None" }, + NeogitChangeMuntracked = { link = "NeogitChangeModified" }, + NeogitChangeAuntracked = { link = "NeogitChangeAdded" }, + NeogitChangeNuntracked = { link = "NeogitChangeNewFile" }, + NeogitChangeDuntracked = { link = "NeogitChangeDeleted" }, + NeogitChangeCuntracked = { link = "NeogitChangeCopied" }, + NeogitChangeUuntracked = { link = "NeogitChangeUpdated" }, + NeogitChangeRuntracked = { link = "NeogitChangeRenamed" }, + NeogitChangeDDuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeUUuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeAAuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeDUuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeUDuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeAUuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeUAuntracked = { link = "NeogitChangeUnmerged" }, + NeogitChangeUntrackeduntracked = { fg = "None" }, + NeogitChangeMunstaged = { link = "NeogitChangeModified" }, + NeogitChangeAunstaged = { link = "NeogitChangeAdded" }, + NeogitChangeNunstaged = { link = "NeogitChangeNewFile" }, + NeogitChangeDunstaged = { link = "NeogitChangeDeleted" }, + NeogitChangeCunstaged = { link = "NeogitChangeCopied" }, + NeogitChangeUunstaged = { link = "NeogitChangeUpdated" }, + NeogitChangeRunstaged = { link = "NeogitChangeRenamed" }, + NeogitChangeDDunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUUunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeAAunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeDUunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUDunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeAUunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUAunstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUntrackedunstaged = { fg = "None" }, + NeogitChangeMstaged = { link = "NeogitChangeModified" }, + NeogitChangeAstaged = { link = "NeogitChangeAdded" }, + NeogitChangeNstaged = { link = "NeogitChangeNewFile" }, + NeogitChangeDstaged = { link = "NeogitChangeDeleted" }, + NeogitChangeCstaged = { link = "NeogitChangeCopied" }, + NeogitChangeUstaged = { link = "NeogitChangeUpdated" }, + NeogitChangeRstaged = { link = "NeogitChangeRenamed" }, + NeogitChangeDDstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUUstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeAAstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeDUstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUDstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeAUstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUAstaged = { link = "NeogitChangeUnmerged" }, + NeogitChangeUntrackedstaged = { fg = "None" }, NeogitChangeModified = { fg = palette.bg_blue, bold = palette.bold, italic = palette.italic }, NeogitChangeAdded = { fg = palette.bg_green, bold = palette.bold, italic = palette.italic }, NeogitChangeDeleted = { fg = palette.bg_red, bold = palette.bold, italic = palette.italic }, From b210b2d1872998e90456841ee10b14c0d0dc9168 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 25 Apr 2024 21:56:25 +0200 Subject: [PATCH 411/443] Add better type annotations to the lib/git so gotodefinition works --- lua/neogit/lib/git.lua | 37 +++++++++++++++++++++++++++--- lua/neogit/lib/git/bisect.lua | 2 +- lua/neogit/lib/git/branch.lua | 1 + lua/neogit/lib/git/cherry.lua | 4 +++- lua/neogit/lib/git/cherry_pick.lua | 1 + lua/neogit/lib/git/cli.lua | 1 + lua/neogit/lib/git/config.lua | 1 + lua/neogit/lib/git/diff.lua | 1 + lua/neogit/lib/git/fetch.lua | 1 + lua/neogit/lib/git/files.lua | 1 + lua/neogit/lib/git/index.lua | 1 + lua/neogit/lib/git/init.lua | 1 + lua/neogit/lib/git/log.lua | 1 + lua/neogit/lib/git/merge.lua | 5 ++-- lua/neogit/lib/git/pull.lua | 1 + lua/neogit/lib/git/push.lua | 1 + lua/neogit/lib/git/rebase.lua | 1 + lua/neogit/lib/git/reflog.lua | 1 + lua/neogit/lib/git/refs.lua | 1 + lua/neogit/lib/git/remote.lua | 1 + lua/neogit/lib/git/reset.lua | 1 + lua/neogit/lib/git/rev_parse.lua | 5 ++-- lua/neogit/lib/git/revert.lua | 1 + lua/neogit/lib/git/sequencer.lua | 2 ++ lua/neogit/lib/git/stash.lua | 1 + lua/neogit/lib/git/status.lua | 1 + lua/neogit/lib/git/tag.lua | 1 + lua/neogit/lib/git/worktree.lua | 1 + 28 files changed, 68 insertions(+), 9 deletions(-) diff --git a/lua/neogit/lib/git.lua b/lua/neogit/lib/git.lua index e1463c01d..3f0a7f669 100644 --- a/lua/neogit/lib/git.lua +++ b/lua/neogit/lib/git.lua @@ -1,4 +1,35 @@ -local Git = { +---@class NeogitGitLib +---@field repo NeogitRepo +---@field bisect NeogitGitBisect +---@field branch NeogitGitBranch +---@field cherry NeogitGitCherry +---@field cherry_pick NeogitGitCherryPick +---@field cli NeogitGitCLI +---@field config NeogitGitConfig +---@field diff NeogitGitDiff +---@field fetch NeogitGitFetch +---@field files NeogitGitFiles +---@field index NeogitGitIndex +---@field init NeogitGitInit +---@field log NeogitGitLog +---@field merge NeogitGitMerge +---@field pull NeogitGitPull +---@field push NeogitGitPush +---@field rebase NeogitGitRebase +---@field reflog NeogitGitReflog +---@field refs NeogitGitRefs +---@field remote NeogitGitRemote +---@field reset NeogitGitReset +---@field rev_parse NeogitGitRevParse +---@field revert NeogitGitRevert +---@field sequencer NeogitGitSequencer +---@field stash NeogitGitStash +---@field status NeogitGitStatus +---@field tag NeogitGitTag +---@field worktree NeogitGitWorktree +local Git = {} + +setmetatable(Git, { __index = function(_, k) if k == "repo" then return require("neogit.lib.git.repository").instance() @@ -6,6 +37,6 @@ local Git = { return require("neogit.lib.git." .. k) end end, -} +}) -return setmetatable({}, Git) +return Git diff --git a/lua/neogit/lib/git/bisect.lua b/lua/neogit/lib/git/bisect.lua index 58ced7a1c..3062c0474 100644 --- a/lua/neogit/lib/git/bisect.lua +++ b/lua/neogit/lib/git/bisect.lua @@ -1,6 +1,6 @@ local git = require("neogit.lib.git") -local logger = require("neogit.logger") +---@class NeogitGitBisect local M = {} local function fire_bisect_event(data) diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index a9b4be175..0f6a0c1b8 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -4,6 +4,7 @@ local util = require("neogit.lib.util") local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") +---@class NeogitGitBranch local M = {} local function parse_branches(branches, include_current) diff --git a/lua/neogit/lib/git/cherry.lua b/lua/neogit/lib/git/cherry.lua index 60c39b5d2..ff7af819f 100644 --- a/lua/neogit/lib/git/cherry.lua +++ b/lua/neogit/lib/git/cherry.lua @@ -1,7 +1,9 @@ -local M = {} local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitCherry +local M = {} + function M.list(upstream, head) local result = git.cli.cherry.verbose.args(upstream, head).call().stdout return util.reverse(util.map(result, function(cherry) diff --git a/lua/neogit/lib/git/cherry_pick.lua b/lua/neogit/lib/git/cherry_pick.lua index bbf782cb4..a70cd964e 100644 --- a/lua/neogit/lib/git/cherry_pick.lua +++ b/lua/neogit/lib/git/cherry_pick.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") +---@class NeogitGitCherryPick local M = {} local function fire_cherrypick_event(data) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 7966310f1..3b1946f68 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -1086,6 +1086,7 @@ local meta = { end, } +---@class NeogitGitCLI local cli = setmetatable({ history = history, insert = handle_new_cmd, diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index a85a2136e..a3b88421d 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local logger = require("neogit.logger") +---@class NeogitGitConfig local M = {} ---@class ConfigEntry diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index fb1b5e62d..027323c55 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -253,6 +253,7 @@ local function invalidate_diff(filter, section, item) end end +---@class NeogitGitDiff return { parse = parse_diff, staged_stats = function() diff --git a/lua/neogit/lib/git/fetch.lua b/lua/neogit/lib/git/fetch.lua index 0503585e2..943ec33b4 100644 --- a/lua/neogit/lib/git/fetch.lua +++ b/lua/neogit/lib/git/fetch.lua @@ -1,5 +1,6 @@ local git = require("neogit.lib.git") +---@class NeogitGitFetch local M = {} ---Fetches from the remote and handles password questions diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index f9793b1e3..1d94f1027 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -1,5 +1,6 @@ local git = require("neogit.lib.git") +---@class NeogitGitFiles local M = {} function M.all() diff --git a/lua/neogit/lib/git/index.lua b/lua/neogit/lib/git/index.lua index 171be1161..6116d55d4 100644 --- a/lua/neogit/lib/git/index.lua +++ b/lua/neogit/lib/git/index.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local Path = require("plenary.path") local util = require("neogit.lib.util") +---@class NeogitGitIndex local M = {} ---Generates a patch that can be applied to index diff --git a/lua/neogit/lib/git/init.lua b/lua/neogit/lib/git/init.lua index 46f2200be..041e158e6 100644 --- a/lua/neogit/lib/git/init.lua +++ b/lua/neogit/lib/git/init.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") local input = require("neogit.lib.input") +---@class NeogitGitInit local M = {} M.create = function(directory, sync) diff --git a/lua/neogit/lib/git/log.lua b/lua/neogit/lib/git/log.lua index 768c90bb5..259dcd711 100644 --- a/lua/neogit/lib/git/log.lua +++ b/lua/neogit/lib/git/log.lua @@ -3,6 +3,7 @@ local util = require("neogit.lib.util") local config = require("neogit.config") local record = require("neogit.lib.record") +---@class NeogitGitLog local M = {} local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)" diff --git a/lua/neogit/lib/git/merge.lua b/lua/neogit/lib/git/merge.lua index bca39d482..a359dc9ec 100644 --- a/lua/neogit/lib/git/merge.lua +++ b/lua/neogit/lib/git/merge.lua @@ -2,10 +2,11 @@ local client = require("neogit.client") local git = require("neogit.lib.git") local notification = require("neogit.lib.notification") -local M = {} - local a = require("plenary.async") +---@class NeogitGitMerge +local M = {} + local function merge_command(cmd) local envs = client.get_envs_git_editor() return cmd.env(envs).show_popup(true):in_pty(true).call { verbose = true } diff --git a/lua/neogit/lib/git/pull.lua b/lua/neogit/lib/git/pull.lua index 6e3bf9481..04edbe443 100644 --- a/lua/neogit/lib/git/pull.lua +++ b/lua/neogit/lib/git/pull.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitPull local M = {} function M.pull_interactive(remote, branch, args) diff --git a/lua/neogit/lib/git/push.lua b/lua/neogit/lib/git/push.lua index a33b639cf..0f3d6fe0c 100644 --- a/lua/neogit/lib/git/push.lua +++ b/lua/neogit/lib/git/push.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitPush local M = {} ---Pushes to the remote and handles password questions diff --git a/lua/neogit/lib/git/rebase.lua b/lua/neogit/lib/git/rebase.lua index 91f07100b..28509c8b5 100644 --- a/lua/neogit/lib/git/rebase.lua +++ b/lua/neogit/lib/git/rebase.lua @@ -3,6 +3,7 @@ local git = require("neogit.lib.git") local client = require("neogit.client") local notification = require("neogit.lib.notification") +---@class NeogitGitRebase local M = {} local a = require("plenary.async") diff --git a/lua/neogit/lib/git/reflog.lua b/lua/neogit/lib/git/reflog.lua index 003f5fb16..7681940a1 100644 --- a/lua/neogit/lib/git/reflog.lua +++ b/lua/neogit/lib/git/reflog.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitReflog local M = {} ---@class ReflogEntry diff --git a/lua/neogit/lib/git/refs.lua b/lua/neogit/lib/git/refs.lua index 9621170a9..2da8e1ace 100644 --- a/lua/neogit/lib/git/refs.lua +++ b/lua/neogit/lib/git/refs.lua @@ -3,6 +3,7 @@ local config = require("neogit.config") local record = require("neogit.lib.record") local util = require("neogit.lib.util") +---@class NeogitGitRefs local M = {} ---@return fun(format?: string, sortby?: string, filter?: table): string[] diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index fca4cbea0..55f9b5719 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitRemote local M = {} -- https://github.com/magit/magit/blob/main/lisp/magit-remote.el#LL141C32-L141C32 diff --git a/lua/neogit/lib/git/reset.lua b/lua/neogit/lib/git/reset.lua index 1abe653b2..7e9527608 100644 --- a/lua/neogit/lib/git/reset.lua +++ b/lua/neogit/lib/git/reset.lua @@ -2,6 +2,7 @@ local notification = require("neogit.lib.notification") local git = require("neogit.lib.git") local a = require("plenary.async") +---@class NeogitGitReset local M = {} local function fire_reset_event(data) diff --git a/lua/neogit/lib/git/rev_parse.lua b/lua/neogit/lib/git/rev_parse.lua index 3d3e9d3d5..f695c1314 100644 --- a/lua/neogit/lib/git/rev_parse.lua +++ b/lua/neogit/lib/git/rev_parse.lua @@ -1,8 +1,9 @@ -local M = {} - local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitRevParse +local M = {} + ---@param oid string ---@return string ---@async diff --git a/lua/neogit/lib/git/revert.lua b/lua/neogit/lib/git/revert.lua index adbde5b01..e6f19de60 100644 --- a/lua/neogit/lib/git/revert.lua +++ b/lua/neogit/lib/git/revert.lua @@ -1,6 +1,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") +---@class NeogitGitRevert local M = {} function M.commits(commits, args) diff --git a/lua/neogit/lib/git/sequencer.lua b/lua/neogit/lib/git/sequencer.lua index 63b0adf6d..c5fc385e8 100644 --- a/lua/neogit/lib/git/sequencer.lua +++ b/lua/neogit/lib/git/sequencer.lua @@ -1,4 +1,6 @@ local git = require("neogit.lib.git") + +---@class NeogitGitSequencer local M = {} -- .git/sequencer/todo does not exist when there is only one commit left. diff --git a/lua/neogit/lib/git/stash.lua b/lua/neogit/lib/git/stash.lua index a6d5fe9bb..bbab8db38 100644 --- a/lua/neogit/lib/git/stash.lua +++ b/lua/neogit/lib/git/stash.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local input = require("neogit.lib.input") local util = require("neogit.lib.util") +---@class NeogitGitStash local M = {} local function perform_stash(include) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 4c862a1f0..6b9203278 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -178,6 +178,7 @@ local function update_status(state) state.staged.items = staged_files end +---@class NeogitGitStatus local status = { stage = function(files) git.cli.add.files(unpack(files)).call() diff --git a/lua/neogit/lib/git/tag.lua b/lua/neogit/lib/git/tag.lua index 18d7b5007..6a43cdfcb 100644 --- a/lua/neogit/lib/git/tag.lua +++ b/lua/neogit/lib/git/tag.lua @@ -1,5 +1,6 @@ local git = require("neogit.lib.git") +---@class NeogitGitTag local M = {} --- Outputs a list of tags locally diff --git a/lua/neogit/lib/git/worktree.lua b/lua/neogit/lib/git/worktree.lua index a9f79724d..575cfd22c 100644 --- a/lua/neogit/lib/git/worktree.lua +++ b/lua/neogit/lib/git/worktree.lua @@ -2,6 +2,7 @@ local git = require("neogit.lib.git") local util = require("neogit.lib.util") local Path = require("plenary.path") +---@class NeogitGitWorktree local M = {} ---Creates new worktree at path for ref From 8378ed9ebb16d0da23e53a8230a9ca7f0f4aeb9c Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 25 Apr 2024 22:34:29 +0200 Subject: [PATCH 412/443] Add pattern escaping to checking remote patterns for pull request templates --- lua/neogit/lib/util.lua | 9 +++++++++ lua/neogit/popups/branch/actions.lua | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index e3f48f5da..38af89384 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -388,6 +388,15 @@ function M.lists_equal(l1, l2) return true end +local special_chars = { "%%", "%(", "%)", "%.", "%+", "%-", "%*", "%?", "%[", "%^", "%$" } +function M.pattern_escape(str) + for _, char in ipairs(special_chars) do + str, _ = str:gsub(char, "%" .. char) + end + + return str +end + function M.pad_right(s, len) return s .. string.rep(" ", math.max(len - #s, 0)) end diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index a9b69f78c..b44926f67 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -268,7 +268,7 @@ M.open_pull_request = operation("open_pull_request", function() local url = git.remote.get_url(git.branch.upstream_remote())[1] for s, v in pairs(config.values.git_services) do - if url:match(s) then + if url:match(util.pattern_escape(s)) then template = v break end From 9f291107a9f26b775f501799d9c50bcf9d71b9cc Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 26 Apr 2024 11:31:49 +0200 Subject: [PATCH 413/443] Allow staged diff to be user configurable --- README.md | 1 + lua/neogit/config.lua | 10 ++++++++-- lua/neogit/popups/commit/actions.lua | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae67e1f4f..f2ed4bf92 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ neogit.setup { }, commit_editor = { kind = "auto", + show_staged_diff = true, }, commit_select_view = { kind = "tab", diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 2a2460be4..8d510baa8 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -57,7 +57,7 @@ function M.get_reversed_commit_editor_maps_I() end ---@alias WindowKind ----|"split" Open in a split +---| "split" Open in a split ---| "vsplit" Open in a vertical split ---| "floating" Open in a floating window ---| "tab" Open in a new tab @@ -69,6 +69,10 @@ end ---@class NeogitConfigPopup Popup window options ---@field kind WindowKind The type of window that should be opened +---@class NeogitCommitEditorConfigPopup Popup window options +---@field kind WindowKind The type of window that should be opened +---@field show_staged_diff? boolean Display staged changes in a buffer when committing + ---@alias NeogitConfigSignsIcon { [1]: string, [2]: string } ---@class NeogitConfigSigns @@ -245,7 +249,7 @@ end ---@field console_timeout? integer Time in milliseconds after a console is created for long running commands ---@field auto_show_console? boolean Automatically show the console if a command takes longer than console_timeout ---@field status? NeogitConfigStatusOptions Status buffer options ----@field commit_editor? NeogitConfigPopup Commit editor options +---@field commit_editor? NeogitCommitEditorConfigPopup Commit editor options ---@field commit_select_view? NeogitConfigPopup Commit select view options ---@field commit_view? NeogitCommitBufferConfig Commit buffer options ---@field log_view? NeogitConfigPopup Log view options @@ -328,6 +332,7 @@ function M.get_default_values() }, commit_editor = { kind = "tab", + show_staged_diff = true, }, commit_select_view = { kind = "tab", @@ -995,6 +1000,7 @@ function M.validate_config() validate_trinary_auto(config.disable_insert_on_commit, "disable_insert_on_commit") -- Commit Editor if validate_type(config.commit_editor, "commit_editor", "table") then + validate_type(config.commit_editor.show_staged_diff, "show_staged_diff", "boolean") validate_kind(config.commit_editor.kind, "commit_editor") end -- Commit Select View diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index 16622fe50..fe1460d36 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -5,6 +5,7 @@ local git = require("neogit.lib.git") local client = require("neogit.client") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") +local config = require("neogit.config") local a = require("plenary.async") local function confirm_modifications() @@ -31,7 +32,7 @@ local function do_commit(popup, cmd) success = "Committed", }, interactive = true, - show_diff = true, + show_diff = config.values.commit_editor.show_staged_diff, }) end From dd2dbf8dfe06b29b79208979be38270501514a23 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 26 Apr 2024 16:07:55 +0200 Subject: [PATCH 414/443] Cleanup in status buffer init with a helper function for removing a file and any open buffers pointing to it. --- lua/neogit/buffers/status/init.lua | 98 +++++++++--------------------- 1 file changed, 28 insertions(+), 70 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index cc3b310aa..83a1bfb03 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -24,6 +24,21 @@ local util = require("neogit.lib.util") local api = vim.api local fn = vim.fn +local function cleanup_items(...) + if vim.in_fast_event() then + a.util.scheduler() + end + + for _, item in ipairs({ ... }) do + local bufnr = fn.bufexists(item.name) + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(item.escaped_path) + end +end + ---@class Semaphore ---@field permits number ---@field acquire function @@ -185,16 +200,7 @@ function M:open(kind, cwd) end if #untracked_files > 0 then - a.util.scheduler() - - for _, file in ipairs(untracked_files) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(file.escaped_path) - end + cleanup_items(unpack(untracked_files)) end if #unstaged_files > 0 then @@ -203,17 +209,7 @@ function M:open(kind, cwd) if #new_files > 0 then git.index.reset(new_files) - - a.util.scheduler() - - for _, file in ipairs(new_files) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(file.escaped_path) - end + cleanup_items(unpack(new_files)) end if #staged_files_modified > 0 then @@ -555,14 +551,7 @@ function M:open(kind, cwd) if section == "untracked" then message = ("Discard %q?"):format(selection.item.name) action = function() - a.util.scheduler() - - local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(selection.item.escaped_path) + cleanup_items(selection.item) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } elseif section == "unstaged" then @@ -591,15 +580,7 @@ function M:open(kind, cwd) action = function() if selection.item.mode == "A" then git.index.reset { selection.item.escaped_path } - - a.util.scheduler() - - local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(selection.item.escaped_path) + cleanup_items(selection.item) else git.index.checkout { selection.item.name } end @@ -611,22 +592,14 @@ function M:open(kind, cwd) action = function() if selection.item.mode == "N" then git.index.reset { selection.item.escaped_path } - - a.util.scheduler() - - local bufnr = fn.bufexists(selection.item.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(selection.item.escaped_path) + cleanup_items(selection.item) elseif selection.item.mode == "M" then git.index.reset { selection.item.escaped_path } git.index.checkout { selection.item.escaped_path } elseif selection.item.mode == "R" then - -- https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#L675 git.index.reset_HEAD(selection.item.name, selection.item.original_name) git.index.checkout { selection.item.original_name } + cleanup_items(selection.item) elseif selection.item.mode == "D" then git.index.reset_HEAD(selection.item.escaped_path) git.index.checkout { selection.item.escaped_path } @@ -686,16 +659,7 @@ function M:open(kind, cwd) if section == "untracked" then message = ("Discard %s files?"):format(#selection.section.items) action = function() - a.util.scheduler() - - for _, file in ipairs(selection.section.items) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(file.escaped_path) - end + cleanup_items(unpack(selection.section.items)) end refresh = { update_diffs = { "untracked:*" } } elseif section == "unstaged" then @@ -708,7 +672,7 @@ function M:open(kind, cwd) end if conflict then - -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#L626 + -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#Lair notification.warn("Resolve conflicts before discarding section.") return else @@ -743,17 +707,7 @@ function M:open(kind, cwd) if #new_files > 0 then -- ensure the file is deleted git.index.reset(new_files) - - a.util.scheduler() - - for _, file in ipairs(new_files) do - local bufnr = fn.bufexists(file.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(file.escaped_path) - end + cleanup_items(unpack(new_files)) end if #staged_files_modified > 0 then @@ -853,6 +807,7 @@ function M:open(kind, cwd) if stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() + assert(item, "Item cannot be nil") local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) @@ -896,6 +851,7 @@ function M:open(kind, cwd) if unstagable then if unstagable.hunk then local item = self.buffer.ui:get_item_under_cursor() + assert(item, "Item cannot be nil") local patch = git.index.generate_patch( item, unstagable.hunk, @@ -949,6 +905,7 @@ function M:open(kind, cwd) self:close() + -- TODO: Does this work? vim.schedule(function() vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) @@ -1131,6 +1088,7 @@ function M:open(kind, cwd) local commit = self.buffer.ui:get_commit_under_cursor() local commits = { commit } + -- TODO: Pass selection here so we can stage/unstage etc stuff p { branch = { commits = commits }, cherry_pick = { commits = commits }, From df527d152d34214a3a24a577bf7ef1f121e8add2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 26 Apr 2024 16:35:01 +0200 Subject: [PATCH 415/443] Fix: More robust cursor positioning for a buffer --- lua/neogit/lib/buffer.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 58e158de0..f22e3fc60 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -217,8 +217,20 @@ function Buffer:set_text(first_line, last_line, first_col, last_col, lines) api.nvim_buf_set_text(self.handle, first_line, first_col, last_line, last_col, lines) end +---@param line nil|number|number[] function Buffer:move_cursor(line) - pcall(api.nvim_win_set_cursor, 0, { line, 0 }) + if not line then + return + end + + local position = { line, 0 } + + if type(line) == "table" then + position = line + end + + -- pcall used in case the line is out of bounds + pcall(api.nvim_win_set_cursor, self.win_handle, position) end function Buffer:cursor_line() From 286de609d7cd504720e590ac8794a2d5abb1730b Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 28 Apr 2024 13:08:07 +0200 Subject: [PATCH 416/443] Remove MappingsManager - this was a holdover from before `vim.keymap.set` and the ability to pass a lua function as a keymap callback. Since there's no need for this added layer of complexity to the keymaps process - it's gone. --- lua/neogit/lib.lua | 1 - lua/neogit/lib/buffer.lua | 26 +++++------ lua/neogit/lib/mappings_manager.lua | 71 ----------------------------- 3 files changed, 13 insertions(+), 85 deletions(-) delete mode 100644 lua/neogit/lib/mappings_manager.lua diff --git a/lua/neogit/lib.lua b/lua/neogit/lib.lua index 0981d3456..676c748ca 100644 --- a/lua/neogit/lib.lua +++ b/lua/neogit/lib.lua @@ -2,5 +2,4 @@ return { git = require("neogit.lib.git"), popup = require("neogit.lib.popup"), notification = require("neogit.lib.notification"), - mappings_manager = require("neogit.lib.mappings_manager"), } diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index f22e3fc60..070728baa 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -2,7 +2,6 @@ local api = vim.api local fn = vim.fn local logger = require("neogit.logger") -local mappings_manager = require("neogit.lib.mappings_manager") local signs = require("neogit.lib.signs") local Ui = require("neogit.lib.ui") @@ -12,7 +11,6 @@ local Path = require("plenary.path") ---@field handle number ---@field win_handle number ---@field namespaces table ----@field mmanager MappingsManager ---@field ui Ui ---@field kind string ---@field disable_line_numbers boolean @@ -30,7 +28,6 @@ function Buffer:new(handle, win_handle) handle = handle, win_handle = win_handle, border = nil, - mmanager = mappings_manager.new(handle), kind = nil, -- how the buffer was opened. For more information look at the create function namespaces = { default = api.nvim_create_namespace("neogit-buffer-" .. handle), @@ -650,18 +647,24 @@ function Buffer.create(config) end if config.mappings then - logger.debug("[BUFFER:" .. buffer.handle .. "] Building mappings table") + logger.debug("[BUFFER:" .. buffer.handle .. "] Building mappings") for mode, val in pairs(config.mappings) do for key, cb in pairs(val) do - if type(key) == "string" then - buffer.mmanager.mappings[mode][key] = function() - cb(buffer) + local fn = function() + cb(buffer) + + if mode == "v" then + api.nvim_feedkeys(api.nvim_replace_termcodes("", true, false, true), "n", false) end + end + + local opts = { buffer = buffer.handle, silent = true, nowait = true } + + if type(key) == "string" then + vim.keymap.set(mode, key, fn, opts) elseif type(key) == "table" then for _, k in ipairs(key) do - buffer.mmanager.mappings[mode][k] = function() - cb(buffer) - end + vim.keymap.set(mode, k, fn, opts) end end end @@ -707,9 +710,6 @@ function Buffer.create(config) }) end - logger.debug("[BUFFER:" .. buffer.handle .. "] Mappings Registered") - buffer.mmanager.register() - if config.after then logger.debug("[BUFFER:" .. buffer.handle .. "] Running config.after callback") buffer:call(function() diff --git a/lua/neogit/lib/mappings_manager.lua b/lua/neogit/lib/mappings_manager.lua deleted file mode 100644 index cec3d02dd..000000000 --- a/lua/neogit/lib/mappings_manager.lua +++ /dev/null @@ -1,71 +0,0 @@ -local managers = {} - ----@alias Mapping string|function|MappingTable - ----@class MappingTable ----@field [1] string mode ----@field [2] string|function func - ----@class MappingsManager ----@field mappings table ----@field callbacks table ----@field id number ----@field register fun():nil -local MappingsManager = {} -MappingsManager.__index = MappingsManager - -function MappingsManager.invoke(id, map_id) - managers[id].callbacks[map_id]() -end - ----@param id number The id of the manager ----@param index number The index of the map ----@param mode string vim mode from vim.fn.mode() ----@return string -function MappingsManager.build_call_string(id, index, mode) - return string.format( - "lua require('neogit.lib.mappings_manager').invoke(%d, %d)%s", - id, - index, - mode == "v" and "" or "" - ) -end - ----@param id number The id of the manager ----@return nil -function MappingsManager.delete(id) - managers[id] = nil -end - ----@return MappingsManager -function MappingsManager.new(id) - local mappings = { n = {}, v = {}, i = {} } - local callbacks = {} - local map_id = 1 - local manager = { - id = id, - callbacks = callbacks, - mappings = mappings, - register = function() - for mode, mode_mappings in pairs(mappings) do - for k, mapping in pairs(mode_mappings) do - vim.keymap.set( - mode, - k, - MappingsManager.build_call_string(id, map_id, mode), - { buffer = id, nowait = true, silent = true, noremap = true } - ) - - callbacks[map_id] = mapping - map_id = map_id + 1 - end - end - end, - } - - managers[id] = manager - - return manager -end - -return MappingsManager From a6df936a70acdb4b32f0ea484eaf3f13713b7a72 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 28 Apr 2024 21:58:20 +0200 Subject: [PATCH 417/443] Cleanup: Add a table wrap method to simplify callers that might get a table, or a value. --- lua/neogit/lib/buffer.lua | 9 +++------ lua/neogit/lib/util.lua | 6 ++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 070728baa..0caac38f6 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -1,6 +1,7 @@ local api = vim.api local fn = vim.fn local logger = require("neogit.logger") +local util = require("neogit.lib.util") local signs = require("neogit.lib.signs") local Ui = require("neogit.lib.ui") @@ -660,12 +661,8 @@ function Buffer.create(config) local opts = { buffer = buffer.handle, silent = true, nowait = true } - if type(key) == "string" then - vim.keymap.set(mode, key, fn, opts) - elseif type(key) == "table" then - for _, k in ipairs(key) do - vim.keymap.set(mode, k, fn, opts) - end + for _, k in ipairs(util.tbl_wrap(key)) do + vim.keymap.set(mode, k, fn, opts) end end end diff --git a/lua/neogit/lib/util.lua b/lua/neogit/lib/util.lua index 38af89384..949508a5b 100644 --- a/lua/neogit/lib/util.lua +++ b/lua/neogit/lib/util.lua @@ -531,4 +531,10 @@ function M.debounce_trailing(ms, fn, hash) end end +---@param value any +---@return table +function M.tbl_wrap(value) + return type(value) == "table" and value or { value } +end + return M From 9c0a04d5812dd5e876b50adcb26096c62e12373e Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 28 Apr 2024 22:19:35 +0200 Subject: [PATCH 418/443] standardize how we set window options --- lua/neogit/lib/buffer.lua | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 0caac38f6..ca760c392 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -405,6 +405,12 @@ function Buffer:get_option(name) end end +function Buffer:get_window_option(name) + if self.win_handle ~= nil then + return api.nvim_get_option_value(name, { win = self.win_handle }) + end +end + function Buffer:set_buffer_option(name, value) if self.handle ~= nil then api.nvim_set_option_value(name, value, { buf = self.handle }) @@ -620,8 +626,8 @@ function Buffer.create(config) local win if config.open ~= false then - logger.debug("[BUFFER:" .. buffer.handle .. "] Showing buffer in window") win = buffer:show() + logger.debug("[BUFFER:" .. buffer.handle .. "] Showing buffer in window " .. win) end logger.debug("[BUFFER:" .. buffer.handle .. "] Setting buffer options") @@ -682,6 +688,8 @@ function Buffer.create(config) buffer:set_window_option("foldtext", "") buffer:set_window_option("listchars", "") buffer:set_window_option("list", false) + buffer:set_window_option("winhl", buffer:get_window_option("winhl") .. ",Folded:NeogitFold") + buffer:set_window_option("fillchars", buffer:get_window_option("fillchars") .. ",fold: ") if vim.fn.has("nvim-0.10") == 1 then buffer:set_window_option("spell", false) @@ -724,13 +732,6 @@ function Buffer.create(config) }) end - buffer:call(function() - logger.debug("[BUFFER:" .. buffer.handle .. "] Running buffer:call") - -- Set fold styling for Neogit windows while preserving user styling - vim.opt_local.winhl:append("Folded:NeogitFold") - vim.opt_local.fillchars:append("fold: ") - end) - if config.context_highlight then logger.debug("[BUFFER:" .. buffer.handle .. "] Setting up context highlighting") buffer:create_namespace("ViewContext") From 1d021f2d08e5f2eaa5055ffcec9914df7e48a135 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 28 Apr 2024 23:28:03 +0200 Subject: [PATCH 419/443] Use win_exec to set folds so we can update the buffer without it being focused. --- lua/neogit/autocmds.lua | 44 ++++++++++++++++++++------------------ lua/neogit/lib/buffer.lua | 30 ++++++-------------------- lua/neogit/lib/ui/init.lua | 5 ----- 3 files changed, 29 insertions(+), 50 deletions(-) diff --git a/lua/neogit/autocmds.lua b/lua/neogit/autocmds.lua index b0e834c25..14d895014 100644 --- a/lua/neogit/autocmds.lua +++ b/lua/neogit/autocmds.lua @@ -1,6 +1,7 @@ local M = {} local api = vim.api + local a = require("plenary.async") local status_buffer = require("neogit.buffers.status") local git = require("neogit.lib.git") @@ -12,32 +13,33 @@ function M.setup() group = group, }) + local autocmd_disabled = false api.nvim_create_autocmd({ "BufWritePost", "ShellCmdPost", "VimResume" }, { - callback = function(o) - if not status_buffer.instance then - return - end - - -- Do not trigger on neogit buffers such as commit - if api.nvim_get_option_value("filetype", { buf = o.buf }):find("Neogit") then - return - end - - a.run(function() + callback = a.void(function(o) + if + not autocmd_disabled + and status_buffer.is_open() + and not api.nvim_get_option_value("filetype", { buf = o.buf }):match("^Neogit") + then local path = git.files.relpath_from_repository(o.file) - if not path then - return + if path then + status_buffer + .instance() + :dispatch_refresh({ update_diffs = { "*:" .. path } }, string.format("%s:%s", o.event, path)) end + end + end), + group = group, + }) - if status_buffer.is_open() then - status_buffer.instance:dispatch_refresh( - { update_diffs = { "*:" .. path } }, - string.format("%s:%s", o.event, o.file) - ) - end - end, function() end) - end, + --- vimpgrep creates and deletes lots of buffers so attaching to each one will + --- waste lots of resource and even slow down vimgrep. + api.nvim_create_autocmd({ "QuickFixCmdPre", "QuickFixCmdPost" }, { group = group, + pattern = "*vimgrep*", + callback = function(args) + autocmd_disabled = args.event == "QuickFixCmdPre" + end, }) end diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index ca760c392..c65a07879 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -98,9 +98,7 @@ function Buffer:restore_view(view, cursor) end function Buffer:write() - self:call(function() - vim.cmd("silent w!") - end) + self:win_exec("silent w!") end function Buffer:get_lines(first, last, strict) @@ -383,14 +381,14 @@ function Buffer:put(lines, after, follow) end function Buffer:create_fold(first, last, _) - vim.cmd(string.format("%d,%dfold", first, last)) + self:win_exec(string.format("%d,%dfold", first, last)) end function Buffer:set_fold_state(first, last, open) if open then - vim.cmd(string.format("%d,%dfoldopen", first, last)) + self:win_exec(string.format("%d,%dfoldopen", first, last)) else - vim.cmd(string.format("%d,%dfoldclose", first, last)) + self:win_exec(string.format("%d,%dfoldclose", first, last)) end end @@ -429,23 +427,7 @@ end function Buffer:replace_content_with(lines) api.nvim_buf_set_lines(self.handle, 0, -1, false, lines) - self:call(function() - vim.cmd("silent w!") - end) -end - -function Buffer:open_fold(line, reset_pos) - local pos - if reset_pos == true then - pos = fn.getpos() - end - - fn.setpos(".", { self.handle, line, 0, 0 }) - vim.cmd("normal zo") - - if reset_pos == true then - fn.setpos(".", pos) - end + self:write() end function Buffer:add_highlight(line, col_start, col_end, name, namespace) @@ -818,7 +800,7 @@ function Buffer.create(config) if config.cwd then logger.debug("[BUFFER:" .. buffer.handle .. "] Setting CWD to: " .. config.cwd) - vim.cmd.lcd(config.cwd) + buffer:win_exec("lcd " .. config.cwd) end return buffer diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index 4a9d9ef35..bd3cd4ae3 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -621,11 +621,6 @@ function Ui:render(...) end function Ui:update() - -- If the buffer is not focused, trying to set folds will raise an error because it's not a proper API. - if not self.buf:is_focused() then - return - end - -- Copy over the old fold state _before_ buffer is rendered so the output of the fold buffer is correct if self._node_fold_state then self:_update_fold_state(self.layout, self._node_fold_state) From 50904dc3d6ff790bfc64a992964b4e1710db7add Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 30 Apr 2024 08:28:32 +0200 Subject: [PATCH 420/443] Bugfix: Don't filter out non-relative paths from ignore suggestions Bugfix: Remove global scope from config call. Seems to prevent cli from returning anything. With no scope flag it returns the right value. --- lua/neogit/lib/git/config.lua | 2 +- lua/neogit/lib/git/repository.lua | 5 ++++- lua/neogit/popups/ignore/actions.lua | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lua/neogit/lib/git/config.lua b/lua/neogit/lib/git/config.lua index a3b88421d..8a4fbd169 100644 --- a/lua/neogit/lib/git/config.lua +++ b/lua/neogit/lib/git/config.lua @@ -114,7 +114,7 @@ end ---@return ConfigEntry function M.get_global(key) - local result = git.cli.config.global.get(key).call_sync({ ignore_error = true }).stdout[1] + local result = git.cli.config.get(key).call_sync({ ignore_error = true }).stdout[1] return ConfigEntry.new(key, result, "global") end diff --git a/lua/neogit/lib/git/repository.lua b/lua/neogit/lib/git/repository.lua index a3189c29a..a72eecc8b 100644 --- a/lua/neogit/lib/git/repository.lua +++ b/lua/neogit/lib/git/repository.lua @@ -1,6 +1,6 @@ local a = require("plenary.async") local logger = require("neogit.logger") -local Path = require("plenary.path") +local Path = require("plenary.path") ---@class Path local git = require("neogit.lib.git") local modules = { @@ -18,6 +18,8 @@ local modules = { } ---@class NeogitRepo +---@field git_path fun(self, ...):Path +---@field refresh fun(self, table) ---@field initialized boolean ---@field git_root string ---@field head NeogitRepoHead @@ -151,6 +153,7 @@ local function empty_state() } end +---@class NeogitRepo local Repo = {} Repo.__index = Repo diff --git a/lua/neogit/popups/ignore/actions.lua b/lua/neogit/popups/ignore/actions.lua index 2feb9f72d..9084e89bf 100644 --- a/lua/neogit/popups/ignore/actions.lua +++ b/lua/neogit/popups/ignore/actions.lua @@ -13,7 +13,9 @@ local function make_rules(popup, relative) return util.deduplicate(vim.tbl_map(function(v) if vim.startswith(v, relative) then - return "/" .. Path:new(v):make_relative(relative) + return Path:new(v):make_relative(relative) + else + return v end end, files)) end From 183212cf217f4741266f7a767740ca43deacc7b4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 30 Apr 2024 09:13:35 +0200 Subject: [PATCH 421/443] Fix: #1269 --- lua/neogit/lib/buffer.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index c65a07879..0158e4c67 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -670,8 +670,13 @@ function Buffer.create(config) buffer:set_window_option("foldtext", "") buffer:set_window_option("listchars", "") buffer:set_window_option("list", false) - buffer:set_window_option("winhl", buffer:get_window_option("winhl") .. ",Folded:NeogitFold") - buffer:set_window_option("fillchars", buffer:get_window_option("fillchars") .. ",fold: ") + buffer:call(function() + vim.opt_local.winhl:append("Folded:NeogitFold") + vim.opt_local.winhl:append("Normal:NeogitNormal") + vim.opt_local.winhl:append("WinSeparator:NeogitWinSeparator") + vim.opt_local.winhl:append("CursorLineNr:NeogitCursorLineNr") + vim.opt_local.fillchars:append("fold: ") + end) if vim.fn.has("nvim-0.10") == 1 then buffer:set_window_option("spell", false) From ac8cd9dfb3632c8510da00064c3d02ecc4899c56 Mon Sep 17 00:00:00 2001 From: David Mejorado Date: Sat, 4 May 2024 22:53:52 -0700 Subject: [PATCH 422/443] Fix error params in process.lua Instead of concatenating it's passing the inspected object as a second parameter. That's throwing an error when files are staged or branches switched from outside Neogit. --- lua/neogit/process.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index fb59a58f4..01667b18f 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -375,7 +375,7 @@ function Process:spawn(cb) }) if job <= 0 then - error("Failed to start process: ", vim.inspect(self)) + error("Failed to start process: " .. vim.inspect(self)) if cb then cb(nil) end From 058a95704d8d978249313781a24c9b54f3761e53 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 4 May 2024 23:41:32 +0200 Subject: [PATCH 423/443] Extract actions from status buffer so we can call them from the help popup --- lua/neogit/buffers/status/actions.lua | 1226 +++++++++++++++++++++++++ lua/neogit/buffers/status/init.lua | 1095 ++-------------------- lua/neogit/popups/init.lua | 8 +- 3 files changed, 1311 insertions(+), 1018 deletions(-) create mode 100644 lua/neogit/buffers/status/actions.lua diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua new file mode 100644 index 000000000..91de91599 --- /dev/null +++ b/lua/neogit/buffers/status/actions.lua @@ -0,0 +1,1226 @@ +-- NOTE: `v_` prefix stands for visual mode actions, `n_` for normal mode. +-- +local a = require("plenary.async") +local git = require("neogit.lib.git") +local popups = require("neogit.popups") +local Buffer = require("neogit.lib.buffer") +local logger = require("neogit.logger") +local input = require("neogit.lib.input") +local notification = require("neogit.lib.notification") + +local fn = vim.fn +local api = vim.api + +local function cleanup_items(...) + if vim.in_fast_event() then + a.util.scheduler() + end + + for _, item in ipairs { ... } do + local bufnr = fn.bufexists(item.name) + if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then + api.nvim_buf_delete(bufnr, { force = true }) + end + + fn.delete(item.escaped_path) + end +end + +---@param self StatusBuffer +---@param item StatusItem +---@return table|nil +local function translate_cursor_location(self, item) + if rawget(item, "diff") then + local line = self.buffer:cursor_line() + + for _, hunk in ipairs(item.diff.hunks) do + if line >= hunk.first and line <= hunk.last then + local offset = line - hunk.first + local row = hunk.disk_from + offset - 1 + + for i = 1, offset do + -- If the line is a deletion, we need to adjust the row + if string.sub(hunk.lines[i], 1, 1) == "-" then + row = row - 1 + end + end + + return { row, 0 } + end + end + end +end + +local function open(type, path, cursor) + path = fn.fnameescape(path) + vim.cmd[type](path) + + local buf = Buffer.from_name(path) + if buf:is_focused() and cursor then + buf:move_cursor(cursor) + end +end + +local M = {} + +---@param self StatusBuffer +M.v_discard = function(self) + return a.void(function() + local selection = self.buffer.ui:get_selection() + + local discard_message = "Discard selection?" + local hunk_count = 0 + local file_count = 0 + + local patches = {} + local untracked_files = {} + local unstaged_files = {} + local new_files = {} + local staged_files_modified = {} + local stashes = {} + + for _, section in ipairs(selection.sections) do + if section.name == "untracked" or section.name == "unstaged" or section.name == "staged" then + file_count = file_count + #section.items + + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) + + if #hunks > 0 then + logger.debug(("Discarding %d hunks from %q"):format(#hunks, item.name)) + + hunk_count = hunk_count + #hunks + if hunk_count > 1 then + discard_message = ("Discard %s hunks?"):format(hunk_count) + end + + for _, hunk in ipairs(hunks) do + table.insert(patches, function() + local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) + + logger.debug(("Discarding Patch: %s"):format(patch)) + + git.index.apply(patch, { + index = section.name == "staged", + reverse = true, + }) + end) + end + else + discard_message = ("Discard %s files?"):format(file_count) + logger.debug(("Discarding in section %s %s"):format(section.name, item.name)) + + if section.name == "untracked" then + table.insert(untracked_files, item.escaped_path) + elseif section.name == "unstaged" then + if item.mode == "A" then + table.insert(new_files, item.escaped_path) + else + table.insert(unstaged_files, item.escaped_path) + end + elseif section.name == "staged" then + if item.mode == "N" then + table.insert(new_files, item.escaped_path) + else + table.insert(staged_files_modified, item.escaped_path) + end + end + end + end + elseif section.name == "stashes" then + discard_message = ("Discard %s stashes?"):format(#selection.items) + + for _, stash in ipairs(selection.items) do + table.insert(stashes, stash.name:match("(stash@{%d+})")) + end + end + end + + if input.get_permission(discard_message) then + if #patches > 0 then + for _, patch in ipairs(patches) do + patch() + end + end + + if #untracked_files > 0 then + cleanup_items(unpack(untracked_files)) + end + + if #unstaged_files > 0 then + git.index.checkout(unstaged_files) + end + + if #new_files > 0 then + git.index.reset(new_files) + cleanup_items(unpack(new_files)) + end + + if #staged_files_modified > 0 then + git.index.reset(staged_files_modified) + git.index.checkout(staged_files_modified) + end + + if #stashes > 0 then + for _, stash in ipairs(stashes) do + git.stash.drop(stash) + end + end + + self:refresh() + end + end) +end + +---@param self StatusBuffer +M.v_stage = function(self) + return a.void(function() + local selection = self.buffer.ui:get_selection() + + local untracked_files = {} + local unstaged_files = {} + local patches = {} + + for _, section in ipairs(selection.sections) do + if section.name == "unstaged" or section.name == "untracked" then + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) + + if #hunks > 0 then + for _, hunk in ipairs(hunks) do + table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) + end + else + if section.name == "unstaged" then + table.insert(unstaged_files, item.escaped_path) + else + table.insert(untracked_files, item.escaped_path) + end + end + end + end + end + + if #untracked_files > 0 then + git.index.add(untracked_files) + end + + if #unstaged_files > 0 then + git.status.stage(unstaged_files) + end + + if #patches > 0 then + for _, patch in ipairs(patches) do + git.index.apply(patch, { cached = true }) + end + end + + if #untracked_files > 0 or #unstaged_files > 0 or #patches > 0 then + self:refresh() + end + end) +end + +---@param self StatusBuffer +M.v_unstage = function(self) + return a.void(function() + local selection = self.buffer.ui:get_selection() + + local files = {} + local patches = {} + + for _, section in ipairs(selection.sections) do + if section.name == "staged" then + for _, item in ipairs(section.items) do + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) + + if #hunks > 0 then + for _, hunk in ipairs(hunks) do + table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) + end + else + table.insert(files, item.escaped_path) + end + end + end + end + + if #files > 0 then + git.status.unstage(files) + end + + if #patches > 0 then + for _, patch in ipairs(patches) do + git.index.apply(patch, { cached = true, reverse = true }) + end + end + + if #files > 0 or #patches > 0 then + self:refresh { update_diffs = { "staged:*" } } + end + end) +end + +---@param self StatusBuffer +M.v_branch_popup = function(self) + return popups.open("branch", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end) +end + +---@param self StatusBuffer +M.v_cherry_pick_popup = function(self) + return popups.open("cherry_pick", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end) +end + +---@param self StatusBuffer +M.v_commit_popup = function(self) + return popups.open("commit", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end) +end + +---@param self StatusBuffer +M.v_merge_popup = function(self) + return popups.open("merge", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end) +end + +---@param self StatusBuffer +M.v_push_popup = function(self) + return popups.open("push", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end) +end + +---@param self StatusBuffer +M.v_rebase_popup = function(self) + return popups.open("rebase", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end) +end + +---@param self StatusBuffer +M.v_revert_popup = function(self) + return popups.open("revert", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end) +end + +---@param self StatusBuffer +M.v_reset_popup = function(self) + return popups.open("reset", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end) +end + +---@param self StatusBuffer +M.v_tag_popup = function(self) + return popups.open("tag", function(p) + local commits = self.buffer.ui:get_commits_in_selection() + if #commits == 1 then + p { commit = commits[1] } + end + end) +end + +---@param self StatusBuffer +M.v_stash_popup = function(self) + return popups.open("stash", function(p) + local stash = self.buffer.ui:get_yankable_under_cursor() + p { name = stash and stash:match("^stash@{%d+}") } + end) +end + +---@param self StatusBuffer +M.v_diff_popup = function(self) + return popups.open("diff", function(p) + local section = self.buffer.ui:get_selection().section + local item = self.buffer.ui:get_yankable_under_cursor() + p { section = { name = section and section.name }, item = { name = item } } + end) +end + +---@param self StatusBuffer +M.v_ignore_popup = function(self) + return popups.open("ignore", function(p) + p { paths = self.buffer.ui:get_filepaths_in_selection(), git_root = git.repo.git_root } + end) +end + +---@param self StatusBuffer +M.v_bisect_popup = function(self) + return popups.open("bisect", function(p) + p { commits = self.buffer.ui:get_commits_in_selection() } + end) +end + +---@param self StatusBuffer +M.v_remote_popup = function(self) + return popups.open("remote") +end + +---@param self StatusBuffer +M.v_fetch_popup = function(self) + return popups.open("fetch") +end + +---@param self StatusBuffer +M.v_pull_popup = function(self) + return popups.open("pull") +end + +---@param self StatusBuffer +M.v_help_popup = function(self) + return popups.open("help") +end + +---@param self StatusBuffer +M.v_log_popup = function(self) + return popups.open("log") +end + +---@param self StatusBuffer +M.v_worktree_popup = function(self) + return popups.open("worktree") +end + +---@param self StatusBuffer +M.n_down = function(self) + return function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "j") + else + vim.cmd("norm! j") + end + + if self.buffer:get_current_line()[1] == "" then + vim.cmd("norm! j") + end + end +end + +---@param self StatusBuffer +M.n_up = function(self) + return function() + if vim.v.count > 0 then + vim.cmd("norm! " .. vim.v.count .. "k") + else + vim.cmd("norm! k") + end + + if self.buffer:get_current_line()[1] == "" then + vim.cmd("norm! k") + end + end +end + +---@param self StatusBuffer +M.n_toggle = function(self) + return function() + local fold = self.buffer.ui:get_fold_under_cursor() + if fold then + if fold.options.on_open then + fold.options.on_open(fold, self.buffer.ui) + else + local start, _ = fold:row_range_abs() + local ok, _ = pcall(vim.cmd, "normal! za") + if ok then + self.buffer:move_cursor(start) + fold.options.folded = not fold.options.folded + end + end + end + end +end + +---@param self StatusBuffer +M.n_close = function(self) + return require("neogit.lib.ui.helpers").close_topmost(self) +end + +---@param self StatusBuffer +M.n_open_or_scroll_down = function(self) + return function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + require("neogit.buffers.commit_view").open_or_scroll_down(commit) + end + end +end + +---@param self StatusBuffer +M.n_open_or_scroll_up = function(self) + return function() + local commit = self.buffer.ui:get_commit_under_cursor() + if commit then + require("neogit.buffers.commit_view").open_or_scroll_up(commit) + end + end +end + +---@param self StatusBuffer +M.n_refresh_buffer = function(self) + return a.void(function() + self:refresh() + end) +end + +---@param self StatusBuffer +M.n_depth1 = function(self) + return function() + local section = self.buffer.ui:get_current_section() + if section then + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + + self.buffer:move_cursor(start) + section:close_all_folds(self.buffer.ui) + + self.buffer.ui:update() + end + end +end + +---@param self StatusBuffer +M.n_depth2 = function(self) + return function() + local section = self.buffer.ui:get_current_section() + local row = self.buffer.ui:get_component_under_cursor() + + if section then + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + + self.buffer:move_cursor(start) + + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 1) + + self.buffer.ui:update() + + if row then + local start, _ = row:row_range_abs() + self.buffer:move_cursor(start) + end + end + end +end + +---@param self StatusBuffer +M.n_depth3 = function(self) + return function() + local section = self.buffer.ui:get_current_section() + local context = self.buffer.ui:get_cursor_context() + + if section then + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + + self.buffer:move_cursor(start) + + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 2) + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 2) + + self.buffer.ui:update() + + if context then + local start, _ = context:row_range_abs() + self.buffer:move_cursor(start) + end + end + end +end + +---@param self StatusBuffer +M.n_depth4 = function(self) + return function() + local section = self.buffer.ui:get_current_section() + local context = self.buffer.ui:get_cursor_context() + + if section then + local start, last = section:row_range_abs() + if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then + return + end + + self.buffer:move_cursor(start) + section:close_all_folds(self.buffer.ui) + section:open_all_folds(self.buffer.ui, 3) + + self.buffer.ui:update() + + if context then + local start, _ = context:row_range_abs() + self.buffer:move_cursor(start) + end + end + end +end + +---@param self StatusBuffer +M.n_command_history = function(self) + return a.void(function() + require("neogit.buffers.git_command_history"):new():show() + end) +end + +---@param self StatusBuffer +M.n_console = function(self) + return function() + require("neogit.process").show_console() + end +end + +---@param self StatusBuffer +M.n_show_refs = function(self) + return a.void(function() + require("neogit.buffers.refs_view").new(git.refs.list_parsed()):open() + end) +end + +---@param self StatusBuffer +M.n_yank_selected = function(self) + return function() + local yank = self.buffer.ui:get_yankable_under_cursor() + if yank then + if yank:match("^stash@{%d+}") then + yank = git.rev_parse.oid(yank:match("^(stash@{%d+})")) + end + + yank = string.format("'%s'", yank) + vim.cmd.let("@+=" .. yank) + vim.cmd.echo(yank) + else + vim.cmd("echo ''") + end + end +end + +---@param self StatusBuffer +M.n_discard = function(self) + return a.void(function() + git.index.update() + + local selection = self.buffer.ui:get_selection() + if not selection.section then + return + end + + local section = selection.section.name + local action, message, choices + local refresh = {} + + if selection.item and selection.item.first == fn.line(".") then -- Discard File + if section == "untracked" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + cleanup_items(selection.item) + end + refresh = { update_diffs = { "untracked:" .. selection.item.name } } + elseif section == "unstaged" then + if selection.item.mode:match("^[UA][UA]") then + choices = { "&ours", "&theirs", "&conflict", "&abort" } + action = function() + local choice = + input.get_choice("Discard conflict by taking...", { values = choices, default = #choices }) + + if choice == "o" then + git.cli.checkout.ours.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + elseif choice == "t" then + git.cli.checkout.theirs.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + elseif choice == "c" then + git.cli.checkout.merge.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + end + end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } + else + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "A" then + git.index.reset { selection.item.escaped_path } + cleanup_items(selection.item) + else + git.index.checkout { selection.item.name } + end + end + end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } + elseif section == "staged" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "N" then + git.index.reset { selection.item.escaped_path } + cleanup_items(selection.item) + elseif selection.item.mode == "M" then + git.index.reset { selection.item.escaped_path } + git.index.checkout { selection.item.escaped_path } + elseif selection.item.mode == "R" then + git.index.reset_HEAD(selection.item.name, selection.item.original_name) + git.index.checkout { selection.item.original_name } + cleanup_items(selection.item) + elseif selection.item.mode == "D" then + git.index.reset_HEAD(selection.item.escaped_path) + git.index.checkout { selection.item.escaped_path } + else + error(("Unhandled file mode %q for %q"):format(selection.item.mode, selection.item.escaped_path)) + end + end + refresh = { update_diffs = { "staged:" .. selection.item.name } } + elseif section == "stashes" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + git.stash.drop(selection.item.name:match("(stash@{%d+})")) + end + refresh = {} + end + elseif selection.item then -- Discard Hunk + if selection.item.mode == "UU" then + -- TODO: https://github.com/emacs-mirror/emacs/blob/master/lisp/vc/smerge-mode.el + notification.warn("Resolve conflicts in file before discarding hunks.") + return + end + + local hunk = + self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] + + local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) + + if section == "untracked" then + message = "Discard hunk?" + action = function() + local hunks = + self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) + + local patch = git.index.generate_patch(selection.item, hunks[1], hunks[1].from, hunks[1].to, true) + + git.index.apply(patch, { reverse = true }) + git.index.apply(patch, { reverse = true }) + end + refresh = { update_diffs = { "untracked:" .. selection.item.name } } + elseif section == "unstaged" then + message = "Discard hunk?" + action = function() + git.index.apply(patch, { reverse = true }) + end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } + elseif section == "staged" then + message = "Discard hunk?" + action = function() + git.index.apply(patch, { index = true, reverse = true }) + end + refresh = { update_diffs = { "staged:" .. selection.item.name } } + end + else -- Discard Section + if section == "untracked" then + message = ("Discard %s files?"):format(#selection.section.items) + action = function() + cleanup_items(unpack(selection.section.items)) + end + refresh = { update_diffs = { "untracked:*" } } + elseif section == "unstaged" then + local conflict = false + for _, item in ipairs(selection.section.items) do + if item.mode == "UU" then + conflict = true + break + end + end + + if conflict then + -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#Lair + notification.warn("Resolve conflicts before discarding section.") + return + else + message = ("Discard %s files?"):format(#selection.section.items) + action = function() + git.index.checkout_unstaged() + end + refresh = { update_diffs = { "unstaged:*" } } + end + elseif section == "staged" then + message = ("Discard %s files?"):format(#selection.section.items) + action = function() + local new_files = {} + local staged_files_modified = {} + local staged_files_renamed = {} + local staged_files_deleted = {} + + for _, item in ipairs(selection.section.items) do + if item.mode == "N" or item.mode == "A" then + table.insert(new_files, item.escaped_path) + elseif item.mode == "M" then + table.insert(staged_files_modified, item.escaped_path) + elseif item.mode == "R" then + table.insert(staged_files_renamed, item) + elseif item.mode == "D" then + table.insert(staged_files_deleted, item.escaped_path) + else + error(("Unknown file mode %q for %q"):format(item.mode, item.escaped_path)) + end + end + + if #new_files > 0 then + -- ensure the file is deleted + git.index.reset(new_files) + cleanup_items(unpack(new_files)) + end + + if #staged_files_modified > 0 then + git.index.reset(staged_files_modified) + git.index.checkout(staged_files_modified) + end + + if #staged_files_renamed > 0 then + for _, item in ipairs(staged_files_renamed) do + git.index.reset_HEAD(item.name, item.original_name) + git.index.checkout { item.original_name } + fn.delete(item.escaped_path) + end + end + + if #staged_files_deleted > 0 then + git.index.reset_HEAD(unpack(staged_files_deleted)) + git.index.checkout(staged_files_deleted) + end + end + refresh = { update_diffs = { "staged:*" } } + elseif section == "stashes" then + message = ("Discard %s stashes?"):format(#selection.section.items) + action = function() + for _, stash in ipairs(selection.section.items) do + git.stash.drop(stash.name:match("(stash@{%d+})")) + end + end + end + end + + if action and (choices or input.get_permission(message)) then + action() + self:refresh(refresh) + end + end) +end + +---@param self StatusBuffer +M.n_go_to_next_hunk_header = function(self) + return function() + local c = self.buffer.ui:get_component_under_cursor(function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "Item" + end) + local section = self.buffer.ui:get_current_section() + + if c and section then + local _, section_last = section:row_range_abs() + local next_location + + if c.options.tag == "Diff" then + next_location = fn.line(".") + 1 + elseif c.options.tag == "Item" then + vim.cmd("normal! zo") + next_location = fn.line(".") + 1 + elseif c.options.tag == "Hunk" then + local _, last = c:row_range_abs() + next_location = last + 1 + end + + if next_location < section_last then + self.buffer:move_cursor(next_location) + end + + vim.cmd("normal! zt") + end + end +end + +---@param self StatusBuffer +M.n_go_to_previous_hunk_header = function(self) + return function() + local function previous_hunk_header(self, line) + local c = self.buffer.ui:get_component_on_line(line, function(c) + return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "Item" + end) + + if c then + local first, _ = c:row_range_abs() + if fn.line(".") == first then + first = previous_hunk_header(self, line - 1) + end + + return first + end + end + + local previous_header = previous_hunk_header(self, fn.line(".")) + if previous_header then + self.buffer:move_cursor(previous_header) + vim.cmd("normal! zt") + end + end +end + +---@param self StatusBuffer +M.n_init_repo = function(self) + return function() + git.init.init_repo() + end +end + +---@param self StatusBuffer +M.n_stage = function(self) + return a.void(function() + local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + local section = self.buffer.ui:get_current_section() + + if stagable and section then + if section.options.section == "staged" then + return + end + + if stagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + assert(item, "Item cannot be nil") + local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) + + git.index.apply(patch, { cached = true }) + self:refresh { update_diffs = { "*:" .. item.escaped_path } } + elseif stagable.filename then + if section.options.section == "unstaged" then + git.status.stage { stagable.filename } + self:refresh { update_diffs = { "unstaged:" .. stagable.filename } } + elseif section.options.section == "untracked" then + git.index.add { stagable.filename } + self:refresh { update_diffs = { "untracked:" .. stagable.filename } } + end + end + elseif section then + if section.options.section == "untracked" then + git.status.stage_untracked() + self:refresh { update_diffs = { "untracked:*" } } + elseif section.options.section == "unstaged" then + git.status.stage_modified() + self:refresh { update_diffs = { "unstaged:*" } } + end + end + end) +end + +---@param self StatusBuffer +M.n_stage_all = function(self) + return a.void(function() + git.status.stage_all() + self:refresh() + end) +end + +---@param self StatusBuffer +M.n_stage_unstaged = function(self) + return a.void(function() + git.status.stage_modified() + self:refresh { update_diffs = { "unstaged:*" } } + end) +end + +---@param self StatusBuffer +M.n_unstage = function(self) + return a.void(function() + local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() + + local section = self.buffer.ui:get_current_section() + if section and section.options.section ~= "staged" then + return + end + + if unstagable then + if unstagable.hunk then + local item = self.buffer.ui:get_item_under_cursor() + assert(item, "Item cannot be nil") + local patch = + git.index.generate_patch(item, unstagable.hunk, unstagable.hunk.from, unstagable.hunk.to, true) + + git.index.apply(patch, { cached = true, reverse = true }) + self:refresh { update_diffs = { "*:" .. item.escaped_path } } + elseif unstagable.filename then + git.status.unstage { unstagable.filename } + self:refresh { update_diffs = { "*:" .. unstagable.filename } } + end + elseif section then + git.status.unstage_all() + self:refresh { update_diffs = { "staged:*" } } + end + end) +end + +---@param self StatusBuffer +M.n_unstage_staged = function(self) + return a.void(function() + git.status.unstage_all() + self:refresh { update_diffs = { "staged:*" } } + end) +end + +---@param self StatusBuffer +M.n_goto_file = function(self) + return function() + local item = self.buffer.ui:get_item_under_cursor() + + -- Goto FILE + if item and item.absolute_path then + local cursor = translate_cursor_location(self, item) + self:close() + + -- TODO: Does this work? + vim.schedule(function() + vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) + + local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) + if buf:is_focused() and cursor then + buf:move_cursor(cursor) + end + end) + + return + end + + -- Goto COMMIT + local ref = self.buffer.ui:get_yankable_under_cursor() + if ref then + require("neogit.buffers.commit_view").new(ref):open() + end + end +end + +---@param self StatusBuffer +M.n_tab_open = function(self) + return function() + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.absolute_path then + open("tabedit", item.absolute_path, translate_cursor_location(self, item)) + end + end +end + +---@param self StatusBuffer +M.n_split_open = function(self) + return function() + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.absolute_path then + open("split", item.absolute_path, translate_cursor_location(self, item)) + end + end +end + +---@param self StatusBuffer +M.n_vertical_split_open = function(self) + return function() + local item = self.buffer.ui:get_item_under_cursor() + + if item and item.absolute_path then + open("vsplit", item.absolute_path, translate_cursor_location(self, item)) + end + end +end + +---@param self StatusBuffer +M.n_branch_popup = function(self) + return popups.open("branch", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end) +end + +---@param self StatusBuffer +M.n_bisect_popup = function(self) + return popups.open("bisect", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end) +end + +---@param self StatusBuffer +M.n_cherry_pick_popup = function(self) + return popups.open("cherry_pick", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end) +end + +---@param self StatusBuffer +M.n_commit_popup = function(self) + return popups.open("commit", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end) +end + +---@param self StatusBuffer +M.n_merge_popup = function(self) + return popups.open("merge", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end) +end + +---@param self StatusBuffer +M.n_push_popup = function(self) + return popups.open("push", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end) +end + +---@param self StatusBuffer +M.n_rebase_popup = function(self) + return popups.open("rebase", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end) +end + +---@param self StatusBuffer +M.n_revert_popup = function(self) + return popups.open("revert", function(p) + p { commits = { self.buffer.ui:get_commit_under_cursor() } } + end) +end + +---@param self StatusBuffer +M.n_reset_popup = function(self) + return popups.open("reset", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end) +end + +---@param self StatusBuffer +M.n_tag_popup = function(self) + return popups.open("tag", function(p) + p { commit = self.buffer.ui:get_commit_under_cursor() } + end) +end + +---@param self StatusBuffer +M.n_stash_popup = function(self) + return popups.open("stash", function(p) + local stash = self.buffer.ui:get_yankable_under_cursor() + p { name = stash and stash:match("^stash@{%d+}") } + end) +end + +---@param self StatusBuffer +M.n_diff_popup = function(self) + return popups.open("diff", function(p) + local section = self.buffer.ui:get_selection().section + local item = self.buffer.ui:get_yankable_under_cursor() + p { + section = { name = section and section.name }, + item = { name = item }, + } + end) +end + +---@param self StatusBuffer +M.n_ignore_popup = function(self) + return popups.open("ignore", function(p) + local path = self.buffer.ui:get_hunk_or_filename_under_cursor() + p { + paths = { path and path.escaped_path }, + git_root = git.repo.git_root, + } + end) +end + +---@param self StatusBuffer +M.n_help_popup = function(self) + return popups.open("help", function(p) + -- Since any other popup can be launched from help, build an ENV for any of them. + local path = self.buffer.ui:get_hunk_or_filename_under_cursor() + local section = self.buffer.ui:get_selection().section + if section then + section = section.name + end + + local item = self.buffer.ui:get_yankable_under_cursor() + local stash = self.buffer.ui:get_yankable_under_cursor() + local commit = self.buffer.ui:get_commit_under_cursor() + local commits = { commit } + + -- TODO: Pass selection here so we can stage/unstage etc stuff + p { + branch = { commits = commits }, + cherry_pick = { commits = commits }, + commit = { commit = commit }, + merge = { commit = commit }, + push = { commit = commit }, + rebase = { commit = commit }, + revert = { commits = commits }, + bisect = { commits = commits }, + reset = { commit = commit }, + tag = { commit = commit }, + stash = { name = stash and stash:match("^stash@{%d+}") }, + diff = { + section = { name = section }, + item = { name = item }, + }, + ignore = { + paths = { path and path.escaped_path }, + git_root = git.repo.git_root, + }, + remote = {}, + fetch = {}, + pull = {}, + log = {}, + worktree = {}, + } + end) +end + +---@param self StatusBuffer +M.n_remote_popup = function(self) + return popups.open("remote") +end + +---@param self StatusBuffer +M.n_fetch_popup = function(self) + return popups.open("fetch") +end + +---@param self StatusBuffer +M.n_pull_popup = function(self) + return popups.open("pull") +end + +---@param self StatusBuffer +M.n_log_popup = function(self) + return popups.open("log") +end + +---@param self StatusBuffer +M.n_worktree_popup = function(self) + return popups.open("worktree") +end + +return M diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 83a1bfb03..c12c198a9 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -1,14 +1,3 @@ --- TODO: When launching the fuzzy finder, any refresh attempted will raise an exception because the set_folds() function --- cannot be called when the buffer is not focused, as it's not a proper API. We could implement some kind of freeze --- mechanism to prevent the buffer from refreshing while the fuzzy finder is open. --- function M:freeze() --- self.frozen = true --- end --- --- function M:unfreeze() --- self.frozen = false --- end - local config = require("neogit.config") local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.status.ui") @@ -29,7 +18,7 @@ local function cleanup_items(...) a.util.scheduler() end - for _, item in ipairs({ ... }) do + for _, item in ipairs { ... } do local bufnr = fn.bufexists(item.name) if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then api.nvim_buf_delete(bufnr, { force = true }) @@ -47,7 +36,6 @@ end ---@field buffer Buffer instance ---@field state NeogitRepo ---@field config NeogitConfig ----@field frozen boolean ---@field root string ---@field refresh_lock Semaphore local M = {} @@ -71,7 +59,6 @@ end ---@return StatusBuffer function M.new(state, config, root) local instance = { - -- frozen = false, state = state, config = config, root = root, @@ -90,6 +77,13 @@ function M.is_open() return (M.instance() and M.instance().buffer and M.instance().buffer:is_visible()) == true end +function M:_action(name) + local action = require("neogit.buffers.status.actions")[name] + assert(action, ("Status Buffer action %q is undefined"):format(name)) + + return action(self) +end + ---@param kind string<"floating" | "split" | "tab" | "split" | "vsplit">|nil ---@param cwd string ---@return StatusBuffer @@ -119,1010 +113,83 @@ function M:open(kind, cwd) vim.o.autochdir = self.prev_autochdir end, + --stylua: ignore start mappings = { v = { - [mappings["Discard"]] = a.void(function() - local selection = self.buffer.ui:get_selection() - - local discard_message = "Discard selection?" - local hunk_count = 0 - local file_count = 0 - - local patches = {} - local untracked_files = {} - local unstaged_files = {} - local new_files = {} - local staged_files_modified = {} - local stashes = {} - - for _, section in ipairs(selection.sections) do - if section.name == "untracked" or section.name == "unstaged" or section.name == "staged" then - file_count = file_count + #section.items - - for _, item in ipairs(section.items) do - local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) - - if #hunks > 0 then - logger.debug(("Discarding %d hunks from %q"):format(#hunks, item.name)) - - hunk_count = hunk_count + #hunks - if hunk_count > 1 then - discard_message = ("Discard %s hunks?"):format(hunk_count) - end - - for _, hunk in ipairs(hunks) do - table.insert(patches, function() - local patch = git.index.generate_patch(item, hunk, hunk.from, hunk.to, true) - - logger.debug(("Discarding Patch: %s"):format(patch)) - - git.index.apply(patch, { - index = section.name == "staged", - reverse = true, - }) - end) - end - else - discard_message = ("Discard %s files?"):format(file_count) - logger.debug(("Discarding in section %s %s"):format(section.name, item.name)) - - if section.name == "untracked" then - table.insert(untracked_files, item.escaped_path) - elseif section.name == "unstaged" then - if item.mode == "A" then - table.insert(new_files, item.escaped_path) - else - table.insert(unstaged_files, item.escaped_path) - end - elseif section.name == "staged" then - if item.mode == "N" then - table.insert(new_files, item.escaped_path) - else - table.insert(staged_files_modified, item.escaped_path) - end - end - end - end - elseif section.name == "stashes" then - discard_message = ("Discard %s stashes?"):format(#selection.items) - - for _, stash in ipairs(selection.items) do - table.insert(stashes, stash.name:match("(stash@{%d+})")) - end - end - end - - if input.get_permission(discard_message) then - if #patches > 0 then - for _, patch in ipairs(patches) do - patch() - end - end - - if #untracked_files > 0 then - cleanup_items(unpack(untracked_files)) - end - - if #unstaged_files > 0 then - git.index.checkout(unstaged_files) - end - - if #new_files > 0 then - git.index.reset(new_files) - cleanup_items(unpack(new_files)) - end - - if #staged_files_modified > 0 then - git.index.reset(staged_files_modified) - git.index.checkout(staged_files_modified) - end - - if #stashes > 0 then - for _, stash in ipairs(stashes) do - git.stash.drop(stash) - end - end - - self:refresh() - end - end), - [mappings["Stage"]] = a.void(function() - local selection = self.buffer.ui:get_selection() - - local untracked_files = {} - local unstaged_files = {} - local patches = {} - - for _, section in ipairs(selection.sections) do - if section.name == "unstaged" or section.name == "untracked" then - for _, item in ipairs(section.items) do - local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) - - if #hunks > 0 then - for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) - end - else - if section.name == "unstaged" then - table.insert(unstaged_files, item.escaped_path) - else - table.insert(untracked_files, item.escaped_path) - end - end - end - end - end - - if #untracked_files > 0 then - git.index.add(untracked_files) - end - - if #unstaged_files > 0 then - git.status.stage(unstaged_files) - end - - if #patches > 0 then - for _, patch in ipairs(patches) do - git.index.apply(patch, { cached = true }) - end - end - - if #untracked_files > 0 or #unstaged_files > 0 or #patches > 0 then - self:refresh() - end - end), - [mappings["Unstage"]] = a.void(function() - local selection = self.buffer.ui:get_selection() - - local files = {} - local patches = {} - - for _, section in ipairs(selection.sections) do - if section.name == "staged" then - for _, item in ipairs(section.items) do - local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) - - if #hunks > 0 then - for _, hunk in ipairs(hunks) do - table.insert(patches, git.index.generate_patch(item, hunk, hunk.from, hunk.to)) - end - else - table.insert(files, item.escaped_path) - end - end - end - end - - if #files > 0 then - git.status.unstage(files) - end - - if #patches > 0 then - for _, patch in ipairs(patches) do - git.index.apply(patch, { cached = true, reverse = true }) - end - end - - if #files > 0 or #patches > 0 then - self:refresh { update_diffs = { "staged:*" } } - end - end), - [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) - p { commits = self.buffer.ui:get_commits_in_selection() } - end), - [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) - p { commits = self.buffer.ui:get_commits_in_selection() } - end), - [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) - local commits = self.buffer.ui:get_commits_in_selection() - if #commits == 1 then - p { commit = commits[1] } - end - end), - [popups.mapping_for("MergePopup")] = popups.open("merge", function(p) - local commits = self.buffer.ui:get_commits_in_selection() - if #commits == 1 then - p { commit = commits[1] } - end - end), - [popups.mapping_for("PushPopup")] = popups.open("push", function(p) - local commits = self.buffer.ui:get_commits_in_selection() - if #commits == 1 then - p { commit = commits[1] } - end - end), - [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) - local commits = self.buffer.ui:get_commits_in_selection() - if #commits == 1 then - p { commit = commits[1] } - end - end), - [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = self.buffer.ui:get_commits_in_selection() } - end), - [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) - local commits = self.buffer.ui:get_commits_in_selection() - if #commits == 1 then - p { commit = commits[1] } - end - end), - [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) - local commits = self.buffer.ui:get_commits_in_selection() - if #commits == 1 then - p { commit = commits[1] } - end - end), - [popups.mapping_for("StashPopup")] = popups.open("stash", function(p) - local stash = self.buffer.ui:get_yankable_under_cursor() - p { name = stash and stash:match("^stash@{%d+}") } - end), - [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local section = self.buffer.ui:get_selection().section - local item = self.buffer.ui:get_yankable_under_cursor() - p { section = { name = section and section.name }, item = { name = item } } - end), - [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) - p { paths = self.buffer.ui:get_filepaths_in_selection(), git_root = git.repo.git_root } - end), - [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) - p { commits = self.buffer.ui:get_commits_in_selection() } - end), - [popups.mapping_for("RemotePopup")] = popups.open("remote"), - [popups.mapping_for("FetchPopup")] = popups.open("fetch"), - [popups.mapping_for("PullPopup")] = popups.open("pull"), - [popups.mapping_for("HelpPopup")] = popups.open("help"), - [popups.mapping_for("LogPopup")] = popups.open("log"), - [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), + [mappings["Discard"]] = self:_action("v_discard"), + [mappings["Stage"]] = self:_action("v_stage"), + [mappings["Unstage"]] = self:_action("v_unstage"), + [popups.mapping_for("BisectPopup")] = self:_action("v_bisect_popup"), + [popups.mapping_for("BranchPopup")] = self:_action("v_branch_popup"), + [popups.mapping_for("CherryPickPopup")] = self:_action("v_cherry_pick_popup"), + [popups.mapping_for("CommitPopup")] = self:_action("v_commit_popup"), + [popups.mapping_for("DiffPopup")] = self:_action("v_diff_popup"), + [popups.mapping_for("FetchPopup")] = self:_action("v_fetch_popup"), + [popups.mapping_for("HelpPopup")] = self:_action("v_help_popup"), + [popups.mapping_for("IgnorePopup")] = self:_action("v_ignore_popup"), + [popups.mapping_for("LogPopup")] = self:_action("v_log_popup"), + [popups.mapping_for("MergePopup")] = self:_action("v_merge_popup"), + [popups.mapping_for("PullPopup")] = self:_action("v_pull_popup"), + [popups.mapping_for("PushPopup")] = self:_action("v_push_popup"), + [popups.mapping_for("RebasePopup")] = self:_action("v_rebase_popup"), + [popups.mapping_for("RemotePopup")] = self:_action("v_remote_popup"), + [popups.mapping_for("ResetPopup")] = self:_action("v_reset_popup"), + [popups.mapping_for("RevertPopup")] = self:_action("v_revert_popup"), + [popups.mapping_for("StashPopup")] = self:_action("v_stash_popup"), + [popups.mapping_for("TagPopup")] = self:_action("v_tag_popup"), + [popups.mapping_for("WorktreePopup")] = self:_action("v_worktree_popup"), }, n = { - ["j"] = function() - if vim.v.count > 0 then - vim.cmd("norm! " .. vim.v.count .. "j") - else - vim.cmd("norm! j") - end - - if self.buffer:get_current_line()[1] == "" then - vim.cmd("norm! j") - end - end, - ["k"] = function() - if vim.v.count > 0 then - vim.cmd("norm! " .. vim.v.count .. "k") - else - vim.cmd("norm! k") - end - - if self.buffer:get_current_line()[1] == "" then - vim.cmd("norm! k") - end - end, - [mappings["Toggle"]] = function() - local fold = self.buffer.ui:get_fold_under_cursor() - if fold then - if fold.options.on_open then - fold.options.on_open(fold, self.buffer.ui) - else - local start, _ = fold:row_range_abs() - local ok, _ = pcall(vim.cmd, "normal! za") - if ok then - self.buffer:move_cursor(start) - fold.options.folded = not fold.options.folded - end - end - end - end, - [mappings["Close"]] = require("neogit.lib.ui.helpers").close_topmost(self), - [mappings["OpenOrScrollDown"]] = function() - local commit = self.buffer.ui:get_commit_under_cursor() - if commit then - require("neogit.buffers.commit_view").open_or_scroll_down(commit) - end - end, - [mappings["OpenOrScrollUp"]] = function() - local commit = self.buffer.ui:get_commit_under_cursor() - if commit then - require("neogit.buffers.commit_view").open_or_scroll_up(commit) - end - end, - [mappings["RefreshBuffer"]] = a.void(function() - self:refresh() - end), - [mappings["Depth1"]] = function() - local section = self.buffer.ui:get_current_section() - if section then - local start, last = section:row_range_abs() - if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then - return - end - - self.buffer:move_cursor(start) - section:close_all_folds(self.buffer.ui) - - self.buffer.ui:update() - end - end, - [mappings["Depth2"]] = function() - local section = self.buffer.ui:get_current_section() - local row = self.buffer.ui:get_component_under_cursor() - - if section then - local start, last = section:row_range_abs() - if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then - return - end - - self.buffer:move_cursor(start) - - section:close_all_folds(self.buffer.ui) - section:open_all_folds(self.buffer.ui, 1) - - self.buffer.ui:update() - - if row then - local start, _ = row:row_range_abs() - self.buffer:move_cursor(start) - end - end - end, - [mappings["Depth3"]] = function() - local section = self.buffer.ui:get_current_section() - local context = self.buffer.ui:get_cursor_context() - - if section then - local start, last = section:row_range_abs() - if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then - return - end - - self.buffer:move_cursor(start) - - section:close_all_folds(self.buffer.ui) - section:open_all_folds(self.buffer.ui, 2) - section:close_all_folds(self.buffer.ui) - section:open_all_folds(self.buffer.ui, 2) - - self.buffer.ui:update() - - if context then - local start, _ = context:row_range_abs() - self.buffer:move_cursor(start) - end - end - end, - [mappings["Depth4"]] = function() - local section = self.buffer.ui:get_current_section() - local context = self.buffer.ui:get_cursor_context() - - if section then - local start, last = section:row_range_abs() - if self.buffer:cursor_line() < start or self.buffer:cursor_line() >= last then - return - end - - self.buffer:move_cursor(start) - section:close_all_folds(self.buffer.ui) - section:open_all_folds(self.buffer.ui, 3) - - self.buffer.ui:update() - - if context then - local start, _ = context:row_range_abs() - self.buffer:move_cursor(start) - end - end - end, - [mappings["CommandHistory"]] = a.void(function() - require("neogit.buffers.git_command_history"):new():show() - end), - [mappings["Console"]] = function() - require("neogit.process").show_console() - end, - [mappings["ShowRefs"]] = a.void(function() - require("neogit.buffers.refs_view").new(git.refs.list_parsed()):open() - end), - [mappings["YankSelected"]] = function() - local yank = self.buffer.ui:get_yankable_under_cursor() - if yank then - if yank:match("^stash@{%d+}") then - yank = git.rev_parse.oid(yank:match("^(stash@{%d+})")) - end - - yank = string.format("'%s'", yank) - vim.cmd.let("@+=" .. yank) - vim.cmd.echo(yank) - else - vim.cmd("echo ''") - end - end, - [mappings["Discard"]] = a.void(function() - git.index.update() - - local selection = self.buffer.ui:get_selection() - if not selection.section then - return - end - - local section = selection.section.name - local action, message, choices - local refresh = {} - - if selection.item and selection.item.first == fn.line(".") then -- Discard File - if section == "untracked" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - cleanup_items(selection.item) - end - refresh = { update_diffs = { "untracked:" .. selection.item.name } } - elseif section == "unstaged" then - if selection.item.mode:match("^[UA][UA]") then - choices = { "&ours", "&theirs", "&conflict", "&abort" } - action = function() - local choice = input.get_choice( - "Discard conflict by taking...", - { values = choices, default = #choices } - ) - - if choice == "o" then - git.cli.checkout.ours.files(selection.item.absolute_path).call_sync() - git.status.stage { selection.item.name } - elseif choice == "t" then - git.cli.checkout.theirs.files(selection.item.absolute_path).call_sync() - git.status.stage { selection.item.name } - elseif choice == "c" then - git.cli.checkout.merge.files(selection.item.absolute_path).call_sync() - git.status.stage { selection.item.name } - end - end - refresh = { update_diffs = { "unstaged:" .. selection.item.name } } - else - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode == "A" then - git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) - else - git.index.checkout { selection.item.name } - end - end - end - refresh = { update_diffs = { "unstaged:" .. selection.item.name } } - elseif section == "staged" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode == "N" then - git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) - elseif selection.item.mode == "M" then - git.index.reset { selection.item.escaped_path } - git.index.checkout { selection.item.escaped_path } - elseif selection.item.mode == "R" then - git.index.reset_HEAD(selection.item.name, selection.item.original_name) - git.index.checkout { selection.item.original_name } - cleanup_items(selection.item) - elseif selection.item.mode == "D" then - git.index.reset_HEAD(selection.item.escaped_path) - git.index.checkout { selection.item.escaped_path } - else - error( - ("Unhandled file mode %q for %q"):format(selection.item.mode, selection.item.escaped_path) - ) - end - end - refresh = { update_diffs = { "staged:" .. selection.item.name } } - elseif section == "stashes" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - git.stash.drop(selection.item.name:match("(stash@{%d+})")) - end - refresh = {} - end - elseif selection.item then -- Discard Hunk - if selection.item.mode == "UU" then - -- TODO: https://github.com/emacs-mirror/emacs/blob/master/lisp/vc/smerge-mode.el - notification.warn("Resolve conflicts in file before discarding hunks.") - return - end - - local hunk = - self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false)[1] - - local patch = git.index.generate_patch(selection.item, hunk, hunk.from, hunk.to, true) - - if section == "untracked" then - message = "Discard hunk?" - action = function() - local hunks = - self.buffer.ui:item_hunks(selection.item, selection.first_line, selection.last_line, false) - - local patch = - git.index.generate_patch(selection.item, hunks[1], hunks[1].from, hunks[1].to, true) - - git.index.apply(patch, { reverse = true }) - git.index.apply(patch, { reverse = true }) - end - refresh = { update_diffs = { "untracked:" .. selection.item.name } } - elseif section == "unstaged" then - message = "Discard hunk?" - action = function() - git.index.apply(patch, { reverse = true }) - end - refresh = { update_diffs = { "unstaged:" .. selection.item.name } } - elseif section == "staged" then - message = "Discard hunk?" - action = function() - git.index.apply(patch, { index = true, reverse = true }) - end - refresh = { update_diffs = { "staged:" .. selection.item.name } } - end - else -- Discard Section - if section == "untracked" then - message = ("Discard %s files?"):format(#selection.section.items) - action = function() - cleanup_items(unpack(selection.section.items)) - end - refresh = { update_diffs = { "untracked:*" } } - elseif section == "unstaged" then - local conflict = false - for _, item in ipairs(selection.section.items) do - if item.mode == "UU" then - conflict = true - break - end - end - - if conflict then - -- TODO: https://github.com/magit/magit/blob/28bcd29db547ab73002fb81b05579e4a2e90f048/lisp/magit-apply.el#Lair - notification.warn("Resolve conflicts before discarding section.") - return - else - message = ("Discard %s files?"):format(#selection.section.items) - action = function() - git.index.checkout_unstaged() - end - refresh = { update_diffs = { "unstaged:*" } } - end - elseif section == "staged" then - message = ("Discard %s files?"):format(#selection.section.items) - action = function() - local new_files = {} - local staged_files_modified = {} - local staged_files_renamed = {} - local staged_files_deleted = {} - - for _, item in ipairs(selection.section.items) do - if item.mode == "N" or item.mode == "A" then - table.insert(new_files, item.escaped_path) - elseif item.mode == "M" then - table.insert(staged_files_modified, item.escaped_path) - elseif item.mode == "R" then - table.insert(staged_files_renamed, item) - elseif item.mode == "D" then - table.insert(staged_files_deleted, item.escaped_path) - else - error(("Unknown file mode %q for %q"):format(item.mode, item.escaped_path)) - end - end - - if #new_files > 0 then - -- ensure the file is deleted - git.index.reset(new_files) - cleanup_items(unpack(new_files)) - end - - if #staged_files_modified > 0 then - git.index.reset(staged_files_modified) - git.index.checkout(staged_files_modified) - end - - if #staged_files_renamed > 0 then - for _, item in ipairs(staged_files_renamed) do - git.index.reset_HEAD(item.name, item.original_name) - git.index.checkout { item.original_name } - fn.delete(item.escaped_path) - end - end - - if #staged_files_deleted > 0 then - git.index.reset_HEAD(unpack(staged_files_deleted)) - git.index.checkout(staged_files_deleted) - end - end - refresh = { update_diffs = { "staged:*" } } - elseif section == "stashes" then - message = ("Discard %s stashes?"):format(#selection.section.items) - action = function() - for _, stash in ipairs(selection.section.items) do - git.stash.drop(stash.name:match("(stash@{%d+})")) - end - end - end - end - - if action and (choices or input.get_permission(message)) then - action() - self:refresh(refresh) - end - end), - [mappings["GoToNextHunkHeader"]] = function() - local c = self.buffer.ui:get_component_under_cursor(function(c) - return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "Item" - end) - local section = self.buffer.ui:get_current_section() - - if c and section then - local _, section_last = section:row_range_abs() - local next_location - - if c.options.tag == "Diff" then - next_location = fn.line(".") + 1 - elseif c.options.tag == "Item" then - vim.cmd("normal! zo") - next_location = fn.line(".") + 1 - elseif c.options.tag == "Hunk" then - local _, last = c:row_range_abs() - next_location = last + 1 - end - - if next_location < section_last then - self.buffer:move_cursor(next_location) - end - - vim.cmd("normal! zt") - end - end, - [mappings["GoToPreviousHunkHeader"]] = function() - local function previous_hunk_header(self, line) - local c = self.buffer.ui:get_component_on_line(line, function(c) - return c.options.tag == "Diff" or c.options.tag == "Hunk" or c.options.tag == "Item" - end) - - if c then - local first, _ = c:row_range_abs() - if fn.line(".") == first then - first = previous_hunk_header(self, line - 1) - end - - return first - end - end - - local previous_header = previous_hunk_header(self, fn.line(".")) - if previous_header then - self.buffer:move_cursor(previous_header) - vim.cmd("normal! zt") - end - end, - [mappings["InitRepo"]] = function() - git.init.init_repo() - end, - [mappings["Stage"]] = a.void(function() - local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() - local section = self.buffer.ui:get_current_section() - - if stagable and section then - if section.options.section == "staged" then - return - end - - if stagable.hunk then - local item = self.buffer.ui:get_item_under_cursor() - assert(item, "Item cannot be nil") - local patch = - git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) - - git.index.apply(patch, { cached = true }) - self:refresh { update_diffs = { "*:" .. item.escaped_path } } - elseif stagable.filename then - if section.options.section == "unstaged" then - git.status.stage { stagable.filename } - self:refresh { update_diffs = { "unstaged:" .. stagable.filename } } - elseif section.options.section == "untracked" then - git.index.add { stagable.filename } - self:refresh { update_diffs = { "untracked:" .. stagable.filename } } - end - end - elseif section then - if section.options.section == "untracked" then - git.status.stage_untracked() - self:refresh { update_diffs = { "untracked:*" } } - elseif section.options.section == "unstaged" then - git.status.stage_modified() - self:refresh { update_diffs = { "unstaged:*" } } - end - end - end), - [mappings["StageAll"]] = a.void(function() - git.status.stage_all() - self:refresh() - end), - [mappings["StageUnstaged"]] = a.void(function() - git.status.stage_modified() - self:refresh { update_diffs = { "unstaged:*" } } - end), - [mappings["Unstage"]] = a.void(function() - local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() - - local section = self.buffer.ui:get_current_section() - if section and section.options.section ~= "staged" then - return - end - - if unstagable then - if unstagable.hunk then - local item = self.buffer.ui:get_item_under_cursor() - assert(item, "Item cannot be nil") - local patch = git.index.generate_patch( - item, - unstagable.hunk, - unstagable.hunk.from, - unstagable.hunk.to, - true - ) - - git.index.apply(patch, { cached = true, reverse = true }) - self:refresh { update_diffs = { "*:" .. item.escaped_path } } - elseif unstagable.filename then - git.status.unstage { unstagable.filename } - self:refresh { update_diffs = { "*:" .. unstagable.filename } } - end - elseif section then - git.status.unstage_all() - self:refresh { update_diffs = { "staged:*" } } - end - end), - [mappings["UnstageStaged"]] = a.void(function() - git.status.unstage_all() - self:refresh { update_diffs = { "staged:*" } } - end), - [mappings["GoToFile"]] = function() - local item = self.buffer.ui:get_item_under_cursor() - - -- Goto FILE - if item and item.absolute_path then - local cursor - -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. - if rawget(item, "diff") then - local line = self.buffer:cursor_line() - - for _, hunk in ipairs(item.diff.hunks) do - if line >= hunk.first and line <= hunk.last then - local offset = line - hunk.first - local row = hunk.disk_from + offset - 1 - - for i = 1, offset do - -- If the line is a deletion, we need to adjust the row - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - - cursor = { row, 0 } - break - end - end - end - - self:close() - - -- TODO: Does this work? - vim.schedule(function() - vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) - - local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if buf:is_focused() and cursor then - buf:move_cursor(cursor) - end - end) - - return - end - - -- Goto COMMIT - local ref = self.buffer.ui:get_yankable_under_cursor() - if ref then - require("neogit.buffers.commit_view").new(ref):open() - end - end, - [mappings["TabOpen"]] = function() - local item = self.buffer.ui:get_item_under_cursor() - - if item and item.absolute_path then - local cursor - -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. - if rawget(item, "diff") then - local line = self.buffer:cursor_line() - - for _, hunk in ipairs(item.diff.hunks) do - if line >= hunk.first and line <= hunk.last then - local offset = line - hunk.first - local row = hunk.disk_from + offset - 1 - - for i = 1, offset do - -- If the line is a deletion, we need to adjust the row - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - - cursor = { row, 0 } - break - end - end - end - - vim.cmd.tabedit(fn.fnameescape(item.absolute_path)) - local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if buf:is_focused() and cursor then - buf:move_cursor(cursor) - end - end - end, - [mappings["SplitOpen"]] = function() - local item = self.buffer.ui:get_item_under_cursor() - - if item and item.absolute_path then - local cursor - -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. - if rawget(item, "diff") then - local line = self.buffer:cursor_line() - - for _, hunk in ipairs(item.diff.hunks) do - if line >= hunk.first and line <= hunk.last then - local offset = line - hunk.first - local row = hunk.disk_from + offset - 1 - - for i = 1, offset do - -- If the line is a deletion, we need to adjust the row - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - - cursor = { row, 0 } - break - end - end - end - - vim.cmd.split(fn.fnameescape(item.absolute_path)) - local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if buf:is_focused() and cursor then - buf:move_cursor(cursor) - end - end - end, - [mappings["VSplitOpen"]] = function() - local item = self.buffer.ui:get_item_under_cursor() - - if item and item.absolute_path then - local cursor - -- If the cursor is located within a hunk, we need to turn that back into a line number in the file. - if rawget(item, "diff") then - local line = self.buffer:cursor_line() - - for _, hunk in ipairs(item.diff.hunks) do - if line >= hunk.first and line <= hunk.last then - local offset = line - hunk.first - local row = hunk.disk_from + offset - 1 - - for i = 1, offset do - -- If the line is a deletion, we need to adjust the row - if string.sub(hunk.lines[i], 1, 1) == "-" then - row = row - 1 - end - end - - cursor = { row, 0 } - break - end - end - end - - vim.cmd.vsplit(fn.fnameescape(item.absolute_path)) - local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if buf:is_focused() and cursor then - buf:move_cursor(cursor) - end - end - end, - [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) - p { commits = { self.buffer.ui:get_commit_under_cursor() } } - end), - [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) - p { commits = { self.buffer.ui:get_commit_under_cursor() } } - end), - [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) - p { commits = { self.buffer.ui:get_commit_under_cursor() } } - end), - [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } - end), - [popups.mapping_for("MergePopup")] = popups.open("merge", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } - end), - [popups.mapping_for("PushPopup")] = popups.open("push", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } - end), - [popups.mapping_for("RebasePopup")] = popups.open("rebase", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } - end), - [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) - p { commits = { self.buffer.ui:get_commit_under_cursor() } } - end), - [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } - end), - [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) - p { commit = self.buffer.ui:get_commit_under_cursor() } - end), - [popups.mapping_for("StashPopup")] = popups.open("stash", function(p) - local stash = self.buffer.ui:get_yankable_under_cursor() - p { name = stash and stash:match("^stash@{%d+}") } - end), - [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - local section = self.buffer.ui:get_selection().section - local item = self.buffer.ui:get_yankable_under_cursor() - p { - section = { name = section and section.name }, - item = { name = item }, - } - end), - [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) - local path = self.buffer.ui:get_hunk_or_filename_under_cursor() - p { - paths = { path and path.escaped_path }, - git_root = git.repo.git_root, - } - end), - [popups.mapping_for("HelpPopup")] = popups.open("help", function(p) - -- Since any other popup can be launched from help, build an ENV for any of them. - local path = self.buffer.ui:get_hunk_or_filename_under_cursor() - local section = self.buffer.ui:get_selection().section - if section then - section = section.name - end - - local item = self.buffer.ui:get_yankable_under_cursor() - local stash = self.buffer.ui:get_yankable_under_cursor() - local commit = self.buffer.ui:get_commit_under_cursor() - local commits = { commit } - - -- TODO: Pass selection here so we can stage/unstage etc stuff - p { - branch = { commits = commits }, - cherry_pick = { commits = commits }, - commit = { commit = commit }, - merge = { commit = commit }, - push = { commit = commit }, - rebase = { commit = commit }, - revert = { commits = commits }, - bisect = { commits = commits }, - reset = { commit = commit }, - tag = { commit = commit }, - stash = { name = stash and stash:match("^stash@{%d+}") }, - diff = { - section = { name = section }, - item = { name = item }, - }, - ignore = { - paths = { path and path.escaped_path }, - git_root = git.repo.git_root, - }, - remote = {}, - fetch = {}, - pull = {}, - log = {}, - worktree = {}, - } - end), - [popups.mapping_for("RemotePopup")] = popups.open("remote"), - [popups.mapping_for("FetchPopup")] = popups.open("fetch"), - [popups.mapping_for("PullPopup")] = popups.open("pull"), - [popups.mapping_for("LogPopup")] = popups.open("log"), - [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), + ["j"] = self:_action("n_down"), + ["k"] = self:_action("n_up"), + [mappings["Toggle"]] = self:_action("n_toggle"), + [mappings["Close"]] = self:_action("n_close"), + [mappings["OpenOrScrollDown"]] = self:_action("n_open_or_scroll_down"), + [mappings["OpenOrScrollUp"]] = self:_action("n_open_or_scroll_up"), + [mappings["RefreshBuffer"]] = self:_action("n_refresh_buffer"), + [mappings["Depth1"]] = self:_action("n_depth1"), + [mappings["Depth2"]] = self:_action("n_depth2"), + [mappings["Depth3"]] = self:_action("n_depth3"), + [mappings["Depth4"]] = self:_action("n_depth4"), + [mappings["CommandHistory"]] = self:_action("n_command_history"), + [mappings["Console"]] = self:_action("n_console"), + [mappings["ShowRefs"]] = self:_action("n_show_refs"), + [mappings["YankSelected"]] = self:_action("n_yank_selected"), + [mappings["Discard"]] = self:_action("n_discard"), + [mappings["GoToNextHunkHeader"]] = self:_action("n_go_to_next_hunk_header"), + [mappings["GoToPreviousHunkHeader"]] = self:_action("n_go_to_previous_hunk_header"), + [mappings["InitRepo"]] = self:_action("n_init_repo"), + [mappings["Stage"]] = self:_action("n_stage"), + [mappings["StageAll"]] = self:_action("n_stage_all"), + [mappings["StageUnstaged"]] = self:_action("n_stage_unstaged"), + [mappings["Unstage"]] = self:_action("n_unstage"), + [mappings["UnstageStaged"]] = self:_action("n_unstage_staged"), + [mappings["GoToFile"]] = self:_action("n_goto_file"), + [mappings["TabOpen"]] = self:_action("n_tab_open"), + [mappings["SplitOpen"]] = self:_action("n_split_open"), + [mappings["VSplitOpen"]] = self:_action("n_vertical_split_open"), + [popups.mapping_for("BisectPopup")] = self:_action("n_bisect_popup"), + [popups.mapping_for("BranchPopup")] = self:_action("n_branch_popup"), + [popups.mapping_for("CherryPickPopup")] = self:_action("n_cherry_pick_popup"), + [popups.mapping_for("CommitPopup")] = self:_action("n_commit_popup"), + [popups.mapping_for("DiffPopup")] = self:_action("n_diff_popup"), + [popups.mapping_for("FetchPopup")] = self:_action("n_fetch_popup"), + [popups.mapping_for("HelpPopup")] = self:_action("n_help_popup"), + [popups.mapping_for("IgnorePopup")] = self:_action("n_ignore_popup"), + [popups.mapping_for("LogPopup")] = self:_action("n_log_popup"), + [popups.mapping_for("MergePopup")] = self:_action("n_merge_popup"), + [popups.mapping_for("PullPopup")] = self:_action("n_pull_popup"), + [popups.mapping_for("PushPopup")] = self:_action("n_push_popup"), + [popups.mapping_for("RebasePopup")] = self:_action("n_rebase_popup"), + [popups.mapping_for("RemotePopup")] = self:_action("n_remote_popup"), + [popups.mapping_for("ResetPopup")] = self:_action("n_reset_popup"), + [popups.mapping_for("RevertPopup")] = self:_action("n_revert_popup"), + [popups.mapping_for("StashPopup")] = self:_action("n_stash_popup"), + [popups.mapping_for("TagPopup")] = self:_action("n_tag_popup"), + [popups.mapping_for("WorktreePopup")] = self:_action("n_worktree_popup"), }, }, + --stylua: ignore end initialize = function() self.prev_autochdir = vim.o.autochdir vim.o.autochdir = false @@ -1186,10 +253,6 @@ end function M:refresh(partial, reason) logger.debug("[STATUS] Beginning refresh from " .. (reason or "unknown")) - -- if self.frozen then - -- return - -- end - local permit = self:_get_refresh_lock(reason) git.repo:refresh { diff --git a/lua/neogit/popups/init.lua b/lua/neogit/popups/init.lua index ffb483eb4..276f20fb8 100644 --- a/lua/neogit/popups/init.lua +++ b/lua/neogit/popups/init.lua @@ -1,8 +1,12 @@ +---@class Popups +---@field open fun(name: string, f: nil|fun(create: fun(...): any)): fun(): any +---@field mapping_for fun(name: string):string|string[] local M = {} +---Creates a curried function which will open the popup with the given name when called ---@param name string ---@param f nil|fun(create: fun(...)): any ---- Creates a curried function which will open the popup with the given name when called +---@return fun(): any function M.open(name, f) f = f or function(c) c() @@ -22,9 +26,9 @@ function M.open(name, f) end end +---Returns the keymapping for a popup ---@param name string ---@return string|string[] ----Returns the keymapping for a popup function M.mapping_for(name) local mappings = require("neogit.config").get_reversed_popup_maps() From 5ee392a076c30eddb771ab87362876f0b4c9c2b5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 5 May 2024 22:42:22 +0200 Subject: [PATCH 424/443] Use user setting for context highlight --- lua/neogit/buffers/commit_view/init.lua | 2 +- lua/neogit/buffers/diff/init.lua | 3 ++- lua/neogit/buffers/status/init.lua | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 8da73d253..111f07aaf 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -129,7 +129,7 @@ function M:open(kind) filetype = "NeogitCommitView", kind = kind, status_column = "", - context_highlight = true, + context_highlight = not config.values.disable_context_highlighting, mappings = { n = { [""] = function() diff --git a/lua/neogit/buffers/diff/init.lua b/lua/neogit/buffers/diff/init.lua index d0b214556..dfbef1123 100644 --- a/lua/neogit/buffers/diff/init.lua +++ b/lua/neogit/buffers/diff/init.lua @@ -1,6 +1,7 @@ local Buffer = require("neogit.lib.buffer") local ui = require("neogit.buffers.diff.ui") local git = require("neogit.lib.git") +local config = require("neogit.config") local status_maps = require("neogit.config").get_reversed_status_maps() local api = vim.api @@ -54,7 +55,7 @@ function M:open() filetype = "NeogitDiffView", status_column = "", kind = "split", - context_highlight = true, + context_highlight = not config.values.disable_context_highlighting, mappings = { n = { ["{"] = function() -- Goto Previous diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index c12c198a9..ae8c7190f 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -102,7 +102,7 @@ function M:open(kind, cwd) name = "NeogitStatus", filetype = "NeogitStatus", cwd = cwd, - context_highlight = true, + context_highlight = not config.values.disable_context_highlighting, kind = kind or config.values.kind, disable_line_numbers = config.values.disable_line_numbers, foldmarkers = not config.values.disable_signs, From 2bfcd2b30eacfcf72a777c0041acfacae15ba65f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 5 May 2024 22:49:11 +0200 Subject: [PATCH 425/443] cleanup --- lua/neogit/buffers/status/init.lua | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index ae8c7190f..6b2ab0cc3 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -5,28 +5,10 @@ local popups = require("neogit.popups") local git = require("neogit.lib.git") local Watcher = require("neogit.watcher") local a = require("plenary.async") -local input = require("neogit.lib.input") local logger = require("neogit.logger") -- TODO: Add logging -local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") local api = vim.api -local fn = vim.fn - -local function cleanup_items(...) - if vim.in_fast_event() then - a.util.scheduler() - end - - for _, item in ipairs { ... } do - local bufnr = fn.bufexists(item.name) - if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then - api.nvim_buf_delete(bufnr, { force = true }) - end - - fn.delete(item.escaped_path) - end -end ---@class Semaphore ---@field permits number From f10084ae6ad75b6c7169dabde70d95e59a0b4c33 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 6 May 2024 21:43:59 +0200 Subject: [PATCH 426/443] Add move merge actions --- lua/neogit/popups/merge/actions.lua | 69 +++++++++++++++++++++++++++-- lua/neogit/popups/merge/init.lua | 27 +++++------ 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index f6c1d70f6..c817b546a 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -1,4 +1,5 @@ local M = {} +local util = require("neogit.lib.util") local git = require("neogit.lib.git") local input = require("neogit.lib.input") @@ -20,10 +21,72 @@ function M.abort() end function M.merge(popup) - local branch = FuzzyFinderBuffer.new(git.refs.list_branches()):open_async() - if branch then - git.merge.merge(branch, popup:get_arguments()) + local refs = util.merge( + { popup.state.env.commit }, + git.refs.list_branches(), + git.refs.list_tags() + ) + + local ref = FuzzyFinderBuffer.new(refs):open_async() + if ref then + local args = popup:get_arguments() + table.insert(args, "--no-edit") + git.merge.merge(ref, args) + end +end + +function M.squash(popup) + local refs = util.merge( + { popup.state.env.commit }, + git.refs.list_branches(), + git.refs.list_tags() + ) + + local ref = FuzzyFinderBuffer.new(refs):open_async() + if ref then + local args = popup:get_arguments() + table.insert(args, "--squash") + git.merge.merge(ref, args) + end +end + +function M.merge_edit(popup) + local refs = util.merge( + { popup.state.env.commit }, + git.refs.list_branches(), + git.refs.list_tags() + ) + + local ref = FuzzyFinderBuffer.new(refs):open_async() + if ref then + local args = popup:get_arguments() + table.insert(args, "--edit") + util.remove_item_from_table(args, "--ff-only") + if not vim.tbl_contains(args, "--no-ff") then + table.insert(args, "--no-ff") + end + + git.merge.merge(ref, args) end end +function M.merge_nocommit(popup) + local refs = util.merge( + { popup.state.env.commit }, + git.refs.list_branches(), + git.refs.list_tags() + ) + + local ref = FuzzyFinderBuffer.new(refs):open_async() + if ref then + local args = popup:get_arguments() + table.insert(args, "--no-commit") + util.remove_item_from_table(args, "--ff-only") + if not vim.tbl_contains(args, "--no-ff") then + table.insert(args, "--no-ff") + end + + git.merge.merge(ref, args) + end +end return M diff --git a/lua/neogit/popups/merge/init.lua b/lua/neogit/popups/merge/init.lua index 21fd09c75..8ea303136 100644 --- a/lua/neogit/popups/merge/init.lua +++ b/lua/neogit/popups/merge/init.lua @@ -3,7 +3,7 @@ local actions = require("neogit.popups.merge.actions") local M = {} -function M.create() +function M.create(env) local in_merge = actions.in_merge() local p = popup .builder() @@ -13,6 +13,14 @@ function M.create() :action_if(in_merge, "a", "Abort merge", actions.abort) :switch_if(not in_merge, "f", "ff-only", "Fast-forward only", { incompatible = { "no-ff" } }) :switch_if(not in_merge, "n", "no-ff", "No fast-forward", { incompatible = { "ff-only" } }) + :option_if(not in_merge, "s", "strategy", "", "Strategy", { + choices = { "resolve", "recursive", "octopus", "ours", "subtree" }, + key_prefix = "-", + }) + :option_if(not in_merge, "X", "strategy-option", "", "Strategy Option", { + choices = { "ours", "theirs", "patience" }, + key_prefix = "-", + }) :switch_if( not in_merge, "b", @@ -27,14 +35,6 @@ function M.create() "Ignore whitespace when comparing lines", { cli_prefix = "-" } ) - :option_if(not in_merge, "s", "strategy", "", "Strategy", { - choices = { "resolve", "recursive", "octopus", "ours", "subtree" }, - key_prefix = "-", - }) - :option_if(not in_merge, "X", "strategy-option", "", "Strategy Option", { - choices = { "ours", "theirs", "patience" }, - key_prefix = "-", - }) :option_if(not in_merge, "A", "Xdiff-algorithm", "", "Diff algorithm", { choices = { "default", "minimal", "patience", "histogram" }, cli_prefix = "-", @@ -43,14 +43,15 @@ function M.create() :option_if(not in_merge, "S", "gpg-sign", "", "Sign using gpg", { key_prefix = "-" }) :group_heading_if(not in_merge, "Actions") :action_if(not in_merge, "m", "Merge", actions.merge) - :action_if(not in_merge, "e", "Merge and edit message") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L105 - :action_if(not in_merge, "n", "Merge but don't commit") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L119 - :action_if(not in_merge, "A", "Absorb") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L158 + :action_if(not in_merge, "e", "Merge and edit message", actions.merge_edit) + :action_if(not in_merge, "n", "Merge but don't commit", actions.merge_nocommit) + :action_if(not in_merge, "a", "Absorb") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L158 :new_action_group_if(not in_merge, "") :action_if(not in_merge, "p", "Preview merge") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L225 - :action_if(not in_merge, "s", "Squash merge") -- -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L217 :group_heading_if(not in_merge, "") + :action_if(not in_merge, "s", "Squash merge", actions.squash) :action_if(not in_merge, "i", "Dissolve") -- https://github.com/magit/magit/blob/main/lisp/magit-merge.el#L131 + :env(env) :build() p:show() From 3492e1371662c7cd931589b9b08512d224beb7f6 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 6 May 2024 21:52:44 +0200 Subject: [PATCH 427/443] Add unmerged type suffex --- lua/neogit/buffers/status/ui.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 62ed3c26b..3c045aa5c 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -251,6 +251,15 @@ local SectionItemFile = function(section, config) ) end + local unmerged_types = { + ["DD"] = " (both deleted)", + ["DU"] = " (deleted by us)", + ["UD"] = " (deleted by them)", + ["AA"] = " (both added)", + ["AU"] = " (added by us)", + ["UA"] = " (added by them)", + } + local name = item.original_name and ("%s -> %s"):format(item.original_name, item.name) or item.name local highlight = ("NeogitChange%s%s"):format(item.mode:gsub("%?", "Untracked"), section) @@ -258,6 +267,7 @@ local SectionItemFile = function(section, config) row { text.highlight(highlight)(mode_text), text(name), + text.highlight("NeogitSubtleText")(unmerged_types[item.mode] or ""), }, }, { foldable = true, From e9a44839739cc463945ba2f7e9accd600a5d6112 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 6 May 2024 23:07:58 +0200 Subject: [PATCH 428/443] move unmerged changes to "staged" section --- lua/neogit/lib/git/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 6b9203278..11123f4ca 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -101,7 +101,7 @@ local function update_status(state) if kind == "u" then local mode, _, _, _, _, _, _, _, _, name = rest:match(match_u) - table.insert(unstaged_files, update_file(cwd, old_files_hash.unstaged_files[name], mode, name)) + table.insert(staged_files, update_file(cwd, old_files_hash.staged_files[name], mode, name)) elseif kind == "?" then table.insert(untracked_files, update_file(cwd, old_files_hash.untracked_files[rest], "?", rest)) elseif kind == "1" then From b6fe380e1708831296349e121b0dfb29320c733d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 6 May 2024 23:09:11 +0200 Subject: [PATCH 429/443] simplify things --- lua/neogit/lib/buffer.lua | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 0158e4c67..5dc30f6c7 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -281,6 +281,7 @@ function Buffer:hide() elseif self.kind == "replace" then if self.old_buf and api.nvim_buf_is_loaded(self.old_buf) then api.nvim_set_current_buf(self.old_buf) + self.old_buf = nil end else api.nvim_win_close(0, true) @@ -311,26 +312,17 @@ function Buffer:show() local win local kind = self.kind + -- https://github.com/nvim-telescope/telescope.nvim/blame/49650f5d749fef3d1e6cf52ba031c02163a59158/lua/telescope/actions/set.lua#L93 if kind == "replace" then self.old_buf = api.nvim_get_current_buf() - api.nvim_set_current_buf(self.handle) - win = api.nvim_get_current_win() elseif kind == "tab" then - vim.cmd("tab split") - api.nvim_set_current_buf(self.handle) - win = api.nvim_get_current_win() + vim.cmd("tabnew") elseif kind == "split" then - vim.cmd("below split") - api.nvim_set_current_buf(self.handle) - win = api.nvim_get_current_win() + vim.cmd("new") elseif kind == "split_above" then - vim.cmd("top split") - api.nvim_set_current_buf(self.handle) - win = api.nvim_get_current_win() + vim.cmd("top new") elseif kind == "vsplit" then - vim.cmd("bot vsplit") - api.nvim_set_current_buf(self.handle) - win = api.nvim_get_current_win() + vim.cmd("vnew") elseif kind == "floating" then -- Creates the border window local vim_height = vim.o.lines @@ -356,6 +348,11 @@ function Buffer:show() win = content_window end + if kind ~= "floating" then + api.nvim_set_current_buf(self.handle) + win = api.nvim_get_current_win() + end + if self.disable_line_numbers then vim.cmd("setlocal nonu") vim.cmd("setlocal nornu") @@ -375,21 +372,12 @@ function Buffer:is_valid() return api.nvim_buf_is_valid(self.handle) end -function Buffer:put(lines, after, follow) - self:focus() - api.nvim_put(lines, "l", after, follow) -end - function Buffer:create_fold(first, last, _) self:win_exec(string.format("%d,%dfold", first, last)) end function Buffer:set_fold_state(first, last, open) - if open then - self:win_exec(string.format("%d,%dfoldopen", first, last)) - else - self:win_exec(string.format("%d,%dfoldclose", first, last)) - end + self:win_exec(string.format("%d,%dfold%s", first, last, open and "open" or "close")) end function Buffer:unlock() From 60f41068fe738f71098abd349c2063dac724ae77 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 6 May 2024 23:23:37 +0200 Subject: [PATCH 430/443] Move conflict resolution to staged --- lua/neogit/buffers/status/actions.lua | 37 ++++++++++++--------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 91de91599..6316495d3 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -645,9 +645,21 @@ M.n_discard = function(self) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } elseif section == "unstaged" then - if selection.item.mode:match("^[UA][UA]") then - choices = { "&ours", "&theirs", "&conflict", "&abort" } - action = function() + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "A" then + git.index.reset { selection.item.escaped_path } + cleanup_items(selection.item) + else + git.index.checkout { selection.item.name } + end + end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } + elseif section == "staged" then + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode:match("^[UAD][UAD]") then + choices = { "&ours", "&theirs", "&conflict", "&abort" } local choice = input.get_choice("Discard conflict by taking...", { values = choices, default = #choices }) @@ -661,24 +673,7 @@ M.n_discard = function(self) git.cli.checkout.merge.files(selection.item.absolute_path).call_sync() git.status.stage { selection.item.name } end - end - refresh = { update_diffs = { "unstaged:" .. selection.item.name } } - else - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode == "A" then - git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) - else - git.index.checkout { selection.item.name } - end - end - end - refresh = { update_diffs = { "unstaged:" .. selection.item.name } } - elseif section == "staged" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode == "N" then + elseif selection.item.mode == "N" then git.index.reset { selection.item.escaped_path } cleanup_items(selection.item) elseif selection.item.mode == "M" then From a80896ba3416437606e843e24e81d8a12d1f15d9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 7 May 2024 16:20:33 +0200 Subject: [PATCH 431/443] Possibly overkill: Handle unmerged files in both staged and unstaged areas --- lua/neogit/buffers/status/actions.lua | 81 ++++++++++++++++++--------- lua/neogit/lib/git/diff.lua | 11 ++++ 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 6316495d3..66ea9382f 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -645,21 +645,40 @@ M.n_discard = function(self) end refresh = { update_diffs = { "untracked:" .. selection.item.name } } elseif section == "unstaged" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode == "A" then - git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) - else - git.index.checkout { selection.item.name } + if selection.item.mode:match("^[UAD][UAD]") then + choices = { "&ours", "&theirs", "&conflict", "&abort" } + action = function() + local choice = + input.get_choice("Discard conflict by taking...", { values = choices, default = #choices }) + + if choice == "o" then + git.cli.checkout.ours.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + elseif choice == "t" then + git.cli.checkout.theirs.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + elseif choice == "c" then + git.cli.checkout.merge.files(selection.item.absolute_path).call_sync() + git.status.stage { selection.item.name } + end + end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } + else + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "A" then + git.index.reset { selection.item.escaped_path } + cleanup_items(selection.item) + else + git.index.checkout { selection.item.name } + end end end refresh = { update_diffs = { "unstaged:" .. selection.item.name } } elseif section == "staged" then - message = ("Discard %q?"):format(selection.item.name) - action = function() - if selection.item.mode:match("^[UAD][UAD]") then - choices = { "&ours", "&theirs", "&conflict", "&abort" } + if selection.item.mode:match("^[UAD][UAD]") then + choices = { "&ours", "&theirs", "&conflict", "&abort" } + action = function() local choice = input.get_choice("Discard conflict by taking...", { values = choices, default = #choices }) @@ -673,24 +692,32 @@ M.n_discard = function(self) git.cli.checkout.merge.files(selection.item.absolute_path).call_sync() git.status.stage { selection.item.name } end - elseif selection.item.mode == "N" then - git.index.reset { selection.item.escaped_path } - cleanup_items(selection.item) - elseif selection.item.mode == "M" then - git.index.reset { selection.item.escaped_path } - git.index.checkout { selection.item.escaped_path } - elseif selection.item.mode == "R" then - git.index.reset_HEAD(selection.item.name, selection.item.original_name) - git.index.checkout { selection.item.original_name } - cleanup_items(selection.item) - elseif selection.item.mode == "D" then - git.index.reset_HEAD(selection.item.escaped_path) - git.index.checkout { selection.item.escaped_path } - else - error(("Unhandled file mode %q for %q"):format(selection.item.mode, selection.item.escaped_path)) end + refresh = { update_diffs = { "unstaged:" .. selection.item.name } } + else + message = ("Discard %q?"):format(selection.item.name) + action = function() + if selection.item.mode == "N" then + git.index.reset { selection.item.escaped_path } + cleanup_items(selection.item) + elseif selection.item.mode == "M" then + git.index.reset { selection.item.escaped_path } + git.index.checkout { selection.item.escaped_path } + elseif selection.item.mode == "R" then + git.index.reset_HEAD(selection.item.name, selection.item.original_name) + git.index.checkout { selection.item.original_name } + cleanup_items(selection.item) + elseif selection.item.mode == "D" then + git.index.reset_HEAD(selection.item.escaped_path) + git.index.checkout { selection.item.escaped_path } + else + error( + ("Unhandled file mode %q for %q"):format(selection.item.mode, selection.item.escaped_path) + ) + end + end + refresh = { update_diffs = { "staged:" .. selection.item.name } } end - refresh = { update_diffs = { "staged:" .. selection.item.name } } elseif section == "stashes" then message = ("Discard %q?"):format(selection.item.name) action = function() diff --git a/lua/neogit/lib/git/diff.lua b/lua/neogit/lib/git/diff.lua index 027323c55..7146e6ead 100644 --- a/lua/neogit/lib/git/diff.lua +++ b/lua/neogit/lib/git/diff.lua @@ -227,6 +227,15 @@ local function raw_unstaged(name) end end +local function raw_staged_unmerged(name) + return function() + local diff = git.cli.diff.no_ext_diff.files(name).call({ hidden = true }).stdout + local stats = git.cli.diff.no_ext_diff.shortstat.files(name).call({ hidden = true }).stdout + + return { diff, stats } + end +end + local function raw_staged(name) return function() local diff = git.cli.diff.no_ext_diff.cached.files(name).call({ hidden = true }).stdout @@ -317,6 +326,8 @@ return { invalidate_diff(filter, "staged", f) if f.mode == "R" then build_metatable(f, raw_staged_renamed(f.name, f.original_name)) + elseif f.mode:match("^[UAD][UAD]") then + build_metatable(f, raw_staged_unmerged(f.name)) else build_metatable(f, raw_staged(f.name)) end From 24eb181e1497f0c92cdfef7bd02d2f741c6bc26c Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 7 May 2024 23:42:15 +0200 Subject: [PATCH 432/443] Replace "comment" hls with NeogitSubtleText (which links to comment) --- lua/neogit/buffers/commit_view/ui.lua | 8 ++++---- lua/neogit/buffers/common.lua | 8 ++++---- lua/neogit/buffers/status/ui.lua | 24 ++++++++++++------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lua/neogit/buffers/commit_view/ui.lua b/lua/neogit/buffers/commit_view/ui.lua index 6f04c57ce..da3dcde67 100644 --- a/lua/neogit/buffers/commit_view/ui.lua +++ b/lua/neogit/buffers/commit_view/ui.lua @@ -34,15 +34,15 @@ function M.CommitHeader(info) text.line_hl("NeogitCommitViewHeader")("Commit " .. info.commit_arg), commit_header_arg(info), row { - text.highlight("Comment")("Author: "), + text.highlight("NeogitSubtleText")("Author: "), text((info.author_name or "") .. " <" .. (info.author_email or "") .. ">"), }, - row { text.highlight("Comment")("AuthorDate: "), text(info.author_date) }, + row { text.highlight("NeogitSubtleText")("AuthorDate: "), text(info.author_date) }, row { - text.highlight("Comment")("Committer: "), + text.highlight("NeogitSubtleText")("Committer: "), text((info.committer_name or "") .. " <" .. (info.committer_email or "") .. ">"), }, - row { text.highlight("Comment")("CommitDate: "), text(info.committer_date) }, + row { text.highlight("NeogitSubtleText")("CommitDate: "), text(info.committer_date) }, } end diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index d818e1404..dfc253143 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -175,7 +175,7 @@ M.CommitEntry = Component.new(function(commit, args) details = col.padding_left(git.log.abbreviated_size() + 1) { row(util.merge(graph, { text(" "), - text("Author: ", { highlight = "Comment" }), + text("Author: ", { highlight = "NeogitSubtleText" }), text(commit.author_name, { highlight = "NeogitGraphAuthor" }), text(" <"), text(commit.author_email), @@ -183,12 +183,12 @@ M.CommitEntry = Component.new(function(commit, args) })), row(util.merge(graph, { text(" "), - text("AuthorDate: ", { highlight = "Comment" }), + text("AuthorDate: ", { highlight = "NeogitSubtleText" }), text(commit.author_date), })), row(util.merge(graph, { text(" "), - text("Commit: ", { highlight = "Comment" }), + text("Commit: ", { highlight = "NeogitSubtleText" }), text(commit.committer_name), text(" <"), text(commit.committer_email), @@ -196,7 +196,7 @@ M.CommitEntry = Component.new(function(commit, args) })), row(util.merge(graph, { text(" "), - text("CommitDate: ", { highlight = "Comment" }), + text("CommitDate: ", { highlight = "NeogitSubtleText" }), text(commit.committer_date), })), row(graph), diff --git a/lua/neogit/buffers/status/ui.lua b/lua/neogit/buffers/status/ui.lua index 3c045aa5c..276d700ee 100755 --- a/lua/neogit/buffers/status/ui.lua +++ b/lua/neogit/buffers/status/ui.lua @@ -55,17 +55,17 @@ local HINT = Component.new(function(props) end return row { - text.highlight("Comment")("Hint: "), + text.highlight("NeogitSubtleText")("Hint: "), entry("Toggle", "toggle"), - text.highlight("Comment")(" | "), + text.highlight("NeogitSubtleText")(" | "), entry("Stage", "stage"), - text.highlight("Comment")(" | "), + text.highlight("NeogitSubtleText")(" | "), entry("Unstage", "unstage"), - text.highlight("Comment")(" | "), + text.highlight("NeogitSubtleText")(" | "), entry("Discard", "discard"), - text.highlight("Comment")(" | "), + text.highlight("NeogitSubtleText")(" | "), entry("CommitPopup", "commit"), - text.highlight("Comment")(" | "), + text.highlight("NeogitSubtleText")(" | "), entry("HelpPopup", "help"), } end) @@ -285,8 +285,8 @@ end local SectionItemStash = Component.new(function(item) local name = ("stash@{%s}"):format(item.idx) return row({ - text.highlight("Comment")(name), - text.highlight("Comment")(": "), + text.highlight("NeogitSubtleText")(name), + text.highlight("NeogitSubtleText")(": "), text(item.message), }, { yankable = name, item = item }) end) @@ -407,15 +407,15 @@ local BisectDetailsSection = Component.new(function(props) return col.tag("Section")({ row(util.merge(props.title, { text(" "), text.highlight("NeogitObjectId")(props.commit.oid) })), row { - text.highlight("Comment")("Author: "), + text.highlight("NeogitSubtleText")("Author: "), text((props.commit.author_name or "") .. " <" .. (props.commit.author_email or "") .. ">"), }, - row { text.highlight("Comment")("AuthorDate: "), text(props.commit.author_date) }, + row { text.highlight("NeogitSubtleText")("AuthorDate: "), text(props.commit.author_date) }, row { - text.highlight("Comment")("Committer: "), + text.highlight("NeogitSubtleText")("Committer: "), text((props.commit.committer_name or "") .. " <" .. (props.commit.committer_email or "") .. ">"), }, - row { text.highlight("Comment")("CommitDate: "), text(props.commit.committer_date) }, + row { text.highlight("NeogitSubtleText")("CommitDate: "), text(props.commit.committer_date) }, EmptyLine(), col( map(props.commit.description, text), From 788e501070be6100fb565a994bede318700cba00 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 7 May 2024 23:43:10 +0200 Subject: [PATCH 433/443] Add: Untrack mapping to status buffer. "K". Untracks a file, or selected files. --- README.md | 1 + lua/neogit/buffers/status/actions.lua | 41 +++++++++++++++++++++++++++ lua/neogit/buffers/status/init.lua | 2 ++ lua/neogit/config.lua | 2 ++ lua/neogit/lib/git/cli.lua | 7 +++++ lua/neogit/lib/git/files.lua | 8 ++++++ lua/neogit/popups/help/actions.lua | 1 + 7 files changed, 62 insertions(+) diff --git a/README.md b/README.md index f2ed4bf92..ba44f4bef 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,7 @@ neogit.setup { ["s"] = "Stage", ["S"] = "StageUnstaged", [""] = "StageAll", + ["K"] = "Untrack", ["u"] = "Unstage", ["U"] = "UnstageStaged", ["$"] = "CommandHistory", diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 66ea9382f..48dd51411 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -7,6 +7,7 @@ local Buffer = require("neogit.lib.buffer") local logger = require("neogit.logger") local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") +local util = require("neogit.lib.util") local fn = vim.fn local api = vim.api @@ -917,6 +918,46 @@ M.n_init_repo = function(self) end end +---@param self StatusBuffer +M.n_untrack = function(self) + return a.void(function() + local selection = self.buffer.ui:get_selection() + if selection.item and selection.item.escaped_path then + if not git.files.is_tracked(selection.item.escaped_path) then + notification.warn(("File %q isn't tracked"):format(selection.item.escaped_path)) + return + end + + git.files.untrack({ selection.item.escaped_path }) + notification.info(("%q untracked"):format(selection.item.escaped_path)) + self:refresh() + end + end) +end + +---@param self StatusBuffer +M.v_untrack = function(self) + return a.void(function() + local selection = self.buffer.ui:get_selection() + if selection.items then + local paths = util.filter_map(selection.items, function(item) + if git.files.is_tracked(item.escaped_path) then + return item.escaped_path + end + end) + + if #paths == 0 then + notification.warn(("Nothing to untrack")) + return + end + + git.files.untrack(paths) + notification.info(("%s files untracked"):format(#paths)) + self:refresh() + end + end) +end + ---@param self StatusBuffer M.n_stage = function(self) return a.void(function() diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index 6b2ab0cc3..d1405bee9 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -101,6 +101,7 @@ function M:open(kind, cwd) [mappings["Discard"]] = self:_action("v_discard"), [mappings["Stage"]] = self:_action("v_stage"), [mappings["Unstage"]] = self:_action("v_unstage"), + [mappings["Untrack"]] = self:_action("v_untrack"), [popups.mapping_for("BisectPopup")] = self:_action("v_bisect_popup"), [popups.mapping_for("BranchPopup")] = self:_action("v_branch_popup"), [popups.mapping_for("CherryPickPopup")] = self:_action("v_cherry_pick_popup"), @@ -124,6 +125,7 @@ function M:open(kind, cwd) n = { ["j"] = self:_action("n_down"), ["k"] = self:_action("n_up"), + [mappings["Untrack"]] = self:_action("n_untrack"), [mappings["Toggle"]] = self:_action("n_toggle"), [mappings["Close"]] = self:_action("n_close"), [mappings["OpenOrScrollDown"]] = self:_action("n_open_or_scroll_down"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 8d510baa8..1f7619d2e 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -130,6 +130,7 @@ end ---| "StageAll" ---| "Unstage" ---| "UnstageStaged" +---| "Untrack" ---| "RefreshBuffer" ---| "GoToFile" ---| "VSplitOpen" @@ -521,6 +522,7 @@ function M.get_default_values() ["S"] = "StageUnstaged", [""] = "StageAll", ["u"] = "Unstage", + ["K"] = "Untrack", ["U"] = "UnstageStaged", ["y"] = "ShowRefs", ["$"] = "CommandHistory", diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 3b1946f68..afea00bca 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -62,6 +62,12 @@ local configurations = { }, }, + rm = config { + flags = { + cached = "--cached" + } + }, + status = config { flags = { short = "-s", @@ -528,6 +534,7 @@ local configurations = { deduplicate = "--deduplicate", exclude_standard = "--exclude-standard", full_name = "--full-name", + error_unmatch = "--error-unmatch", }, }, diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index 1d94f1027..dc58c9197 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -30,4 +30,12 @@ function M.relpath_from_repository(path) return result.stdout[1] end +function M.is_tracked(path) + return git.cli["ls-files"].error_unmatch.files(path).call({ hidden = true, ignore_error = true }).code == 0 +end + +function M.untrack(paths) + return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }).stdout +end + return M diff --git a/lua/neogit/popups/help/actions.lua b/lua/neogit/popups/help/actions.lua index 318ddfa7e..2800a1821 100644 --- a/lua/neogit/popups/help/actions.lua +++ b/lua/neogit/popups/help/actions.lua @@ -115,6 +115,7 @@ M.actions = function() { "Unstage", "Unstage", NONE }, { "UnstageStaged", "Unstage-Staged", NONE }, { "Discard", "Discard", NONE }, + { "Untrack", "Untrack", NONE }, } end From db029ae8ae709f9eabdcd348416455fd0900d33c Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 7 May 2024 23:46:45 +0200 Subject: [PATCH 434/443] Check for success --- lua/neogit/buffers/status/actions.lua | 14 ++++++++------ lua/neogit/lib/git/files.lua | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 48dd51411..4c699d5e6 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -928,9 +928,10 @@ M.n_untrack = function(self) return end - git.files.untrack({ selection.item.escaped_path }) - notification.info(("%q untracked"):format(selection.item.escaped_path)) - self:refresh() + if git.files.untrack({ selection.item.escaped_path }) then + notification.info(("%q untracked"):format(selection.item.escaped_path)) + self:refresh() + end end end) end @@ -951,9 +952,10 @@ M.v_untrack = function(self) return end - git.files.untrack(paths) - notification.info(("%s files untracked"):format(#paths)) - self:refresh() + if git.files.untrack(paths) then + notification.info(("%s files untracked"):format(#paths)) + self:refresh() + end end end) end diff --git a/lua/neogit/lib/git/files.lua b/lua/neogit/lib/git/files.lua index dc58c9197..362ba8ba7 100644 --- a/lua/neogit/lib/git/files.lua +++ b/lua/neogit/lib/git/files.lua @@ -35,7 +35,7 @@ function M.is_tracked(path) end function M.untrack(paths) - return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }).stdout + return git.cli.rm.cached.files(unpack(paths)).call({ hidden = true }).code == 0 end return M From f17da7624eff864215304257221d483ba4c34a08 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 May 2024 13:04:14 +0200 Subject: [PATCH 435/443] fix: this needs to be "unstaged" so resolutions can be staged. --- lua/neogit/lib/git/status.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/git/status.lua b/lua/neogit/lib/git/status.lua index 11123f4ca..6b9203278 100644 --- a/lua/neogit/lib/git/status.lua +++ b/lua/neogit/lib/git/status.lua @@ -101,7 +101,7 @@ local function update_status(state) if kind == "u" then local mode, _, _, _, _, _, _, _, _, name = rest:match(match_u) - table.insert(staged_files, update_file(cwd, old_files_hash.staged_files[name], mode, name)) + table.insert(unstaged_files, update_file(cwd, old_files_hash.unstaged_files[name], mode, name)) elseif kind == "?" then table.insert(untracked_files, update_file(cwd, old_files_hash.untracked_files[rest], "?", rest)) elseif kind == "1" then From ec9574fea5a67c3871e43067e739c5286f3e2a2e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 May 2024 14:42:01 +0200 Subject: [PATCH 436/443] Improvement: Untrack files will list _all_ tracked files in a fuzzy finder, with the selected files on top of the list. Multiple files can be selected. --- lua/neogit/buffers/status/actions.lua | 55 +++++++++++++++++---------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 4c699d5e6..28b5977e6 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -9,6 +9,8 @@ local input = require("neogit.lib.input") local notification = require("neogit.lib.notification") local util = require("neogit.lib.util") +local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder") + local fn = vim.fn local api = vim.api @@ -922,16 +924,25 @@ end M.n_untrack = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() - if selection.item and selection.item.escaped_path then - if not git.files.is_tracked(selection.item.escaped_path) then - notification.warn(("File %q isn't tracked"):format(selection.item.escaped_path)) - return - end + local paths = git.files.all_tree() + + if selection.item + and selection.item.escaped_path + and git.files.is_tracked(selection.item.escaped_path) then + paths = util.deduplicate(util.merge({ selection.item.escaped_path }, paths)) + end - if git.files.untrack({ selection.item.escaped_path }) then - notification.info(("%q untracked"):format(selection.item.escaped_path)) - self:refresh() + local selected = FuzzyFinderBuffer.new(paths):open_async { prompt_prefix = "Untrack file(s)", allow_multi = true } + if selected and #selected > 0 and git.files.untrack(selected) then + local message + if #selected > 1 then + message = ("%s files untracked"):format(#selected) + else + message = ("%q untracked"):format(selected[1]) end + + notification.info(message) + self:refresh() end end) end @@ -940,22 +951,24 @@ end M.v_untrack = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() - if selection.items then - local paths = util.filter_map(selection.items, function(item) - if git.files.is_tracked(item.escaped_path) then - return item.escaped_path - end - end) - - if #paths == 0 then - notification.warn(("Nothing to untrack")) - return + local selected_paths = util.filter_map(selection.items or {}, function(item) + if git.files.is_tracked(item.escaped_path) then + return item.escaped_path end + end) - if git.files.untrack(paths) then - notification.info(("%s files untracked"):format(#paths)) - self:refresh() + local paths = util.deduplicate(util.merge(selected_paths, git.files.all_tree())) + local selected = FuzzyFinderBuffer.new(paths):open_async { prompt_prefix = "Untrack file(s)", allow_multi = true } + if selected and #selected > 0 and git.files.untrack(selected) then + local message + if #selected > 1 then + message = ("Untracked %s files"):format(#selected) + else + message = ("%q untracked"):format(selected[1]) end + + notification.info(message) + self:refresh() end end) end From c72c25ac6a5093ff18ee527a3eda4fd91cb8e3f8 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 May 2024 23:36:06 +0200 Subject: [PATCH 437/443] Add better highlighting of status buffer when file has a merge conflict. --- lua/neogit/buffers/common.lua | 36 +++++++++++++++++++++------ lua/neogit/buffers/status/actions.lua | 11 ++++++++ lua/neogit/lib/hl.lua | 3 +++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lua/neogit/buffers/common.lua b/lua/neogit/buffers/common.lua index dfc253143..b3357296e 100644 --- a/lua/neogit/buffers/common.lua +++ b/lua/neogit/buffers/common.lua @@ -18,9 +18,6 @@ M.EmptyLine = Component.new(function() return col { row { text("") } } end) -local diff_add_start = "+" -local diff_delete_start = "-" - M.Diff = Component.new(function(diff) return col.tag("Diff")({ text(string.format("%s %s", diff.kind, diff.file), { line_hl = "NeogitDiffHeader" }), @@ -52,15 +49,38 @@ M.DiffHunks = Component.new(function(diff) } end) +local diff_add_start = "+" +local diff_add_start_2 = " +" +local diff_delete_start = "-" +local diff_delete_start_2 = " -" + local HunkLine = Component.new(function(line) local line_hl - if string.sub(line, 1, 1) == diff_add_start then - line_hl = "NeogitDiffAdd" - elseif string.sub(line, 1, 1) == diff_delete_start then - line_hl = "NeogitDiffDelete" + -- TODO: Should use file mode, not merge head + if git.repo.state.merge.head then + if + line:match("..<<<<<<<") + or line:match("..|||||||") + or line:match("..=======") + or line:match("..>>>>>>>") + then + line_hl = "NeogitHunkMergeHeader" + elseif string.sub(line, 1, 1) == diff_add_start or string.sub(line, 1, 2) == diff_add_start_2 then + line_hl = "NeogitDiffAdd" + elseif string.sub(line, 1, 1) == diff_delete_start or string.sub(line, 1, 2) == diff_delete_start_2 then + line_hl = "NeogitDiffDelete" + else + line_hl = "NeogitDiffContext" + end else - line_hl = "NeogitDiffContext" + if string.sub(line, 1, 1) == diff_add_start then + line_hl = "NeogitDiffAdd" + elseif string.sub(line, 1, 1) == diff_delete_start then + line_hl = "NeogitDiffDelete" + else + line_hl = "NeogitDiffContext" + end end return text(line, { line_hl = line_hl }) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 28b5977e6..3b0dc731e 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -187,6 +187,11 @@ M.v_stage = function(self) for _, section in ipairs(selection.sections) do if section.name == "unstaged" or section.name == "untracked" then for _, item in ipairs(section.items) do + if item.mode == "UU" then + notification.info("Conflicts must be resolved before staging lines") + return + end + local hunks = self.buffer.ui:item_hunks(item, selection.first_line, selection.last_line, true) if #hunks > 0 then @@ -987,6 +992,12 @@ M.n_stage = function(self) if stagable.hunk then local item = self.buffer.ui:get_item_under_cursor() assert(item, "Item cannot be nil") + + if item.mode == "UU" then + notification.info("Conflicts must be resolved before staging hunks") + return + end + local patch = git.index.generate_patch(item, stagable.hunk, stagable.hunk.from, stagable.hunk.to) git.index.apply(patch, { cached = true }) diff --git a/lua/neogit/lib/hl.lua b/lua/neogit/lib/hl.lua index c9c8fdf0e..90d600b91 100644 --- a/lua/neogit/lib/hl.lua +++ b/lua/neogit/lib/hl.lua @@ -149,6 +149,9 @@ function M.setup() NeogitSignatureGoodExpiredKey = { link = "NeogitGraphYellow" }, NeogitSignatureGoodRevokedKey = { link = "NeogitGraphRed" }, NeogitCursorLine = { link = "CursorLine" }, + NeogitHunkMergeHeader = { fg = palette.bg2, bg = palette.grey, bold = palette.bold }, + NeogitHunkMergeHeaderHighlight= { fg = palette.bg0, bg = palette.bg_cyan, bold = palette.bold }, + NeogitHunkMergeHeaderCursor = { fg = palette.bg0, bg = palette.bg_cyan, bold = palette.bold }, NeogitHunkHeader = { fg = palette.bg0, bg = palette.grey, bold = palette.bold }, NeogitHunkHeaderHighlight = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, NeogitHunkHeaderCursor = { fg = palette.bg0, bg = palette.md_purple, bold = palette.bold }, From b8ace31783425df144e393e98480bf0aa62017e2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 May 2024 23:53:41 +0200 Subject: [PATCH 438/443] replace plenary.job with vim.system --- lua/neogit/lib/git/cli.lua | 57 +++++++++----------------------------- 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index 01d70018b..b16626ccf 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -64,8 +64,8 @@ local configurations = { rm = config { flags = { - cached = "--cached" - } + cached = "--cached", + }, }, status = config { @@ -321,15 +321,15 @@ local configurations = { absorb = config { flags = { verbose = "--verbose", - and_rebase = "--and-rebase" + and_rebase = "--and-rebase", }, aliases = { base = function(tbl) return function(commit) return tbl.args("--base", commit) end - end - } + end, + }, }, commit = config { @@ -612,45 +612,15 @@ local configurations = { -- git_root_of_cwd() returns the git repo of the cwd, which can change anytime -- after git_root_of_cwd() has been called. local function git_root(dir) - local job = require("plenary.job") - local args = { "-C", dir, "rev-parse", "--show-toplevel" } - local gitdir = Path:new(dir):absolute() -- default to current directory - job - :new({ - command = "git", - args = args, - on_exit = function(job_output, return_val) - if return_val == 0 then - -- Replace directory with the output of the git toplevel directory - gitdir = Path:new(job_output):absolute() - else - logger.warn("[CLI]: ", job_output) - end - end, - }) - :sync() - return gitdir + local cmd = { "git", "-C", dir, "rev-parse", "--show-toplevel" } + local result = vim.system(cmd, { text = true }):wait() + return Path:new(vim.trim(result.stdout)):absolute() end -local is_inside_worktree = function(cwd) - local job = require("plenary.job") - local args = { "rev-parse", "--is-inside-work-tree" } - local returnval = false - if cwd then - args = { "-C", cwd, "rev-parse", "--is-inside-work-tree" } - end - job - :new({ - command = "git", - args = args, - on_exit = function(_, return_val) - if return_val == 0 then - returnval = true - end - end, - }) - :sync() - return returnval +local function is_inside_worktree(dir) + local cmd = { "git", "-C", dir, "rev-parse", "--is-inside-work-tree" } + local result = vim.system(cmd):wait() + return result.code == 0 end local history = {} @@ -861,8 +831,7 @@ local function handle_line_interactive(p, line) handler = handle_interactive_authenticity elseif line:match("^Username for ") then handler = handle_interactive_username - elseif line:match("^Enter passphrase") - or line:match("^Password for") then + elseif line:match("^Enter passphrase") or line:match("^Password for") then handler = handle_interactive_password end From 767c6ed6ab5e11649e6b338e5d5caee22f229c8a Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 10 May 2024 23:03:08 +0200 Subject: [PATCH 439/443] remove buffer buffers - not needed --- lua/neogit/lib/buffer.lua | 66 ++------------------------------------- 1 file changed, 2 insertions(+), 64 deletions(-) diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index bbc19a552..5d36191e2 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -35,11 +35,6 @@ function Buffer:new(handle, win_handle) namespaces = { default = api.nvim_create_namespace("neogit-buffer-" .. handle), }, - line_buffer = {}, - hl_buffer = {}, - line_hl_buffer = {}, - ext_buffer = {}, - fold_buffer = {}, } this.ui = Ui.new(this) @@ -124,40 +119,8 @@ function Buffer:insert_line(line) api.nvim_buf_set_lines(self.handle, line_nr, line_nr, false, { line }) end -function Buffer:buffered_set_line(line) - table.insert(self.line_buffer, line) -end - -function Buffer:buffered_add_highlight(...) - table.insert(self.hl_buffer, { ... }) -end - -function Buffer:buffered_set_extmark(...) - table.insert(self.ext_buffer, { ... }) -end - -function Buffer:buffered_create_fold(...) - table.insert(self.fold_buffer, { ... }) -end - -function Buffer:buffered_add_line_highlight(...) - table.insert(self.line_hl_buffer, { ... }) -end - function Buffer:resize(length) - api.nvim_buf_set_lines(self.handle, length or #self.line_buffer, -1, false, {}) -end - -function Buffer:flush_line_buffer() - if self.line_buffer[1] then - api.nvim_buf_set_lines(self.handle, 0, -1, false, self.line_buffer) - self.line_buffer = {} - end -end - -function Buffer:flush_highlight_buffer() - self:set_highlights(self.hl_buffer) - self.hl_buffer = {} + api.nvim_buf_set_lines(self.handle, length, -1, false, {}) end function Buffer:set_highlights(highlights) @@ -166,32 +129,18 @@ function Buffer:set_highlights(highlights) end end -function Buffer:flush_extmark_buffer() - self:set_extmarks(self.ext_buffer) - self.ext_buffer = {} -end - function Buffer:set_extmarks(extmarks) for _, ext in ipairs(extmarks) do self:set_extmark(unpack(ext)) end end -function Buffer:flush_line_highlight_buffer() - self:set_line_highlights(self.line_hl_buffer) - self.line_hl_buffer = {} -end - function Buffer:set_line_highlights(highlights) for _, hl in ipairs(highlights) do self:add_line_highlight(unpack(hl)) end end -function Buffer:flush_fold_buffer() - self:set_folds(self.fold_buffer) - self.fold_buffer = {} -end function Buffer:set_folds(folds) self:set_window_option("foldmethod", "manual") @@ -202,15 +151,6 @@ function Buffer:set_folds(folds) end end -function Buffer:flush_buffers() - self:clear_namespace("default") - self:flush_line_buffer() - self:flush_highlight_buffer() - self:flush_extmark_buffer() - self:flush_line_highlight_buffer() - self:flush_fold_buffer() -end - function Buffer:set_text(first_line, last_line, first_col, last_col, lines) api.nvim_buf_set_text(self.handle, first_line, first_col, last_line, last_col, lines) end @@ -740,13 +680,11 @@ function Buffer.create(config) line == cursor and "Cursor" or "Highlight" ) - buffer:buffered_add_line_highlight(line - 1, line_hl, { + buffer:add_line_highlight(line - 1, line_hl, { priority = 200, namespace = "ViewContext", }) end - - buffer:flush_line_highlight_buffer() end, }) end From 5033eb3e768215b9ebf566c048f7ee4042912bba Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 11 May 2024 23:18:20 +0200 Subject: [PATCH 440/443] Refactor: Remove mapping for Console buffer, change buffer to be ephemeral, change default to be a floating window. --- lua/neogit/buffers/process/init.lua | 107 +++++++++++++++++++++++ lua/neogit/buffers/status/actions.lua | 7 -- lua/neogit/buffers/status/init.lua | 1 - lua/neogit/config.lua | 4 +- lua/neogit/lib/buffer.lua | 32 +++++-- lua/neogit/process.lua | 121 ++++---------------------- 6 files changed, 152 insertions(+), 120 deletions(-) create mode 100644 lua/neogit/buffers/process/init.lua diff --git a/lua/neogit/buffers/process/init.lua b/lua/neogit/buffers/process/init.lua new file mode 100644 index 000000000..c41e2ee4e --- /dev/null +++ b/lua/neogit/buffers/process/init.lua @@ -0,0 +1,107 @@ +local Buffer = require("neogit.lib.buffer") +local config = require("neogit.config") +local status_maps = require("neogit.config").get_reversed_status_maps() + +---@class ProcessBuffer +---@field buffer Buffer +---@field open fun(self) +---@field hide fun(self) +---@field close fun(self) +---@field focus fun(self) +---@field show fun(self) +---@field is_visible fun(self): boolean +---@field append fun(self, data: string) +---@field new fun(self): ProcessBuffer +---@see Buffer +---@see Ui +local M = {} +M.__index = M + +---@return ProcessBuffer +---@param process Process +function M:new(process) + local instance = { + content = string.format("> %s\r\n", table.concat(process.cmd, " ")), + process = process, + buffer = nil, + } + + setmetatable(instance, self) + return instance +end + +function M:hide() + if self.buffer then + self.buffer:hide() + end +end + +function M:close() + if self.buffer then + self.buffer:close() + self.buffer = nil + end +end + +function M:focus() + assert(self.buffer, "Create a buffer first") + self.buffer:focus() +end + +function M:show() + if not self.buffer then + self:open() + end + + self.buffer:chan_send(self.content) + self.buffer:show() + self.buffer:call(vim.cmd.normal, "G") +end + +function M:is_visible() + return self.buffer and self.buffer:is_visible() +end + +function M:append(data) + vim.schedule(function() + self.content = table.concat({ self.content, data }, "\r\n") + end) +end + +---@return ProcessBuffer +function M:open() + if self.buffer then + return self + end + + self.buffer = Buffer.create { + name = "NeogitConsole", + filetype = "NeogitConsole", + bufhidden = "hide", + open = false, + buftype = false, + kind = config.values.preview_buffer.kind, + on_detach = function() + self.buffer = nil + end, + autocmds = { + ["WinLeave"] = function() + pcall(self.close, self) + end, + }, + mappings = { + n = { + [status_maps["Close"]] = function() + self:hide() + end, + [""] = function() + self:hide() + end, + }, + }, + } + + return self +end + +return M diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 3b0dc731e..1861280ee 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -599,13 +599,6 @@ M.n_command_history = function(self) end) end ----@param self StatusBuffer -M.n_console = function(self) - return function() - require("neogit.process").show_console() - end -end - ---@param self StatusBuffer M.n_show_refs = function(self) return a.void(function() diff --git a/lua/neogit/buffers/status/init.lua b/lua/neogit/buffers/status/init.lua index d1405bee9..52677a265 100644 --- a/lua/neogit/buffers/status/init.lua +++ b/lua/neogit/buffers/status/init.lua @@ -136,7 +136,6 @@ function M:open(kind, cwd) [mappings["Depth3"]] = self:_action("n_depth3"), [mappings["Depth4"]] = self:_action("n_depth4"), [mappings["CommandHistory"]] = self:_action("n_command_history"), - [mappings["Console"]] = self:_action("n_console"), [mappings["ShowRefs"]] = self:_action("n_show_refs"), [mappings["YankSelected"]] = self:_action("n_yank_selected"), [mappings["Discard"]] = self:_action("n_discard"), diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 58ee3ad67..9f4c6ed2b 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -138,7 +138,6 @@ end ---| "TabOpen" ---| "GoToPreviousHunkHeader" ---| "GoToNextHunkHeader" ----| "Console" ---| "CommandHistory" ---| "ShowRefs" ---| "InitRepo" @@ -363,7 +362,7 @@ function M.get_default_values() kind = "auto", }, preview_buffer = { - kind = "split", + kind = "floating", }, popup = { kind = "split", @@ -528,7 +527,6 @@ function M.get_default_values() ["U"] = "UnstageStaged", ["y"] = "ShowRefs", ["$"] = "CommandHistory", - ["#"] = "Console", ["Y"] = "YankSelected", [""] = "RefreshBuffer", [""] = "GoToFile", diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 5d36191e2..8d9d4c975 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -12,6 +12,7 @@ local Path = require("plenary.path") ---@field handle number ---@field win_handle number ---@field namespaces table +---@field autocmd_group number ---@field ui Ui ---@field kind string ---@field disable_line_numbers boolean @@ -28,6 +29,7 @@ Buffer.__index = Buffer ---@return Buffer function Buffer:new(handle, win_handle) local this = { + autocmd_group = api.nvim_create_augroup("Neogit-augroup-" .. handle, { clear = true }), handle = handle, win_handle = win_handle, border = nil, @@ -283,7 +285,7 @@ function Buffer:show() row = row, style = "minimal", focusable = false, - border = "single", + border = "rounded", }) api.nvim_win_set_cursor(content_window, { 1, 0 }) @@ -439,8 +441,15 @@ function Buffer:set_filetype(ft) self:set_buffer_option("filetype", ft) end -function Buffer:call(f) - api.nvim_buf_call(self.handle, f) +function Buffer:call(f, ...) + local args = { ... } + api.nvim_buf_call(self.handle, function() + f(unpack(args)) + end) +end + +function Buffer:chan_send(data) + api.nvim_chan_send(api.nvim_open_term(self.handle, {}), data) end function Buffer:win_exec(cmd) @@ -505,7 +514,7 @@ end ---@field filetype string|nil ---@field bufhidden string|nil ---@field header string|nil ----@field buftype string|nil +---@field buftype string|nil|boolean ---@field cwd string|nil ---@field status_column string|nil ---@field load boolean|nil @@ -551,11 +560,14 @@ function Buffer.create(config) logger.debug("[BUFFER:" .. buffer.handle .. "] Setting buffer options") buffer:set_buffer_option("swapfile", false) buffer:set_buffer_option("bufhidden", config.bufhidden or "wipe") - buffer:set_buffer_option("buftype", config.buftype or "nofile") buffer:set_buffer_option("modifiable", config.modifiable or false) buffer:set_buffer_option("modified", config.modifiable or false) buffer:set_buffer_option("readonly", config.readonly or false) + if config.buftype ~= false then + buffer:set_buffer_option("buftype", config.buftype or "nofile") + end + if vim.fn.has("nvim-0.10") ~= 1 then logger.debug("[BUFFER:" .. buffer.handle .. "] Setting foldtext function for nvim < 0.10") -- selene: allow(global_usage) @@ -628,13 +640,19 @@ function Buffer.create(config) buffer.ui:render(unpack(config.render(buffer))) end - local neogit_augroup = require("neogit").autocmd_group for event, callback in pairs(config.autocmds or {}) do logger.debug("[BUFFER:" .. buffer.handle .. "] Setting autocmd for: " .. event) api.nvim_create_autocmd(event, { callback = callback, buffer = buffer.handle, - group = neogit_augroup, + group = buffer.autocmd_group, + }) + + api.nvim_buf_attach(buffer.handle, false, { + on_detach = function() + logger.debug("[BUFFER:" .. buffer.handle .. "] Clearing autocmd group") + api.nvim_del_augroup_by_id(buffer.autocmd_group) + end, }) end diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index 01667b18f..cf658b739 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -1,7 +1,6 @@ local a = require("plenary.async") local notification = require("neogit.lib.notification") -local Buffer = require("neogit.lib.buffer") local config = require("neogit.config") local logger = require("neogit.logger") @@ -29,6 +28,7 @@ end ---@field job number|nil ---@field stdin number|nil ---@field pty boolean|nil +---@field buffer ProcessBuffer ---@field on_partial_line fun(process: Process, data: string, raw: string)|nil callback on complete lines ---@field on_error (fun(res: ProcessResult): boolean) Intercept the error externally, returning false prevents the error from being logged local Process = {} @@ -36,6 +36,7 @@ Process.__index = Process ---@type { number: Process } local processes = {} +setmetatable(processes, { __mode = "k" }) ---@class ProcessResult ---@field stdout string[] @@ -64,112 +65,25 @@ ProcessResult.__index = ProcessResult ---@param process Process ---@return Process function Process.new(process) + process.buffer = require("neogit.buffers.process"):new(process) return setmetatable(process, Process) end -local preview_buffer = nil - -local function create_preview_buffer() - local kind = config.values.preview_buffer.kind - - -- May be called multiple times due to scheduling - if preview_buffer then - if preview_buffer.buffer then - logger.trace("[PROCESS] Preview buffer already exists. Focusing the existing one") - preview_buffer.buffer:focus() - end - return - end - - local name = "NeogitConsole" - local cur = vim.fn.bufnr(name) - if cur and cur ~= -1 then - vim.api.nvim_buf_delete(cur, { force = true }) - end - - local buffer = Buffer.create { - name = name, - bufhidden = "hide", - filetype = "NeogitConsole", - kind = kind, - open = false, - mappings = { - n = { - ["q"] = function(buffer) - buffer:hide(true) - end, - [""] = function(buffer) - buffer:hide(true) - end, - }, - }, - autocmds = { - ["BufUnload"] = function() - preview_buffer = nil - end, - }, - } - - preview_buffer = { - buffer = buffer, - current_span = nil, - content = "", - } -end - -function Process.show_console() - create_preview_buffer() - - vim.api.nvim_chan_send(vim.api.nvim_open_term(preview_buffer.buffer.handle, {}), preview_buffer.content) - preview_buffer.buffer:show() - -- Scroll to the end of viewable text - vim.cmd.startinsert() - vim.defer_fn(vim.cmd.stopinsert, 50) - - -- vim.api.nvim_win_call(win, function() - -- vim.cmd.normal("G") - -- end) -end - ----@param process Process ----@param data string -local function append_log(process, data) - local function append() - if data == "" then - return - end - - if preview_buffer.current_span ~= process.job then - preview_buffer.content = preview_buffer.content - .. string.format("> %s\r\n", table.concat(process.cmd, " ")) - preview_buffer.current_span = process.job - end - - preview_buffer.content = preview_buffer.content .. data .. "\r\n" - end - - vim.schedule(function() - create_preview_buffer() - append() - end) -end - local hide_console = false function Process.hide_preview_buffers() hide_console = true + --- Stop all times from opening the buffer for _, v in pairs(processes) do v:stop_timer() end - - if preview_buffer then - preview_buffer.buffer:hide() - end end function Process:start_timer() if self.timer == nil then local timer = vim.loop.new_timer() + self.timer = timer + timer:start( config.values.console_timeout, 0, @@ -177,24 +91,26 @@ function Process:start_timer() if not self.timer then return end + self:stop_timer() + if not self.result or (self.result.code ~= 0) then local message = string.format( "Command %q running for more than: %.1f seconds", - table.concat(self.cmd, " "), + mask_command(table.concat(self.cmd, " ")), math.ceil((vim.loop.now() - self.start) / 100) / 10 ) - append_log(self, message) + self.buffer:append(message) + if config.values.auto_show_console then - Process.show_console() + self.buffer:show() else notification.warn(message .. "\n\nOpen the console for details") end end end) ) - self.timer = timer end end @@ -203,6 +119,7 @@ function Process:stop_timer() local timer = self.timer self.timer = nil timer:stop() + if not timer:is_closing() then timer:close() end @@ -277,7 +194,7 @@ function Process:spawn(cb) }, ProcessResult) assert(self.job == nil, "Process started twice") - -- An empty table is treated as an array + self.env = self.env or {} self.env.TERM = "xterm-256color" @@ -314,14 +231,14 @@ function Process:spawn(cb) table.insert(res.stdout_raw, raw) if self.verbose then table.insert(res.output, line) - append_log(self, raw) + self.buffer:append(raw) end end) local on_stderr, stderr_cleanup = handle_output(function() end, function(line, raw) table.insert(res.stderr, line) table.insert(res.output, line) - append_log(self, raw) + self.buffer:append(raw) end) local function on_exit(_, code) @@ -336,9 +253,9 @@ function Process:spawn(cb) stdout_cleanup() stderr_cleanup() - if not hide_console and code > 0 and self.on_error(res) then - append_log(self, string.format("Process exited with code: %d", code)) + self.buffer:append(string.format("Process exited with code: %d", code)) + if not self.buffer:is_visible() and code > 0 and self.on_error(res) then local output = {} local start = math.max(#res.output - 16, 1) for i = start, math.min(#res.output, start + 16) do @@ -366,7 +283,7 @@ function Process:spawn(cb) local job = vim.fn.jobstart(self.cmd, { cwd = self.cwd, env = self.env, - pty = not not self.pty, -- Fake a small standard terminal + pty = not not self.pty, width = 80, height = 24, on_stdout = on_stdout, From eedcb6b954432b7d1901dd7e3758d829b4e8f20d Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 May 2024 10:06:46 +0200 Subject: [PATCH 441/443] add diff popup to commit view --- lua/neogit/buffers/commit_view/init.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 111f07aaf..eda91a522 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -251,6 +251,12 @@ function M:open(kind) p { commit = self.commit_info.oid } end), [popups.mapping_for("PullPopup")] = popups.open("pull"), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + p { + section = { name = "log" }, + item = { name = self.commit_info.oid }, + } + end), [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.commit_info.oid } } end), From 4f0ca51a8222284e4d7553537ecd6012efb85827 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 May 2024 11:17:58 +0200 Subject: [PATCH 442/443] improve how we goto files --- lua/neogit/buffers/status/actions.lua | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index 1861280ee..ad0adc14d 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -55,13 +55,7 @@ local function translate_cursor_location(self, item) end local function open(type, path, cursor) - path = fn.fnameescape(path) - vim.cmd[type](path) - - local buf = Buffer.from_name(path) - if buf:is_focused() and cursor then - buf:move_cursor(cursor) - end + vim.cmd(("silent! %s %s | %s | norm! zz"):format(type, fn.fnameescape(path), cursor and cursor[1] or "1")) end local M = {} @@ -1079,17 +1073,7 @@ M.n_goto_file = function(self) if item and item.absolute_path then local cursor = translate_cursor_location(self, item) self:close() - - -- TODO: Does this work? - vim.schedule(function() - vim.cmd("edit! " .. fn.fnameescape(item.absolute_path)) - - local buf = Buffer.from_name(fn.fnameescape(item.absolute_path)) - if buf:is_focused() and cursor then - buf:move_cursor(cursor) - end - end) - + open("edit", item.absolute_path, cursor) return end From eca97dbbb964d0de1e17a21525d4f895669b1b2f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 May 2024 11:19:25 +0200 Subject: [PATCH 443/443] lint --- lua/neogit/buffers/diff/ui.lua | 4 +++- lua/neogit/buffers/editor/init.lua | 2 +- lua/neogit/buffers/status/actions.lua | 12 ++++++++---- lua/neogit/client.lua | 6 ++++-- lua/neogit/lib/buffer.lua | 1 - lua/neogit/lib/popup/init.lua | 18 ++++++------------ lua/neogit/popups/commit/actions.lua | 10 ++++++---- lua/neogit/popups/log/actions.lua | 3 ++- lua/neogit/popups/merge/actions.lua | 24 ++++-------------------- lua/neogit/process.lua | 2 +- 10 files changed, 35 insertions(+), 47 deletions(-) diff --git a/lua/neogit/buffers/diff/ui.lua b/lua/neogit/buffers/diff/ui.lua index bd151ae7e..c48bb7286 100644 --- a/lua/neogit/buffers/diff/ui.lua +++ b/lua/neogit/buffers/diff/ui.lua @@ -25,7 +25,9 @@ function M.OverviewFile(file_padding) end function M.DiffView(header, stats, diffs) - local file_padding = util.max_length(map(diffs, function(diff) return diff.file end)) + local file_padding = util.max_length(map(diffs, function(diff) + return diff.file + end)) return { text.highlight("NeogitFloatHeaderHighlight")(header), diff --git a/lua/neogit/buffers/editor/init.lua b/lua/neogit/buffers/editor/init.lua index a5f891314..1159b3922 100644 --- a/lua/neogit/buffers/editor/init.lua +++ b/lua/neogit/buffers/editor/init.lua @@ -88,7 +88,7 @@ function M:open(kind) diff_view:close() diff_view = nil end - end + end, }, on_detach = function(buffer) logger.debug("[EDITOR] Cleaning Up") diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index ad0adc14d..7d2f2e61b 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -918,13 +918,16 @@ M.n_untrack = function(self) local selection = self.buffer.ui:get_selection() local paths = git.files.all_tree() - if selection.item + if + selection.item and selection.item.escaped_path - and git.files.is_tracked(selection.item.escaped_path) then + and git.files.is_tracked(selection.item.escaped_path) + then paths = util.deduplicate(util.merge({ selection.item.escaped_path }, paths)) end - local selected = FuzzyFinderBuffer.new(paths):open_async { prompt_prefix = "Untrack file(s)", allow_multi = true } + local selected = FuzzyFinderBuffer.new(paths) + :open_async { prompt_prefix = "Untrack file(s)", allow_multi = true } if selected and #selected > 0 and git.files.untrack(selected) then local message if #selected > 1 then @@ -950,7 +953,8 @@ M.v_untrack = function(self) end) local paths = util.deduplicate(util.merge(selected_paths, git.files.all_tree())) - local selected = FuzzyFinderBuffer.new(paths):open_async { prompt_prefix = "Untrack file(s)", allow_multi = true } + local selected = FuzzyFinderBuffer.new(paths) + :open_async { prompt_prefix = "Untrack file(s)", allow_multi = true } if selected and #selected > 0 and git.files.untrack(selected) then local message if #selected > 1 then diff --git a/lua/neogit/client.lua b/lua/neogit/client.lua index f2adf264c..a52c954ab 100644 --- a/lua/neogit/client.lua +++ b/lua/neogit/client.lua @@ -14,7 +14,8 @@ function M.get_nvim_remote_editor(show_diff) logger.debug("[CLIENT] Neogit path: " .. neogit_path) logger.debug("[CLIENT] Neovim path: " .. nvim_path) local runtimepath_cmd = fn.shellescape(fmt("set runtimepath^=%s", fn.fnameescape(tostring(neogit_path)))) - local lua_cmd = fn.shellescape("lua require('neogit.client').client({ show_diff = " .. tostring(show_diff) .. " })") + local lua_cmd = + fn.shellescape("lua require('neogit.client').client({ show_diff = " .. tostring(show_diff) .. " })") local shell_cmd = { nvim_path, @@ -63,7 +64,8 @@ function M.client(opts) local client = fn.serverstart() logger.debug(("[CLIENT] Client address: %s"):format(client)) - local lua_cmd = fmt('lua require("neogit.client").editor("%s", "%s", %s)', file_target, client, opts.show_diff) + local lua_cmd = + fmt('lua require("neogit.client").editor("%s", "%s", %s)', file_target, client, opts.show_diff) if vim.loop.os_uname().sysname == "Windows_NT" then lua_cmd = lua_cmd:gsub("\\", "/") diff --git a/lua/neogit/lib/buffer.lua b/lua/neogit/lib/buffer.lua index 8d9d4c975..c5dc0cf3b 100644 --- a/lua/neogit/lib/buffer.lua +++ b/lua/neogit/lib/buffer.lua @@ -143,7 +143,6 @@ function Buffer:set_line_highlights(highlights) end end - function Buffer:set_folds(folds) self:set_window_option("foldmethod", "manual") diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index e80ddf5a8..bc2d7fe6c 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -170,14 +170,11 @@ function M:set_option(option) elseif option.fn then option.value = option.fn(self, option) else - local input = input.get_user_input( - option.cli, - { - separator = "=", - default = option.value, - cancel = option.value, - } - ) + local input = input.get_user_input(option.cli, { + separator = "=", + default = option.value, + cancel = option.value, + }) -- If the option specifies a default value, and the user set the value to be empty, defer to default value. -- This is handy to prevent the user from accidentally loading thousands of log entries by accident. @@ -210,10 +207,7 @@ function M:set_config(config) elseif config.fn then config.value = config.fn(self, config) else - local result = input.get_user_input( - config.name, - { default = config.value, cancel = config.value } - ) + local result = input.get_user_input(config.name, { default = config.value, cancel = config.value }) config.value = result git.config.set(config.name, config.value) diff --git a/lua/neogit/popups/commit/actions.lua b/lua/neogit/popups/commit/actions.lua index fe1460d36..9d188830e 100644 --- a/lua/neogit/popups/commit/actions.lua +++ b/lua/neogit/popups/commit/actions.lua @@ -170,10 +170,12 @@ function M.absorb(popup) end end - local commit = popup.state.env.commit or CommitSelectViewBuffer.new( - git.log.list { "HEAD" }, - "Select a base commit for the absorb stack with , or to abort" - ):open_async()[1] + local commit = popup.state.env.commit + or CommitSelectViewBuffer.new( + git.log.list { "HEAD" }, + "Select a base commit for the absorb stack with , or to abort" + ) + :open_async()[1] if not commit then return end diff --git a/lua/neogit/popups/log/actions.lua b/lua/neogit/popups/log/actions.lua index 5f97b4f01..4d00da511 100644 --- a/lua/neogit/popups/log/actions.lua +++ b/lua/neogit/popups/log/actions.lua @@ -40,7 +40,8 @@ M.log_current = operation("log_current", function(popup) popup:get_internal_arguments(), popup.state.env.files, fetch_more_commits(popup, {}) - ):open() + ) + :open() end) function M.log_head(popup) diff --git a/lua/neogit/popups/merge/actions.lua b/lua/neogit/popups/merge/actions.lua index c817b546a..272c50de1 100644 --- a/lua/neogit/popups/merge/actions.lua +++ b/lua/neogit/popups/merge/actions.lua @@ -21,11 +21,7 @@ function M.abort() end function M.merge(popup) - local refs = util.merge( - { popup.state.env.commit }, - git.refs.list_branches(), - git.refs.list_tags() - ) + local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) local ref = FuzzyFinderBuffer.new(refs):open_async() if ref then @@ -36,11 +32,7 @@ function M.merge(popup) end function M.squash(popup) - local refs = util.merge( - { popup.state.env.commit }, - git.refs.list_branches(), - git.refs.list_tags() - ) + local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) local ref = FuzzyFinderBuffer.new(refs):open_async() if ref then @@ -51,11 +43,7 @@ function M.squash(popup) end function M.merge_edit(popup) - local refs = util.merge( - { popup.state.env.commit }, - git.refs.list_branches(), - git.refs.list_tags() - ) + local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) local ref = FuzzyFinderBuffer.new(refs):open_async() if ref then @@ -71,11 +59,7 @@ function M.merge_edit(popup) end function M.merge_nocommit(popup) - local refs = util.merge( - { popup.state.env.commit }, - git.refs.list_branches(), - git.refs.list_tags() - ) + local refs = util.merge({ popup.state.env.commit }, git.refs.list_branches(), git.refs.list_tags()) local ref = FuzzyFinderBuffer.new(refs):open_async() if ref then diff --git a/lua/neogit/process.lua b/lua/neogit/process.lua index cf658b739..cd734cc95 100644 --- a/lua/neogit/process.lua +++ b/lua/neogit/process.lua @@ -231,7 +231,7 @@ function Process:spawn(cb) table.insert(res.stdout_raw, raw) if self.verbose then table.insert(res.output, line) - self.buffer:append(raw) + self.buffer:append(raw) end end)