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"
|
||||
|
||||
---@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
|
||||
---@field repo ow.Git.Repo
|
||||
---@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 invocation_win integer?
|
||||
---@field diff_left_win integer?
|
||||
@@ -110,6 +116,7 @@ local function render(bufnr, status)
|
||||
lines,
|
||||
string.format("%s (%d)", display_name(kind), #entries)
|
||||
)
|
||||
meta[#lines] = { is_header = true, kind = kind }
|
||||
for _, entry in ipairs(entries) do
|
||||
local line, hl, hl_len = format_entry(entry)
|
||||
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
|
||||
return
|
||||
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
|
||||
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
|
||||
|
||||
---@param bufnr integer
|
||||
---@return ow.Git.StatusView.State?
|
||||
---@return ow.Git.Status.Entry?
|
||||
---@return ow.Git.StatusView.Item?
|
||||
local function current_entry(bufnr)
|
||||
local s = state[bufnr]
|
||||
if not s then
|
||||
@@ -442,23 +426,41 @@ end
|
||||
|
||||
---@param focus_left boolean
|
||||
local function preview_or_open(focus_left)
|
||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not entry then
|
||||
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not item or item.is_header then
|
||||
return
|
||||
end
|
||||
view_entry(s, entry, focus_left)
|
||||
---@cast item ow.Git.Status.Entry
|
||||
view_entry(s, item, focus_left)
|
||||
end
|
||||
|
||||
local function action_stage()
|
||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not entry then
|
||||
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not item then
|
||||
return
|
||||
end
|
||||
if entry.kind == "staged" then
|
||||
local paths = {}
|
||||
if item.is_header then
|
||||
if item.kind == "staged" or item.kind == "ignored" then
|
||||
return
|
||||
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(
|
||||
{ "git", "add", "--", entry.path },
|
||||
cmd,
|
||||
{ cwd = s.repo.worktree },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if obj.code ~= 0 then
|
||||
@@ -469,18 +471,30 @@ local function action_stage()
|
||||
end
|
||||
|
||||
local function action_unstage()
|
||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not entry then
|
||||
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not item then
|
||||
return
|
||||
end
|
||||
if entry.kind ~= "staged" then
|
||||
if item.kind ~= "staged" then
|
||||
return
|
||||
end
|
||||
local cmd = { "git", "restore", "--staged", "--" }
|
||||
if entry.orig then
|
||||
table.insert(cmd, entry.orig)
|
||||
local entries
|
||||
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
|
||||
table.insert(cmd, entry.path)
|
||||
vim.system(
|
||||
cmd,
|
||||
{ cwd = s.repo.worktree },
|
||||
@@ -496,36 +510,37 @@ local function action_unstage()
|
||||
end
|
||||
|
||||
local function action_discard()
|
||||
local s, entry = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not entry then
|
||||
local s, item = current_entry(vim.api.nvim_get_current_buf())
|
||||
if not s or not item or item.is_header then
|
||||
return
|
||||
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'")
|
||||
return
|
||||
end
|
||||
|
||||
local prompt, action
|
||||
if entry.kind == "untracked" then
|
||||
local is_dir = entry.path:sub(-1) == "/"
|
||||
if item.kind == "untracked" then
|
||||
local is_dir = item.path:sub(-1) == "/"
|
||||
prompt = string.format(
|
||||
"Delete untracked %s %s?",
|
||||
is_dir and "directory" or "file",
|
||||
entry.path
|
||||
item.path
|
||||
)
|
||||
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 "")
|
||||
if rc ~= 0 then
|
||||
util.error("failed to delete %s", entry.path)
|
||||
util.error("failed to delete %s", item.path)
|
||||
end
|
||||
refresh(vim.api.nvim_get_current_buf())
|
||||
end
|
||||
elseif entry.kind == "unstaged" then
|
||||
prompt = string.format("Discard changes to %s?", entry.path)
|
||||
elseif item.kind == "unstaged" then
|
||||
prompt = string.format("Discard changes to %s?", item.path)
|
||||
action = function()
|
||||
vim.system(
|
||||
{ "git", "checkout", "--", entry.path },
|
||||
{ "git", "checkout", "--", item.path },
|
||||
{ cwd = s.repo.worktree },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if obj.code ~= 0 then
|
||||
|
||||
Reference in New Issue
Block a user