Compare commits
7 Commits
428ded2c4c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a78950ac68 | |||
| c4585b7768 | |||
| 79120ed4f7 | |||
| 182e507dc7 | |||
| 22309fe8fd | |||
| 20be779891 | |||
| ff7b20ec46 |
@@ -23,7 +23,7 @@ The plugin auto-initializes from its `plugin/git.lua` and exposes `lua/git/`. No
|
|||||||
With `vim.pack`:
|
With `vim.pack`:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
vim.pack.add({ "https://git.owall.dev/warg/git.nvim" })
|
vim.pack.add({ "https://git.owall.se/warg/git.nvim" })
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|||||||
+221
-66
@@ -1,10 +1,12 @@
|
|||||||
local object = require("git.object")
|
local object = require("git.object")
|
||||||
|
local popup = require("git.core.popup")
|
||||||
local repo = require("git.core.repo")
|
local repo = require("git.core.repo")
|
||||||
local util = require("git.core.util")
|
local util = require("git.core.util")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local NS_POPUP = vim.api.nvim_create_namespace("ow.git.blame.popup")
|
local NS_POPUP = vim.api.nvim_create_namespace("ow.git.blame.popup")
|
||||||
|
local NS_OVERFLOW = vim.api.nvim_create_namespace("ow.git.blame.popup.overflow")
|
||||||
|
|
||||||
local ZERO_SHA = string.rep("0", 40)
|
local ZERO_SHA = string.rep("0", 40)
|
||||||
|
|
||||||
@@ -19,6 +21,8 @@ local ZERO_SHA = string.rep("0", 40)
|
|||||||
---@class ow.Git.Blame.Result
|
---@class ow.Git.Blame.Result
|
||||||
---@field commits table<string, ow.Git.Blame.Commit>
|
---@field commits table<string, ow.Git.Blame.Commit>
|
||||||
---@field line_sha table<integer, string>
|
---@field line_sha table<integer, string>
|
||||||
|
---@field line_orig table<integer, integer>
|
||||||
|
---@field line_file table<integer, string>
|
||||||
|
|
||||||
---@class ow.Git.Blame.Source
|
---@class ow.Git.Blame.Source
|
||||||
---@field repo ow.Git.Repo
|
---@field repo ow.Git.Repo
|
||||||
@@ -31,6 +35,8 @@ local ZERO_SHA = string.rep("0", 40)
|
|||||||
---@field revision string?
|
---@field revision string?
|
||||||
---@field commits table<string, ow.Git.Blame.Commit>
|
---@field commits table<string, ow.Git.Blame.Commit>
|
||||||
---@field line_sha table<integer, string>
|
---@field line_sha table<integer, string>
|
||||||
|
---@field line_orig table<integer, integer>
|
||||||
|
---@field line_file table<integer, string>
|
||||||
---@field tick integer?
|
---@field tick integer?
|
||||||
---@field epoch integer
|
---@field epoch integer
|
||||||
---@field pending fun()[]
|
---@field pending fun()[]
|
||||||
@@ -50,21 +56,6 @@ function M.state(buf)
|
|||||||
return states[buf]
|
return states[buf]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param ts integer
|
|
||||||
---@param tz string
|
|
||||||
---@return string
|
|
||||||
local function format_author_time(ts, tz)
|
|
||||||
local sign, hh, mm = tz:match("^([+-])(%d%d)(%d%d)$")
|
|
||||||
---@type number
|
|
||||||
local offset = 0
|
|
||||||
if sign then
|
|
||||||
local h = tonumber(hh) or 0
|
|
||||||
local m = tonumber(mm) or 0
|
|
||||||
offset = (h * 3600 + m * 60) * (sign == "-" and -1 or 1)
|
|
||||||
end
|
|
||||||
return os.date("!%Y-%m-%d %T ", ts + offset) .. tz
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param stdout string
|
---@param stdout string
|
||||||
---@return ow.Git.Blame.Result
|
---@return ow.Git.Blame.Result
|
||||||
local function parse_porcelain(stdout)
|
local function parse_porcelain(stdout)
|
||||||
@@ -72,20 +63,33 @@ local function parse_porcelain(stdout)
|
|||||||
local commits = {}
|
local commits = {}
|
||||||
---@type table<integer, string>
|
---@type table<integer, string>
|
||||||
local line_sha = {}
|
local line_sha = {}
|
||||||
|
---@type table<integer, integer>
|
||||||
|
local line_orig = {}
|
||||||
|
---@type table<integer, string>
|
||||||
|
local line_file = {}
|
||||||
local cur_sha ---@type string?
|
local cur_sha ---@type string?
|
||||||
local cur_lnum ---@type integer?
|
local cur_lnum ---@type integer?
|
||||||
|
local cur_orig ---@type integer?
|
||||||
|
local cur_file ---@type string?
|
||||||
for _, line in ipairs(util.split_lines(stdout)) do
|
for _, line in ipairs(util.split_lines(stdout)) do
|
||||||
if line:sub(1, 1) == "\t" then
|
if line:sub(1, 1) == "\t" then
|
||||||
if cur_sha and cur_lnum then
|
if cur_sha and cur_lnum and cur_orig then
|
||||||
line_sha[cur_lnum] = cur_sha
|
line_sha[cur_lnum] = cur_sha
|
||||||
|
line_orig[cur_lnum] = cur_orig
|
||||||
|
if cur_file then
|
||||||
|
line_file[cur_lnum] = cur_file
|
||||||
|
end
|
||||||
end
|
end
|
||||||
cur_sha = nil
|
cur_sha = nil
|
||||||
cur_lnum = nil
|
cur_lnum = nil
|
||||||
|
cur_orig = nil
|
||||||
|
cur_file = nil
|
||||||
else
|
else
|
||||||
local sha, final = line:match("^(%x+) %d+ (%d+)")
|
local sha, orig, final = line:match("^(%x+) (%d+) (%d+)")
|
||||||
if sha and #sha >= 40 then
|
if sha and #sha >= 40 then
|
||||||
cur_sha = sha
|
cur_sha = sha
|
||||||
cur_lnum = tonumber(final) --[[@as integer?]]
|
cur_orig = tonumber(orig) --[[@as integer]]
|
||||||
|
cur_lnum = tonumber(final) --[[@as integer]]
|
||||||
if not commits[sha] then
|
if not commits[sha] then
|
||||||
commits[sha] = {
|
commits[sha] = {
|
||||||
sha = sha,
|
sha = sha,
|
||||||
@@ -99,7 +103,9 @@ local function parse_porcelain(stdout)
|
|||||||
else
|
else
|
||||||
local key, value = line:match("^(%S+) (.*)$")
|
local key, value = line:match("^(%S+) (.*)$")
|
||||||
local commit = cur_sha and commits[cur_sha]
|
local commit = cur_sha and commits[cur_sha]
|
||||||
if commit and key then
|
if key == "filename" and value then
|
||||||
|
cur_file = value
|
||||||
|
elseif commit and key then
|
||||||
if key == "author" then
|
if key == "author" then
|
||||||
commit.author = value
|
commit.author = value
|
||||||
elseif key == "author-mail" then
|
elseif key == "author-mail" then
|
||||||
@@ -115,7 +121,12 @@ local function parse_porcelain(stdout)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return { commits = commits, line_sha = line_sha }
|
return {
|
||||||
|
commits = commits,
|
||||||
|
line_sha = line_sha,
|
||||||
|
line_orig = line_orig,
|
||||||
|
line_file = line_file,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param line_count integer
|
---@param line_count integer
|
||||||
@@ -138,6 +149,8 @@ local function synth_uncommitted(line_count)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
line_sha = line_sha,
|
line_sha = line_sha,
|
||||||
|
line_orig = {},
|
||||||
|
line_file = {},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -179,14 +192,26 @@ local function resolve_source(buf)
|
|||||||
local name = vim.api.nvim_buf_get_name(buf)
|
local name = vim.api.nvim_buf_get_name(buf)
|
||||||
if util.is_uri(name) then
|
if util.is_uri(name) then
|
||||||
local rev = object.parse_uri(name)
|
local rev = object.parse_uri(name)
|
||||||
if not rev or not rev.base or not rev.path then
|
if not rev then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local r = repo.find(buf)
|
local r = repo.find(buf)
|
||||||
if not r then
|
if not r then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
return { repo = r, rel = rev.path, revision = rev.base }
|
local rel = rev.path
|
||||||
|
if not rel then
|
||||||
|
local state = r:state(buf)
|
||||||
|
rel = state and state.blob_path or nil
|
||||||
|
if not rel then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local revision = nil
|
||||||
|
if rev.path and rev.base and rev.stage == nil then
|
||||||
|
revision = rev.base
|
||||||
|
end
|
||||||
|
return { repo = r, rel = rel, revision = revision }
|
||||||
end
|
end
|
||||||
if not repo.is_worktree_buf(buf) then
|
if not repo.is_worktree_buf(buf) then
|
||||||
return nil
|
return nil
|
||||||
@@ -219,6 +244,8 @@ local function ensure_state(buf)
|
|||||||
revision = src.revision,
|
revision = src.revision,
|
||||||
commits = {},
|
commits = {},
|
||||||
line_sha = {},
|
line_sha = {},
|
||||||
|
line_orig = {},
|
||||||
|
line_file = {},
|
||||||
tick = nil,
|
tick = nil,
|
||||||
epoch = 0,
|
epoch = 0,
|
||||||
pending = {},
|
pending = {},
|
||||||
@@ -266,6 +293,8 @@ local function run_blame(state, buf, done)
|
|||||||
or synth_uncommitted(vim.api.nvim_buf_line_count(buf))
|
or synth_uncommitted(vim.api.nvim_buf_line_count(buf))
|
||||||
state.commits = data.commits
|
state.commits = data.commits
|
||||||
state.line_sha = data.line_sha
|
state.line_sha = data.line_sha
|
||||||
|
state.line_orig = data.line_orig
|
||||||
|
state.line_file = data.line_file
|
||||||
state.tick = tick
|
state.tick = tick
|
||||||
local pending = state.pending
|
local pending = state.pending
|
||||||
state.pending = {}
|
state.pending = {}
|
||||||
@@ -275,20 +304,100 @@ local function run_blame(state, buf, done)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param lines string[]
|
local NEW_FILE_MARKER = "(new file)"
|
||||||
---@return integer width
|
|
||||||
---@return integer height
|
---@param diff_text string
|
||||||
local function size_for(lines)
|
---@param orig_line integer
|
||||||
local width = 1
|
---@return string[]?
|
||||||
for _, l in ipairs(lines) do
|
local function extract_commit_hunk(diff_text, orig_line)
|
||||||
local w = vim.api.nvim_strwidth(l)
|
local lines = util.split_lines(diff_text)
|
||||||
if w > width then
|
for _, line in ipairs(lines) do
|
||||||
width = w
|
if line == "--- /dev/null" then
|
||||||
|
return { NEW_FILE_MARKER }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
width = math.min(math.max(width + 1, 30), vim.o.columns - 4)
|
local hunk_starts = {} ---@type integer[]
|
||||||
local height = math.min(math.max(#lines, 1), math.floor(vim.o.lines / 2))
|
local target ---@type integer?
|
||||||
return width, height
|
for i, line in ipairs(lines) do
|
||||||
|
local c, d = line:match("^@@ %-%d+,?%d* %+(%d+),?(%d*) @@")
|
||||||
|
if c then
|
||||||
|
table.insert(hunk_starts, i)
|
||||||
|
if not target then
|
||||||
|
local ns = tonumber(c) --[[@as integer]]
|
||||||
|
local nc = (d == "" or d == nil) and 1 or tonumber(d) --[[@as integer]]
|
||||||
|
if orig_line >= ns and orig_line < ns + nc then
|
||||||
|
target = #hunk_starts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not target then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local out = {} ---@type string[]
|
||||||
|
table.insert(out, ("Hunk %d of %d"):format(target, #hunk_starts))
|
||||||
|
local start_idx = hunk_starts[target]
|
||||||
|
local end_idx = hunk_starts[target + 1] and (hunk_starts[target + 1] - 1)
|
||||||
|
or #lines
|
||||||
|
for j = start_idx, end_idx do
|
||||||
|
local line = lines[j] --[[@as string]]
|
||||||
|
if line:match("^diff %-%-git") then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
table.insert(out, line)
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local POPUP_SEP = "GIT_BLAME_POPUP_SEP"
|
||||||
|
|
||||||
|
---@param r ow.Git.Repo
|
||||||
|
---@param sha string
|
||||||
|
---@param rel string
|
||||||
|
---@param orig_line integer
|
||||||
|
---@param done fun(message: string[]?, hunk: string[]?)
|
||||||
|
local function fetch_show(r, sha, rel, orig_line, done)
|
||||||
|
util.git({
|
||||||
|
"show",
|
||||||
|
"--diff-merges=first-parent",
|
||||||
|
"--format=%B%n" .. POPUP_SEP .. "%n",
|
||||||
|
sha,
|
||||||
|
"--",
|
||||||
|
rel,
|
||||||
|
}, {
|
||||||
|
cwd = r.worktree,
|
||||||
|
silent = true,
|
||||||
|
on_exit = function(res)
|
||||||
|
if res.code ~= 0 then
|
||||||
|
done(nil, nil)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local lines = util.split_lines(res.stdout or "")
|
||||||
|
local sep_idx ---@type integer?
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
if line == POPUP_SEP then
|
||||||
|
sep_idx = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not sep_idx then
|
||||||
|
done(nil, nil)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local message = {} ---@type string[]
|
||||||
|
for i = 1, sep_idx - 1 do
|
||||||
|
table.insert(message, lines[i])
|
||||||
|
end
|
||||||
|
while #message > 0 and message[#message] == "" do
|
||||||
|
table.remove(message)
|
||||||
|
end
|
||||||
|
local diff_text = table.concat(lines, "\n", sep_idx + 1) ---@type string
|
||||||
|
done(
|
||||||
|
#message > 0 and message or nil,
|
||||||
|
extract_commit_hunk(diff_text, orig_line)
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local popup_win ---@type integer?
|
local popup_win ---@type integer?
|
||||||
@@ -303,14 +412,23 @@ end
|
|||||||
---@param pbuf integer
|
---@param pbuf integer
|
||||||
---@param win integer
|
---@param win integer
|
||||||
---@param head string[]
|
---@param head string[]
|
||||||
---@param body string[]?
|
---@param message string[]?
|
||||||
|
---@param diff string[]?
|
||||||
---@param sha_len integer?
|
---@param sha_len integer?
|
||||||
---@param date_col integer?
|
---@param date_col integer?
|
||||||
local function apply_popup(pbuf, win, head, body, sha_len, date_col)
|
local function apply_popup(pbuf, win, head, message, diff, sha_len, date_col)
|
||||||
local lines = {}
|
local lines = {} ---@type string[]
|
||||||
vim.list_extend(lines, head)
|
vim.list_extend(lines, head)
|
||||||
if body then
|
if message then
|
||||||
vim.list_extend(lines, body)
|
vim.list_extend(lines, message)
|
||||||
|
end
|
||||||
|
local diff_start ---@type integer?
|
||||||
|
if diff then
|
||||||
|
if #lines > #head then
|
||||||
|
table.insert(lines, "")
|
||||||
|
end
|
||||||
|
diff_start = #lines
|
||||||
|
vim.list_extend(lines, diff)
|
||||||
end
|
end
|
||||||
vim.bo[pbuf].modifiable = true
|
vim.bo[pbuf].modifiable = true
|
||||||
vim.api.nvim_buf_set_lines(pbuf, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(pbuf, 0, -1, false, lines)
|
||||||
@@ -334,9 +452,32 @@ local function apply_popup(pbuf, win, head, body, sha_len, date_col)
|
|||||||
hl_group = "GitBlameDate",
|
hl_group = "GitBlameDate",
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
local width, height = size_for(lines)
|
if diff_start and diff then
|
||||||
|
if #diff == 1 and diff[1] == NEW_FILE_MARKER then
|
||||||
|
pcall(
|
||||||
|
vim.api.nvim_buf_set_extmark,
|
||||||
|
pbuf,
|
||||||
|
NS_POPUP,
|
||||||
|
diff_start,
|
||||||
|
0,
|
||||||
|
{ line_hl_group = "GitHunkAdded" }
|
||||||
|
)
|
||||||
|
else
|
||||||
|
util.paint_diff_lines(pbuf, NS_POPUP, diff, diff_start)
|
||||||
|
pcall(
|
||||||
|
vim.api.nvim_buf_set_extmark,
|
||||||
|
pbuf,
|
||||||
|
NS_POPUP,
|
||||||
|
diff_start,
|
||||||
|
0,
|
||||||
|
{ line_hl_group = "GitHunkAnnotation" }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local width, height = popup.size_for(lines)
|
||||||
pcall(vim.api.nvim_win_set_width, win, width)
|
pcall(vim.api.nvim_win_set_width, win, width)
|
||||||
pcall(vim.api.nvim_win_set_height, win, height)
|
pcall(vim.api.nvim_win_set_height, win, height)
|
||||||
|
popup.paint_overflow(pbuf, win, NS_OVERFLOW)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param watch_buf integer
|
---@param watch_buf integer
|
||||||
@@ -362,18 +503,23 @@ local function setup_popup_autocmds(watch_buf, pbuf, win)
|
|||||||
pcall(vim.api.nvim_del_augroup_by_name, "ow.git.blame.popup")
|
pcall(vim.api.nvim_del_augroup_by_name, "ow.git.blame.popup")
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
vim.api.nvim_create_autocmd("WinScrolled", {
|
||||||
|
group = group,
|
||||||
|
pattern = tostring(win),
|
||||||
|
callback = function()
|
||||||
|
popup.paint_overflow(pbuf, win, NS_OVERFLOW)
|
||||||
|
end,
|
||||||
|
})
|
||||||
vim.keymap.set("n", "q", close_popup, { buffer = pbuf, nowait = true })
|
vim.keymap.set("n", "q", close_popup, { buffer = pbuf, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param r ow.Git.Repo
|
---@param state ow.Git.Blame.BufState
|
||||||
---@param commits table<string, ow.Git.Blame.Commit>
|
|
||||||
---@param line_sha table<integer, string>
|
|
||||||
---@param lnum integer
|
---@param lnum integer
|
||||||
---@param watch_buf integer
|
---@param watch_buf integer
|
||||||
local function open_popup(r, commits, line_sha, lnum, watch_buf)
|
local function open_popup(state, lnum, watch_buf)
|
||||||
close_popup()
|
close_popup()
|
||||||
local sha = line_sha[lnum]
|
local sha = state.line_sha[lnum]
|
||||||
local commit = sha and commits[sha]
|
local commit = sha and state.commits[sha]
|
||||||
if not commit then
|
if not commit then
|
||||||
util.warning("git blame: no blame information for line %d", lnum)
|
util.warning("git blame: no blame information for line %d", lnum)
|
||||||
return
|
return
|
||||||
@@ -385,7 +531,7 @@ local function open_popup(r, commits, line_sha, lnum, watch_buf)
|
|||||||
head = { "Not Committed Yet" }
|
head = { "Not Committed Yet" }
|
||||||
else
|
else
|
||||||
local short = sha:sub(1, 8)
|
local short = sha:sub(1, 8)
|
||||||
local date = format_author_time(commit.author_time, commit.author_tz)
|
local date = util.format_git_time(commit.author_time, commit.author_tz)
|
||||||
sha_len = #short
|
sha_len = #short
|
||||||
date_col = sha_len + 2 + #commit.author + 2
|
date_col = sha_len + 2 + #commit.author + 2
|
||||||
head = {
|
head = {
|
||||||
@@ -393,15 +539,16 @@ local function open_popup(r, commits, line_sha, lnum, watch_buf)
|
|||||||
"",
|
"",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
local body = sha_len and { commit.summary } or nil
|
local message = sha_len and { commit.summary } or nil ---@type string[]?
|
||||||
local lines = {}
|
local diff ---@type string[]?
|
||||||
vim.list_extend(lines, head)
|
|
||||||
if body then
|
|
||||||
vim.list_extend(lines, body)
|
|
||||||
end
|
|
||||||
local width, height = size_for(lines)
|
|
||||||
local pbuf = vim.api.nvim_create_buf(false, true)
|
local pbuf = vim.api.nvim_create_buf(false, true)
|
||||||
vim.bo[pbuf].bufhidden = "wipe"
|
vim.bo[pbuf].bufhidden = "wipe"
|
||||||
|
local initial = {} ---@type string[]
|
||||||
|
vim.list_extend(initial, head)
|
||||||
|
if message then
|
||||||
|
vim.list_extend(initial, message)
|
||||||
|
end
|
||||||
|
local width, height = popup.size_for(initial)
|
||||||
local win = vim.api.nvim_open_win(pbuf, false, {
|
local win = vim.api.nvim_open_win(pbuf, false, {
|
||||||
relative = "cursor",
|
relative = "cursor",
|
||||||
row = 1,
|
row = 1,
|
||||||
@@ -411,29 +558,37 @@ local function open_popup(r, commits, line_sha, lnum, watch_buf)
|
|||||||
style = "minimal",
|
style = "minimal",
|
||||||
})
|
})
|
||||||
popup_win = win
|
popup_win = win
|
||||||
apply_popup(pbuf, win, head, body, sha_len, date_col)
|
|
||||||
|
local function redraw()
|
||||||
|
apply_popup(pbuf, win, head, message, diff, sha_len, date_col)
|
||||||
|
end
|
||||||
|
|
||||||
|
redraw()
|
||||||
setup_popup_autocmds(watch_buf, pbuf, win)
|
setup_popup_autocmds(watch_buf, pbuf, win)
|
||||||
if not sha_len then
|
if not sha_len then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
util.git({ "show", "-s", "--format=%B", sha }, {
|
local orig = state.line_orig[lnum]
|
||||||
cwd = r.worktree,
|
local rel = state.line_file[lnum] or state.rel
|
||||||
silent = true,
|
if not orig then
|
||||||
on_exit = function(res)
|
return
|
||||||
|
end
|
||||||
|
fetch_show(state.repo, sha, rel, orig, function(msg, hunk)
|
||||||
if
|
if
|
||||||
popup_win ~= win
|
popup_win ~= win
|
||||||
or not vim.api.nvim_win_is_valid(win)
|
or not vim.api.nvim_win_is_valid(win)
|
||||||
or not vim.api.nvim_buf_is_valid(pbuf)
|
or not vim.api.nvim_buf_is_valid(pbuf)
|
||||||
or res.code ~= 0
|
|
||||||
then
|
then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local msg = util.split_lines(res.stdout or "")
|
if msg then
|
||||||
if #msg > 0 then
|
message = msg
|
||||||
apply_popup(pbuf, win, head, msg, sha_len, date_col)
|
|
||||||
end
|
end
|
||||||
end,
|
if hunk then
|
||||||
})
|
diff = hunk
|
||||||
|
end
|
||||||
|
redraw()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param buf integer?
|
---@param buf integer?
|
||||||
@@ -457,7 +612,7 @@ function M.line_popup(buf)
|
|||||||
then
|
then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
open_popup(state.repo, state.commits, state.line_sha, lnum, buf)
|
open_popup(state, lnum, buf)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param win integer
|
||||||
|
---@return boolean
|
||||||
|
local function has_border(win)
|
||||||
|
local b = vim.api.nvim_win_get_config(win).border
|
||||||
|
if not b or b == "none" or b == "" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if type(b) == "table" and #b == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param lines string[]
|
||||||
|
---@param opts? { min_width?: integer, padding?: integer }
|
||||||
|
---@return integer width
|
||||||
|
---@return integer height
|
||||||
|
function M.size_for(lines, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local width = 1
|
||||||
|
for _, l in ipairs(lines) do
|
||||||
|
local w = vim.api.nvim_strwidth(l)
|
||||||
|
if w > width then
|
||||||
|
width = w
|
||||||
|
end
|
||||||
|
end
|
||||||
|
width = math.min(
|
||||||
|
math.max(width + (opts.padding or 1), opts.min_width or 30),
|
||||||
|
100,
|
||||||
|
vim.o.columns - 4
|
||||||
|
)
|
||||||
|
local height =
|
||||||
|
math.min(math.max(#lines, 1), 20, math.floor(vim.o.lines / 2))
|
||||||
|
return width, height
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param pbuf integer
|
||||||
|
---@param win integer
|
||||||
|
---@param ns integer
|
||||||
|
function M.paint_overflow(pbuf, win, ns)
|
||||||
|
vim.schedule(function()
|
||||||
|
if
|
||||||
|
not vim.api.nvim_buf_is_valid(pbuf)
|
||||||
|
or not vim.api.nvim_win_is_valid(win)
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_clear_namespace(pbuf, ns, 0, -1)
|
||||||
|
local total = vim.api.nvim_buf_line_count(pbuf)
|
||||||
|
local top, bottom = unpack(vim.api.nvim_win_call(win, function()
|
||||||
|
return { vim.fn.line("w0"), vim.fn.line("w$") }
|
||||||
|
end)) ---@cast top integer
|
||||||
|
---@cast bottom integer
|
||||||
|
local needs_top = top > 1
|
||||||
|
local needs_bottom = bottom < total
|
||||||
|
if has_border(win) then
|
||||||
|
pcall(vim.api.nvim_win_set_config, win, {
|
||||||
|
title = needs_top and { { "▲ ", "FloatBorder" } } or "",
|
||||||
|
title_pos = needs_top and "right" or nil,
|
||||||
|
footer = needs_bottom and { { "▼ ", "FloatBorder" } } or "",
|
||||||
|
footer_pos = needs_bottom and "right" or nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if needs_top then
|
||||||
|
pcall(vim.api.nvim_buf_set_extmark, pbuf, ns, top - 1, 0, {
|
||||||
|
virt_text = { { "▲ ", "GitPopupEnd" } },
|
||||||
|
virt_text_pos = "right_align",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if needs_bottom then
|
||||||
|
pcall(vim.api.nvim_buf_set_extmark, pbuf, ns, bottom - 1, 0, {
|
||||||
|
virt_text = { { "▼ ", "GitPopupEnd" } },
|
||||||
|
virt_text_pos = "right_align",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -19,6 +19,7 @@ end
|
|||||||
---@field immutable boolean?
|
---@field immutable boolean?
|
||||||
---@field index_writer boolean?
|
---@field index_writer boolean?
|
||||||
---@field index_mode string?
|
---@field index_mode string?
|
||||||
|
---@field blob_path string?
|
||||||
|
|
||||||
---@alias ow.Git.Repo.Event
|
---@alias ow.Git.Repo.Event
|
||||||
---| "change"
|
---| "change"
|
||||||
|
|||||||
@@ -120,6 +120,51 @@ function M.split_lines(content)
|
|||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param ts integer
|
||||||
|
---@param tz string
|
||||||
|
---@return string
|
||||||
|
function M.format_git_time(ts, tz)
|
||||||
|
local sign, hh, mm = tz:match("^([+-])(%d%d)(%d%d)$")
|
||||||
|
local offset = 0
|
||||||
|
if sign then
|
||||||
|
local h = tonumber(hh) or 0
|
||||||
|
local m = tonumber(mm) or 0
|
||||||
|
offset = math.floor(h * 3600 + m * 60)
|
||||||
|
if sign == "-" then
|
||||||
|
offset = -offset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return os.date("!%a %b %e %T %Y ", ts + offset) .. tz
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
---@param ns integer
|
||||||
|
---@param lines string[]
|
||||||
|
---@param start_row integer 0-indexed row of the first line in `lines`
|
||||||
|
function M.paint_diff_lines(buf, ns, lines, start_row)
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
local hl ---@type string?
|
||||||
|
local prefix = line:sub(1, 1)
|
||||||
|
if prefix == "+" then
|
||||||
|
hl = "GitHunkAdded"
|
||||||
|
elseif prefix == "-" then
|
||||||
|
hl = "GitHunkRemoved"
|
||||||
|
elseif vim.startswith(line, "@@") then
|
||||||
|
hl = "GitHunkHeader"
|
||||||
|
end
|
||||||
|
if hl then
|
||||||
|
pcall(
|
||||||
|
vim.api.nvim_buf_set_extmark,
|
||||||
|
buf,
|
||||||
|
ns,
|
||||||
|
start_row + i - 1,
|
||||||
|
0,
|
||||||
|
{ line_hl_group = hl }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@class ow.Git.Util.DebounceHandle
|
---@class ow.Git.Util.DebounceHandle
|
||||||
---@field cancel fun()
|
---@field cancel fun()
|
||||||
---@field flush fun()
|
---@field flush fun()
|
||||||
|
|||||||
+14
-9
@@ -1,3 +1,4 @@
|
|||||||
|
local popup = require("git.core.popup")
|
||||||
local repo = require("git.core.repo")
|
local repo = require("git.core.repo")
|
||||||
local util = require("git.core.util")
|
local util = require("git.core.util")
|
||||||
|
|
||||||
@@ -5,6 +6,9 @@ local M = {}
|
|||||||
|
|
||||||
local NS_SIGNS = vim.api.nvim_create_namespace("ow.git.hunks")
|
local NS_SIGNS = vim.api.nvim_create_namespace("ow.git.hunks")
|
||||||
local NS_OVERLAY = vim.api.nvim_create_namespace("ow.git.hunks.overlay")
|
local NS_OVERLAY = vim.api.nvim_create_namespace("ow.git.hunks.overlay")
|
||||||
|
local NS_PREVIEW = vim.api.nvim_create_namespace("ow.git.hunks.preview")
|
||||||
|
local NS_PREVIEW_OVERFLOW =
|
||||||
|
vim.api.nvim_create_namespace("ow.git.hunks.preview.overflow")
|
||||||
|
|
||||||
---@alias ow.Git.Hunks.HunkType "add"|"change"|"delete"
|
---@alias ow.Git.Hunks.HunkType "add"|"change"|"delete"
|
||||||
|
|
||||||
@@ -905,16 +909,9 @@ function M.preview_hunk(buf)
|
|||||||
local lines = hunk_body(h)
|
local lines = hunk_body(h)
|
||||||
local pbuf = vim.api.nvim_create_buf(false, true)
|
local pbuf = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_buf_set_lines(pbuf, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(pbuf, 0, -1, false, lines)
|
||||||
vim.bo[pbuf].filetype = "diff"
|
|
||||||
vim.bo[pbuf].bufhidden = "wipe"
|
vim.bo[pbuf].bufhidden = "wipe"
|
||||||
local width = 0
|
util.paint_diff_lines(pbuf, NS_PREVIEW, lines, 0)
|
||||||
for _, l in ipairs(lines) do
|
local width, height = popup.size_for(lines, { min_width = 40, padding = 2 })
|
||||||
if #l > width then
|
|
||||||
width = #l
|
|
||||||
end
|
|
||||||
end
|
|
||||||
width = math.min(math.max(width + 2, 40), vim.o.columns - 4)
|
|
||||||
local height = math.min(#lines, math.floor(vim.o.lines / 2))
|
|
||||||
local win = vim.api.nvim_open_win(pbuf, false, {
|
local win = vim.api.nvim_open_win(pbuf, false, {
|
||||||
relative = "cursor",
|
relative = "cursor",
|
||||||
row = 1,
|
row = 1,
|
||||||
@@ -924,6 +921,7 @@ function M.preview_hunk(buf)
|
|||||||
style = "minimal",
|
style = "minimal",
|
||||||
})
|
})
|
||||||
preview_win = win
|
preview_win = win
|
||||||
|
popup.paint_overflow(pbuf, win, NS_PREVIEW_OVERFLOW)
|
||||||
|
|
||||||
local function close()
|
local function close()
|
||||||
if vim.api.nvim_win_is_valid(win) then
|
if vim.api.nvim_win_is_valid(win) then
|
||||||
@@ -949,6 +947,13 @@ function M.preview_hunk(buf)
|
|||||||
pcall(vim.api.nvim_del_augroup_by_name, "ow.git.hunks.preview")
|
pcall(vim.api.nvim_del_augroup_by_name, "ow.git.hunks.preview")
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
vim.api.nvim_create_autocmd("WinScrolled", {
|
||||||
|
group = group,
|
||||||
|
pattern = tostring(win),
|
||||||
|
callback = function()
|
||||||
|
popup.paint_overflow(pbuf, win, NS_PREVIEW_OVERFLOW)
|
||||||
|
end,
|
||||||
|
})
|
||||||
vim.keymap.set("n", "q", close, { buffer = pbuf, nowait = true })
|
vim.keymap.set("n", "q", close, { buffer = pbuf, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+94
-12
@@ -159,6 +159,29 @@ function M.buf_for(r, rev)
|
|||||||
return buf
|
return buf
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param lines string[]
|
||||||
|
local function format_header_dates(lines)
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
if line == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local prefix, ts, tz = line:match("^(author .-) (%d+) ([+-]%d%d%d%d)$")
|
||||||
|
if not prefix then
|
||||||
|
prefix, ts, tz = line:match("^(committer .-) (%d+) ([+-]%d%d%d%d)$")
|
||||||
|
end
|
||||||
|
if not prefix then
|
||||||
|
prefix, ts, tz = line:match("^(tagger .-) (%d+) ([+-]%d%d%d%d)$")
|
||||||
|
end
|
||||||
|
if prefix then
|
||||||
|
local n = math.floor(assert(tonumber(ts)))
|
||||||
|
lines[i] = ("%s %s"):format(
|
||||||
|
prefix,
|
||||||
|
util.format_git_time(n, tz --[[@as string]])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@param r ow.Git.Repo
|
---@param r ow.Git.Repo
|
||||||
---@param rev ow.Git.Revision
|
---@param rev ow.Git.Revision
|
||||||
@@ -189,7 +212,9 @@ local function populate(buf, r, rev, state, rev_sha)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
util.set_buf_lines(buf, 0, -1, util.split_lines(stdout))
|
local lines = util.split_lines(stdout)
|
||||||
|
format_header_dates(lines)
|
||||||
|
util.set_buf_lines(buf, 0, -1, lines)
|
||||||
state.sha = rev_sha
|
state.sha = rev_sha
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -296,6 +321,10 @@ local function side_buf(r, blob, path)
|
|||||||
if full then
|
if full then
|
||||||
local buf = M.buf_for(r, Revision.new({ base = full }))
|
local buf = M.buf_for(r, Revision.new({ base = full }))
|
||||||
set_ft_from_path(buf, path)
|
set_ft_from_path(buf, path)
|
||||||
|
local state = r:state(buf)
|
||||||
|
if state then
|
||||||
|
state.blob_path = path
|
||||||
|
end
|
||||||
return buf
|
return buf
|
||||||
end
|
end
|
||||||
local p = vim.fs.joinpath(r.worktree, path)
|
local p = vim.fs.joinpath(r.worktree, path)
|
||||||
@@ -308,10 +337,45 @@ local function side_buf(r, blob, path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param r ow.Git.Repo
|
---@param r ow.Git.Repo
|
||||||
|
---@param commit string
|
||||||
|
---@param parent boolean
|
||||||
|
---@param path string
|
||||||
|
---@return integer?
|
||||||
|
local function commit_side_buf(r, commit, parent, path)
|
||||||
|
local rev = parent and (commit .. "^") or commit
|
||||||
|
local sha = r:rev_parse(rev, false)
|
||||||
|
if not sha then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if not r:rev_parse(sha .. ":" .. path, false) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return M.buf_for(r, Revision.new({ base = sha, path = path }))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param r ow.Git.Repo
|
||||||
|
---@param commit string?
|
||||||
|
---@param parent boolean
|
||||||
---@param blob string?
|
---@param blob string?
|
||||||
---@param path string
|
---@param path string
|
||||||
local function load_side(r, blob, path)
|
---@return integer?
|
||||||
local buf = side_buf(r, blob, path)
|
local function side(r, commit, parent, blob, path)
|
||||||
|
if commit then
|
||||||
|
local buf = commit_side_buf(r, commit, parent, path)
|
||||||
|
if buf then
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return side_buf(r, blob, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param r ow.Git.Repo
|
||||||
|
---@param commit string?
|
||||||
|
---@param parent boolean
|
||||||
|
---@param blob string?
|
||||||
|
---@param path string
|
||||||
|
local function load_side(r, commit, parent, blob, path)
|
||||||
|
local buf = side(r, commit, parent, blob, path)
|
||||||
if not buf then
|
if not buf then
|
||||||
util.error("no content for %s", path)
|
util.error("no content for %s", path)
|
||||||
return
|
return
|
||||||
@@ -321,14 +385,15 @@ local function load_side(r, blob, path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param r ow.Git.Repo
|
---@param r ow.Git.Repo
|
||||||
|
---@param commit string?
|
||||||
---@param section ow.Git.DiffSection
|
---@param section ow.Git.DiffSection
|
||||||
local function open_section(r, section)
|
local function open_section(r, commit, section)
|
||||||
if not section.blob_a or not section.blob_b then
|
if not section.blob_a or not section.blob_b then
|
||||||
util.error("no index line, cannot determine blob SHAs")
|
util.error("no index line, cannot determine blob SHAs")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local left = side_buf(r, section.blob_a, section.path_a)
|
local left = side(r, commit, true, section.blob_a, section.path_a)
|
||||||
local right = side_buf(r, section.blob_b, section.path_b)
|
local right = side(r, commit, false, section.blob_b, section.path_b)
|
||||||
if left and right then
|
if left and right then
|
||||||
vim.cmd.normal({ "m'", bang = true })
|
vim.cmd.normal({ "m'", bang = true })
|
||||||
vim.api.nvim_set_current_buf(right)
|
vim.api.nvim_set_current_buf(right)
|
||||||
@@ -348,6 +413,21 @@ local function open_section(r, section)
|
|||||||
vim.api.nvim_set_current_buf(buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param r ow.Git.Repo
|
||||||
|
---@param buf integer
|
||||||
|
---@return string?
|
||||||
|
local function commit_in_buf(r, buf)
|
||||||
|
local name = vim.api.nvim_buf_get_name(buf)
|
||||||
|
if not vim.startswith(name, M.URI_PREFIX) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local rev = M.parse_uri(name)
|
||||||
|
if not rev or not rev.base or rev.path then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return r:rev_parse(rev.base .. "^{commit}", false)
|
||||||
|
end
|
||||||
|
|
||||||
---@class ow.Git.Object.OpenOpts
|
---@class ow.Git.Object.OpenOpts
|
||||||
---@field split (false|"above"|"below"|"left"|"right")?
|
---@field split (false|"above"|"below"|"left"|"right")?
|
||||||
|
|
||||||
@@ -394,7 +474,7 @@ function M.open_under_cursor()
|
|||||||
line:match("^%d+ (%w+) (%x+)\t(.+)$")
|
line:match("^%d+ (%w+) (%x+)\t(.+)$")
|
||||||
if entry_sha then
|
if entry_sha then
|
||||||
if entry_type == "blob" then
|
if entry_type == "blob" then
|
||||||
load_side(r, entry_sha, entry_name --[[@as string]])
|
load_side(r, nil, false, entry_sha, entry_name --[[@as string]])
|
||||||
else
|
else
|
||||||
M.open(r, entry_sha, { split = false })
|
M.open(r, entry_sha, { split = false })
|
||||||
end
|
end
|
||||||
@@ -406,24 +486,26 @@ function M.open_under_cursor()
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local commit = commit_in_buf(r, vim.api.nvim_get_current_buf())
|
||||||
|
|
||||||
if line:match("^diff %-%-git ") then
|
if line:match("^diff %-%-git ") then
|
||||||
open_section(r, section)
|
open_section(r, commit, section)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
if line:match("^%-%-%- ") then
|
if line:match("^%-%-%- ") then
|
||||||
load_side(r, section.blob_a, section.path_a)
|
load_side(r, commit, true, section.blob_a, section.path_a)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
if line:match("^%+%+%+ ") then
|
if line:match("^%+%+%+ ") then
|
||||||
load_side(r, section.blob_b, section.path_b)
|
load_side(r, commit, false, section.blob_b, section.path_b)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
local prefix = line:sub(1, 1)
|
local prefix = line:sub(1, 1)
|
||||||
if prefix == "+" then
|
if prefix == "+" then
|
||||||
load_side(r, section.blob_b, section.path_b)
|
load_side(r, commit, false, section.blob_b, section.path_b)
|
||||||
return true
|
return true
|
||||||
elseif prefix == "-" then
|
elseif prefix == "-" then
|
||||||
load_side(r, section.blob_a, section.path_a)
|
load_side(r, commit, true, section.blob_a, section.path_a)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -40,12 +40,16 @@ local DEFAULT_HIGHLIGHTS = {
|
|||||||
GitHunkAdded = "Added",
|
GitHunkAdded = "Added",
|
||||||
GitHunkChanged = "Changed",
|
GitHunkChanged = "Changed",
|
||||||
GitHunkRemoved = "Removed",
|
GitHunkRemoved = "Removed",
|
||||||
|
GitHunkHeader = "Statement",
|
||||||
|
GitHunkAnnotation = "Title",
|
||||||
GitHunkAddLine = "DiffAdd",
|
GitHunkAddLine = "DiffAdd",
|
||||||
GitHunkDeleteLine = "DiffDelete",
|
GitHunkDeleteLine = "DiffDelete",
|
||||||
|
|
||||||
GitBlameAuthor = "GitAuthor",
|
GitBlameAuthor = "GitAuthor",
|
||||||
GitBlameDate = "GitDate",
|
GitBlameDate = "GitDate",
|
||||||
GitBlameSha = "GitSha",
|
GitBlameSha = "GitSha",
|
||||||
|
|
||||||
|
GitPopupEnd = "NonText",
|
||||||
}
|
}
|
||||||
local STAGED_HUNK_HL = {
|
local STAGED_HUNK_HL = {
|
||||||
GitHunkStagedAdded = "GitHunkAdded",
|
GitHunkStagedAdded = "GitHunkAdded",
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
if exists("b:current_syntax")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
syntax match gitlogGraph contained /^[*|\\\/_ ]*/
|
||||||
|
\ nextgroup=gitlogHash
|
||||||
|
syntax match gitlogHash contained /\<\x\{7,64\}\>/
|
||||||
|
\ nextgroup=gitlogDate skipwhite
|
||||||
|
syntax match gitlogDate contained /\<\d\{4}-\d\{2}-\d\{2}\>/
|
||||||
|
\ nextgroup=gitlogAuthor skipwhite
|
||||||
|
syntax match gitlogAuthor contained /{[^}]\+}/
|
||||||
|
\ nextgroup=gitlogRef skipwhite
|
||||||
|
syntax match gitlogRef contained /([^)]\+)/
|
||||||
|
syntax match gitlogLine
|
||||||
|
\ /^[*|\\\/_ ]*\x\{7,64}\s\+\d\{4}-\d\{2}-\d\{2}\s\+{[^}]\+}.*/
|
||||||
|
\ contains=gitlogGraph
|
||||||
|
syntax match gitlogGraphLine /^[*|\\\/_ ]\+$/
|
||||||
|
\ contains=gitlogGraph
|
||||||
|
|
||||||
|
highlight default link gitlogGraph Comment
|
||||||
|
highlight default link gitlogHash GitSha
|
||||||
|
highlight default link gitlogDate GitDate
|
||||||
|
highlight default link gitlogAuthor GitAuthor
|
||||||
|
highlight default link gitlogRef Constant
|
||||||
|
|
||||||
|
let b:current_syntax = "gitlog"
|
||||||
+306
-2
@@ -86,9 +86,9 @@ t.test("line popup formats the datetime in the author timezone", function()
|
|||||||
)
|
)
|
||||||
t.truthy(
|
t.truthy(
|
||||||
(lines[1] or ""):match(
|
(lines[1] or ""):match(
|
||||||
"%d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d [+%-]%d%d%d%d$"
|
"%u%l%l %u%l%l +%d+ %d%d:%d%d:%d%d %d%d%d%d [+%-]%d%d%d%d$"
|
||||||
),
|
),
|
||||||
"the head line ends with an ISO datetime and numeric timezone"
|
"the head line ends with a human datetime and numeric timezone"
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -199,6 +199,226 @@ t.test("line popup shows the commit for the cursor line", function()
|
|||||||
t.truthy((lines[1] or ""):find("t", 1, true), "author shown")
|
t.truthy((lines[1] or ""):find("t", 1, true), "author shown")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
t.test("line popup appends the commit's hunk for the cursor line", function()
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "alpha\nbeta\ngamma\n" })
|
||||||
|
t.write(dir, "a.txt", "alpha\nBETA\ngamma\n")
|
||||||
|
h.git(dir, "add", "a.txt")
|
||||||
|
h.git(dir, "commit", "-q", "-m", "change beta")
|
||||||
|
vim.cmd.edit(dir .. "/a.txt")
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||||
|
blame.line_popup(buf)
|
||||||
|
local float = wait_float()
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
t.wait_for(function()
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
for _, l in ipairs(lines) do
|
||||||
|
if l:match("^@@") then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end, "hunk header to appear in the blame popup")
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
local has = {} ---@type table<string, boolean>
|
||||||
|
for _, l in ipairs(lines) do
|
||||||
|
has[l] = true
|
||||||
|
end
|
||||||
|
t.truthy(has["-beta"], "popup includes the pre-image of the changed line")
|
||||||
|
t.truthy(has["+BETA"], "popup includes the post-image of the changed line")
|
||||||
|
t.truthy(
|
||||||
|
has["Hunk 1 of 1"],
|
||||||
|
"popup annotates the hunk index even when the commit only has one"
|
||||||
|
)
|
||||||
|
local pbuf = vim.api.nvim_win_get_buf(float)
|
||||||
|
local seen = {} ---@type table<string, boolean>
|
||||||
|
for _, m in
|
||||||
|
ipairs(vim.api.nvim_buf_get_extmarks(pbuf, -1, 0, -1, {
|
||||||
|
details = true,
|
||||||
|
}))
|
||||||
|
do
|
||||||
|
local hl = m[4] and m[4].line_hl_group
|
||||||
|
if hl then
|
||||||
|
seen[hl] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t.truthy(seen["GitHunkAdded"], "+ line gets GitHunkAdded")
|
||||||
|
t.truthy(seen["GitHunkRemoved"], "- line gets GitHunkRemoved")
|
||||||
|
t.truthy(seen["GitHunkHeader"], "@@ header gets GitHunkHeader")
|
||||||
|
t.truthy(
|
||||||
|
seen["GitHunkAnnotation"],
|
||||||
|
"hunk-index annotation gets GitHunkAnnotation"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
t.test(
|
||||||
|
"line popup picks the hunk that contains the cursor, not the first one",
|
||||||
|
function()
|
||||||
|
local lines_initial = {}
|
||||||
|
local lines_changed = {}
|
||||||
|
for i = 1, 12 do
|
||||||
|
table.insert(lines_initial, "line" .. i)
|
||||||
|
table.insert(lines_changed, "line" .. i)
|
||||||
|
end
|
||||||
|
lines_changed[2] = "FIRST"
|
||||||
|
lines_changed[10] = "SECOND"
|
||||||
|
local initial = table.concat(lines_initial, "\n") .. "\n"
|
||||||
|
local changed = table.concat(lines_changed, "\n") .. "\n"
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = initial })
|
||||||
|
t.write(dir, "a.txt", changed)
|
||||||
|
h.git(dir, "add", "a.txt")
|
||||||
|
h.git(dir, "commit", "-q", "-m", "change line 2 and line 10")
|
||||||
|
vim.cmd.edit(dir .. "/a.txt")
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 10, 0 })
|
||||||
|
blame.line_popup(buf)
|
||||||
|
local float = wait_float()
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
t.wait_for(function()
|
||||||
|
for _, l in
|
||||||
|
ipairs(
|
||||||
|
vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
do
|
||||||
|
if l == "+SECOND" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end, "popup to include the second hunk's added line")
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
local has = {} ---@type table<string, boolean>
|
||||||
|
for _, l in ipairs(lines) do
|
||||||
|
has[l] = true
|
||||||
|
end
|
||||||
|
t.truthy(has["-line10"], "popup includes second hunk pre-image")
|
||||||
|
t.truthy(has["+SECOND"], "popup includes second hunk post-image")
|
||||||
|
t.falsy(has["-line2"], "popup omits the first hunk's pre-image")
|
||||||
|
t.falsy(has["+FIRST"], "popup omits the first hunk's post-image")
|
||||||
|
t.truthy(
|
||||||
|
has["Hunk 2 of 2"],
|
||||||
|
"popup annotates which hunk is selected and how many total"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
t.test(
|
||||||
|
"line popup finds the diff via the historical filename after a rename",
|
||||||
|
function()
|
||||||
|
local dir = h.make_repo({ ["old.txt"] = "alpha\nbeta\n" })
|
||||||
|
t.write(dir, "old.txt", "ALPHA\nbeta\n")
|
||||||
|
h.git(dir, "add", "old.txt")
|
||||||
|
h.git(dir, "commit", "-q", "-m", "change line 1")
|
||||||
|
h.git(dir, "mv", "old.txt", "new.txt")
|
||||||
|
h.git(dir, "commit", "-q", "-m", "rename")
|
||||||
|
vim.cmd.edit(dir .. "/new.txt")
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||||
|
blame.line_popup(buf)
|
||||||
|
local float = wait_float()
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
t.wait_for(function()
|
||||||
|
for _, l in
|
||||||
|
ipairs(
|
||||||
|
vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
do
|
||||||
|
if l == "+ALPHA" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end, "popup to include the pre-rename commit's diff via old.txt")
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
t.test("line popup collapses a new-file diff to a single marker", function()
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "alpha\nbeta\ngamma\n" })
|
||||||
|
vim.cmd.edit(dir .. "/a.txt")
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||||
|
blame.line_popup(buf)
|
||||||
|
local float = wait_float()
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
t.wait_for(function()
|
||||||
|
for _, l in
|
||||||
|
ipairs(
|
||||||
|
vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
do
|
||||||
|
if l == "(new file)" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end, "popup to show the new-file marker")
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
for _, l in ipairs(lines) do
|
||||||
|
t.falsy(
|
||||||
|
vim.startswith(l, "+alpha")
|
||||||
|
or vim.startswith(l, "+beta")
|
||||||
|
or vim.startswith(l, "+gamma"),
|
||||||
|
"popup does not dump the file content as + lines"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local pbuf = vim.api.nvim_win_get_buf(float)
|
||||||
|
local seen = {} ---@type table<string, boolean>
|
||||||
|
for _, m in
|
||||||
|
ipairs(vim.api.nvim_buf_get_extmarks(pbuf, -1, 0, -1, {
|
||||||
|
details = true,
|
||||||
|
}))
|
||||||
|
do
|
||||||
|
local hl = m[4] and m[4].line_hl_group
|
||||||
|
if hl then
|
||||||
|
seen[hl] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t.truthy(seen["GitHunkAdded"], "new-file marker gets GitHunkAdded")
|
||||||
|
end)
|
||||||
|
|
||||||
t.test("re-invoking the line popup focuses the open float", function()
|
t.test("re-invoking the line popup focuses the open float", function()
|
||||||
local _, buf = setup("alpha\nbeta\n")
|
local _, buf = setup("alpha\nbeta\n")
|
||||||
vim.api.nvim_set_current_buf(buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
@@ -247,6 +467,90 @@ t.test("line popup works in a git:// object buffer", function()
|
|||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
t.test(
|
||||||
|
"line popup works in a git://<blob-sha> buffer with a recovered path",
|
||||||
|
function()
|
||||||
|
local object = require("git.object")
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "alpha\nbeta\ngamma\n" })
|
||||||
|
local sha = h.git(dir, "rev-parse", "HEAD").stdout
|
||||||
|
local tree = h.git(dir, "rev-parse", "HEAD^{tree}").stdout
|
||||||
|
local blob = h.git(dir, "rev-parse", "HEAD:a.txt").stdout
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
|
||||||
|
object.open(r, tree, { split = false })
|
||||||
|
local tree_buf = vim.api.nvim_get_current_buf()
|
||||||
|
local found ---@type integer?
|
||||||
|
for i, l in ipairs(vim.api.nvim_buf_get_lines(tree_buf, 0, -1, false)) do
|
||||||
|
if l:match("^%d+ blob %x+\ta%.txt$") then
|
||||||
|
found = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local lnum = assert(found, "expected an a.txt tree entry")
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||||
|
t.truthy(object.open_under_cursor())
|
||||||
|
local gbuf = vim.api.nvim_get_current_buf()
|
||||||
|
t.eq(vim.api.nvim_buf_get_name(gbuf), "git://" .. blob)
|
||||||
|
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||||
|
blame.line_popup(gbuf)
|
||||||
|
local float = wait_float()
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
t.truthy(
|
||||||
|
vim.startswith(lines[1] or "", sha:sub(1, 8)),
|
||||||
|
"the popup blames the commit even in a bare-blob git:// buffer"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
t.test("line popup works in a git://:0: index buffer", function()
|
||||||
|
local object = require("git.object")
|
||||||
|
local Revision = require("git.core.revision")
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "alpha\nbeta\ngamma\n" })
|
||||||
|
local sha = h.git(dir, "rev-parse", "HEAD").stdout
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
|
||||||
|
local ibuf = object.buf_for(r, Revision.new({ stage = 0, path = "a.txt" }))
|
||||||
|
vim.api.nvim_set_current_buf(ibuf)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||||
|
blame.line_popup(ibuf)
|
||||||
|
local float = wait_float()
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(
|
||||||
|
vim.api.nvim_win_get_buf(float),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
t.truthy(
|
||||||
|
vim.startswith(lines[1] or "", sha:sub(1, 8)),
|
||||||
|
"the popup blames the commit for an index entry"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
t.test("line popup skips a bare git://<commit-sha> object dump", function()
|
||||||
|
local object = require("git.object")
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "alpha\n" })
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
|
||||||
|
object.open(r, "HEAD", { split = false })
|
||||||
|
local cbuf = vim.api.nvim_get_current_buf()
|
||||||
|
t.quietly(function()
|
||||||
|
blame.line_popup(cbuf)
|
||||||
|
end)
|
||||||
|
t.eq(blame.state(cbuf), nil, "no blame state on a commit dump")
|
||||||
|
end)
|
||||||
|
|
||||||
t.test("open_commit opens the commit that last touched the line", function()
|
t.test("open_commit opens the commit that last touched the line", function()
|
||||||
local _, buf = setup("alpha\nbeta\ngamma\n")
|
local _, buf = setup("alpha\nbeta\ngamma\n")
|
||||||
vim.api.nvim_set_current_buf(buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
|
|||||||
@@ -608,6 +608,32 @@ t.test("preview_hunk shows the hunk body without file headers", function()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
t.test("preview_hunk highlights diff lines via per-line extmarks", function()
|
||||||
|
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
|
||||||
|
vim.api.nvim_set_current_buf(buf)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||||
|
hunks.preview_hunk(buf)
|
||||||
|
local float = assert(find_float(), "preview float should open")
|
||||||
|
t.defer(function()
|
||||||
|
pcall(vim.api.nvim_win_close, float, true)
|
||||||
|
end)
|
||||||
|
local pbuf = vim.api.nvim_win_get_buf(float)
|
||||||
|
local seen = {} ---@type table<string, boolean>
|
||||||
|
for _, m in
|
||||||
|
ipairs(vim.api.nvim_buf_get_extmarks(pbuf, -1, 0, -1, {
|
||||||
|
details = true,
|
||||||
|
}))
|
||||||
|
do
|
||||||
|
local hl = m[4] and m[4].line_hl_group
|
||||||
|
if hl then
|
||||||
|
seen[hl] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t.truthy(seen["GitHunkHeader"], "@@ header gets GitHunkHeader")
|
||||||
|
t.truthy(seen["GitHunkAdded"], "+ line gets GitHunkAdded")
|
||||||
|
t.truthy(seen["GitHunkRemoved"], "- line gets GitHunkRemoved")
|
||||||
|
end)
|
||||||
|
|
||||||
t.test("preview_hunk re-invocation focuses the open float", function()
|
t.test("preview_hunk re-invocation focuses the open float", function()
|
||||||
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
|
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
|
||||||
vim.api.nvim_set_current_buf(buf)
|
vim.api.nvim_set_current_buf(buf)
|
||||||
|
|||||||
@@ -134,6 +134,45 @@ t.test("read_uri opens stage-0 entry as a writable index buffer", function()
|
|||||||
t.eq(vim.api.nvim_buf_get_lines(buf, 0, -1, false), { "first" })
|
t.eq(vim.api.nvim_buf_get_lines(buf, 0, -1, false), { "first" })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
t.test("M.open renders author/committer dates, not unix timestamps", function()
|
||||||
|
local dir = h.make_repo({ a = "first\n" })
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
|
||||||
|
object.open(r, "HEAD", { split = false })
|
||||||
|
local author = assert(find_line(0, "author "), "expected an author line")
|
||||||
|
local committer =
|
||||||
|
assert(find_line(0, "committer "), "expected a committer line")
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||||
|
for _, lnum in ipairs({ author, committer }) do
|
||||||
|
local line = assert(lines[lnum])
|
||||||
|
t.truthy(
|
||||||
|
line:match(
|
||||||
|
" %u%l%l %u%l%l +%d+ %d%d:%d%d:%d%d %d%d%d%d [+-]%d%d%d%d$"
|
||||||
|
),
|
||||||
|
"expected formatted date on: " .. line
|
||||||
|
)
|
||||||
|
t.falsy(
|
||||||
|
line:match(" %d%d%d%d%d%d%d%d%d%d? [+-]%d%d%d%d$"),
|
||||||
|
"expected no unix timestamp on: " .. line
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
t.test("M.open renders tagger dates on annotated tags", function()
|
||||||
|
local dir = h.make_repo({ a = "first\n" })
|
||||||
|
h.git(dir, "tag", "-m", "rel", "v1")
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
|
||||||
|
object.open(r, "v1", { split = false })
|
||||||
|
local tagger = assert(find_line(0, "tagger "), "expected a tagger line")
|
||||||
|
local line =
|
||||||
|
assert(vim.api.nvim_buf_get_lines(0, tagger - 1, tagger, false)[1])
|
||||||
|
t.truthy(
|
||||||
|
line:match(" %u%l%l %u%l%l +%d+ %d%d:%d%d:%d%d %d%d%d%d [+-]%d%d%d%d$"),
|
||||||
|
"expected formatted date on: " .. line
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
t.test("open_under_cursor on a 'tree <sha>' line opens the tree", function()
|
t.test("open_under_cursor on a 'tree <sha>' line opens the tree", function()
|
||||||
local dir = h.make_repo({ a = "first\n" })
|
local dir = h.make_repo({ a = "first\n" })
|
||||||
local r = assert(require("git.core.repo").resolve(dir))
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
@@ -163,18 +202,58 @@ t.test("open_under_cursor on a 'parent <sha>' line opens the parent", function()
|
|||||||
t.eq(vim.api.nvim_buf_get_name(0), "git://" .. parent_sha)
|
t.eq(vim.api.nvim_buf_get_name(0), "git://" .. parent_sha)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
t.test("open_under_cursor on a '+++ b/<path>' line loads the blob", function()
|
t.test(
|
||||||
|
"open_under_cursor on a '+++ b/<path>' line opens the file at the commit",
|
||||||
|
function()
|
||||||
local dir = h.make_repo({ ["a.txt"] = "first\n" })
|
local dir = h.make_repo({ ["a.txt"] = "first\n" })
|
||||||
local r = assert(require("git.core.repo").resolve(dir))
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
local blob_sha = h.git(dir, "rev-parse", "HEAD:a.txt").stdout
|
local commit_sha = h.git(dir, "rev-parse", "HEAD").stdout
|
||||||
|
|
||||||
object.open(r, "HEAD", { split = false })
|
object.open(r, "HEAD", { split = false })
|
||||||
local lnum = assert(find_line(0, "+++ b/a.txt"), "expected a +++ line")
|
local lnum = assert(find_line(0, "+++ b/a.txt"), "expected a +++ line")
|
||||||
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||||
|
|
||||||
|
t.truthy(object.open_under_cursor())
|
||||||
|
t.eq(vim.api.nvim_buf_get_name(0), "git://" .. commit_sha .. ":a.txt")
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
t.test(
|
||||||
|
"open_under_cursor on a '--- a/<path>' line opens the parent at that path",
|
||||||
|
function()
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "first\n" })
|
||||||
|
t.write(dir, "a.txt", "second\n")
|
||||||
|
h.git(dir, "add", "a.txt")
|
||||||
|
h.git(dir, "commit", "-q", "-m", "second")
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
local parent_sha = h.git(dir, "rev-parse", "HEAD~").stdout
|
||||||
|
|
||||||
|
object.open(r, "HEAD", { split = false })
|
||||||
|
local lnum = assert(find_line(0, "--- a/a.txt"), "expected a --- line")
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||||
|
|
||||||
|
t.truthy(object.open_under_cursor())
|
||||||
|
t.eq(vim.api.nvim_buf_get_name(0), "git://" .. parent_sha .. ":a.txt")
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
t.test(
|
||||||
|
"open_under_cursor on a tree-entry blob still uses the bare blob URI",
|
||||||
|
function()
|
||||||
|
local dir = h.make_repo({ ["a.txt"] = "first\n" })
|
||||||
|
local r = assert(require("git.core.repo").resolve(dir))
|
||||||
|
local tree_sha = h.git(dir, "rev-parse", "HEAD^{tree}").stdout
|
||||||
|
local blob_sha = h.git(dir, "rev-parse", "HEAD:a.txt").stdout
|
||||||
|
|
||||||
|
object.open(r, tree_sha, { split = false })
|
||||||
|
local lnum =
|
||||||
|
assert(find_line(0, "100644 blob "), "expected a tree entry line")
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||||
|
|
||||||
t.truthy(object.open_under_cursor())
|
t.truthy(object.open_under_cursor())
|
||||||
t.eq(vim.api.nvim_buf_get_name(0), "git://" .. blob_sha)
|
t.eq(vim.api.nvim_buf_get_name(0), "git://" .. blob_sha)
|
||||||
end)
|
end
|
||||||
|
)
|
||||||
|
|
||||||
t.test("open_under_cursor returns false on a non-dispatchable line", function()
|
t.test("open_under_cursor returns false on a non-dispatchable line", function()
|
||||||
local dir = h.make_repo({ a = "first\n" })
|
local dir = h.make_repo({ a = "first\n" })
|
||||||
|
|||||||
Reference in New Issue
Block a user