feat(git): stable cursor + section-header s/u in status view
This commit is contained in:
+64
-49
@@ -27,10 +27,16 @@ end
|
|||||||
|
|
||||||
---@alias ow.Git.StatusView.Placement "sidebar"|"split"|"current"
|
---@alias ow.Git.StatusView.Placement "sidebar"|"split"|"current"
|
||||||
|
|
||||||
|
---@class ow.Git.StatusView.Header
|
||||||
|
---@field is_header true
|
||||||
|
---@field kind ow.Git.Status.EntryKind
|
||||||
|
|
||||||
|
---@alias ow.Git.StatusView.Item ow.Git.Status.Entry | ow.Git.StatusView.Header
|
||||||
|
|
||||||
---@class ow.Git.StatusView.State
|
---@class ow.Git.StatusView.State
|
||||||
---@field repo ow.Git.Repo
|
---@field repo ow.Git.Repo
|
||||||
---@field placement ow.Git.StatusView.Placement
|
---@field placement ow.Git.StatusView.Placement
|
||||||
---@field lines table<integer, ow.Git.Status.Entry>
|
---@field lines table<integer, ow.Git.StatusView.Item>
|
||||||
---@field win integer?
|
---@field win integer?
|
||||||
---@field invocation_win integer?
|
---@field invocation_win integer?
|
||||||
---@field diff_left_win integer?
|
---@field diff_left_win integer?
|
||||||
@@ -110,6 +116,7 @@ local function render(bufnr, status)
|
|||||||
lines,
|
lines,
|
||||||
string.format("%s (%d)", display_name(kind), #entries)
|
string.format("%s (%d)", display_name(kind), #entries)
|
||||||
)
|
)
|
||||||
|
meta[#lines] = { is_header = true, kind = kind }
|
||||||
for _, entry in ipairs(entries) do
|
for _, entry in ipairs(entries) do
|
||||||
local line, hl, hl_len = format_entry(entry)
|
local line, hl, hl_len = format_entry(entry)
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
@@ -144,36 +151,13 @@ local function refresh(bufnr)
|
|||||||
if not s or not vim.api.nvim_buf_is_valid(bufnr) then
|
if not s or not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local saved_path
|
|
||||||
local status_win = win_for(s)
|
|
||||||
if status_win then
|
|
||||||
local lnum = vim.api.nvim_win_get_cursor(status_win)[1]
|
|
||||||
local entry = s.lines[lnum]
|
|
||||||
if entry then
|
|
||||||
saved_path = entry.path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
s.last_shown_key = nil
|
s.last_shown_key = nil
|
||||||
render(bufnr, s.repo.status)
|
render(bufnr, s.repo.status)
|
||||||
if not saved_path then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for lnum, entry in pairs(s.lines) do
|
|
||||||
if entry.path == saved_path then
|
|
||||||
local win = win_for(s)
|
|
||||||
if win then
|
|
||||||
pcall(vim.api.nvim_win_set_cursor, win, { lnum, 0 })
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return ow.Git.StatusView.State?
|
---@return ow.Git.StatusView.State?
|
||||||
---@return ow.Git.Status.Entry?
|
---@return ow.Git.StatusView.Item?
|
||||||
local function current_entry(bufnr)
|
local function current_entry(bufnr)
|
||||||
local s = state[bufnr]
|
local s = state[bufnr]
|
||||||
if not s then
|
if not s then
|
||||||
@@ -442,23 +426,41 @@ end
|
|||||||
|
|
||||||
---@param focus_left boolean
|
---@param focus_left boolean
|
||||||
local function preview_or_open(focus_left)
|
local function preview_or_open(focus_left)
|
||||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||||
if not s or not entry then
|
if not s or not item or item.is_header then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
view_entry(s, entry, focus_left)
|
---@cast item ow.Git.Status.Entry
|
||||||
|
view_entry(s, item, focus_left)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function action_stage()
|
local function action_stage()
|
||||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||||
if not s or not entry then
|
if not s or not item then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if entry.kind == "staged" then
|
local paths = {}
|
||||||
|
if item.is_header then
|
||||||
|
if item.kind == "staged" or item.kind == "ignored" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
for _, e in ipairs(s.repo.status:by_kind(item.kind)) do
|
||||||
|
table.insert(paths, e.path)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
---@cast item ow.Git.Status.Entry
|
||||||
|
if item.kind == "staged" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.insert(paths, item.path)
|
||||||
|
end
|
||||||
|
if #paths == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cmd = { "git", "add", "--" }
|
||||||
|
vim.list_extend(cmd, paths)
|
||||||
vim.system(
|
vim.system(
|
||||||
{ "git", "add", "--", entry.path },
|
cmd,
|
||||||
{ cwd = s.repo.worktree },
|
{ cwd = s.repo.worktree },
|
||||||
vim.schedule_wrap(function(obj)
|
vim.schedule_wrap(function(obj)
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
@@ -469,18 +471,30 @@ local function action_stage()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function action_unstage()
|
local function action_unstage()
|
||||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||||
if not s or not entry then
|
if not s or not item then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if entry.kind ~= "staged" then
|
if item.kind ~= "staged" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local cmd = { "git", "restore", "--staged", "--" }
|
local cmd = { "git", "restore", "--staged", "--" }
|
||||||
if entry.orig then
|
local entries
|
||||||
table.insert(cmd, entry.orig)
|
if item.is_header then
|
||||||
|
entries = s.repo.status:by_kind("staged")
|
||||||
|
else
|
||||||
|
---@cast item ow.Git.Status.Entry
|
||||||
|
entries = { item }
|
||||||
|
end
|
||||||
|
if #entries == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _, e in ipairs(entries) do
|
||||||
|
if e.orig then
|
||||||
|
table.insert(cmd, e.orig)
|
||||||
|
end
|
||||||
|
table.insert(cmd, e.path)
|
||||||
end
|
end
|
||||||
table.insert(cmd, entry.path)
|
|
||||||
vim.system(
|
vim.system(
|
||||||
cmd,
|
cmd,
|
||||||
{ cwd = s.repo.worktree },
|
{ cwd = s.repo.worktree },
|
||||||
@@ -496,36 +510,37 @@ local function action_unstage()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function action_discard()
|
local function action_discard()
|
||||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||||
if not s or not entry then
|
if not s or not item or item.is_header then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if entry.kind == "staged" then
|
---@cast item ow.Git.Status.Entry
|
||||||
|
if item.kind == "staged" then
|
||||||
util.warning("file has staged changes, unstage first with 'u'")
|
util.warning("file has staged changes, unstage first with 'u'")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local prompt, action
|
local prompt, action
|
||||||
if entry.kind == "untracked" then
|
if item.kind == "untracked" then
|
||||||
local is_dir = entry.path:sub(-1) == "/"
|
local is_dir = item.path:sub(-1) == "/"
|
||||||
prompt = string.format(
|
prompt = string.format(
|
||||||
"Delete untracked %s %s?",
|
"Delete untracked %s %s?",
|
||||||
is_dir and "directory" or "file",
|
is_dir and "directory" or "file",
|
||||||
entry.path
|
item.path
|
||||||
)
|
)
|
||||||
action = function()
|
action = function()
|
||||||
local target = vim.fs.joinpath(s.repo.worktree, entry.path)
|
local target = vim.fs.joinpath(s.repo.worktree, item.path)
|
||||||
local rc = vim.fn.delete(target, is_dir and "rf" or "")
|
local rc = vim.fn.delete(target, is_dir and "rf" or "")
|
||||||
if rc ~= 0 then
|
if rc ~= 0 then
|
||||||
util.error("failed to delete %s", entry.path)
|
util.error("failed to delete %s", item.path)
|
||||||
end
|
end
|
||||||
refresh(vim.api.nvim_get_current_buf())
|
refresh(vim.api.nvim_get_current_buf())
|
||||||
end
|
end
|
||||||
elseif entry.kind == "unstaged" then
|
elseif item.kind == "unstaged" then
|
||||||
prompt = string.format("Discard changes to %s?", entry.path)
|
prompt = string.format("Discard changes to %s?", item.path)
|
||||||
action = function()
|
action = function()
|
||||||
vim.system(
|
vim.system(
|
||||||
{ "git", "checkout", "--", entry.path },
|
{ "git", "checkout", "--", item.path },
|
||||||
{ cwd = s.repo.worktree },
|
{ cwd = s.repo.worktree },
|
||||||
vim.schedule_wrap(function(obj)
|
vim.schedule_wrap(function(obj)
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
|
|||||||
Reference in New Issue
Block a user