Vendor gitpanel 0.3.0 (2.1.8-5)
Git panel integrated into the treeview pane: uncommitted changes with diff stats, staging, commits and history, toggled from a git-branch button in the treeview toolbar or the status bar. History is pinned to the bottom half of the panel; commit patches open with diff syntax highlighting from the already bundled language_diff. Pinned to lite-xl-gitpanel @ 0731a63 (see debian/extra/README.source).
This commit is contained in:
Vendored
+12
@@ -1,3 +1,15 @@
|
||||
lite-xl (2.1.8-5) stonking; urgency=medium
|
||||
|
||||
* Vendor gitpanel 0.3.0 (own plugin, lite-xl-gitpanel @ 0731a63): a git
|
||||
panel integrated into the treeview pane - uncommitted changes with
|
||||
diff stats, staging, commits and history - toggled from a git-branch
|
||||
button in the treeview toolbar or the status bar branch indicator.
|
||||
History is pinned to the bottom half of the panel; clicking a commit
|
||||
opens its patch with syntax highlighting via the already bundled
|
||||
language_diff.
|
||||
|
||||
-- Valentin Haudiquet <valentin.haudiquet@canonical.com> Wed, 17 Sep 2026 01:55:00 +0200
|
||||
|
||||
lite-xl (2.1.8-4) stonking; urgency=medium
|
||||
|
||||
* Bundle a curated set of community plugins (vendored, pinned in
|
||||
|
||||
Vendored
+5
@@ -4,6 +4,11 @@ Vendored from upstream, pinned by commit:
|
||||
plugins/: gitstatus, gitopen, fontconfig, open_ext, select_colorscheme,
|
||||
smoothcaret, nerdicons, language_* (all 104; none overlap the ones bundled
|
||||
in the core data/ directory)
|
||||
- lite-xl-gitpanel (own plugin, source tree lite-xl-gitpanel) @ 0731a63 (main, 2026-09)
|
||||
plugins/: gitpanel -- git panel integrated into the treeview pane
|
||||
(changes, staging, commits, history) with a toolbar toggle button;
|
||||
uses the bundled language_diff for commit diff highlighting
|
||||
|
||||
- lite-xl-lsp https://github.com/lite-xl/lite-xl-lsp @ d1432ae (master, 2026-09)
|
||||
vendored whole as plugins/lsp/ (screenshots removed)
|
||||
- lite-xl-widgets https://github.com/lite-xl/lite-xl-widgets @ dcf1c8c
|
||||
|
||||
Vendored
+678
@@ -0,0 +1,678 @@
|
||||
-- mod-version:3
|
||||
-- version: 0.3.0
|
||||
-- Git panel for Lite XL: uncommitted changes, staging, commits, history.
|
||||
-- Self-contained, needs only the `git` CLI in PATH.
|
||||
-- Toggle with Ctrl+Shift+G, the git button in the treeview toolbar or the
|
||||
-- branch name in the status bar. When the treeview plugin is present the
|
||||
-- panel replaces the folder list in the left pane; otherwise it opens as
|
||||
-- its own pane.
|
||||
-- In the panel: click a file to open it, click a commit to view its patch.
|
||||
-- Keys (panel focused): s stage · u unstage · a stage all · c commit · r refresh
|
||||
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local command = require "core.command"
|
||||
local syntax = require "core.syntax"
|
||||
local View = require "core.view"
|
||||
local Doc = require "core.doc"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
config.plugins.gitpanel = common.merge({
|
||||
history_length = 40,
|
||||
}, config.plugins.gitpanel)
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Async git helpers (run inside core.add_thread coroutines)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Run a command, reading incrementally so large outputs (git show) cannot
|
||||
-- deadlock on the pipe buffer. Must be called from a coroutine.
|
||||
local function exec(args)
|
||||
local proc = process.start(args)
|
||||
if not proc then return "" end
|
||||
local chunks = {}
|
||||
while proc:running() do
|
||||
local chunk = proc:read_stdout(65536)
|
||||
if chunk and #chunk > 0 then
|
||||
chunks[#chunks + 1] = chunk
|
||||
else
|
||||
coroutine.yield(0.05)
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local chunk = proc:read_stdout(65536)
|
||||
if chunk and #chunk > 0 then chunks[#chunks + 1] = chunk else break end
|
||||
end
|
||||
return table.concat(chunks)
|
||||
end
|
||||
|
||||
local state = { branch = nil, changes = {}, history = {}, clean = false, loading = false }
|
||||
|
||||
local function parse_numstat(out, map)
|
||||
for line in (out .. "\n"):gmatch("([^\n]*)\n") do
|
||||
local ins, del, path = line:match("^(%d+)%s+(%d+)%s+(.+)$")
|
||||
if path then
|
||||
path = path:match("^.- ->%s*(.+)$") or path -- renames: keep new path
|
||||
map[path] = { n = tonumber(ins) or 0, d = tonumber(del) or 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function refresh(callback)
|
||||
if state.loading then return end
|
||||
state.loading = true
|
||||
core.add_thread(function()
|
||||
local branch, changes, history
|
||||
-- pcall so a failed git run (spawn error, ...) can never leave
|
||||
-- state.loading stuck and silently kill every later refresh
|
||||
local ok, err = pcall(function()
|
||||
branch, changes, history = nil, {}, {}
|
||||
if system.get_file_info(core.project_dir .. PATHSEP .. ".git") then
|
||||
local work, staged = {}, {}
|
||||
parse_numstat(exec { "git", "diff", "--numstat" }, work)
|
||||
parse_numstat(exec { "git", "diff", "--cached", "--numstat" }, staged)
|
||||
|
||||
local status = exec { "git", "status", "--porcelain=v1", "-b" }
|
||||
local first = true
|
||||
for line in (status .. "\n"):gmatch("([^\n]*)\n") do
|
||||
if first and line:match("^## ") then
|
||||
branch = line:sub(4):match("^(.-)%.%.%.") or line:sub(4):match("^(%S+)")
|
||||
first = false
|
||||
elseif line ~= "" then
|
||||
local xy, path = line:match("^(..) (.+)$")
|
||||
if xy then
|
||||
path = path:match("^(.-)%s+->%s+(.+)$") or path
|
||||
local x, y = xy:sub(1, 1), xy:sub(2, 2)
|
||||
local nums = (x ~= " ") and staged[path] or work[path]
|
||||
changes[#changes + 1] = {
|
||||
xy = xy,
|
||||
st = xy == "??" and "??" or (x ~= " " and x or y),
|
||||
path = path,
|
||||
staged = x ~= " " and x ~= "?",
|
||||
untracked = xy == "??",
|
||||
plus = nums and nums.n or nil,
|
||||
minus = nums and nums.d or nil,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local log = exec { "git", "log", "-n", tostring(config.plugins.gitpanel.history_length),
|
||||
"--pretty=format:%h %s" }
|
||||
for line in (log .. "\n"):gmatch("([^\n]*)\n") do
|
||||
local hash, subject = line:match("^(%S+) (.*)$")
|
||||
if hash then history[#history + 1] = { hash = hash, subject = subject } end
|
||||
end
|
||||
end
|
||||
end)
|
||||
state.loading = false
|
||||
if ok then
|
||||
state.branch, state.changes, state.history = branch, changes, history
|
||||
state.clean = branch ~= nil and #changes == 0
|
||||
else
|
||||
core.error("git-panel: refresh failed: %s", tostring(err))
|
||||
end
|
||||
if callback and ok then callback() end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Panel view
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function row_height()
|
||||
return style.font:get_height() + common.round(4 * SCALE)
|
||||
end
|
||||
|
||||
local function status_color(st)
|
||||
if st == "M" or st == "T" or st == "R" or st == "C" then
|
||||
return style.gitstatus_modification or style.warn
|
||||
end
|
||||
if st == "A" or st == "??" then return style.gitstatus_addition or style.good end
|
||||
if st == "D" or st == "U" then return style.gitstatus_deletion or style.error end
|
||||
return style.text
|
||||
end
|
||||
|
||||
local GitPanelView = View:extend()
|
||||
|
||||
local show_commit -- forward declaration: called from on_mouse_pressed below
|
||||
|
||||
-- Set once the treeview plugin is available (it loads after us); when nil
|
||||
-- the panel falls back to opening as its own split pane.
|
||||
local treeview = nil
|
||||
|
||||
function GitPanelView:new()
|
||||
GitPanelView.super.new(self)
|
||||
self.hover = nil -- { section = "changes"|"history", idx }
|
||||
self.mouse = { x = 0, y = 0 }
|
||||
self.changes_offset = 0
|
||||
self.history_offset = 0
|
||||
self.changes_rows = {}
|
||||
self.history_rows = {}
|
||||
self.changes_visible = 1
|
||||
self.history_visible = 1
|
||||
self.mid_y = 0
|
||||
end
|
||||
|
||||
function GitPanelView:get_name()
|
||||
return "Git"
|
||||
end
|
||||
|
||||
-- While embedded in the treeview's pane, the pane width follows our size.x
|
||||
-- (the node is locked along x). Animate toward the treeview's target so
|
||||
-- divider drags resize us, and the folder view inherits the final width.
|
||||
function GitPanelView:set_target_size(axis, value)
|
||||
if axis == "x" and treeview then
|
||||
treeview:set_target_size(axis, value)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function GitPanelView:update()
|
||||
if treeview then
|
||||
self:move_towards(self.size, "x", treeview.target_size)
|
||||
end
|
||||
GitPanelView.super.update(self)
|
||||
end
|
||||
|
||||
-- The panel is split in two independently scrolling sections: the changes list
|
||||
-- on the top half and the history pinned to the bottom half.
|
||||
function GitPanelView:build_changes_rows()
|
||||
local rows = {}
|
||||
for _, e in ipairs(state.changes) do rows[#rows + 1] = { type = "file", entry = e } end
|
||||
if #rows == 0 then
|
||||
rows[#rows + 1] = {
|
||||
type = "empty",
|
||||
label = state.loading and "refreshing..."
|
||||
or (state.branch and "working tree clean" or "not a git repository"),
|
||||
}
|
||||
end
|
||||
return rows
|
||||
end
|
||||
|
||||
function GitPanelView:build_history_rows()
|
||||
local rows = {}
|
||||
for _, c in ipairs(state.history) do rows[#rows + 1] = { type = "commit", entry = c } end
|
||||
if #rows == 0 then
|
||||
rows[#rows + 1] = {
|
||||
type = "empty",
|
||||
label = state.loading and "refreshing..." or "no commits yet",
|
||||
}
|
||||
end
|
||||
return rows
|
||||
end
|
||||
|
||||
local function draw_row_hover(x, y, w, rh, hovered)
|
||||
if hovered then
|
||||
local c = { table.unpack(style.treeview_hover_bg or style.line_highlight) }
|
||||
c[4] = 120
|
||||
renderer.draw_rect(x, y, w, rh, c)
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_file_row(x, y, w, rh, e, pad)
|
||||
common.draw_text(style.font, status_color(e.st), e.st, nil, x + pad, y, 0, rh)
|
||||
local tx = x + pad + style.font:get_width(e.st) + common.round(6 * SCALE)
|
||||
common.draw_text(style.font, style.treeview_text or style.text, e.path, nil, tx, y, 0, rh)
|
||||
if e.plus or e.minus then
|
||||
local label = string.format("+%d -%d", e.plus or 0, e.minus or 0)
|
||||
common.draw_text(style.font, style.dim, label, nil,
|
||||
x + w - style.font:get_width(label) - pad, y, 0, rh)
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_commit_row(x, y, rh, e, pad)
|
||||
common.draw_text(style.font, style.dim, e.hash, nil, x + pad, y, 0, rh)
|
||||
local tx = x + pad + style.font:get_width("0000000") + common.round(6 * SCALE)
|
||||
common.draw_text(style.font, style.treeview_text or style.text, e.subject, nil, tx, y, 0, rh)
|
||||
end
|
||||
|
||||
function GitPanelView:draw()
|
||||
self:draw_background(style.background2)
|
||||
local x, y = self.position.x, self.position.y
|
||||
local w, h = self.size.x, self.size.y
|
||||
local rh = row_height()
|
||||
local pad = common.round(5 * SCALE)
|
||||
local font_h = style.font:get_height()
|
||||
|
||||
-- history is pinned to the bottom half; snap the divider to a row boundary
|
||||
local footer_h = font_h + common.round(4 * SCALE)
|
||||
local half_rows = math.max(1, math.floor((h - footer_h) / 2 / rh))
|
||||
local mid = y + half_rows * rh
|
||||
self.mid_y = mid
|
||||
|
||||
self.changes_rows = self:build_changes_rows()
|
||||
self.history_rows = self:build_history_rows()
|
||||
self.changes_visible = math.max(1, half_rows - 2) -- branch + section headers
|
||||
self.history_visible = math.max(1, half_rows - 1) -- section header
|
||||
self.changes_offset = common.clamp(self.changes_offset,
|
||||
0, math.max(0, #self.changes_rows - self.changes_visible))
|
||||
self.history_offset = common.clamp(self.history_offset,
|
||||
0, math.max(0, #self.history_rows - self.history_visible))
|
||||
|
||||
if state.branch then
|
||||
common.draw_text(style.font, style.accent, "branch " .. state.branch, nil, x + pad, y, 0, rh)
|
||||
else
|
||||
common.draw_text(style.font, style.dim,
|
||||
state.loading and "loading..." or "not a git repository", nil, x + pad, y, 0, rh)
|
||||
end
|
||||
|
||||
common.draw_text(style.font, style.accent,
|
||||
string.format("changes (%d)", #state.changes), nil, x + pad, y + rh, 0, rh)
|
||||
local cy = y + 2 * rh
|
||||
core.push_clip_rect(x, cy, w, mid - cy)
|
||||
for i = self.changes_offset + 1,
|
||||
math.min(#self.changes_rows, self.changes_offset + self.changes_visible) do
|
||||
local row = self.changes_rows[i]
|
||||
local ry = cy + (i - 1 - self.changes_offset) * rh
|
||||
draw_row_hover(x, ry, w, rh,
|
||||
self.hover and self.hover.section == "changes" and self.hover.idx == i)
|
||||
if row.type == "file" then
|
||||
draw_file_row(x, ry, w, rh, row.entry, pad)
|
||||
else
|
||||
common.draw_text(style.font, style.dim, row.label, nil, x + pad, ry, 0, rh)
|
||||
end
|
||||
end
|
||||
core.pop_clip_rect()
|
||||
|
||||
renderer.draw_rect(x, mid, w, SCALE, style.divider or style.dim)
|
||||
|
||||
common.draw_text(style.font, style.accent,
|
||||
string.format("history (%d)", #state.history), nil, x + pad, mid, 0, rh)
|
||||
local hy = mid + rh
|
||||
core.push_clip_rect(x, hy, w, y + h - footer_h - hy)
|
||||
for i = self.history_offset + 1,
|
||||
math.min(#self.history_rows, self.history_offset + self.history_visible) do
|
||||
local row = self.history_rows[i]
|
||||
local ry = hy + (i - 1 - self.history_offset) * rh
|
||||
draw_row_hover(x, ry, w, rh,
|
||||
self.hover and self.hover.section == "history" and self.hover.idx == i)
|
||||
if row.type == "commit" then
|
||||
draw_commit_row(x, ry, rh, row.entry, pad)
|
||||
else
|
||||
common.draw_text(style.font, style.dim, row.label, nil, x + pad, ry, 0, rh)
|
||||
end
|
||||
end
|
||||
core.pop_clip_rect()
|
||||
|
||||
local hint = "s stage - u unstage - a all - c commit - r refresh"
|
||||
common.draw_text(style.font, style.dim, hint, nil, x + pad,
|
||||
y + h - font_h - common.round(2 * SCALE), 0, font_h)
|
||||
end
|
||||
|
||||
function GitPanelView:on_mouse_moved(px, py, ...)
|
||||
GitPanelView.super.on_mouse_moved(self, px, py, ...)
|
||||
self.mouse.x, self.mouse.y = px, py
|
||||
self.hover = nil
|
||||
local rh = row_height()
|
||||
local top_start = self.position.y + 2 * rh
|
||||
local hist_start = self.mid_y + rh
|
||||
if py >= top_start and py < self.mid_y then
|
||||
local idx = math.floor((py - top_start) / rh) + self.changes_offset + 1
|
||||
if self.changes_rows[idx] then self.hover = { section = "changes", idx = idx } end
|
||||
elseif py >= hist_start then
|
||||
local idx = math.floor((py - hist_start) / rh) + self.history_offset + 1
|
||||
if self.history_rows[idx] then self.hover = { section = "history", idx = idx } end
|
||||
end
|
||||
end
|
||||
|
||||
function GitPanelView:on_mouse_pressed(button, px, py_, clicks)
|
||||
GitPanelView.super.on_mouse_pressed(self, button, px, py_, clicks)
|
||||
local hov = self.hover
|
||||
if hov then
|
||||
local row = hov.section == "changes"
|
||||
and self.changes_rows[hov.idx] or self.history_rows[hov.idx]
|
||||
if row and row.type == "file" and row.entry.st ~= "D" then
|
||||
-- deleted files can't be opened
|
||||
core.root_view:open_doc(core.open_doc(core.project_dir .. PATHSEP .. row.entry.path))
|
||||
elseif row and row.type == "commit" then
|
||||
show_commit(row.entry.hash, row.entry.subject)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function GitPanelView:on_mouse_left()
|
||||
GitPanelView.super.on_mouse_left(self)
|
||||
self.hover = nil
|
||||
end
|
||||
|
||||
function GitPanelView:on_mouse_wheel(dy)
|
||||
GitPanelView.super.on_mouse_wheel(self, dy)
|
||||
local in_history = self.mouse.y >= self.mid_y
|
||||
local rows_n = in_history and #self.history_rows or #self.changes_rows
|
||||
local visible = in_history and self.history_visible or self.changes_visible
|
||||
local offset = in_history and self.history_offset or self.changes_offset
|
||||
offset = common.clamp(offset - dy * 3, 0, math.max(0, rows_n - visible))
|
||||
if in_history then self.history_offset = offset else self.changes_offset = offset end
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Actions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local panel = nil -- the open GitPanelView instance, if any
|
||||
|
||||
local function open_scratch(name, text, syn)
|
||||
local doc = Doc()
|
||||
doc:reset()
|
||||
local lines = {}
|
||||
for line in (text .. "\n"):gmatch("([^\n]*)\n") do lines[#lines + 1] = line .. "\n" end
|
||||
if #lines == 0 then lines[1] = "\n" end
|
||||
doc.lines = lines
|
||||
doc:set_filename(name, nil)
|
||||
doc:reset_syntax()
|
||||
if syn then doc.syntax = syn end -- else it stays whatever reset_syntax picked
|
||||
core.root_view:open_doc(doc)
|
||||
end
|
||||
|
||||
-- Commit diffs are highlighted with the official language_diff plugin when it
|
||||
-- is installed (it registers its syntax under the name "Diff"); otherwise this
|
||||
-- fallback covers `git show` output with the same colors.
|
||||
style.syntax.diff_add = style.syntax.diff_add or { common.color "#72b886" }
|
||||
style.syntax.diff_del = style.syntax.diff_del or { common.color "#f36161" }
|
||||
|
||||
local diff_syntax_fallback = syntax.add({
|
||||
name = "Git Diff (gitpanel)",
|
||||
patterns = {
|
||||
{ pattern = "^commit %x+", type = "function" },
|
||||
{ pattern = "^[%a%-]+: ", type = "keyword" },
|
||||
{ pattern = "^diff %-%-git .*", type = "function" },
|
||||
{ pattern = "^index .*", type = "comment" },
|
||||
{ pattern = "^new .*", type = "comment" },
|
||||
{ pattern = "^deleted .*", type = "comment" },
|
||||
{ pattern = "^similarity index .*", type = "comment" },
|
||||
{ pattern = "^rename .*", type = "comment" },
|
||||
{ pattern = "^@@ .-@@", type = "number" },
|
||||
{ pattern = "^%-%-%- .*", type = "keyword" },
|
||||
{ pattern = "^%+%+%+ .*", type = "keyword" },
|
||||
{ pattern = "^%-.*", type = "diff_del" },
|
||||
{ pattern = "^%+.*", type = "diff_add" },
|
||||
{ pattern = "^ %d+ files? changed.*", type = "function" },
|
||||
},
|
||||
symbols = {},
|
||||
})
|
||||
|
||||
local function get_diff_syntax()
|
||||
for _, s in ipairs(syntax.items) do
|
||||
if s.name == "Diff" then return s end
|
||||
end
|
||||
return diff_syntax_fallback
|
||||
end
|
||||
|
||||
function show_commit(hash, subject)
|
||||
core.add_thread(function()
|
||||
open_scratch("[git] " .. hash .. " " .. (subject or ""),
|
||||
exec { "git", "show", "--no-color", hash }, get_diff_syntax())
|
||||
end)
|
||||
end
|
||||
local function hovered_file_entry()
|
||||
if not (panel and panel.hover and panel.hover.section == "changes") then return nil end
|
||||
local row = panel.changes_rows[panel.hover.idx]
|
||||
return row and row.type == "file" and row.entry or nil
|
||||
end
|
||||
|
||||
local function run_on_entry(args_for)
|
||||
local e = hovered_file_entry()
|
||||
if not e or e.untracked and args_for == "unstage" then return end
|
||||
local args
|
||||
if args_for == "stage" then
|
||||
args = { "git", "add", "--", e.path }
|
||||
else
|
||||
args = { "git", "reset", "-q", "HEAD", "--", e.path }
|
||||
end
|
||||
core.add_thread(function()
|
||||
exec(args)
|
||||
refresh()
|
||||
end)
|
||||
end
|
||||
|
||||
local function panel_active()
|
||||
return panel ~= nil and core.active_view == panel
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Treeview integration: the panel replaces the folder list in the left pane,
|
||||
-- toggled from the toolbar's git button. The treeview plugin loads after us,
|
||||
-- so require it lazily. The treeview's leaf node is locked (no tab bar is
|
||||
-- drawn there), which makes it a clean container to swap views in.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function treeview_ready()
|
||||
if treeview then return true end
|
||||
if config.plugins.treeview == false then return false end
|
||||
local ok, tv = pcall(require, "plugins.treeview")
|
||||
if ok and tv and tv.node then
|
||||
treeview = tv
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function treeview_leaf()
|
||||
return treeview.node:get_node_for_view(treeview)
|
||||
end
|
||||
|
||||
local function pane_active()
|
||||
if not (treeview and panel) then return false end
|
||||
local leaf = treeview_leaf()
|
||||
return leaf ~= nil and leaf.active_view == panel
|
||||
end
|
||||
|
||||
local function pane_attach()
|
||||
if not panel then panel = GitPanelView() end
|
||||
local leaf = treeview_leaf()
|
||||
if not leaf:get_view_idx(panel) then
|
||||
panel.size.x = treeview.size.x -- else the locked pane would collapse on us
|
||||
local locked = leaf.locked
|
||||
leaf.locked = nil -- add_view() refuses locked nodes
|
||||
leaf:add_view(panel) -- makes the panel the node's active view and focuses it
|
||||
leaf.locked = locked
|
||||
else
|
||||
leaf:set_active_view(panel)
|
||||
end
|
||||
refresh()
|
||||
end
|
||||
|
||||
local function pane_detach()
|
||||
local leaf = treeview_leaf()
|
||||
local focused = core.active_view == panel
|
||||
leaf.active_view = treeview -- route the pane back without stealing focus
|
||||
leaf:remove_view(core.root_view.root_node, panel)
|
||||
panel:on_mouse_left()
|
||||
if focused then core.set_active_view(treeview) end
|
||||
end
|
||||
|
||||
local function open_panel()
|
||||
if panel and core.root_view:get_node_for_view(panel) then
|
||||
refresh()
|
||||
core.set_active_view(panel)
|
||||
return
|
||||
end
|
||||
panel = GitPanelView()
|
||||
refresh()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:split("left", panel)
|
||||
core.set_active_view(panel)
|
||||
end
|
||||
|
||||
local function close_panel()
|
||||
if not panel then return end
|
||||
local node = core.root_view:get_node_for_view(panel)
|
||||
if node then node:close_view(core.root_view.node, panel) end
|
||||
panel = nil
|
||||
end
|
||||
|
||||
local function toggle_panel()
|
||||
if treeview_ready() then
|
||||
if pane_active() then pane_detach() else pane_attach() end
|
||||
else
|
||||
if panel_active() then close_panel() else open_panel() end
|
||||
end
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["git-panel:toggle"] = function() toggle_panel() end,
|
||||
})
|
||||
|
||||
command.add(panel_active, {
|
||||
["git-panel:stage"] = function()
|
||||
if hovered_file_entry() then run_on_entry("stage") end
|
||||
end,
|
||||
["git-panel:unstage"] = function()
|
||||
if hovered_file_entry() then run_on_entry("unstage") end
|
||||
end,
|
||||
["git-panel:stage-all"] = function()
|
||||
core.add_thread(function()
|
||||
exec { "git", "add", "-A" }
|
||||
refresh()
|
||||
end)
|
||||
end,
|
||||
["git-panel:refresh"] = function() refresh() end,
|
||||
["git-panel:commit"] = function()
|
||||
local staged = 0
|
||||
for _, e in ipairs(state.changes) do if e.staged then staged = staged + 1 end end
|
||||
if staged == 0 then
|
||||
core.error("git-panel: nothing staged, press s on changed files first")
|
||||
return
|
||||
end
|
||||
core.command_view:enter("Commit message", function(text)
|
||||
text = text:match("^%s*(.-)%s*$")
|
||||
if text == "" then
|
||||
core.error("git-panel: empty commit message")
|
||||
return
|
||||
end
|
||||
core.add_thread(function()
|
||||
exec { "git", "commit", "-m", text }
|
||||
refresh(function()
|
||||
local left = 0
|
||||
for _, e in ipairs(state.changes) do if e.staged then left = left + 1 end end
|
||||
if left == 0 then
|
||||
core.log("git-panel: committed: " .. text)
|
||||
else
|
||||
core.error("git-panel: commit failed, staged files remain")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+shift+g"] = "git-panel:toggle",
|
||||
}
|
||||
|
||||
keymap.add {
|
||||
["s"] = "git-panel:stage",
|
||||
["u"] = "git-panel:unstage",
|
||||
["a"] = "git-panel:stage-all",
|
||||
["c"] = "git-panel:commit",
|
||||
["r"] = "git-panel:refresh",
|
||||
}
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Toolbar button: a git-branch icon next to the treeview toolbar buttons,
|
||||
-- highlighted while the panel replaces the folder list. treeview loads after
|
||||
-- us (plugins load alphabetically), so wait for it in a thread.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- VS Code-style git-branch icon (vscode-codicons, MIT), rasterized offline to
|
||||
-- opaque rect runs on a 34x34 grid; soft = partial-coverage edge cells
|
||||
local GIT_ICON_GRID = 34
|
||||
local GIT_ICON = {
|
||||
{8,0,3,1},{6,1,7,1},{5,2,3,1},{11,2,3,1},{5,3,2,1},{12,3,2,1},{5,4,1,1},
|
||||
{13,4,2,3},{4,5,2,1},{5,6,1,1},{5,7,2,1},{12,7,2,1},{21,7,6,1},{6,8,2,1},
|
||||
{11,8,3,1},{20,8,3,1},{26,8,2,1},{7,9,6,1},{20,9,2,1},{27,9,2,1},{9,10,2,10},
|
||||
{19,10,2,3},{28,10,1,1},{28,11,2,1},{28,12,1,1},{20,13,2,1},{27,13,2,1},
|
||||
{20,14,3,1},{26,14,3,1},{21,15,7,1},{23,16,3,1},{23,17,2,1},{22,18,3,1},
|
||||
{12,19,12,1},{9,20,14,1},{9,21,3,1},{9,22,2,2},{7,24,6,1},{6,25,2,1},
|
||||
{11,25,3,1},{5,26,2,1},{12,26,2,1},{5,27,1,1},{13,27,2,3},{4,28,2,1},
|
||||
{5,29,1,1},{5,30,2,1},{12,30,2,1},{5,31,3,1},{11,31,3,1},{6,32,7,1},{8,33,3,1}
|
||||
}
|
||||
local GIT_ICON_SOFT = {
|
||||
{7,0,5,1},{5,1,9,1},{5,2,4,1},{10,2,4,1},{4,3,3,4},{12,3,3,5},{22,6,5,1},
|
||||
{4,7,4,1},{21,7,7,1},{5,8,9,1},{20,8,9,1},{6,9,7,1},{19,9,3,5},{26,9,4,1},
|
||||
{7,10,5,1},{27,10,3,4},{8,11,3,8},{20,14,4,1},{25,14,4,1},{20,15,9,1},
|
||||
{22,16,5,1},{23,17,3,1},{21,18,4,1},{8,19,16,1},{8,20,15,1},{8,21,13,1},
|
||||
{8,22,4,1},{7,23,5,1},{6,24,7,1},{5,25,9,1},{4,26,4,1},{12,26,3,5},
|
||||
{4,27,3,4},{5,31,4,1},{10,31,4,1},{5,32,9,1},{7,33,5,1}
|
||||
}
|
||||
|
||||
local function draw_git_icon(x, y, h, color)
|
||||
local s = h / GIT_ICON_GRID
|
||||
local soft = { table.unpack(color) }
|
||||
soft[4] = (soft[4] or 255) * 0.5
|
||||
local eps = s / 6 -- hide seams between adjacent runs
|
||||
for _, r in ipairs(GIT_ICON_SOFT) do
|
||||
renderer.draw_rect(x + r[1] * s, y + r[2] * s, r[3] * s + eps, r[4] * s + eps, soft)
|
||||
end
|
||||
for _, r in ipairs(GIT_ICON) do
|
||||
renderer.draw_rect(x + r[1] * s, y + r[2] * s, r[3] * s + eps, r[4] * s + eps, color)
|
||||
end
|
||||
end
|
||||
|
||||
core.add_thread(function()
|
||||
for _ = 1, 20 do
|
||||
if treeview_ready() then break end
|
||||
coroutine.yield(1)
|
||||
end
|
||||
if not treeview then return end
|
||||
local toolbar = treeview.toolbar
|
||||
if not toolbar then return end -- toolbarview disabled: keybinding still works
|
||||
-- blank symbol: the icon itself is painted by hand below
|
||||
local git_button = { symbol = " ", command = "git-panel:toggle" }
|
||||
table.insert(toolbar.toolbar_commands, git_button)
|
||||
-- widen the pane if the default is too narrow for the extra button
|
||||
treeview:set_target_size("x", math.max(treeview.target_size or 0, toolbar:get_min_width()))
|
||||
-- dim by default, lit on hover, accent while the panel is showing
|
||||
local toolbar_draw = toolbar.draw
|
||||
function toolbar.draw(self)
|
||||
toolbar_draw(self)
|
||||
for item, x, y, w, h in self:each_item() do
|
||||
if item == git_button then
|
||||
local color = pane_active() and style.accent
|
||||
or (self.hovered_item == item and style.text or style.dim)
|
||||
draw_git_icon(x + (w - h) / 2, y, h, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Status bar "git button": make gitstatus's branch item open the panel.
|
||||
-- gitstatus loads after us, so poll until its item exists.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
core.add_thread(function()
|
||||
for _ = 1, 20 do
|
||||
local ok, item = pcall(core.status_view.get_item, core.status_view, "status:git")
|
||||
if ok and item then
|
||||
item.command = "git-panel:toggle"
|
||||
item.tooltip = "git: click to open panel"
|
||||
return
|
||||
end
|
||||
coroutine.yield(1)
|
||||
end
|
||||
-- gitstatus absent: add our own fallback branch button
|
||||
core.status_view:add_item({
|
||||
name = "git-panel:branch",
|
||||
alignment = StatusView.Item.RIGHT,
|
||||
position = -2,
|
||||
get_item = function()
|
||||
if not state.branch then return {} end
|
||||
return { style.accent, "branch: ", style.text, state.branch }
|
||||
end,
|
||||
on_click = function() command.perform("git-panel:toggle") end,
|
||||
tooltip = "git: click to open panel",
|
||||
separator = core.status_view.separator2,
|
||||
})
|
||||
end)
|
||||
Vendored
+1
@@ -6,6 +6,7 @@ debian/extra/libraries/font_symbols_nerdfont_mono_regular.lua usr/share/lite-xl/
|
||||
debian/extra/fonts/SymbolsNerdFontMono-Regular.ttf usr/share/lite-xl/libraries/font_symbols_nerdfont_mono_regular/
|
||||
debian/extra/plugins/fontconfig.lua usr/share/lite-xl/plugins/
|
||||
debian/extra/plugins/gitopen.lua usr/share/lite-xl/plugins/
|
||||
debian/extra/plugins/gitpanel.lua usr/share/lite-xl/plugins/
|
||||
debian/extra/plugins/gitstatus.lua usr/share/lite-xl/plugins/
|
||||
debian/extra/plugins/language_R.lua usr/share/lite-xl/plugins/
|
||||
debian/extra/plugins/language_angelscript.lua usr/share/lite-xl/plugins/
|
||||
|
||||
Reference in New Issue
Block a user