Skip to content

Add log margin popup #1715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
14 changes: 14 additions & 0 deletions lua/neogit/buffers/status/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,13 @@ M.v_log_popup = function(_self)
return popups.open("log")
end

---@param self StatusBuffer
M.v_margin_popup = function(self)
return popups.open("margin", function(p)
p { buffer = self }
end)
end

---@param _self StatusBuffer
M.v_worktree_popup = function(_self)
return popups.open("worktree")
Expand Down Expand Up @@ -1408,6 +1415,13 @@ M.n_log_popup = function(_self)
return popups.open("log")
end

---@param self StatusBuffer
M.n_margin_popup = function(self)
return popups.open("margin", function(p)
p { buffer = self }
end)
end

---@param _self StatusBuffer
M.n_worktree_popup = function(_self)
return popups.open("worktree")
Expand Down
2 changes: 2 additions & 0 deletions lua/neogit/buffers/status/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ function M:open(kind)
[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("MarginPopup")] = self:_action("v_margin_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"),
Expand Down Expand Up @@ -182,6 +183,7 @@ function M:open(kind)
[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("MarginPopup")] = self:_action("n_margin_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"),
Expand Down
82 changes: 81 additions & 1 deletion lua/neogit/buffers/status/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ 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 config = require("neogit.config")
local a = require("plenary.async")
local state = require("neogit.lib.state")

local col = Ui.col
local row = Ui.row
Expand Down Expand Up @@ -359,6 +361,79 @@ local SectionItemCommit = Component.new(function(item)
end
end

local virtual_text

-- Render author and date in margin, if visible
if state.get({ "margin", "visibility" }, false) then
local margin_date_style = state.get({ "margin", "date_style" }, 1)
local details = state.get({ "margin", "details" }, false)

local date
local rel_date
local date_width = 10
local clamp_width = 30 -- to avoid having too much space when relative date is short

-- Render date
if item.commit.rel_date:match(" years?,") then
rel_date, _ = item.commit.rel_date:gsub(" years?,", "y")
rel_date = rel_date .. " "
elseif item.commit.rel_date:match("^%d ") then
rel_date = " " .. item.commit.rel_date
else
rel_date = item.commit.rel_date
end

if margin_date_style == 1 then -- relative date (short)
local unpacked = vim.split(rel_date, " ")

-- above, we added a space if the rel_date started with a single number
-- we get the last two elements to deal with that
local date_number = unpacked[#unpacked - 1]
local date_quantifier = unpacked[#unpacked]
if date_quantifier:match("months?") then
date_quantifier = date_quantifier:gsub("m", "M") -- to distinguish from minutes
end

-- add back the space if we have a single number
local left_pad
if #unpacked > 2 then
left_pad = " "
else
left_pad = ""
end

date = left_pad .. date_number .. date_quantifier:sub(1, 1)
date_width = 3
clamp_width = 23
elseif margin_date_style == 2 then -- relative date (long)
date = rel_date
date_width = 10
else -- local iso date
if config.values.log_date_format == nil then
-- we get the unix date to be able to convert the date to the local timezone
date = os.date("%Y-%m-%d %H:%M", item.commit.unix_date)
date_width = 16 -- TODO: what should the width be here?
else
date = item.commit.log_date
date_width = 16
end
end

local author_table = { "" }
if details then
author_table = {
util.str_clamp(item.commit.author_name, clamp_width - (#date > date_width and #date or date_width)),
"NeogitGraphAuthor",
}
end

virtual_text = {
{ " ", "Constant" },
author_table,
{ util.str_min_width(date, date_width), "Special" },
}
end

return row(
util.merge(
{ text.highlight("NeogitObjectId")(item.commit.abbreviated_commit) },
Expand All @@ -367,7 +442,12 @@ local SectionItemCommit = Component.new(function(item)
ref_last,
{ text(item.commit.subject) }
),
{ oid = item.commit.oid, yankable = item.commit.oid, item = item }
{
virtual_text = virtual_text,
oid = item.commit.oid,
yankable = item.commit.oid,
item = item,
}
)
end)

Expand Down
2 changes: 2 additions & 0 deletions lua/neogit/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ end
---| "PushPopup"
---| "CommitPopup"
---| "LogPopup"
---| "MarginPopup"
---| "RevertPopup"
---| "StashPopup"
---| "IgnorePopup"
Expand Down Expand Up @@ -626,6 +627,7 @@ function M.get_default_values()
["c"] = "CommitPopup",
["f"] = "FetchPopup",
["l"] = "LogPopup",
["L"] = "MarginPopup",
["m"] = "MergePopup",
["p"] = "PullPopup",
["r"] = "RebasePopup",
Expand Down
2 changes: 2 additions & 0 deletions lua/neogit/lib/git/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)"
---@field verification_flag string?
---@field rel_date string
---@field log_date string
---@field unix_date string

---Parses the provided list of lines into a CommitLogEntry
---@param raw string[]
Expand Down Expand Up @@ -340,6 +341,7 @@ local function format(show_signature)
committer_date = "%cD",
rel_date = "%cr",
log_date = "%cd",
unix_date = "%ct",
}

if show_signature then
Expand Down
15 changes: 12 additions & 3 deletions lua/neogit/lib/popup/builder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ local M = {}
---@field description string
---@field callback function
---@field heading string?
---@field persist_popup boolean? set to true to prevent closing the popup when invoking

---@class PopupActionOptions
---@field persist_popup boolean Controls if the action should close the popup (false/nil) or keep it open (true)

---@class PopupSwitchOpts
---@field enabled? boolean Controls if the switch should default to 'on' state
Expand Down Expand Up @@ -429,8 +433,11 @@ end
---@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? fun(popup: PopupData) Function that gets run in async context
---@param opts? PopupActionOptions
---@return self
function M:action(keys, description, callback)
function M:action(keys, description, callback, opts)
opts = opts or {}

if type(keys) == "string" then
keys = { keys }
end
Expand All @@ -448,6 +455,7 @@ function M:action(keys, description, callback)
keys = keys,
description = description,
callback = callback,
persist_popup = opts.persist_popup or false
})

return self
Expand All @@ -459,10 +467,11 @@ end
---@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? fun(popup: PopupData) Function that gets run in async context
---@param opts? PopupActionOptions
---@return self
function M:action_if(cond, keys, description, callback)
function M:action_if(cond, keys, description, callback, opts)
if cond then
return self:action(keys, description, callback)
return self:action(keys, description, callback, opts)
end

return self
Expand Down
6 changes: 5 additions & 1 deletion lua/neogit/lib/popup/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,11 @@ function M:mappings()
for _, key in ipairs(action.keys) do
mappings.n[key] = a.void(function()
logger.debug(string.format("[POPUP]: Invoking action %q of %s", key, self.state.name))
self:close()
if not action.persist_popup then
logger.debug("[POPUP]: Closing popup")
self:close()
end

action.callback(self)
Watcher.instance():dispatch_refresh()
end)
Expand Down
3 changes: 3 additions & 0 deletions lua/neogit/popups/help/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ M.popups = function(env)
{ "LogPopup", "Log", popups.open("log", function(p)
p(env.log)
end) },
{ "MarginPopup", "Margin", popups.open("margin", function(p)
p(env.margin)
end) },
{
"CherryPickPopup",
"Cherry Pick",
Expand Down
32 changes: 32 additions & 0 deletions lua/neogit/popups/margin/actions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
local M = {}

local state = require("neogit.lib.state")
local a = require("plenary.async")

function M.refresh_buffer(buffer)
return a.void(function()
buffer:dispatch_refresh({ update_diffs = { "*:*" } }, "margin_refresh_buffer")
end)
end

function M.toggle_visibility()
local visibility = state.get({ "margin", "visibility" }, false)
local new_visibility = not visibility
state.set({ "margin", "visibility" }, new_visibility)
end

function M.cycle_date_style()
local styles = { "relative_short", "relative_long", "local_datetime" }
local current_index = state.get({ "margin", "date_style" }, #styles)
local next_index = (current_index % #styles) + 1 -- wrap around to the first style

state.set({ "margin", "date_style" }, next_index)
end

function M.toggle_details()
local details = state.get({ "margin", "details" }, false)
local new_details = not details
state.set({ "margin", "details" }, new_details)
end

return M
55 changes: 55 additions & 0 deletions lua/neogit/popups/margin/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
local popup = require("neogit.lib.popup")
local config = require("neogit.config")
local actions = require("neogit.popups.margin.actions")

local M = {}

function M.create(env)
local p = popup
.builder()
:name("NeogitMarginPopup")
:option("n", "max-count", "256", "Limit number of commits", { default = "256", key_prefix = "-" })
:switch("o", "topo", "Order commits by", {
cli_suffix = "-order",
options = {
{ display = "", value = "" },
{ display = "topo", value = "topo" },
{ display = "author-date", value = "author-date" },
{ display = "date", value = "date" },
},
})
:switch("g", "graph", "Show graph", {
enabled = true,
internal = true,
incompatible = { "reverse" },
dependent = { "color" },
})
:switch_if(
config.values.graph_style == "ascii" or config.values.graph_style == "kitty",
"c",
"color",
"Show graph in color",
{ internal = true, incompatible = { "reverse" } }
)
:switch("d", "decorate", "Show refnames", { enabled = true, internal = true })
:group_heading("Refresh")
:action_if(
env.buffer,
"g",
"buffer",
actions.refresh_buffer(env.buffer),
{ persist_popup = true }
)
:new_action_group("Margin")
:action("L", "toggle visibility", actions.toggle_visibility, { persist_popup = true })
:action("l", "cycle style", actions.cycle_date_style, { persist_popup = true })
:action("d", "toggle details", actions.toggle_details, { persist_popup = true })
:action("x", "toggle shortstat", actions.log_current, { persist_popup = true })
:build()

p:show()

return p
end

return M
14 changes: 7 additions & 7 deletions spec/popups/help_popup_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
" Commands Applying changes Essential commands ",
" $ History M Remote <c-s> Stage all <c-r> Refresh ",
" A Cherry Pick m Merge K Untrack <cr> Go to file ",
" b Branch P Push s Stage <tab> Toggle ",
" B Bisect p Pull S Stage unstaged ",
" b Branch p Pull s Stage <tab> Toggle ",
" B Bisect P Push S Stage unstaged ",
" c Commit Q Command u Unstage ",
" d Diff r Rebase U Unstage all ",
" f Fetch t Tag x Discard ",
" I Init v Revert ",
" i Ignore w Worktree ",
" l Log X Reset ",
" Z Stash "
" i Ignore v Revert ",
" I Init w Worktree ",
" L Margin X Reset ",
" l Log Z Stash "
]
end

%w[$ A b B c d f i I l M m P p r t v w X Z].each { include_examples "interaction", _1 }
%w[$ A b B c d f i I l L M m P p r t v w X Z].each { include_examples "interaction", _1 }
end
26 changes: 26 additions & 0 deletions spec/popups/margin_popup_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe "Margin Popup", :git, :nvim, :popup do # rubocop:disable RSpec/EmptyExampleGroup
before { nvim.keys("L") }

let(:view) do
[
" Arguments ",
" -n Limit number of commits (--max-count=256) ",
" -o Order commits by (--[topo|author-date|date]-order) ",
" -g Show graph (--graph) ",
" -c Show graph in color (--color) ",
" -d Show refnames (--decorate) ",
" ",
" Refresh Margin ",
" g buffer L toggle visibility ",
" l cycle style ",
" d toggle details ",
" x toggle shortstat "
]
end

%w[L l d].each { include_examples "interaction", _1 }
end
Loading