mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
feat(picker): added git_branches picker. Closes #614
This commit is contained in:
parent
903431903b
commit
8563dfce68
8 changed files with 196 additions and 7 deletions
|
|
@ -144,6 +144,21 @@ function M.git_stage(picker)
|
|||
end
|
||||
end
|
||||
|
||||
function M.git_checkout(picker, item)
|
||||
picker:close()
|
||||
if item then
|
||||
local what = item.branch or item.commit
|
||||
if not what then
|
||||
Snacks.notify.warn("No branch or commit found", { title = "Snacks Picker" })
|
||||
return
|
||||
end
|
||||
local cmd = { "git", "checkout", what }
|
||||
Snacks.picker.util.cmd(cmd, function()
|
||||
Snacks.notify("Checkout " .. what, { title = "Snacks Picker" })
|
||||
end, { cwd = item.cwd })
|
||||
end
|
||||
end
|
||||
|
||||
---@param items snacks.picker.Item[]
|
||||
---@param opts? {win?:number}
|
||||
local function setqflist(items, opts)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ Snacks.util.set_hl({
|
|||
KeymapRhs = "NonText",
|
||||
GitCommit = "@variable.builtin",
|
||||
GitBreaking = "Error",
|
||||
GitBranch = "Title",
|
||||
GitBranchCurrent = "Number",
|
||||
GitDate = "Special",
|
||||
GitIssue = "Number",
|
||||
GitType = "Title", -- conventional commit type
|
||||
|
|
|
|||
|
|
@ -126,6 +126,22 @@ M.files = {
|
|||
supports_live = true,
|
||||
}
|
||||
|
||||
M.git_branches = {
|
||||
finder = "git_branches",
|
||||
format = "git_branch",
|
||||
preview = "git_log",
|
||||
confirm = "git_checkout",
|
||||
on_show = function(picker)
|
||||
for i, item in ipairs(picker:items()) do
|
||||
if item.current then
|
||||
picker.list:view(i)
|
||||
Snacks.picker.actions.list_scroll_center(picker)
|
||||
break
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
-- Find git files
|
||||
---@class snacks.picker.git.files.Config: snacks.picker.Config
|
||||
---@field untracked? boolean show untracked files
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ function M.new(opts, main)
|
|||
colorcolumn = "",
|
||||
number = true,
|
||||
relativenumber = true,
|
||||
list = false,
|
||||
},
|
||||
},
|
||||
opts.win.preview,
|
||||
|
|
@ -93,6 +94,9 @@ end
|
|||
---@param picker snacks.Picker
|
||||
function M:show(picker)
|
||||
local item, prev = picker:current(), self.item
|
||||
if self.item == item then
|
||||
return
|
||||
end
|
||||
self.item = item
|
||||
if item then
|
||||
local buf = self.win.buf
|
||||
|
|
@ -160,6 +164,7 @@ function M:reset()
|
|||
vim.o.eventignore = "all"
|
||||
vim.bo[self.win.buf].filetype = "snacks_picker_preview"
|
||||
vim.bo[self.win.buf].syntax = ""
|
||||
vim.bo[self.win.buf].buftype = "nofile"
|
||||
vim.o.eventignore = ei
|
||||
self:wo({ cursorline = false })
|
||||
self:wo(self.win.opts.wo)
|
||||
|
|
@ -174,7 +179,7 @@ function M:scratch()
|
|||
vim.bo[buf].filetype = "snacks_picker_preview"
|
||||
vim.o.eventignore = ei
|
||||
vim.api.nvim_win_set_buf(self.win.win, buf)
|
||||
self:wo({ number = false, relativenumber = false })
|
||||
self:wo({ number = false, relativenumber = false, signcolumn = "no" })
|
||||
return buf
|
||||
end
|
||||
|
||||
|
|
@ -232,6 +237,10 @@ end
|
|||
---@param level? "info" | "warn" | "error"
|
||||
---@param opts? {item?:boolean}
|
||||
function M:notify(msg, level, opts)
|
||||
if not self.win:buf_valid() then
|
||||
Snacks.notify(msg, { level = level })
|
||||
return
|
||||
end
|
||||
level = level or "info"
|
||||
local lines = vim.split(level .. ": " .. msg, "\n", { plain = true })
|
||||
local msg_len = #lines
|
||||
|
|
|
|||
|
|
@ -98,8 +98,11 @@ function M.git_log(item, picker)
|
|||
local ret = {} ---@type snacks.picker.Highlight[]
|
||||
ret[#ret + 1] = { picker.opts.icons.git.commit, "SnacksPickerGitCommit" }
|
||||
ret[#ret + 1] = { item.commit, "SnacksPickerGitCommit" }
|
||||
|
||||
ret[#ret + 1] = { " " }
|
||||
ret[#ret + 1] = { a(item.date, 16), "SnacksPickerGitDate" }
|
||||
if item.date then
|
||||
ret[#ret + 1] = { a(item.date, 16), "SnacksPickerGitDate" }
|
||||
end
|
||||
|
||||
local msg = item.msg ---@type string
|
||||
local type, scope, breaking, body = msg:match("^(%S+)(%(.-%))(!?):%s*(.*)$")
|
||||
|
|
@ -130,6 +133,24 @@ function M.git_log(item, picker)
|
|||
return ret
|
||||
end
|
||||
|
||||
function M.git_branch(item, picker)
|
||||
local a = Snacks.picker.util.align
|
||||
local ret = {} ---@type snacks.picker.Highlight[]
|
||||
if item.current then
|
||||
ret[#ret + 1] = { a("", 2), "SnacksPickerGitBranchCurrent" }
|
||||
ret[#ret + 1] = { a(item.branch, 30, { truncate = true }), "SnacksPickerGitBranch" }
|
||||
else
|
||||
ret[#ret + 1] = { a("", 2) }
|
||||
ret[#ret + 1] = { a(item.branch, 30, { truncate = true }), "SnacksPickerGitBranch" }
|
||||
end
|
||||
ret[#ret + 1] = { " " }
|
||||
local offset = Snacks.picker.highlight.offset(ret)
|
||||
local log = M.git_log(item, picker)
|
||||
Snacks.picker.highlight.fix_offset(log, offset)
|
||||
vim.list_extend(ret, log)
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.lsp_symbol(item, picker)
|
||||
local ret = {} ---@type snacks.picker.Highlight[]
|
||||
if item.hierarchy then
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ function M.file(ctx)
|
|||
|
||||
file:close()
|
||||
|
||||
vim.bo[ctx.buf].buftype = ""
|
||||
vim.api.nvim_buf_set_lines(ctx.buf, 0, -1, false, lines)
|
||||
vim.bo[ctx.buf].modifiable = false
|
||||
ctx.preview:highlight({ file = path, ft = ctx.picker.opts.previewers.file.ft, buf = ctx.buf })
|
||||
|
|
@ -125,14 +126,29 @@ end
|
|||
|
||||
---@param cmd string[]
|
||||
---@param ctx snacks.picker.preview.ctx
|
||||
---@param opts? {env?:table<string, string>, pty?:boolean, ft?:string}
|
||||
---@param opts? {add?:fun(text:string, row:number), env?:table<string, string>, pty?:boolean, ft?:string}
|
||||
function M.cmd(cmd, ctx, opts)
|
||||
opts = opts or {}
|
||||
local buf = ctx.preview:scratch()
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
local pty = opts.pty ~= false and not opts.ft
|
||||
local killed = false
|
||||
local chan = pty and vim.api.nvim_open_term(buf, {}) or nil
|
||||
local output = {} ---@type string[]
|
||||
local line ---@type string?
|
||||
local l = 0
|
||||
|
||||
---@param text string
|
||||
local function add_line(text)
|
||||
l = l + 1
|
||||
vim.bo[buf].modifiable = true
|
||||
if opts.add then
|
||||
opts.add(text, l)
|
||||
else
|
||||
vim.api.nvim_buf_set_lines(buf, -1, -1, false, { text })
|
||||
end
|
||||
vim.bo[buf].modifiable = false
|
||||
end
|
||||
|
||||
---@param data string
|
||||
local function add(data)
|
||||
|
|
@ -144,10 +160,12 @@ function M.cmd(cmd, ctx, opts)
|
|||
end)
|
||||
end
|
||||
else
|
||||
vim.bo[buf].modifiable = true
|
||||
local lines = vim.split(table.concat(output, "\n"), "\n")
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
vim.bo[buf].modifiable = false
|
||||
line = (line or "") .. data
|
||||
local lines = vim.split(line, "\r?\n")
|
||||
line = table.remove(lines)
|
||||
for _, text in ipairs(lines) do
|
||||
add_line(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -167,6 +185,9 @@ function M.cmd(cmd, ctx, opts)
|
|||
add(table.concat(data, "\n"))
|
||||
end,
|
||||
on_exit = function(_, code)
|
||||
if not killed and line and line ~= "" and vim.api.nvim_buf_is_valid(buf) then
|
||||
add_line(line)
|
||||
end
|
||||
if not killed and code ~= 0 then
|
||||
Snacks.notify.error(
|
||||
("Terminal **cmd** `%s` failed with code `%d`:\n- `vim.o.shell = %q`\n\nOutput:\n%s"):format(
|
||||
|
|
@ -217,6 +238,48 @@ function M.git_show(ctx)
|
|||
M.cmd(cmd, ctx, { ft = not native and "git" or nil })
|
||||
end
|
||||
|
||||
---@param ctx snacks.picker.preview.ctx
|
||||
function M.git_log(ctx)
|
||||
local native = ctx.picker.opts.previewers.git.native
|
||||
local cmd = {
|
||||
"git",
|
||||
"-c",
|
||||
"delta." .. vim.o.background .. "=true",
|
||||
"log",
|
||||
"--pretty=format:%h %s (%ch)",
|
||||
"--abbrev-commit",
|
||||
"--decorate",
|
||||
"--date=short",
|
||||
"--color=never",
|
||||
"--no-show-signature",
|
||||
"--no-patch",
|
||||
ctx.item.branch,
|
||||
}
|
||||
if not native then
|
||||
table.insert(cmd, 2, "--no-pager")
|
||||
end
|
||||
local row = 0
|
||||
M.cmd(cmd, ctx, {
|
||||
ft = not native and "git" or nil,
|
||||
---@param text string
|
||||
add = not native and function(text)
|
||||
local commit, msg, date = text:match("^(%S+) (.*) %((.*)%)$")
|
||||
if commit then
|
||||
row = row + 1
|
||||
local hl = Snacks.picker.format.git_log({
|
||||
idx = 1,
|
||||
score = 0,
|
||||
text = "",
|
||||
commit = commit,
|
||||
msg = msg,
|
||||
date = date,
|
||||
}, ctx.picker)
|
||||
Snacks.picker.highlight.set(ctx.buf, ns, row, hl)
|
||||
end
|
||||
end or nil,
|
||||
})
|
||||
end
|
||||
|
||||
---@param ctx snacks.picker.preview.ctx
|
||||
function M.git_diff(ctx)
|
||||
local native = ctx.picker.opts.previewers.git.native
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ end
|
|||
---@type snacks.picker.finder
|
||||
function M.status(opts)
|
||||
local args = {
|
||||
"--no-pager",
|
||||
"status",
|
||||
"-uall",
|
||||
"--porcelain=v1",
|
||||
|
|
@ -154,4 +155,26 @@ function M.diff(opts)
|
|||
end
|
||||
end
|
||||
|
||||
---@param opts snacks.picker.Config
|
||||
---@type snacks.picker.finder
|
||||
function M.branches(opts)
|
||||
local args = { "--no-pager", "branch", "--no-color", "-vvl" }
|
||||
local cwd = vim.fs.normalize(opts and opts.cwd or uv.cwd() or ".") or nil
|
||||
cwd = Snacks.git.get_root(cwd)
|
||||
return require("snacks.picker.source.proc").proc(vim.tbl_deep_extend("force", {
|
||||
cwd = cwd,
|
||||
cmd = "git",
|
||||
args = args,
|
||||
---@param item snacks.picker.finder.Item
|
||||
transform = function(item)
|
||||
local status, branch, commit, msg = item.text:match("^(.)%s(%S+)%s+([a-zA-Z0-9]+)%s*(.*)$")
|
||||
item.cwd = cwd
|
||||
item.current = status == "*"
|
||||
item.branch = branch
|
||||
item.commit = commit
|
||||
item.msg = msg
|
||||
end,
|
||||
}, opts or {}))
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -224,4 +224,44 @@ function M.to_text(line, opts)
|
|||
return table.concat(parts), ret
|
||||
end
|
||||
|
||||
---@param hl snacks.picker.Highlight[]
|
||||
function M.fix_offset(hl, offset)
|
||||
for _, t in ipairs(hl) do
|
||||
if t.col then
|
||||
t.col = t.col + offset
|
||||
end
|
||||
if t.end_col then
|
||||
t.end_col = t.end_col + offset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param ns number
|
||||
---@param row number
|
||||
---@param hl snacks.picker.Highlight[]
|
||||
function M.set(buf, ns, row, hl)
|
||||
while #hl > 0 and type(hl[#hl][1]) == "string" and hl[#hl][1]:find("^%s*$") do
|
||||
table.remove(hl)
|
||||
end
|
||||
local line_text, extmarks = Snacks.picker.highlight.to_text(hl)
|
||||
vim.api.nvim_buf_set_lines(buf, row - 1, row, false, { line_text })
|
||||
for _, extmark in ipairs(extmarks) do
|
||||
local col = extmark.col
|
||||
extmark.col = nil
|
||||
extmark.row = nil
|
||||
extmark.field = nil
|
||||
local ok, err = pcall(vim.api.nvim_buf_set_extmark, buf, ns, row - 1, col, extmark)
|
||||
if not ok then
|
||||
Snacks.notify.error(
|
||||
"Failed to set extmark. This should not happen. Please report.\n"
|
||||
.. err
|
||||
.. "\n```lua\n"
|
||||
.. vim.inspect(extmark)
|
||||
.. "\n```"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue