From 87dec805baf6f219cb4928bc704d2932b84bb9f0 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 10 Jun 2024 15:35:27 +0100 Subject: [PATCH] feat: support for statuscolumn --- README.md | 1 + doc/gitsigns.txt | 13 ++++++++++ gen_help.lua | 19 +++++++++++--- lua/gitsigns.lua | 35 +++++++++++++++++++------- lua/gitsigns/manager.lua | 53 +++++++++++++++++++++++++++++++++++++++- lua/gitsigns/signs.lua | 22 +++++++++-------- 6 files changed, 120 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ad7764e89..95d356c22 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Super fast git decorations implemented purely in Lua. - Live intra-line word diff - Ability to display deleted/changed lines via virtual lines. - Support for detached working trees. +- Support for `'statuscolumn'`. ## Requirements diff --git a/doc/gitsigns.txt b/doc/gitsigns.txt index abfc63e72..e8b39d4d4 100644 --- a/doc/gitsigns.txt +++ b/doc/gitsigns.txt @@ -100,6 +100,19 @@ setup({cfg}) *gitsigns.setup()* {cfg} (table|nil): Configuration for Gitsigns. See |gitsigns-usage| for more details. +statuscolumn() *gitsigns.statuscolumn()* + Function that can be used in the 'statuscolumn' option. + + Note calling this function will automatically disable + |gitsigns-config-signcolumn|. + + e.g. >lua + vim.o.statuscolumn = "%s%l%C%{%v:lua.require'gitsigns'.statuscolumn()%} " +< + + Returns: ~ + (string) + attach({bufnr}, {ctx}, {callback?}) *gitsigns.attach()* Attach Gitsigns to the buffer. diff --git a/gen_help.lua b/gen_help.lua index d5446796b..8e6551512 100755 --- a/gen_help.lua +++ b/gen_help.lua @@ -179,6 +179,19 @@ local function parse_param(x) return name, ty, des end +--- @param x string +--- @return string? type +--- @return string? name +--- @return string? description +local function parse_return(x) + local ty, name, des = x:match('([^ ]+) +([^ ]+) *(.*)') + if ty then + return ty, name, des + end + ty = x:match('([^ ]+)') + return ty +end + --- @param x string[] --- @return string[] local function trim_lines(x) @@ -207,11 +220,11 @@ local function render_param_or_return(name, ty, desc, name_pad) ty = ty:gsub('Gitsigns%.%w+', 'table') name_pad = name_pad and (name_pad + 3) or 0 - local name_str --- @type string + local name_str = '' --- @type string if name == ':' then name_str = '' - else + elseif name then local nf = '%-' .. tostring(name_pad) .. 's' name_str = nf:format(string.format('{%s} ', name)) end @@ -266,7 +279,7 @@ local function process_doc_comment(state, doc_comment, desc, params, returns) end if emmy_type == 'return' then - local ty, name, rdesc = parse_param(emmy_str) + local ty, name, rdesc = parse_return(emmy_str) returns[#returns + 1] = { name, ty, { rdesc } } return 'in_return' end diff --git a/lua/gitsigns.lua b/lua/gitsigns.lua index 78a786221..0b4b5f0e8 100644 --- a/lua/gitsigns.lua +++ b/lua/gitsigns.lua @@ -1,8 +1,4 @@ local async = require('gitsigns.async') -local log = require('gitsigns.debug.log') - -local gs_config = require('gitsigns.config') -local config = gs_config.config local api = vim.api local uv = vim.uv or vim.loop @@ -101,6 +97,7 @@ local update_cwd_head = async.create(function() {}, async.create(function(err) local __FUNC__ = 'cwd_watcher_cb' + local log = require('gitsigns.debug.log') if err then log.dprintf('Git dir update error: %s', err) return @@ -125,13 +122,16 @@ local function setup_cli() }) end -local function setup_debug() +--- @param config Gitsigns.Config +local function setup_debug(config) + local log = require('gitsigns.debug.log') log.debug_mode = config.debug_mode log.verbose = config._verbose end --- @async -local function setup_attach() +--- @param config Gitsigns.Config +local function setup_attach(config) if not config.auto_attach then return end @@ -146,6 +146,7 @@ local function setup_attach() local bufnr = args.buf --[[@as integer]] if attach_autocmd_disabled then local __FUNC__ = 'attach_autocmd' + local log = require('gitsigns.debug.log') log.dprint('Attaching is disabled') return end @@ -199,11 +200,25 @@ local function setup_cwd_head() }) end +--- Function that can be used in the 'statuscolumn' option. +--- +--- Note calling this function will automatically disable +--- |gitsigns-config-signcolumn|. +--- +--- e.g. >lua +--- vim.o.statuscolumn = "%s%l%C%{%v:lua.require'gitsigns'.statuscolumn()%} " +--- < +--- @return string +function M.statuscolumn() + return require('gitsigns.manager').statuscolumn() +end + --- Setup and start Gitsigns. --- --- @param cfg table|nil Configuration for Gitsigns. --- See |gitsigns-usage| for more details. function M.setup(cfg) + local gs_config = require('gitsigns.config') gs_config.build(cfg) if vim.fn.executable('git') == 0 then @@ -213,10 +228,12 @@ function M.setup(cfg) api.nvim_create_augroup('gitsigns', {}) - setup_debug() + local config = gs_config.config + + setup_debug(config) setup_cli() require('gitsigns.highlight').setup() - setup_attach() + setup_attach(config) setup_cwd_head() end @@ -232,7 +249,7 @@ return setmetatable(M, { return actions[f] end - if config.debug_mode then + if require('gitsigns.config').config.debug_mode then local debug = require('gitsigns.debug') if debug[f] then return debug[f] diff --git a/lua/gitsigns/manager.lua b/lua/gitsigns/manager.lua index 924ab30b9..f6123555e 100644 --- a/lua/gitsigns/manager.lua +++ b/lua/gitsigns/manager.lua @@ -26,6 +26,52 @@ local signs_staged --- @type Gitsigns.Signs local M = {} +local statuscolumn_active = false + +--- @param bufnr? integer +--- @param top? integer +--- @param bot? integer +local function redraw_statuscol(bufnr, top, bot) + if statuscolumn_active then + api.nvim__redraw({ + buf = bufnr, + range = { top, bot }, + statuscolumn = true, + }) + end +end + +function M.statuscolumn() + if not statuscolumn_active then + config.signcolumn = false + statuscolumn_active = true + end + + local res = {} + local res_len = 0 + local max_pad = 0 + local lnum = vim.v.lnum + for _, signs in pairs({ signs_normal, signs_staged }) do + if next(signs.signs) then + max_pad = 2 + end + local marks = api.nvim_buf_get_extmarks(0, signs.ns, { lnum - 1, 0 }, { lnum - 1, -1 }, {}) + for _, mark in pairs(marks) do + local id = mark[1] + local s = signs.signs[id] + if s then + table.insert(res, '%#' .. s[2] .. '#') + table.insert(res, s[1]) + res_len = res_len + vim.str_utfindex(s[1]) + table.insert(res, '%#NONE#') + end + end + end + + local pad = math.max(0, max_pad - res_len) + return table.concat(res) .. string.rep(' ', pad) +end + --- @param bufnr integer --- @param signs Gitsigns.Signs --- @param hunks Gitsigns.Hunk.Hunk[] @@ -77,6 +123,9 @@ local function apply_win_signs(bufnr, top, bot, clear) if signs_staged then apply_win_signs0(bufnr, signs_staged, bcache.hunks_staged, top, bot, clear, false) end + if clear then + redraw_statuscol(bufnr, top, bot) + end end --- @param blame table? @@ -349,7 +398,7 @@ function M.show_deleted_in_float(bufnr, nsd, hunk, staged) -- Navigate to hunk vim.cmd('normal ' .. tostring(hunk.removed.start) .. 'gg') - vim.cmd('normal ' .. vim.api.nvim_replace_termcodes('z', true, false, true)) + vim.cmd('normal ' .. api.nvim_replace_termcodes('z', true, false, true)) end) local last_lnum = api.nvim_buf_line_count(bufnr) @@ -531,6 +580,7 @@ function M.detach(bufnr, keep_signs) if signs_staged then signs_staged:remove(bufnr) end + redraw_statuscol(bufnr) end end @@ -542,6 +592,7 @@ function M.reset_signs() if signs_staged then signs_staged:reset() end + redraw_statuscol() end --- @param _cb 'win' diff --git a/lua/gitsigns/signs.lua b/lua/gitsigns/signs.lua index d0f69e8b8..e3e8fac4d 100644 --- a/lua/gitsigns/signs.lua +++ b/lua/gitsigns/signs.lua @@ -11,6 +11,7 @@ local config = require('gitsigns.config').config --- @field hls table --- @field name string --- @field group string +--- @field signs table --- @field config table --- @field ns integer local M = {} @@ -32,7 +33,11 @@ end function M:remove(bufnr, start_lnum, end_lnum) if start_lnum then api.nvim_buf_clear_namespace(bufnr, self.ns, start_lnum - 1, end_lnum or start_lnum) + for i = start_lnum - 1, (end_lnum or start_lnum) - 1 do + self.signs[i] = nil + end else + self.signs = {} api.nvim_buf_clear_namespace(bufnr, self.ns, 0, -1) end end @@ -40,16 +45,11 @@ end ---@param bufnr integer ---@param signs Gitsigns.Sign[] function M:add(bufnr, signs) - if not config.signcolumn and not config.numhl and not config.linehl then - -- Don't place signs if it won't show anything - return - end - for _, s in ipairs(signs) do if not self:contains(bufnr, s.lnum) then local cs = self.config[s.type] local text = cs.text - if config.signcolumn and cs.show_count and s.count then + if cs.show_count and s.count then local count = s.count local cc = config.count_chars local count_char = cc[count] or cc['+'] or '' @@ -58,8 +58,7 @@ function M:add(bufnr, signs) local hls = self.hls[s.type] - local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, self.ns, s.lnum - 1, -1, { - id = s.lnum, + local ok, id_or_err = pcall(api.nvim_buf_set_extmark, bufnr, self.ns, s.lnum - 1, -1, { sign_text = config.signcolumn and text or '', priority = config.sign_priority, sign_hl_group = hls.hl, @@ -67,11 +66,13 @@ function M:add(bufnr, signs) line_hl_group = config.linehl and hls.linehl or nil, }) - if not ok and config.debug_mode then + if ok then + self.signs[id_or_err] = { text, hls.hl } + elseif config.debug_mode then vim.schedule(function() error(table.concat({ string.format('Error placing extmark on line %d', s.lnum), - err, + id_or_err, }, '\n')) end) end @@ -129,6 +130,7 @@ function M.new(cfg, name) self.hls = name == 'staged' and config._signs_staged or config.signs self.group = 'gitsigns_signs_' .. (name or '') self.ns = api.nvim_create_namespace(self.group) + self.signs = {} return self end