feat(blame): support format strings

- config.current_line_blame_formatter can now be specified as a format
string.

- changed default of `config.current_line_blame_formatter` to
  '<author>, <author_time:%R> - <summary>' which is equivalent to the
  previous function default.

- deprecated config.current_line_blame_formatter_opts

Resolves #291
This commit is contained in:
Lewis Russell 2022-02-10 17:37:26 +00:00 committed by Lewis Russell
parent 6dd2e6fcd7
commit 420db8ddb9
9 changed files with 355 additions and 215 deletions

View File

@ -105,9 +105,7 @@ require('gitsigns').setup {
delay = 1000, delay = 1000,
ignore_whitespace = false, ignore_whitespace = false,
}, },
current_line_blame_formatter_opts = { current_line_blame_formatter = '<author>, <author_time:%Y-%m-%d> - <summary>',
relative_time = false
},
sign_priority = 6, sign_priority = 6,
update_debounce = 100, update_debounce = 100,
status_formatter = nil, -- Use default status_formatter = nil, -- Use default

View File

@ -57,9 +57,7 @@ of the default settings:
delay = 1000, delay = 1000,
ignore_whitespace = false, ignore_whitespace = false,
}, },
current_line_blame_formatter_opts = { current_line_blame_formatter = '<author>, <author_time:%Y-%m-%d> - <summary>',
relative_time = false
},
sign_priority = 6, sign_priority = 6,
update_debounce = 100, update_debounce = 100,
status_formatter = nil, -- Use default status_formatter = nil, -- Use default
@ -658,6 +656,8 @@ current_line_blame_opts *gitsigns-config-current_line_blame_opts*
current_line_blame_formatter_opts current_line_blame_formatter_opts
*gitsigns-config-current_line_blame_formatter_opts* *gitsigns-config-current_line_blame_formatter_opts*
DEPRECATED
Type: `table[extended]` Type: `table[extended]`
Default: > Default: >
{ {
@ -670,61 +670,81 @@ current_line_blame_formatter_opts
• relative_time: boolean • relative_time: boolean
current_line_blame_formatter *gitsigns-config-current_line_blame_formatter* current_line_blame_formatter *gitsigns-config-current_line_blame_formatter*
Type: `function` Type: `string|function`, Default: `' <author>, <author_time> - <summary>'`
Default: >
function(name, blame_info, opts)
if blame_info.author == name then
blame_info.author = 'You'
end
local text String or function used to format the virtual text of
if blame_info.author == 'Not Committed Yet' then
text = blame_info.author
else
local date_time
if opts.relative_time then
date_time = require('gitsigns.util').get_relative_time(tonumber(blame_info['author_time']))
else
date_time = os.date('%Y-%m-%d', tonumber(blame_info['author_time']))
end
text = string.format('%s, %s - %s', blame_info.author, date_time, blame_info.summary)
end
return {{' '..text, 'GitSignsCurrentLineBlame'}}
end
<
Function used to format the virtual text of
|gitsigns-config-current_line_blame|. |gitsigns-config-current_line_blame|.
Parameters: ~ When a string, accepts the following format specifiers:
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
• `abbrev_sha`: string
• `orig_lnum`: integer
• `final_lnum`: integer
• `author`: string
• `author_mail`: string
• `author_time`: integer
• `author_tz`: string
• `committer`: string
• `committer_mail`: string
• `committer_time`: integer
• `committer_tz`: string
• `summary`: string
• `previous`: string
• `filename`: string
Note that the keys map onto the output of: • `<abbrev_sha>`
`git blame --line-porcelain` • `<orig_lnum>`
• `<final_lnum>`
• `<author>`
• `<author_mail>`
• `<author_time>` or `<author_time:FORMAT>`
• `<author_tz>`
• `<committer>`
• `<committer_mail>`
• `<committer_time>` or `<committer_time:FORMAT>`
• `<committer_tz>`
• `<summary>`
• `<previous>`
• `<filename>`
{opts} Passed directly from For `<author_time:FORMAT>` and `<committer_time:FORMAT>`, `FORMAT` can
|gitsigns-config-current_line_blame_formatter_opts|. be any valid date format that is accepted by `os.date()` with the
addition of `%R` (defaults to `%Y-%m-%d`):
Return: ~ • `%a` abbreviated weekday name (e.g., Wed)
The result of this function is passed directly to the `opts.virt_text` • `%A` full weekday name (e.g., Wednesday)
field of |nvim_buf_set_extmark|. • `%b` abbreviated month name (e.g., Sep)
• `%B` full month name (e.g., September)
• `%c` date and time (e.g., 09/16/98 23:48:10)
• `%d` day of the month (16) [01-31]
• `%H` hour, using a 24-hour clock (23) [00-23]
• `%I` hour, using a 12-hour clock (11) [01-12]
• `%M` minute (48) [00-59]
• `%m` month (09) [01-12]
• `%p` either "am" or "pm" (pm)
• `%S` second (10) [00-61]
• `%w` weekday (3) [0-6 = Sunday-Saturday]
• `%x` date (e.g., 09/16/98)
• `%X` time (e.g., 23:48:10)
• `%Y` full year (1998)
• `%y` two-digit year (98) [00-99]
• `%%` the character `%´
• `%R` relative (e.g., 4 months ago)
When a function:
Parameters: ~
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
• `abbrev_sha`: string
• `orig_lnum`: integer
• `final_lnum`: integer
• `author`: string
• `author_mail`: string
• `author_time`: integer
• `author_tz`: string
• `committer`: string
• `committer_mail`: string
• `committer_time`: integer
• `committer_tz`: string
• `summary`: string
• `previous`: string
• `filename`: string
Note that the keys map onto the output of:
`git blame --line-porcelain`
{opts} Passed directly from
|gitsigns-config-current_line_blame_formatter_opts|.
Return: ~
The result of this function is passed directly to the `opts.virt_text`
field of |nvim_buf_set_extmark| and thus must be a list of
[text, highlight] tuples.
trouble *gitsigns-config-trouble* trouble *gitsigns-config-trouble*
Type: `boolean`, Default: true if installed Type: `boolean`, Default: true if installed

View File

@ -87,7 +87,7 @@ local function get_default(field)
local l = cfg[i] local l = cfg[i]
if l:match('^ default =') then if l:match('^ default =') then
ds = i ds = i
if l:match('},') or l:match('nil,') then if l:match('},') or l:match('nil,') or l:match("default = '.*'") then
de = i de = i
break break
end end
@ -172,6 +172,9 @@ local function gen_config_doc_field(field, out)
if v.type == 'table' and v.deep_extend then if v.type == 'table' and v.deep_extend then
return 'table[extended]' return 'table[extended]'
end end
if type(v.type) == 'table' then
v.type = table.concat(v.type, '|')
end
return v.type return v.type
end)() end)()

149
lua/gitsigns/config.lua generated
View File

@ -459,6 +459,7 @@ M.schema = {
current_line_blame_formatter_opts = { current_line_blame_formatter_opts = {
type = 'table', type = 'table',
deep_extend = true, deep_extend = true,
deprecated = true,
default = { default = {
relative_time = false, relative_time = false,
}, },
@ -471,82 +472,82 @@ M.schema = {
}, },
current_line_blame_formatter = { current_line_blame_formatter = {
type = 'function', type = { 'string', 'function' },
default = function(name, blame_info, opts) default = ' <author>, <author_time> - <summary>',
if blame_info.author == name then
blame_info.author = 'You'
end
local text
if blame_info.author == 'Not Committed Yet' then
text = blame_info.author
else
local date_time
if opts.relative_time then
date_time = require('gitsigns.util').get_relative_time(tonumber(blame_info['author_time']))
else
date_time = os.date('%Y-%m-%d', tonumber(blame_info['author_time']))
end
text = string.format('%s, %s - %s', blame_info.author, date_time, blame_info.summary)
end
return { { ' ' .. text, 'GitSignsCurrentLineBlame' } }
end,
default_help = [[function(name, blame_info, opts)
if blame_info.author == name then
blame_info.author = 'You'
end
local text
if blame_info.author == 'Not Committed Yet' then
text = blame_info.author
else
local date_time
if opts.relative_time then
date_time = require('gitsigns.util').get_relative_time(tonumber(blame_info['author_time']))
else
date_time = os.date('%Y-%m-%d', tonumber(blame_info['author_time']))
end
text = string.format('%s, %s - %s', blame_info.author, date_time, blame_info.summary)
end
return {{' '..text, 'GitSignsCurrentLineBlame'}}
end]],
description = [[ description = [[
Function used to format the virtual text of String or function used to format the virtual text of
|gitsigns-config-current_line_blame|. |gitsigns-config-current_line_blame|.
Parameters: ~ When a string, accepts the following format specifiers:
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
`abbrev_sha`: string
`orig_lnum`: integer
`final_lnum`: integer
`author`: string
`author_mail`: string
`author_time`: integer
`author_tz`: string
`committer`: string
`committer_mail`: string
`committer_time`: integer
`committer_tz`: string
`summary`: string
`previous`: string
`filename`: string
Note that the keys map onto the output of: `<abbrev_sha>`
`git blame --line-porcelain` `<orig_lnum>`
`<final_lnum>`
`<author>`
`<author_mail>`
`<author_time>` or `<author_time:FORMAT>`
`<author_tz>`
`<committer>`
`<committer_mail>`
`<committer_time>` or `<committer_time:FORMAT>`
`<committer_tz>`
`<summary>`
`<previous>`
`<filename>`
{opts} Passed directly from For `<author_time:FORMAT>` and `<committer_time:FORMAT>`, `FORMAT` can
|gitsigns-config-current_line_blame_formatter_opts|. be any valid date format that is accepted by `os.date()` with the
addition of `%R` (defaults to `%Y-%m-%d`):
Return: ~ `%a` abbreviated weekday name (e.g., Wed)
The result of this function is passed directly to the `opts.virt_text` `%A` full weekday name (e.g., Wednesday)
field of |nvim_buf_set_extmark|. `%b` abbreviated month name (e.g., Sep)
`%B` full month name (e.g., September)
`%c` date and time (e.g., 09/16/98 23:48:10)
`%d` day of the month (16) [01-31]
`%H` hour, using a 24-hour clock (23) [00-23]
`%I` hour, using a 12-hour clock (11) [01-12]
`%M` minute (48) [00-59]
`%m` month (09) [01-12]
`%p` either "am" or "pm" (pm)
`%S` second (10) [00-61]
`%w` weekday (3) [0-6 = Sunday-Saturday]
`%x` date (e.g., 09/16/98)
`%X` time (e.g., 23:48:10)
`%Y` full year (1998)
`%y` two-digit year (98) [00-99]
`%%` the character `%´
`%R` relative (e.g., 4 months ago)
When a function:
Parameters: ~
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
`abbrev_sha`: string
`orig_lnum`: integer
`final_lnum`: integer
`author`: string
`author_mail`: string
`author_time`: integer
`author_tz`: string
`committer`: string
`committer_mail`: string
`committer_time`: integer
`committer_tz`: string
`summary`: string
`previous`: string
`filename`: string
Note that the keys map onto the output of:
`git blame --line-porcelain`
{opts} Passed directly from
|gitsigns-config-current_line_blame_formatter_opts|.
Return: ~
The result of this function is passed directly to the `opts.virt_text`
field of |nvim_buf_set_extmark| and thus must be a list of
[text, highlight] tuples.
]], ]],
}, },
@ -655,9 +656,11 @@ local function validate_config(config)
if kschema == nil then if kschema == nil then
warn("gitsigns: Ignoring invalid configuration field '%s'", k) warn("gitsigns: Ignoring invalid configuration field '%s'", k)
elseif kschema.type then elseif kschema.type then
vim.validate({ if type(kschema.type) == 'string' then
[k] = { v, kschema.type }, vim.validate({
}) [k] = { v, kschema.type },
})
end
end end
end end
end end

View File

@ -66,6 +66,52 @@ function BlameCache:get(bufnr, lnum)
return self.contents[bufnr].cache[lnum] return self.contents[bufnr].cache[lnum]
end end
local function expand_blame_format(fmt, name, info)
local m
if info.author == name then
info.author = 'You'
end
if info.author == 'Not Committed Yet' then
return info.author
end
for k, v in pairs({
author_time = info.author_time,
committer_time = info.committer_time,
}) do
for _ = 1, 10 do
m = fmt:match('<' .. k .. ':([^>]+)>')
if not m then
break
end
if m:match('%%R') then
m = m:gsub('%%R', util.get_relative_time(v))
end
m = os.date(m, v)
fmt = fmt:gsub('<' .. k .. ':[^>]+>', m)
end
end
for k, v in pairs(info) do
for _ = 1, 10 do
m = fmt:match('<' .. k .. '>')
if not m then
break
end
if vim.endswith(k, '_time') then
if config.current_line_blame_formatter_opts.relative_time then
v = util.get_relative_time(v)
else
v = os.date('%Y-%m-%d', v)
end
end
fmt = fmt:gsub('<' .. k .. '>', v)
end
end
return fmt
end
M.update = void(function() M.update = void(function()
M.reset() M.reset()
@ -108,13 +154,24 @@ M.update = void(function()
api.nvim_buf_set_var(bufnr, 'gitsigns_blame_line_dict', result) api.nvim_buf_set_var(bufnr, 'gitsigns_blame_line_dict', result)
if opts.virt_text and result then if opts.virt_text and result then
api.nvim_buf_set_extmark(bufnr, namespace, lnum - 1, 0, { local virt_text
id = 1, local clb_formatter = config.current_line_blame_formatter
virt_text = config.current_line_blame_formatter( if type(clb_formatter) == "string" then
virt_text = { {
expand_blame_format(clb_formatter, bcache.git_obj.repo.username, result),
'GitSignsCurrentLineBlame',
}, }
else
virt_text = clb_formatter(
bcache.git_obj.repo.username, bcache.git_obj.repo.username,
result, result,
config.current_line_blame_formatter_opts), config.current_line_blame_formatter_opts)
end
api.nvim_buf_set_extmark(bufnr, namespace, lnum - 1, 0, {
id = 1,
virt_text = virt_text,
virt_text_pos = opts.virt_text_pos, virt_text_pos = opts.virt_text_pos,
hl_mode = 'combine', hl_mode = 'combine',
}) })

View File

@ -8,7 +8,7 @@ do
end end
local record SchemaElem local record SchemaElem
type: string type: string|{string}
deep_extend: boolean deep_extend: boolean
default: any default: any
@ -70,7 +70,7 @@ local record M
relative_time: boolean relative_time: boolean
end end
current_line_blame_formatter: function(string, {string:any}, current_line_blame_formatter_opts): string current_line_blame_formatter: string|function(string, {string:any}, current_line_blame_formatter_opts): {{string,string}}
record current_line_blame_opts record current_line_blame_opts
virt_text: boolean virt_text: boolean
@ -459,6 +459,7 @@ M.schema = {
current_line_blame_formatter_opts = { current_line_blame_formatter_opts = {
type = 'table', type = 'table',
deep_extend = true, deep_extend = true,
deprecated = true,
default = { default = {
relative_time = false relative_time = false
}, },
@ -471,82 +472,82 @@ M.schema = {
}, },
current_line_blame_formatter = { current_line_blame_formatter = {
type = 'function', type = {'string', 'function'},
default = function(name: string, blame_info: {string:string}, opts: M.Config.current_line_blame_formatter_opts): {{string}} default = ' <author>, <author_time> - <summary>',
if blame_info.author == name then
blame_info.author = 'You'
end
local text: string
if blame_info.author == 'Not Committed Yet' then
text = blame_info.author
else
local date_time: string
if opts.relative_time then
date_time = require('gitsigns.util').get_relative_time(tonumber(blame_info['author_time']))
else
date_time = os.date('%Y-%m-%d', tonumber(blame_info['author_time']))
end
text = string.format('%s, %s - %s', blame_info.author, date_time, blame_info.summary)
end
return {{' '..text, 'GitSignsCurrentLineBlame'}}
end,
default_help = [[function(name, blame_info, opts)
if blame_info.author == name then
blame_info.author = 'You'
end
local text
if blame_info.author == 'Not Committed Yet' then
text = blame_info.author
else
local date_time
if opts.relative_time then
date_time = require('gitsigns.util').get_relative_time(tonumber(blame_info['author_time']))
else
date_time = os.date('%Y-%m-%d', tonumber(blame_info['author_time']))
end
text = string.format('%s, %s - %s', blame_info.author, date_time, blame_info.summary)
end
return {{' '..text, 'GitSignsCurrentLineBlame'}}
end]],
description = [[ description = [[
Function used to format the virtual text of String or function used to format the virtual text of
|gitsigns-config-current_line_blame|. |gitsigns-config-current_line_blame|.
Parameters: ~ When a string, accepts the following format specifiers:
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
`abbrev_sha`: string
`orig_lnum`: integer
`final_lnum`: integer
`author`: string
`author_mail`: string
`author_time`: integer
`author_tz`: string
`committer`: string
`committer_mail`: string
`committer_time`: integer
`committer_tz`: string
`summary`: string
`previous`: string
`filename`: string
Note that the keys map onto the output of: `<abbrev_sha>`
`git blame --line-porcelain` `<orig_lnum>`
`<final_lnum>`
`<author>`
`<author_mail>`
`<author_time>` or `<author_time:FORMAT>`
`<author_tz>`
`<committer>`
`<committer_mail>`
`<committer_time>` or `<committer_time:FORMAT>`
`<committer_tz>`
`<summary>`
`<previous>`
`<filename>`
{opts} Passed directly from For `<author_time:FORMAT>` and `<committer_time:FORMAT>`, `FORMAT` can
|gitsigns-config-current_line_blame_formatter_opts|. be any valid date format that is accepted by `os.date()` with the
addition of `%R` (defaults to `%Y-%m-%d`):
Return: ~ `%a` abbreviated weekday name (e.g., Wed)
The result of this function is passed directly to the `opts.virt_text` `%A` full weekday name (e.g., Wednesday)
field of |nvim_buf_set_extmark|. `%b` abbreviated month name (e.g., Sep)
`%B` full month name (e.g., September)
`%c` date and time (e.g., 09/16/98 23:48:10)
`%d` day of the month (16) [01-31]
`%H` hour, using a 24-hour clock (23) [00-23]
`%I` hour, using a 12-hour clock (11) [01-12]
`%M` minute (48) [00-59]
`%m` month (09) [01-12]
`%p` either "am" or "pm" (pm)
`%S` second (10) [00-61]
`%w` weekday (3) [0-6 = Sunday-Saturday]
`%x` date (e.g., 09/16/98)
`%X` time (e.g., 23:48:10)
`%Y` full year (1998)
`%y` two-digit year (98) [00-99]
`%%` the character `%´
`%R` relative (e.g., 4 months ago)
When a function:
Parameters: ~
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
`abbrev_sha`: string
`orig_lnum`: integer
`final_lnum`: integer
`author`: string
`author_mail`: string
`author_time`: integer
`author_tz`: string
`committer`: string
`committer_mail`: string
`committer_time`: integer
`committer_tz`: string
`summary`: string
`previous`: string
`filename`: string
Note that the keys map onto the output of:
`git blame --line-porcelain`
{opts} Passed directly from
|gitsigns-config-current_line_blame_formatter_opts|.
Return: ~
The result of this function is passed directly to the `opts.virt_text`
field of |nvim_buf_set_extmark| and thus must be a list of
[text, highlight] tuples.
]] ]]
}, },
@ -655,9 +656,11 @@ local function validate_config(config: {string:any})
if kschema == nil then if kschema == nil then
warn("gitsigns: Ignoring invalid configuration field '%s'", k) warn("gitsigns: Ignoring invalid configuration field '%s'", k)
elseif kschema.type then elseif kschema.type then
vim.validate { if type(kschema.type) == 'string' then
[k] = { v, kschema.type } as {any}; vim.validate {
} [k] = { v, kschema.type } as {any};
}
end
end end
end end
end end

View File

@ -66,6 +66,52 @@ function BlameCache:get(bufnr: integer, lnum: integer): BlameInfo
return self.contents[bufnr].cache[lnum] return self.contents[bufnr].cache[lnum]
end end
local function expand_blame_format(fmt: string, name: string, info: BlameInfo): string
local m: string
if info.author == name then
info.author = 'You'
end
if info.author == 'Not Committed Yet' then
return info.author
end
for k, v in pairs{
author_time = info.author_time,
committer_time = info.committer_time,
} do
for _ = 1, 10 do -- loop protection
m = fmt:match('<'..k..':([^>]+)>')
if not m then
break
end
if m:match('%%R') then
m = m:gsub('%%R', util.get_relative_time(v))
end
m = os.date(m, v)
fmt = fmt:gsub('<'..k..':[^>]+>', m)
end
end
for k, v in pairs(info as {string:string}) do
for _ = 1, 10 do -- loop protection
m = fmt:match('<'..k..'>')
if not m then
break
end
if vim.endswith(k, '_time') then
if config.current_line_blame_formatter_opts.relative_time then
v = util.get_relative_time(v as integer)
else
v = os.date('%Y-%m-%d', v as integer)
end
end
fmt = fmt:gsub('<'..k..'>', v)
end
end
return fmt
end
-- Update function, must be called in async context -- Update function, must be called in async context
M.update = void(function() M.update = void(function()
M.reset() M.reset()
@ -108,13 +154,24 @@ M.update = void(function()
api.nvim_buf_set_var(bufnr, 'gitsigns_blame_line_dict', result) api.nvim_buf_set_var(bufnr, 'gitsigns_blame_line_dict', result)
if opts.virt_text and result then if opts.virt_text and result then
api.nvim_buf_set_extmark(bufnr, namespace, lnum-1, 0, { local virt_text: {{string, string}}
id = 1, local clb_formatter = config.current_line_blame_formatter
virt_text = config.current_line_blame_formatter( if clb_formatter is string then
virt_text = {{
expand_blame_format(clb_formatter, bcache.git_obj.repo.username, result),
'GitSignsCurrentLineBlame'
}}
else -- function
virt_text = clb_formatter(
bcache.git_obj.repo.username, bcache.git_obj.repo.username,
result, result,
config.current_line_blame_formatter_opts config.current_line_blame_formatter_opts
), )
end
api.nvim_buf_set_extmark(bufnr, namespace, lnum-1, 0, {
id = 1,
virt_text = virt_text,
virt_text_pos = opts.virt_text_pos, virt_text_pos = opts.virt_text_pos,
hl_mode = 'combine', hl_mode = 'combine',
}) })

View File

@ -246,9 +246,7 @@ describe('gitsigns', function()
describe('current line blame', function() describe('current line blame', function()
before_each(function() before_each(function()
config.current_line_blame = true config.current_line_blame = true
config.current_line_blame_formatter_opts = { config.current_line_blame_formatter = ' <author>, <author_time:%R> - <summary>'
relative_time = true,
}
setup_gitsigns(config) setup_gitsigns(config)
end) end)

View File

@ -414,6 +414,7 @@ global record vim
gsplit: function(string, string, boolean): function(): string gsplit: function(string, string, boolean): function(): string
startswith: function(string, string): boolean startswith: function(string, string): boolean
endswith: function(string, string): boolean
schedule_wrap: function(function(...:any): any...): function(...:any): any... schedule_wrap: function(function(...:any): any...): function(...:any): any...
schedule: function(function) schedule: function(function)