Newer
Older
dotfiles / .config / lite-xl / plugins / gitdiff_highlight / gitdiff.lua
-- mod-version:1
local gitdiff = {}

-- liquidev is a genius
local function extract_hunks(input)
	local hunks = {}
	local current_hunk = {}

	local function end_hunk(new_line)
		if #current_hunk > 0 then
			table.insert(hunks, current_hunk)
			current_hunk = { new_line }
		end
	end

	for line in input:gmatch("(.-)\n") do
		if line:match("^@") then
			end_hunk(line)
		else
			table.insert(current_hunk, line)
		end
	end

	-- add the last hunk to the table
	end_hunk("")

	return hunks
end

-- determines if line is a hunk header and returns
-- the starting line of changed version (second set of numbers part before
-- optional comma) or nil
---@param line string
---@return integer | nil
local function get_hunk_start(line)
	if "string" ~= type(line) or not line:match("^@@[^@]+@@") then
		return nil
	end

	-- doing "@@%s+-%d+(,%d+)?%s++(%d-)(,%d+)?%s+@@" did not work
	-- so we loop through a couple of patterns
	local patterns = {
		"@@%s+-%d+,%d+%s++(%d-),%d+%s+@@",
		"@@%s+-%d+%s++(%d-),%d+%s+@@",
		"@@%s+-%d+,%d+%s++(%d-)%s+@@",
		"@@%s+-%d+%s++(%d-)%s+@@"
	}

	local start
	for _, p in ipairs(patterns) do
		start = line:match(p)
		if start then return tonumber(start) end
	end
	return nil
end

-- this will only work on single-file diffs
function gitdiff.changed_lines(diff)
	if not diff then return {} end
	local changed_lines = {}
	local hunks = extract_hunks(diff)
	-- iterate over hunks
	for _, hunk in ipairs(hunks) do
		local current_line
		local hunk_start = get_hunk_start(hunk[1])
		if not hunk_start then -- mod
			goto continue
		end

		current_line = hunk_start - 1

		-- remove hunk header
		hunk[1] = ""

		for _, line in ipairs(hunk) do
			if line:match("^%s-%[%-.-%-]$") then
				table.insert(changed_lines, {
					line_number = current_line,
					change_type = "deletion"
				})
				-- do not add to the current line
				goto skip_line
			end

			if line:match("^%s-{%+.-+}$") then
				table.insert(changed_lines, {
					line_number = current_line,
					change_type = "addition"
				})

			elseif line:match("{%+.-+}") or line:match("%[%-.-%-]") then
				table.insert(changed_lines, {
					line_number = current_line,
					change_type = "modification"
				})
			end

			current_line = current_line + 1
			::skip_line::
		end
		::continue::
	end

	local indexed_changed_lines = {}
	for _, line in ipairs(changed_lines) do
		indexed_changed_lines[line.line_number] = line.change_type
	end

	return indexed_changed_lines
end

return gitdiff