File gitdiff_highlight-init.lua of Package lite-xl-plugins
-- mod-version:3
local core = require "core"
local config = require "core.config"
local DocView = require "core.docview"
local Doc = require "core.doc"
local common = require "core.common"
local command = require "core.command"
local style = require "core.style"
local gitdiff = require "plugins.gitdiff_highlight.gitdiff"
local _, MiniMap = pcall(require, "plugins.minimap")
-- vscode defaults
style.gitdiff_addition = style.gitdiff_addition or {common.color "#587c0c"}
style.gitdiff_modification = style.gitdiff_modification or {common.color "#0c7d9d"}
style.gitdiff_deletion = style.gitdiff_deletion or {common.color "#94151b"}
local function color_for_diff(diff)
if diff == "addition" then
return style.gitdiff_addition
elseif diff == "modification" then
return style.gitdiff_modification
else
return style.gitdiff_deletion
end
end
style.gitdiff_width = style.gitdiff_width or 3
local last_doc_lines = 0
-- maximum size of git diff to read, multiplied by current filesize
config.max_diff_size = 2
local diffs = setmetatable({}, { __mode = "k" })
local function get_diff(doc)
return diffs[doc] or {is_in_repo = false}
end
local function init_diff(doc)
diffs[doc] = {is_in_repo = true}
end
local function gitdiff_padding(dv)
return style.padding.x * 1.5 + dv:get_font():get_width(#dv.doc.lines)
end
local function update_diff(doc)
if doc == nil or doc.filename == nil then return end
local current_file
if system.get_file_info(doc.filename) then
current_file = system.absolute_path(doc.filename)
else
current_file = doc.filename
end
core.log_quiet("updating diff for " .. current_file)
if not get_diff(doc).is_in_repo then
local is_in_repo = process.start({"git", "ls-files", "--error-unmatch", current_file})
while is_in_repo:running() do
coroutine.yield(0.1)
end
is_in_repo = is_in_repo:returncode()
is_in_repo = is_in_repo == 0
if is_in_repo then
init_diff(doc)
else
core.log_quiet("file ".. current_file .." is not in a git repository")
return
end
end
local max_diff_size = system.get_file_info(current_file).size * config.max_diff_size
local diff_proc = process.start({"git", "diff", "HEAD", "--word-diff", "--unified=1", "--no-color", current_file})
while diff_proc:running() do
coroutine.yield(0.1)
end
local raw_diff = diff_proc:read_stdout(max_diff_size)
local parsed_diff = gitdiff.changed_lines(raw_diff)
diffs[doc] = parsed_diff
diffs[doc].is_in_repo = true
end
local old_docview_gutter = DocView.draw_line_gutter
local old_gutter_width = DocView.get_gutter_width
function DocView:draw_line_gutter(idx, x, y, width)
if not get_diff(self.doc).is_in_repo then
return old_docview_gutter(self, idx, x, y, width)
end
local gw, gpad = old_gutter_width(self)
old_docview_gutter(self, idx, x, y, gpad and gw - gpad or gw)
if diffs[self.doc][idx] == nil then
return
end
local color = color_for_diff(diffs[self.doc][idx])
-- add margin in between highlight and text
x = x + gitdiff_padding(self)
local yoffset = self:get_line_text_y_offset()
if diffs[self.doc][idx] ~= "deletion" then
renderer.draw_rect(x, y + yoffset, style.gitdiff_width, self:get_line_height(), color)
return
end
renderer.draw_rect(x - style.gitdiff_width * 2, y + yoffset, style.gitdiff_width * 4, 2, color)
end
function DocView:get_gutter_width()
if not get_diff(self.doc).is_in_repo then return old_gutter_width(self) end
return old_gutter_width(self) + style.padding.x * style.gitdiff_width / 12
end
local old_text_change = Doc.on_text_change
function Doc:on_text_change(type)
local line
if not get_diff(self).is_in_repo then goto end_of_function end
line = self:get_selection()
if diffs[self][line] == "addition" then goto end_of_function end
-- TODO figure out how to detect an addition
if type == "insert" or (type == "remove" and #self.lines == last_doc_lines) then
diffs[self][line] = "modification"
elseif type == "remove" then
diffs[self][line] = "deletion"
end
::end_of_function::
last_doc_lines = #self.lines
return old_text_change(self, type)
end
local old_doc_save = Doc.save
function Doc:save(...)
old_doc_save(self, ...)
core.add_thread(function()
update_diff(self)
end)
end
local old_docview_new = DocView.new
function DocView:new(...)
old_docview_new(self, ...)
core.add_thread(function()
update_diff(self.doc)
end)
end
local old_doc_load = Doc.load
function Doc:load(...)
old_doc_load(self, ...)
core.add_thread(function()
update_diff(self)
end)
end
if type(MiniMap) == "table" then
-- Override MiniMap's line_highlight_color, but first
-- stash the old one (using [] in case it is not there at all)
local old_line_highlight_color = MiniMap["line_highlight_color"]
function MiniMap:line_highlight_color(line_index)
local diff = get_diff(core.active_view.doc)
if diff.is_in_repo and diff[line_index] then
return color_for_diff(diff[line_index])
end
return old_line_highlight_color(line_index)
end
end
local function jump_to_next_change()
local doc = core.active_view.doc
local line, col = doc:get_selection()
if not get_diff(doc).is_in_repo then return end
while diffs[doc][line] do
line = line + 1
end
while line < #doc.lines do
if diffs[doc][line] then
doc:set_selection(line, col, line, col)
return
end
line = line + 1
end
end
local function jump_to_previous_change()
local doc = core.active_view.doc
local line, col = doc:get_selection()
if not get_diff(doc).is_in_repo then return end
while diffs[doc][line] do
line = line - 1
end
while line > 0 do
if diffs[doc][line] then
doc:set_selection(line, col, line, col)
return
end
line = line - 1
end
end
command.add("core.docview", {
["gitdiff:previous-change"] = function()
jump_to_previous_change()
end,
["gitdiff:next-change"] = function()
jump_to_next_change()
end,
})