diff --git a/lua/git/status_view.lua b/lua/git/status_view.lua index cc7facc..b8e24ed 100644 --- a/lua/git/status_view.lua +++ b/lua/git/status_view.lua @@ -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 +---@field lines table ---@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