refactor(git/status): rework entries into typed variants on porcelain v2
This commit is contained in:
+111
-79
@@ -2,6 +2,7 @@ local Revision = require("git.revision")
|
||||
local diff = require("git.diff")
|
||||
local object = require("git.object")
|
||||
local repo = require("git.repo")
|
||||
local status = require("git.status")
|
||||
local util = require("git.util")
|
||||
|
||||
local M = {}
|
||||
@@ -11,8 +12,8 @@ M.URI_PREFIX = "gitstatus://"
|
||||
---@type ow.Git.StatusView.Placement[]
|
||||
M.PLACEMENTS = { "sidebar", "split", "current" }
|
||||
|
||||
---@type ow.Git.Status.EntryKind[]
|
||||
local KINDS = { "untracked", "unstaged", "staged", "unmerged" }
|
||||
---@type ow.Git.Status.Section[]
|
||||
local SECTIONS = { "untracked", "unstaged", "staged", "unmerged" }
|
||||
local WINDOW_WIDTH = 50
|
||||
|
||||
---@param name string
|
||||
@@ -29,9 +30,9 @@ end
|
||||
|
||||
---@class ow.Git.StatusView.Header
|
||||
---@field is_header true
|
||||
---@field kind ow.Git.Status.EntryKind
|
||||
---@field section ow.Git.Status.Section
|
||||
|
||||
---@alias ow.Git.StatusView.Item ow.Git.Status.Entry | ow.Git.StatusView.Header
|
||||
---@alias ow.Git.StatusView.Item ow.Git.Status.Row | ow.Git.StatusView.Header
|
||||
|
||||
---@class ow.Git.StatusView.State
|
||||
---@field repo ow.Git.Repo
|
||||
@@ -83,20 +84,26 @@ local function win_for(s)
|
||||
return win
|
||||
end
|
||||
|
||||
---@param entry ow.Git.Status.Entry
|
||||
---@param row ow.Git.Status.Row
|
||||
---@return string line
|
||||
---@return string hl_group
|
||||
---@return integer hl_len
|
||||
local function format_entry(entry)
|
||||
local label = entry.orig and (entry.orig .. " -> " .. entry.path)
|
||||
or entry.path
|
||||
return string.format(" %s %s", entry.char, label), entry.hl, #entry.char
|
||||
local function format_row(row)
|
||||
local entry = row.entry
|
||||
local orig
|
||||
if entry.kind == "changed" then
|
||||
---@cast entry ow.Git.Status.ChangedEntry
|
||||
orig = entry.orig
|
||||
end
|
||||
local label = orig and (orig .. " -> " .. entry.path) or entry.path
|
||||
local mark = status.mark_for(entry, row.side)
|
||||
return string.format(" %s %s", mark.char, label), mark.hl, #mark.char
|
||||
end
|
||||
|
||||
---@param kind ow.Git.Status.EntryKind
|
||||
---@param section ow.Git.Status.Section
|
||||
---@return string
|
||||
local function display_name(kind)
|
||||
return (kind:gsub("^%l", string.upper))
|
||||
local function display_name(section)
|
||||
return (section:gsub("^%l", string.upper))
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
@@ -118,18 +125,18 @@ local function render(bufnr, status)
|
||||
|
||||
local meta = {}
|
||||
local marks = {}
|
||||
for _, kind in ipairs(KINDS) do
|
||||
local entries = status:by_kind(kind)
|
||||
if #entries > 0 then
|
||||
for _, section in ipairs(SECTIONS) do
|
||||
local rows = status:rows(section)
|
||||
if #rows > 0 then
|
||||
table.insert(
|
||||
lines,
|
||||
string.format("%s (%d)", display_name(kind), #entries)
|
||||
string.format("%s (%d)", display_name(section), #rows)
|
||||
)
|
||||
meta[#lines] = { is_header = true, kind = kind }
|
||||
for _, entry in ipairs(entries) do
|
||||
local line, hl, hl_len = format_entry(entry)
|
||||
meta[#lines] = { is_header = true, section = section }
|
||||
for _, row in ipairs(rows) do
|
||||
local line, hl, hl_len = format_row(row)
|
||||
table.insert(lines, line)
|
||||
meta[#lines] = entry
|
||||
meta[#lines] = row
|
||||
table.insert(marks, {
|
||||
row = #lines - 1,
|
||||
col = 2,
|
||||
@@ -195,10 +202,10 @@ local function worktree_pane(r, path)
|
||||
end
|
||||
|
||||
---@param s ow.Git.StatusView.State
|
||||
---@param entry ow.Git.Status.Entry
|
||||
---@param path string
|
||||
---@return ow.Git.Diff.Side
|
||||
local function index_pane(s, entry)
|
||||
local rev = Revision.new({ stage = 0, path = entry.path })
|
||||
local function index_pane(s, path)
|
||||
local rev = Revision.new({ stage = 0, path = path })
|
||||
return {
|
||||
buf = object.buf_for(s.repo, rev),
|
||||
name = object.format_uri(rev),
|
||||
@@ -206,38 +213,43 @@ local function index_pane(s, entry)
|
||||
end
|
||||
|
||||
---@param s ow.Git.StatusView.State
|
||||
---@param entry ow.Git.Status.Entry
|
||||
---@param row ow.Git.Status.Row
|
||||
---@return ow.Git.Diff.Side?
|
||||
local function older_pane(s, entry)
|
||||
if entry.kind == "staged" then
|
||||
if entry.char == "A" then
|
||||
local function older_pane(s, row)
|
||||
local entry = row.entry
|
||||
if row.section == "staged" then
|
||||
---@cast entry ow.Git.Status.ChangedEntry
|
||||
if entry.staged == "added" then
|
||||
return nil
|
||||
end
|
||||
return head_pane(s.repo, entry.orig or entry.path)
|
||||
end
|
||||
if entry.kind == "unstaged" then
|
||||
return index_pane(s, entry)
|
||||
if row.section == "unstaged" then
|
||||
return index_pane(s, entry.path)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param s ow.Git.StatusView.State
|
||||
---@param entry ow.Git.Status.Entry
|
||||
---@param row ow.Git.Status.Row
|
||||
---@return ow.Git.Diff.Side?
|
||||
local function newer_pane(s, entry)
|
||||
if entry.kind == "staged" then
|
||||
if entry.char == "D" then
|
||||
local function newer_pane(s, row)
|
||||
local entry = row.entry
|
||||
if row.section == "staged" then
|
||||
---@cast entry ow.Git.Status.ChangedEntry
|
||||
if entry.staged == "deleted" then
|
||||
return nil
|
||||
end
|
||||
return index_pane(s, entry)
|
||||
return index_pane(s, entry.path)
|
||||
end
|
||||
if entry.kind == "unstaged" then
|
||||
if entry.char == "D" then
|
||||
if row.section == "unstaged" then
|
||||
---@cast entry ow.Git.Status.ChangedEntry
|
||||
if entry.unstaged == "deleted" then
|
||||
return nil
|
||||
end
|
||||
return worktree_pane(s.repo, entry.path)
|
||||
end
|
||||
if entry.kind == "untracked" then
|
||||
if row.section == "untracked" then
|
||||
return worktree_pane(s.repo, entry.path)
|
||||
end
|
||||
return nil
|
||||
@@ -296,10 +308,16 @@ local function adopt_diff_wins(s, status_win)
|
||||
return left, right
|
||||
end
|
||||
|
||||
---@param entry ow.Git.Status.Entry
|
||||
---@param row ow.Git.Status.Row
|
||||
---@return string
|
||||
local function entry_key(entry)
|
||||
return entry.kind .. "|" .. entry.path .. "|" .. (entry.orig or "")
|
||||
local function row_key(row)
|
||||
local entry = row.entry
|
||||
local orig
|
||||
if entry.kind == "changed" then
|
||||
---@cast entry ow.Git.Status.ChangedEntry
|
||||
orig = entry.orig
|
||||
end
|
||||
return row.section .. "|" .. entry.path .. "|" .. (orig or "")
|
||||
end
|
||||
|
||||
---@param target_win integer
|
||||
@@ -335,18 +353,22 @@ local function ensure_right_win(s, status_win, right_win)
|
||||
end
|
||||
|
||||
---@param s ow.Git.StatusView.State
|
||||
---@param entry ow.Git.Status.Entry
|
||||
---@param row ow.Git.Status.Row
|
||||
---@param focus_left boolean
|
||||
local function view_entry(s, entry, focus_left)
|
||||
local function view_row(s, row, focus_left)
|
||||
local status_win = win_for(s)
|
||||
if not status_win then
|
||||
return
|
||||
end
|
||||
|
||||
local left = older_pane(s, entry)
|
||||
local right = newer_pane(s, entry)
|
||||
local left = older_pane(s, row)
|
||||
local right = newer_pane(s, row)
|
||||
if not left and not right then
|
||||
util.warning("no content for %s entry: %s", entry.kind, entry.path)
|
||||
util.warning(
|
||||
"no content for %s row: %s",
|
||||
row.section,
|
||||
row.entry.path
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -362,7 +384,7 @@ local function view_entry(s, entry, focus_left)
|
||||
return
|
||||
end
|
||||
|
||||
local key = entry_key(entry)
|
||||
local key = row_key(row)
|
||||
local left_win, right_win = adopt_diff_wins(s, status_win)
|
||||
local want_pair = left and right
|
||||
|
||||
@@ -433,8 +455,8 @@ local function preview_or_open(focus_left)
|
||||
if not s or not item or item.is_header then
|
||||
return
|
||||
end
|
||||
---@cast item ow.Git.Status.Entry
|
||||
view_entry(s, item, focus_left)
|
||||
---@cast item ow.Git.Status.Row
|
||||
view_row(s, item, focus_left)
|
||||
end
|
||||
|
||||
local function action_stage()
|
||||
@@ -444,18 +466,18 @@ local function action_stage()
|
||||
end
|
||||
local paths = {}
|
||||
if item.is_header then
|
||||
if item.kind == "staged" or item.kind == "ignored" then
|
||||
if item.section == "staged" or item.section == "ignored" then
|
||||
return
|
||||
end
|
||||
for _, e in ipairs(s.repo.status:by_kind(item.kind)) do
|
||||
table.insert(paths, e.path)
|
||||
for _, row in ipairs(s.repo.status:rows(item.section)) do
|
||||
table.insert(paths, row.entry.path)
|
||||
end
|
||||
else
|
||||
---@cast item ow.Git.Status.Entry
|
||||
if item.kind == "staged" then
|
||||
---@cast item ow.Git.Status.Row
|
||||
if item.section == "staged" then
|
||||
return
|
||||
end
|
||||
table.insert(paths, item.path)
|
||||
table.insert(paths, item.entry.path)
|
||||
end
|
||||
if #paths == 0 then
|
||||
return
|
||||
@@ -477,25 +499,33 @@ local function action_unstage()
|
||||
if not s or not item then
|
||||
return
|
||||
end
|
||||
if item.kind ~= "staged" then
|
||||
local rows
|
||||
if item.is_header then
|
||||
if item.section ~= "staged" then
|
||||
return
|
||||
end
|
||||
rows = s.repo.status:rows("staged")
|
||||
else
|
||||
---@cast item ow.Git.Status.Row
|
||||
if item.section ~= "staged" then
|
||||
return
|
||||
end
|
||||
rows = { item }
|
||||
end
|
||||
---@cast rows ow.Git.Status.Row[]
|
||||
if #rows == 0 then
|
||||
return
|
||||
end
|
||||
local args = { "restore", "--staged", "--" }
|
||||
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(args, e.orig)
|
||||
for _, row in ipairs(rows) do
|
||||
local entry = row.entry
|
||||
if entry.kind == "changed" then
|
||||
---@cast entry ow.Git.Status.ChangedEntry
|
||||
if entry.orig then
|
||||
table.insert(args, entry.orig)
|
||||
end
|
||||
end
|
||||
table.insert(args, e.path)
|
||||
table.insert(args, entry.path)
|
||||
end
|
||||
util.git(args, {
|
||||
cwd = s.repo.worktree,
|
||||
@@ -515,32 +545,34 @@ local function action_discard()
|
||||
if not s or not item or item.is_header then
|
||||
return
|
||||
end
|
||||
---@cast item ow.Git.Status.Entry
|
||||
if item.kind == "staged" then
|
||||
---@cast item ow.Git.Status.Row
|
||||
if item.section == "staged" then
|
||||
util.warning("file has staged changes, unstage first with 'u'")
|
||||
return
|
||||
end
|
||||
local entry = item.entry
|
||||
local path = entry.path
|
||||
|
||||
local prompt, action
|
||||
if item.kind == "untracked" then
|
||||
local is_dir = item.path:sub(-1) == "/"
|
||||
if item.section == "untracked" then
|
||||
local is_dir = path:sub(-1) == "/"
|
||||
prompt = string.format(
|
||||
"Delete untracked %s %s?",
|
||||
is_dir and "directory" or "file",
|
||||
item.path
|
||||
path
|
||||
)
|
||||
action = function()
|
||||
local target = vim.fs.joinpath(s.repo.worktree, item.path)
|
||||
local target = vim.fs.joinpath(s.repo.worktree, path)
|
||||
local rc = vim.fn.delete(target, is_dir and "rf" or "")
|
||||
if rc ~= 0 then
|
||||
util.error("failed to delete %s", item.path)
|
||||
util.error("failed to delete %s", path)
|
||||
end
|
||||
refresh(vim.api.nvim_get_current_buf())
|
||||
end
|
||||
elseif item.kind == "unstaged" then
|
||||
prompt = string.format("Discard changes to %s?", item.path)
|
||||
elseif item.section == "unstaged" then
|
||||
prompt = string.format("Discard changes to %s?", path)
|
||||
action = function()
|
||||
util.git({ "checkout", "--", item.path }, {
|
||||
util.git({ "checkout", "--", path }, {
|
||||
cwd = s.repo.worktree,
|
||||
on_exit = function(result)
|
||||
if result.code ~= 0 then
|
||||
|
||||
Reference in New Issue
Block a user