mirror of
https://github.com/lewis6991/gitsigns.nvim
synced 2024-09-08 02:35:46 +02:00
refactor: remove teal
This commit is contained in:
parent
4455bb5364
commit
4d63d996b0
84 changed files with 4589 additions and 13442 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
neovim_branch: ['v0.8.3', 'nightly']
|
||||
neovim_branch: ['v0.8.3', 'v0.9.1', 'nightly']
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NEOVIM_BRANCH: ${{ matrix.neovim_branch }}
|
||||
|
@ -56,11 +56,5 @@ jobs:
|
|||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: make test_deps
|
||||
|
||||
- name: Install Lua Deps
|
||||
run: make lua_deps
|
||||
|
||||
- name: Check lua files are built from latest teal
|
||||
run: make tl-ensure
|
||||
|
||||
- name: Run Test
|
||||
run: make test
|
||||
|
|
6
.stylua.toml
Normal file
6
.stylua.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
column_width = 100
|
||||
line_endings = "Unix"
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
quote_style = "AutoPreferSingle"
|
||||
call_parentheses = "Always"
|
|
@ -3,23 +3,9 @@
|
|||
- [Luarocks](https://luarocks.org/)
|
||||
- `brew install luarocks`
|
||||
|
||||
## Writing Teal
|
||||
|
||||
**Do not edit files in the lua dir**.
|
||||
|
||||
Gitsigns is implemented in teal which is essentially lua+types.
|
||||
The teal source files are generated into lua files and must be checked in together when making changes.
|
||||
CI will enforce this.
|
||||
|
||||
Once you have made changes in teal, the corresponding lua files can be built with:
|
||||
|
||||
```
|
||||
make tl-build
|
||||
```
|
||||
|
||||
## Generating docs
|
||||
|
||||
Most of the documentation is handwritten however the documentation for the configuration is generated from `teal/gitsigns/config.tl` which contains the configuration schema.
|
||||
Most of the documentation is handwritten however the documentation for the configuration is generated from `lua/gitsigns/config.lua` which contains the configuration schema.
|
||||
The documentation is generated with the lua script `gen_help.lua` which has been developed just enough to handle the current configuration schema so from time to time this script might need small improvements to handle new features but for the most part it works.
|
||||
|
||||
The documentation can be updated with:
|
||||
|
@ -46,40 +32,3 @@ To run the testsuite:
|
|||
```
|
||||
make test
|
||||
```
|
||||
|
||||
## [Diagnostic-ls](https://github.com/iamcco/diagnostic-languageserver) config for teal
|
||||
|
||||
```
|
||||
require('lspconfig').diagnosticls.setup{
|
||||
filetypes = {'teal'},
|
||||
init_options = {
|
||||
filetypes = {teal = {'tealcheck'}},
|
||||
linters = {
|
||||
tealcheck = {
|
||||
sourceName = "tealcheck",
|
||||
command = "tl",
|
||||
args = {'check', '%file'},
|
||||
isStdout = false, isStderr = true,
|
||||
rootPatterns = {"tlconfig.lua", ".git"},
|
||||
formatPattern = {
|
||||
'^([^:]+):(\\d+):(\\d+): (.+)$', {
|
||||
sourceName = 1, sourceNameFilter = true,
|
||||
line = 2, column = 3, message = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## [null-ls.nvim](https://github.com/jose-elias-alvarez/null-ls.nvim) config for teal
|
||||
|
||||
```
|
||||
local null_ls = require("null-ls")
|
||||
|
||||
null_ls.config {sources = {
|
||||
null_ls.builtins.diagnostics.teal
|
||||
}}
|
||||
require("lspconfig")["null-ls"].setup {}
|
||||
```
|
||||
|
|
28
Makefile
28
Makefile
|
@ -4,13 +4,12 @@ export PJ_ROOT=$(PWD)
|
|||
FILTER ?= .*
|
||||
|
||||
LUA_VERSION := 5.1
|
||||
TL_VERSION := 0.14.1
|
||||
NEOVIM_BRANCH ?= master
|
||||
|
||||
DEPS_DIR := $(PWD)/deps/nvim-$(NEOVIM_BRANCH)
|
||||
NVIM_DIR := $(DEPS_DIR)/neovim
|
||||
|
||||
LUAROCKS := $(DEPS_DIR)/luarocks/usr/bin/luarocks
|
||||
LUAROCKS := luarocks
|
||||
LUAROCKS_TREE := $(DEPS_DIR)/luarocks/usr
|
||||
LUAROCKS_LPATH := $(LUAROCKS_TREE)/share/lua/$(LUA_VERSION)
|
||||
LUAROCKS_INIT := eval $$($(LUAROCKS) --tree $(LUAROCKS_TREE) path) &&
|
||||
|
@ -26,17 +25,12 @@ $(NVIM_DIR):
|
|||
CMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
CMAKE_EXTRA_FLAGS='-DCI_BUILD=OFF -DENABLE_LTO=OFF'
|
||||
|
||||
TL := $(LUAROCKS_TREE)/bin/tl
|
||||
|
||||
$(TL): $(NVIM_DIR)
|
||||
@mkdir -p $$(dirname $@)
|
||||
$(LUAROCKS) --tree $(LUAROCKS_TREE) install tl $(TL_VERSION)
|
||||
|
||||
INSPECT := $(LUAROCKS_LPATH)/inspect.lua
|
||||
|
||||
$(INSPECT): $(NVIM_DIR)
|
||||
@mkdir -p $$(dirname $@)
|
||||
$(LUAROCKS) --tree $(LUAROCKS_TREE) install inspect
|
||||
touch $@
|
||||
|
||||
LUV := $(LUAROCKS_TREE)/lib/lua/$(LUA_VERSION)/luv.so
|
||||
|
||||
|
@ -45,7 +39,7 @@ $(LUV): $(NVIM_DIR)
|
|||
$(LUAROCKS) --tree $(LUAROCKS_TREE) install luv
|
||||
|
||||
.PHONY: lua_deps
|
||||
lua_deps: $(TL) $(INSPECT)
|
||||
lua_deps: $(INSPECT)
|
||||
|
||||
.PHONY: test_deps
|
||||
test_deps: $(NVIM_DIR)
|
||||
|
@ -73,24 +67,10 @@ test: $(NVIM_DIR)
|
|||
|
||||
-@stty sane
|
||||
|
||||
.PHONY: tl-check
|
||||
tl-check: $(TL)
|
||||
$(TL) check teal/*.tl teal/**/*.tl
|
||||
|
||||
.PHONY: tl-build
|
||||
tl-build: tlconfig.lua $(TL) $(LUV)
|
||||
@$(TL) build
|
||||
@$(LUAROCKS_INIT) ./etc/add_comments.lua
|
||||
@echo Updated lua files
|
||||
|
||||
.PHONY: gen_help
|
||||
gen_help: $(INSPECT)
|
||||
@$(LUAROCKS_INIT) ./gen_help.lua
|
||||
@echo Updated help
|
||||
|
||||
.PHONY: build
|
||||
build: tl-build gen_help
|
||||
|
||||
.PHONY: tl-ensure
|
||||
tl-ensure: tl-build
|
||||
git diff --exit-code -- lua
|
||||
build: gen_help
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
|
||||
[![Gitter](https://badges.gitter.im/gitsigns-nvim/community.svg)](https://gitter.im/gitsigns-nvim/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
Super fast git decorations implemented purely in lua/teal.
|
||||
Super fast git decorations implemented purely in Lua.
|
||||
|
||||
## Preview
|
||||
|
||||
|
|
|
@ -594,11 +594,11 @@ on_attach *gitsigns-config-on_attach*
|
|||
watch_gitdir *gitsigns-config-watch_gitdir*
|
||||
Type: `table[extended]`
|
||||
Default: >
|
||||
{
|
||||
`{
|
||||
enable = true,
|
||||
interval = 1000,
|
||||
follow_files = true
|
||||
}
|
||||
follow_files = true,
|
||||
interval = 1000
|
||||
}`
|
||||
<
|
||||
When opening a file, a libuv watcher is placed on the respective
|
||||
`.git` directory to detect when changes happen to use as a trigger to
|
||||
|
@ -685,18 +685,9 @@ base *gitsigns-config-base*
|
|||
count_chars *gitsigns-config-count_chars*
|
||||
Type: `table`
|
||||
Default: >
|
||||
{
|
||||
[1] = '1', -- '₁',
|
||||
[2] = '2', -- '₂',
|
||||
[3] = '3', -- '₃',
|
||||
[4] = '4', -- '₄',
|
||||
[5] = '5', -- '₅',
|
||||
[6] = '6', -- '₆',
|
||||
[7] = '7', -- '₇',
|
||||
[8] = '8', -- '₈',
|
||||
[9] = '9', -- '₉',
|
||||
['+'] = '>', -- '₊',
|
||||
}
|
||||
`{ "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
["+"] = ">"
|
||||
}`
|
||||
<
|
||||
The count characters used when `signs.*.show_count` is enabled. The
|
||||
`+` entry is used as a fallback. With the default, any count outside
|
||||
|
@ -728,13 +719,13 @@ max_file_length *gitsigns-config-max_file_length*
|
|||
preview_config *gitsigns-config-preview_config*
|
||||
Type: `table[extended]`
|
||||
Default: >
|
||||
{
|
||||
border = 'single',
|
||||
style = 'minimal',
|
||||
relative = 'cursor',
|
||||
row = 0,
|
||||
col = 1
|
||||
}
|
||||
`{
|
||||
border = "single",
|
||||
col = 1,
|
||||
relative = "cursor",
|
||||
row = 0,
|
||||
style = "minimal"
|
||||
}`
|
||||
<
|
||||
Option overrides for the Gitsigns preview window. Table is passed directly
|
||||
to `nvim_open_win`.
|
||||
|
@ -760,12 +751,12 @@ current_line_blame *gitsigns-config-current_line_blame*
|
|||
current_line_blame_opts *gitsigns-config-current_line_blame_opts*
|
||||
Type: `table[extended]`
|
||||
Default: >
|
||||
{
|
||||
`{
|
||||
delay = 1000,
|
||||
virt_text = true,
|
||||
virt_text_pos = 'eol',
|
||||
virt_text_priority = 100,
|
||||
delay = 1000
|
||||
}
|
||||
virt_text_pos = "eol",
|
||||
virt_text_priority = 100
|
||||
}`
|
||||
<
|
||||
Options for the current line blame annotation.
|
||||
|
||||
|
@ -792,9 +783,9 @@ current_line_blame_formatter_opts
|
|||
|
||||
Type: `table[extended]`
|
||||
Default: >
|
||||
{
|
||||
relative_time = false
|
||||
}
|
||||
`{
|
||||
relative_time = false
|
||||
}`
|
||||
<
|
||||
Options for the current line blame annotation formatter.
|
||||
|
||||
|
@ -802,7 +793,7 @@ current_line_blame_formatter_opts
|
|||
• relative_time: boolean
|
||||
|
||||
current_line_blame_formatter *gitsigns-config-current_line_blame_formatter*
|
||||
Type: `string|function`, Default: `' <author>, <author_time> - <summary> '`
|
||||
Type: `string|function`, Default: `" <author>, <author_time> - <summary> "`
|
||||
|
||||
String or function used to format the virtual text of
|
||||
|gitsigns-config-current_line_blame|.
|
||||
|
@ -880,7 +871,7 @@ current_line_blame_formatter *gitsigns-config-current_line_blame_formatter*
|
|||
|
||||
current_line_blame_formatter_nc
|
||||
*gitsigns-config-current_line_blame_formatter_nc*
|
||||
Type: `string|function`, Default: `' <author>'`
|
||||
Type: `string|function`, Default: `" <author>"`
|
||||
|
||||
String or function used to format the virtual text of
|
||||
|gitsigns-config-current_line_blame| for lines that aren't committed.
|
||||
|
@ -894,8 +885,12 @@ trouble *gitsigns-config-trouble*
|
|||
quickfix/location list window.
|
||||
|
||||
yadm *gitsigns-config-yadm*
|
||||
Type: `table`, Default: `{ enable = false }`
|
||||
|
||||
Type: `table`
|
||||
Default: >
|
||||
`{
|
||||
enable = false
|
||||
}`
|
||||
<
|
||||
yadm configuration.
|
||||
|
||||
word_diff *gitsigns-config-word_diff*
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
#!/bin/sh
|
||||
_=[[
|
||||
exec luajit "$0" "$@"
|
||||
]]
|
||||
|
||||
local uv = require'luv'
|
||||
|
||||
local function read_file(path)
|
||||
local f = assert(io.open(path, 'r'))
|
||||
local t = f:read("*all")
|
||||
f:close()
|
||||
return t
|
||||
end
|
||||
|
||||
local function join_paths(...)
|
||||
return table.concat({ ... }, '/'):gsub('//+', '/')
|
||||
end
|
||||
|
||||
local function dir(path)
|
||||
--- @async
|
||||
return coroutine.wrap(function()
|
||||
local dirs = { { path, 1 } }
|
||||
while #dirs > 0 do
|
||||
local dir0, level = unpack(table.remove(dirs, 1))
|
||||
local dir1 = level == 1 and dir0 or join_paths(path, dir0)
|
||||
local fs = uv.fs_scandir(dir1)
|
||||
while fs do
|
||||
local name, t = uv.fs_scandir_next(fs)
|
||||
if not name then
|
||||
break
|
||||
end
|
||||
local f = level == 1 and name or join_paths(dir0, name)
|
||||
if t == 'directory' then
|
||||
dirs[#dirs + 1] = { f, level + 1 }
|
||||
else
|
||||
coroutine.yield(f, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function write_file(path, lines)
|
||||
local f = assert(io.open(path, 'w'))
|
||||
f:write(table.concat(lines, '\n'))
|
||||
f:close()
|
||||
end
|
||||
|
||||
local function read_file_lines(path)
|
||||
local lines = {}
|
||||
for l in read_file(path):gmatch("([^\n]*)\n?") do
|
||||
table.insert(lines, l)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
for p in dir('teal') do
|
||||
local path = join_paths('teal', p)
|
||||
local op = p:gsub('%.tl$', '.lua')
|
||||
local opath = join_paths('lua', op)
|
||||
|
||||
local lines = read_file_lines(path)
|
||||
|
||||
local comments = {}
|
||||
for i, l in ipairs(lines) do
|
||||
local comment = l:match('%s*%-%-.*')
|
||||
if comment then
|
||||
comments[i] = comment:gsub(' ', ' ')
|
||||
end
|
||||
end
|
||||
|
||||
local olines = read_file_lines(opath)
|
||||
|
||||
for i, l in pairs(comments) do
|
||||
if not olines[i]:match('%-%-.*') then
|
||||
olines[i] = olines[i]..l
|
||||
end
|
||||
end
|
||||
write_file(opath, olines)
|
||||
end
|
||||
|
114
gen_help.lua
114
gen_help.lua
|
@ -15,10 +15,6 @@ function table.slice(tbl, first, last, step)
|
|||
return sliced
|
||||
end
|
||||
|
||||
local function is_simple_type(t)
|
||||
return t == 'number' or t == 'string' or t == 'boolean'
|
||||
end
|
||||
|
||||
local function startswith(str, start)
|
||||
return str.sub(str, 1, string.len(start)) == start
|
||||
end
|
||||
|
@ -30,18 +26,11 @@ local function read_file(path)
|
|||
return t
|
||||
end
|
||||
|
||||
local function read_file_lines(path)
|
||||
local lines = {}
|
||||
for l in read_file(path):gmatch("([^\n]*)\n?") do
|
||||
table.insert(lines, l)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
-- To make sure the output is consistent between runs (to minimise diffs), we
|
||||
-- need to iterate through the schema keys in a deterministic way. To do this we
|
||||
-- do a smple scan over the file the schema is defined in and collect the keys
|
||||
-- in the order they are defined.
|
||||
--- @return string[]
|
||||
local function get_ordered_schema_keys()
|
||||
local c = read_file('lua/gitsigns/config.lua')
|
||||
|
||||
|
@ -58,7 +47,7 @@ local function get_ordered_schema_keys()
|
|||
if startswith(l, '}') then
|
||||
break
|
||||
end
|
||||
if l:find('^ (%w+).*') then
|
||||
if l:find('^ (%w+).*') then
|
||||
local lc = l:gsub('^%s*([%w_]+).*', '%1')
|
||||
table.insert(keys, lc)
|
||||
end
|
||||
|
@ -67,52 +56,6 @@ local function get_ordered_schema_keys()
|
|||
return keys
|
||||
end
|
||||
|
||||
local function get_default(field)
|
||||
local cfg = read_file_lines('teal/gitsigns/config.tl')
|
||||
|
||||
local fs, fe
|
||||
for i = 1, #cfg do
|
||||
local l = cfg[i]
|
||||
if l:match('^ '..field..' =') then
|
||||
fs = i
|
||||
end
|
||||
if fs and l:match('^ }') then
|
||||
fe = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local ds, de
|
||||
for i = fs, fe do
|
||||
local l = cfg[i]
|
||||
if l:match('^ default =') then
|
||||
ds = i
|
||||
if l:match('},') or l:match('nil,') or l:match("default = '.*'") then
|
||||
de = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if ds and l:match('^ }') then
|
||||
de = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local ret = {}
|
||||
for i = ds, de do
|
||||
local l = cfg[i]
|
||||
if i == ds then
|
||||
l = l:gsub('%s*default = ', '')
|
||||
end
|
||||
if i == de then
|
||||
l = l:gsub('(.*),', '%1')
|
||||
end
|
||||
table.insert(ret, l)
|
||||
end
|
||||
|
||||
return table.concat(ret, '\n')
|
||||
end
|
||||
|
||||
local function gen_config_doc_deprecated(dep_info, out)
|
||||
if type(dep_info) == 'table' and dep_info.hard then
|
||||
out(' HARD-DEPRECATED')
|
||||
|
@ -153,19 +96,12 @@ local function gen_config_doc_field(field, out)
|
|||
end
|
||||
|
||||
if v.description then
|
||||
local d
|
||||
local d --- @type string
|
||||
if v.default_help ~= nil then
|
||||
d = v.default_help
|
||||
elseif is_simple_type(v.type) then
|
||||
d = inspect(v.default)
|
||||
d = ('`%s`'):format(d)
|
||||
else
|
||||
d = get_default(field)
|
||||
if d:find('\n') then
|
||||
d = d:gsub('\n([^\n\r])', '\n%1')
|
||||
else
|
||||
d = ('`%s`'):format(d)
|
||||
end
|
||||
d = inspect(v.default):gsub('\n', '\n ')
|
||||
d = ('`%s`'):format(d)
|
||||
end
|
||||
|
||||
local vtype = (function()
|
||||
|
@ -192,8 +128,9 @@ local function gen_config_doc_field(field, out)
|
|||
end
|
||||
end
|
||||
|
||||
--- @return string
|
||||
local function gen_config_doc()
|
||||
local res = {}
|
||||
local res = {} ---@type string[]
|
||||
local function out(line)
|
||||
res[#res+1] = line or ''
|
||||
end
|
||||
|
@ -203,6 +140,8 @@ local function gen_config_doc()
|
|||
return table.concat(res, '\n')
|
||||
end
|
||||
|
||||
--- @param line string
|
||||
--- @return string
|
||||
local function parse_func_header(line)
|
||||
local func = line:match('%w+%.([%w_]+)')
|
||||
if not func then
|
||||
|
@ -212,7 +151,7 @@ local function parse_func_header(line)
|
|||
line:match('function%((.*)%)') or -- M.name = function(args)
|
||||
line:match('function%s+%w+%.[%w_]+%((.*)%)') -- function M.name(args)
|
||||
local args = {}
|
||||
for k in string.gmatch(args_raw, "([%w_]+):") do
|
||||
for k in string.gmatch(args_raw, "([%w_]+)") do
|
||||
if k:sub(1, 1) ~= '_' then
|
||||
args[#args+1] = string.format('{%s}', k)
|
||||
end
|
||||
|
@ -224,6 +163,8 @@ local function parse_func_header(line)
|
|||
)
|
||||
end
|
||||
|
||||
--- @param path string
|
||||
--- @return string
|
||||
local function gen_functions_doc_from_file(path)
|
||||
local i = read_file(path):gmatch("([^\n]*)\n?")
|
||||
|
||||
|
@ -262,6 +203,8 @@ local function gen_functions_doc_from_file(path)
|
|||
return table.concat(res, '\n')
|
||||
end
|
||||
|
||||
--- @param files string[]
|
||||
--- @return string
|
||||
local function gen_functions_doc(files)
|
||||
local res = ''
|
||||
for _, path in ipairs(files) do
|
||||
|
@ -270,8 +213,9 @@ local function gen_functions_doc(files)
|
|||
return res
|
||||
end
|
||||
|
||||
--- @return string
|
||||
local function gen_highlights_doc()
|
||||
local res = {}
|
||||
local res = {} --- @type string[]
|
||||
local highlights = require('lua.gitsigns.highlight')
|
||||
|
||||
local name_max = 0
|
||||
|
@ -286,12 +230,11 @@ local function gen_highlights_doc()
|
|||
for _, hl in ipairs(highlights.hls) do
|
||||
for name, spec in pairs(hl) do
|
||||
if not spec.hidden then
|
||||
local fallbacks_tbl = {}
|
||||
local fallbacks_tbl = {} --- @type string[]
|
||||
for _, f in ipairs(spec) do
|
||||
fallbacks_tbl[#fallbacks_tbl+1] = string.format('`%s`', f)
|
||||
end
|
||||
local fallbacks = table.concat(fallbacks_tbl, ', ')
|
||||
local pad = string.rep(' ', name_max - name:len())
|
||||
res[#res+1] = string.format('%s*hl-%s*', string.rep(' ', 56), name)
|
||||
res[#res+1] = string.format('%s', name)
|
||||
if spec.desc then
|
||||
|
@ -306,11 +249,12 @@ local function gen_highlights_doc()
|
|||
return table.concat(res, '\n')
|
||||
end
|
||||
|
||||
--- @return string
|
||||
local function get_setup_from_readme()
|
||||
local i = read_file('README.md'):gmatch("([^\n]*)\n?")
|
||||
local res = {}
|
||||
local res = {} --- @type string[]
|
||||
local function append(line)
|
||||
res[#res+1] = line ~= '' and ' '..line or ''
|
||||
res[#res+1] = line ~= '' and ' '..line or ''
|
||||
end
|
||||
for l in i do
|
||||
if l:match("require%('gitsigns'%).setup {") then
|
||||
|
@ -332,14 +276,16 @@ end
|
|||
local function get_marker_text(marker)
|
||||
return ({
|
||||
VERSION = '0.7-dev',
|
||||
CONFIG = gen_config_doc,
|
||||
FUNCTIONS = gen_functions_doc{
|
||||
'teal/gitsigns.tl',
|
||||
'teal/gitsigns/attach.tl',
|
||||
'teal/gitsigns/actions.tl',
|
||||
},
|
||||
HIGHLIGHTS = gen_highlights_doc,
|
||||
SETUP = get_setup_from_readme
|
||||
CONFIG = function() return gen_config_doc() end,
|
||||
FUNCTIONS = function()
|
||||
return gen_functions_doc{
|
||||
'lua/gitsigns.lua',
|
||||
'lua/gitsigns/attach.lua',
|
||||
'lua/gitsigns/actions.lua',
|
||||
}
|
||||
end,
|
||||
HIGHLIGHTS = function() return gen_highlights_doc() end,
|
||||
SETUP = function() return get_setup_from_readme() end,
|
||||
})[marker]
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ version = _MODREV .. _SPECREV
|
|||
description = {
|
||||
summary = 'Git signs written in pure lua',
|
||||
detailed = [[
|
||||
Super fast git decorations implemented purely in lua/teal.
|
||||
Super fast git decorations implemented purely in Lua.
|
||||
]],
|
||||
homepage = 'http://github.com/lewis6991/gitsigns.nvim',
|
||||
license = 'MIT/X11',
|
||||
|
|
3
lua/README.md
generated
3
lua/README.md
generated
|
@ -1,3 +0,0 @@
|
|||
**WARNING**: Do not edit the files in this directory. The files are generated from [teal](https://github.com/teal-language/tl). For the original source files please look in the [teal](../teal) directory.
|
||||
|
||||
See [Makefile](../Makefile) for targets on handling the teal files.
|
252
lua/gitsigns.lua
generated
252
lua/gitsigns.lua
generated
|
@ -2,7 +2,6 @@ local void = require('gitsigns.async').void
|
|||
local scheduler = require('gitsigns.async').scheduler
|
||||
|
||||
local gs_config = require('gitsigns.config')
|
||||
local Config = gs_config.Config
|
||||
local config = gs_config.config
|
||||
|
||||
local log = require('gitsigns.debug.log')
|
||||
|
@ -14,193 +13,186 @@ local uv = require('gitsigns.uv')
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
-- from attach.tl
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local cwd_watcher
|
||||
|
||||
local update_cwd_head = void(function()
|
||||
local paths = vim.fs.find('.git', {
|
||||
limit = 1,
|
||||
upward = true,
|
||||
type = 'directory',
|
||||
})
|
||||
local paths = vim.fs.find('.git', {
|
||||
limit = 1,
|
||||
upward = true,
|
||||
type = 'directory',
|
||||
})
|
||||
|
||||
if #paths == 0 then
|
||||
return
|
||||
end
|
||||
if #paths == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if cwd_watcher then
|
||||
cwd_watcher:stop()
|
||||
else
|
||||
cwd_watcher = uv.new_fs_poll(true)
|
||||
end
|
||||
if cwd_watcher then
|
||||
cwd_watcher:stop()
|
||||
else
|
||||
cwd_watcher = uv.new_fs_poll(true)
|
||||
end
|
||||
|
||||
local cwd = vim.loop.cwd()
|
||||
local gitdir, head
|
||||
local cwd = vim.loop.cwd()
|
||||
local gitdir, head
|
||||
|
||||
local gs_cache = require('gitsigns.cache')
|
||||
local gs_cache = require('gitsigns.cache')
|
||||
|
||||
-- Look in the cache first
|
||||
for _, bcache in pairs(gs_cache.cache) do
|
||||
local repo = bcache.git_obj.repo
|
||||
if repo.toplevel == cwd then
|
||||
head = repo.abbrev_head
|
||||
gitdir = repo.gitdir
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Look in the cache first
|
||||
for _, bcache in pairs(gs_cache.cache) do
|
||||
local repo = bcache.git_obj.repo
|
||||
if repo.toplevel == cwd then
|
||||
head = repo.abbrev_head
|
||||
gitdir = repo.gitdir
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local git = require('gitsigns.git')
|
||||
local git = require('gitsigns.git')
|
||||
|
||||
if not head or not gitdir then
|
||||
local info = git.get_repo_info(cwd)
|
||||
gitdir = info.gitdir
|
||||
head = info.abbrev_head
|
||||
end
|
||||
if not head or not gitdir then
|
||||
local info = git.get_repo_info(cwd)
|
||||
gitdir = info.gitdir
|
||||
head = info.abbrev_head
|
||||
end
|
||||
|
||||
scheduler()
|
||||
vim.g.gitsigns_head = head
|
||||
scheduler()
|
||||
vim.g.gitsigns_head = head
|
||||
|
||||
if not gitdir then
|
||||
return
|
||||
end
|
||||
if not gitdir then
|
||||
return
|
||||
end
|
||||
|
||||
local towatch = gitdir .. '/HEAD'
|
||||
local towatch = gitdir .. '/HEAD'
|
||||
|
||||
if cwd_watcher:getpath() == towatch then
|
||||
-- Already watching
|
||||
return
|
||||
end
|
||||
if cwd_watcher:getpath() == towatch then
|
||||
-- Already watching
|
||||
return
|
||||
end
|
||||
|
||||
-- Watch .git/HEAD to detect branch changes
|
||||
cwd_watcher:start(
|
||||
towatch,
|
||||
config.watch_gitdir.interval,
|
||||
void(function(err)
|
||||
-- Watch .git/HEAD to detect branch changes
|
||||
cwd_watcher:start(
|
||||
towatch,
|
||||
config.watch_gitdir.interval,
|
||||
void(function(err)
|
||||
local __FUNC__ = 'cwd_watcher_cb'
|
||||
if err then
|
||||
dprintf('Git dir update error: %s', err)
|
||||
return
|
||||
dprintf('Git dir update error: %s', err)
|
||||
return
|
||||
end
|
||||
dprint('Git cwd dir update')
|
||||
|
||||
local new_head = git.get_repo_info(cwd).abbrev_head
|
||||
scheduler()
|
||||
vim.g.gitsigns_head = new_head
|
||||
end))
|
||||
|
||||
end)
|
||||
)
|
||||
end)
|
||||
|
||||
local function setup_cli()
|
||||
api.nvim_create_user_command('Gitsigns', function(params)
|
||||
require('gitsigns.cli').run(params)
|
||||
end, {
|
||||
force = true,
|
||||
nargs = '*',
|
||||
range = true,
|
||||
complete = function(arglead, line)
|
||||
return require('gitsigns.cli').complete(arglead, line)
|
||||
end, })
|
||||
api.nvim_create_user_command('Gitsigns', function(params)
|
||||
require('gitsigns.cli').run(params)
|
||||
end, {
|
||||
force = true,
|
||||
nargs = '*',
|
||||
range = true,
|
||||
complete = function(arglead, line)
|
||||
return require('gitsigns.cli').complete(arglead, line)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local exported = {
|
||||
'attach',
|
||||
'actions',
|
||||
'attach',
|
||||
'actions',
|
||||
}
|
||||
|
||||
local function setup_debug()
|
||||
log.debug_mode = config.debug_mode
|
||||
log.verbose = config._verbose
|
||||
log.debug_mode = config.debug_mode
|
||||
log.verbose = config._verbose
|
||||
|
||||
if config.debug_mode then
|
||||
exported[#exported + 1] = 'debug'
|
||||
end
|
||||
if config.debug_mode then
|
||||
exported[#exported + 1] = 'debug'
|
||||
end
|
||||
end
|
||||
|
||||
local function setup_attach()
|
||||
scheduler()
|
||||
scheduler()
|
||||
|
||||
-- Attach to all open buffers
|
||||
for _, buf in ipairs(api.nvim_list_bufs()) do
|
||||
if api.nvim_buf_is_loaded(buf) and
|
||||
api.nvim_buf_get_name(buf) ~= '' then
|
||||
M.attach(buf, nil, 'setup')
|
||||
scheduler()
|
||||
end
|
||||
end
|
||||
-- Attach to all open buffers
|
||||
for _, buf in ipairs(api.nvim_list_bufs()) do
|
||||
if api.nvim_buf_is_loaded(buf) and api.nvim_buf_get_name(buf) ~= '' then
|
||||
M.attach(buf, nil, 'setup')
|
||||
scheduler()
|
||||
end
|
||||
end
|
||||
|
||||
api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'BufWritePost' }, {
|
||||
group = 'gitsigns',
|
||||
callback = function(data)
|
||||
M.attach(nil, nil, data.event)
|
||||
end,
|
||||
})
|
||||
api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'BufWritePost' }, {
|
||||
group = 'gitsigns',
|
||||
callback = function(data)
|
||||
M.attach(nil, nil, data.event)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local function setup_cwd_head()
|
||||
scheduler()
|
||||
update_cwd_head()
|
||||
-- Need to debounce in case some plugin changes the cwd too often
|
||||
-- (like vim-grepper)
|
||||
api.nvim_create_autocmd('DirChanged', {
|
||||
group = 'gitsigns',
|
||||
callback = function()
|
||||
local debounce = require("gitsigns.debounce").debounce_trailing
|
||||
debounce(100, update_cwd_head)
|
||||
end,
|
||||
})
|
||||
scheduler()
|
||||
update_cwd_head()
|
||||
-- Need to debounce in case some plugin changes the cwd too often
|
||||
-- (like vim-grepper)
|
||||
api.nvim_create_autocmd('DirChanged', {
|
||||
group = 'gitsigns',
|
||||
callback = function()
|
||||
local debounce = require('gitsigns.debounce').debounce_trailing
|
||||
debounce(100, update_cwd_head)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
--- Setup and start Gitsigns.
|
||||
---
|
||||
--- Attributes: ~
|
||||
--- {async}
|
||||
--- {async}
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- {cfg} Table object containing configuration for
|
||||
--- Gitsigns. See |gitsigns-usage| for more details.
|
||||
--- {cfg} Table object containing configuration for
|
||||
--- Gitsigns. See |gitsigns-usage| for more details.
|
||||
M.setup = void(function(cfg)
|
||||
gs_config.build(cfg)
|
||||
gs_config.build(cfg)
|
||||
|
||||
if vim.fn.executable('git') == 0 then
|
||||
print('gitsigns: git not in path. Aborting setup')
|
||||
return
|
||||
end
|
||||
if vim.fn.executable('git') == 0 then
|
||||
print('gitsigns: git not in path. Aborting setup')
|
||||
return
|
||||
end
|
||||
|
||||
if config.yadm.enable and vim.fn.executable('yadm') == 0 then
|
||||
print("gitsigns: yadm not in path. Ignoring 'yadm.enable' in config")
|
||||
config.yadm.enable = false
|
||||
return
|
||||
end
|
||||
if config.yadm.enable and vim.fn.executable('yadm') == 0 then
|
||||
print("gitsigns: yadm not in path. Ignoring 'yadm.enable' in config")
|
||||
config.yadm.enable = false
|
||||
return
|
||||
end
|
||||
|
||||
setup_debug()
|
||||
setup_cli()
|
||||
setup_debug()
|
||||
setup_cli()
|
||||
|
||||
api.nvim_create_augroup('gitsigns', {})
|
||||
api.nvim_create_augroup('gitsigns', {})
|
||||
|
||||
if config._test_mode then
|
||||
require('gitsigns.attach')._setup()
|
||||
require('gitsigns.git')._set_version(config._git_version)
|
||||
end
|
||||
if config._test_mode then
|
||||
require('gitsigns.attach')._setup()
|
||||
require('gitsigns.git')._set_version(config._git_version)
|
||||
end
|
||||
|
||||
setup_attach()
|
||||
setup_cwd_head()
|
||||
setup_attach()
|
||||
setup_cwd_head()
|
||||
|
||||
M._setup_done = true
|
||||
M._setup_done = true
|
||||
end)
|
||||
|
||||
return setmetatable(M, {
|
||||
__index = function(_, f)
|
||||
for _, mod in ipairs(exported) do
|
||||
local m = (require)('gitsigns.' .. mod)
|
||||
if m[f] then
|
||||
return m[f]
|
||||
end
|
||||
__index = function(_, f)
|
||||
for _, mod in ipairs(exported) do
|
||||
local m = (require)('gitsigns.' .. mod)
|
||||
if m[f] then
|
||||
return m[f]
|
||||
end
|
||||
end,
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
|
1632
lua/gitsigns/actions.lua
generated
1632
lua/gitsigns/actions.lua
generated
File diff suppressed because it is too large
Load diff
219
lua/gitsigns/async.lua
generated
219
lua/gitsigns/async.lua
generated
|
@ -1,42 +1,15 @@
|
|||
|
||||
-- Order by highest number of return types
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Order by highest number of return types
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
local Async_T = {}
|
||||
|
||||
-- Handle for an object currently running on the event loop.
|
||||
-- The coroutine is paused while this is active.
|
||||
-- Must provide methods cancel() and is_cancelled()
|
||||
--
|
||||
-- Handle gets updated on each call to a wrapped functions, so provide access
|
||||
-- to it via a proxy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Handle for an object currently running on the event loop.
|
||||
-- The coroutine is paused while this is active.
|
||||
-- Must provide methods cancel() and is_cancelled()
|
||||
--
|
||||
-- Handle gets updated on each call to a wrapped functions, so provide access
|
||||
-- to it via a proxy
|
||||
|
||||
-- Coroutine.running() was changed between Lua 5.1 and 5.2:
|
||||
-- - 5.1: Returns the running coroutine, or nil when called by the main thread.
|
||||
|
@ -53,106 +26,104 @@ local handles = setmetatable({}, { __mode = 'k' })
|
|||
|
||||
--- Returns whether the current execution context is async.
|
||||
function M.running()
|
||||
local current = coroutine.running()
|
||||
if current and handles[current] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- hack: teal doesn't know table.maxn exists
|
||||
local function maxn(x)
|
||||
return ((table).maxn)(x)
|
||||
local current = coroutine.running()
|
||||
if current and handles[current] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function is_Async_T(handle)
|
||||
if handle and
|
||||
type(handle) == 'table' and
|
||||
vim.is_callable(handle.cancel) and
|
||||
vim.is_callable(handle.is_cancelled) then
|
||||
return true
|
||||
end
|
||||
if
|
||||
handle
|
||||
and type(handle) == 'table'
|
||||
and vim.is_callable(handle.cancel)
|
||||
and vim.is_callable(handle.is_cancelled)
|
||||
then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Analogous to uv.close
|
||||
function Async_T:cancel(cb)
|
||||
-- Cancel anything running on the event loop
|
||||
if self._current and not self._current:is_cancelled() then
|
||||
self._current:cancel(cb)
|
||||
end
|
||||
-- Cancel anything running on the event loop
|
||||
if self._current and not self._current:is_cancelled() then
|
||||
self._current:cancel(cb)
|
||||
end
|
||||
end
|
||||
|
||||
function Async_T.new(co)
|
||||
local handle = setmetatable({}, { __index = Async_T })
|
||||
handles[co] = handle
|
||||
return handle
|
||||
local handle = setmetatable({}, { __index = Async_T })
|
||||
handles[co] = handle
|
||||
return handle
|
||||
end
|
||||
|
||||
-- Analogous to uv.is_closing
|
||||
function Async_T:is_cancelled()
|
||||
return self._current and self._current:is_cancelled()
|
||||
return self._current and self._current:is_cancelled()
|
||||
end
|
||||
|
||||
local function run(func, callback, ...)
|
||||
local co = coroutine.create(func)
|
||||
local handle = Async_T.new(co)
|
||||
local co = coroutine.create(func)
|
||||
local handle = Async_T.new(co)
|
||||
|
||||
local function step(...)
|
||||
local ret = { coroutine.resume(co, ...) }
|
||||
local stat = ret[1]
|
||||
local function step(...)
|
||||
local ret = { coroutine.resume(co, ...) }
|
||||
local stat = ret[1]
|
||||
|
||||
if not stat then
|
||||
local err = ret[2]
|
||||
error(string.format("The coroutine failed with this message: %s\n%s",
|
||||
err, debug.traceback(co)))
|
||||
if not stat then
|
||||
local err = ret[2]
|
||||
error(
|
||||
string.format('The coroutine failed with this message: %s\n%s', err, debug.traceback(co))
|
||||
)
|
||||
end
|
||||
|
||||
if coroutine.status(co) == 'dead' then
|
||||
if callback then
|
||||
callback(unpack(ret, 4, table.maxn(ret)))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if coroutine.status(co) == 'dead' then
|
||||
if callback then
|
||||
callback(unpack(ret, 4, maxn(ret)))
|
||||
end
|
||||
return
|
||||
end
|
||||
local _, nargs, fn = unpack(ret)
|
||||
|
||||
local _, nargs, fn = unpack(ret)
|
||||
assert(type(fn) == 'function', 'type error :: expected func')
|
||||
|
||||
assert(type(fn) == 'function', "type error :: expected func")
|
||||
local args = { select(4, unpack(ret)) }
|
||||
args[nargs] = step
|
||||
|
||||
local args = { select(4, unpack(ret)) }
|
||||
args[nargs] = step
|
||||
local r = fn(unpack(args, 1, nargs))
|
||||
if is_Async_T(r) then
|
||||
handle._current = r
|
||||
end
|
||||
end
|
||||
|
||||
local r = fn(unpack(args, 1, nargs))
|
||||
if is_Async_T(r) then
|
||||
handle._current = r
|
||||
end
|
||||
end
|
||||
|
||||
step(...)
|
||||
return handle
|
||||
step(...)
|
||||
return handle
|
||||
end
|
||||
|
||||
function M.wait(argc, func, ...)
|
||||
-- Always run the wrapped functions in xpcall and re-raise the error in the
|
||||
-- coroutine. This makes pcall work as normal.
|
||||
local function pfunc(...)
|
||||
local args = { ... }
|
||||
local cb = args[argc]
|
||||
args[argc] = function(...)
|
||||
cb(true, ...)
|
||||
end
|
||||
xpcall(func, function(err)
|
||||
cb(false, err, debug.traceback())
|
||||
end, unpack(args, 1, argc))
|
||||
end
|
||||
-- Always run the wrapped functions in xpcall and re-raise the error in the
|
||||
-- coroutine. This makes pcall work as normal.
|
||||
local function pfunc(...)
|
||||
local args = { ... }
|
||||
local cb = args[argc]
|
||||
args[argc] = function(...)
|
||||
cb(true, ...)
|
||||
end
|
||||
xpcall(func, function(err)
|
||||
cb(false, err, debug.traceback())
|
||||
end, unpack(args, 1, argc))
|
||||
end
|
||||
|
||||
local ret = { coroutine.yield(argc, pfunc, ...) }
|
||||
local ret = { coroutine.yield(argc, pfunc, ...) }
|
||||
|
||||
local ok = ret[1]
|
||||
if not ok then
|
||||
local _, err, traceback = unpack(ret)
|
||||
error(string.format("Wrapped function failed: %s\n%s", err, traceback))
|
||||
end
|
||||
local ok = ret[1]
|
||||
if not ok then
|
||||
local _, err, traceback = unpack(ret)
|
||||
error(string.format('Wrapped function failed: %s\n%s', err, traceback))
|
||||
end
|
||||
|
||||
return unpack(ret, 2, maxn(ret))
|
||||
return unpack(ret, 2, table.maxn(ret))
|
||||
end
|
||||
|
||||
---Creates an async function with a callback style function.
|
||||
|
@ -160,13 +131,13 @@ end
|
|||
---@param argc number: The number of arguments of func. Must be included.
|
||||
---@return function: Returns an async function
|
||||
function M.wrap(func, argc)
|
||||
assert(argc)
|
||||
return function(...)
|
||||
if not M.running() then
|
||||
return func(...)
|
||||
end
|
||||
return M.wait(argc, func, ...)
|
||||
end
|
||||
assert(argc)
|
||||
return function(...)
|
||||
if not M.running() then
|
||||
return func(...)
|
||||
end
|
||||
return M.wait(argc, func, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---Use this to create a function which executes in an async context but
|
||||
|
@ -174,14 +145,14 @@ end
|
|||
---since it is non-blocking
|
||||
---@param func function
|
||||
function M.create(func, argc)
|
||||
argc = argc or 0
|
||||
return function(...)
|
||||
if M.running() then
|
||||
return func(...)
|
||||
end
|
||||
local callback = select(argc + 1, ...)
|
||||
return run(func, callback, unpack({ ... }, 1, argc))
|
||||
end
|
||||
argc = argc or 0
|
||||
return function(...)
|
||||
if M.running() then
|
||||
return func(...)
|
||||
end
|
||||
local callback = select(argc + 1, ...)
|
||||
return run(func, callback, unpack({ ... }, 1, argc))
|
||||
end
|
||||
end
|
||||
|
||||
---Use this to create a function which executes in an async context but
|
||||
|
@ -189,12 +160,12 @@ end
|
|||
---since it is non-blocking
|
||||
---@param func function
|
||||
function M.void(func)
|
||||
return function(...)
|
||||
if M.running() then
|
||||
return func(...)
|
||||
end
|
||||
return run(func, nil, ...)
|
||||
end
|
||||
return function(...)
|
||||
if M.running() then
|
||||
return func(...)
|
||||
end
|
||||
return run(func, nil, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---An async function that when called will yield to the Neovim scheduler to be
|
||||
|
|
551
lua/gitsigns/attach.lua
generated
551
lua/gitsigns/attach.lua
generated
|
@ -1,7 +1,7 @@
|
|||
local async = require('gitsigns.async')
|
||||
local git = require('gitsigns.git')
|
||||
|
||||
local log = require("gitsigns.debug.log")
|
||||
local log = require('gitsigns.debug.log')
|
||||
local dprintf = log.dprintf
|
||||
local dprint = log.dprint
|
||||
|
||||
|
@ -11,7 +11,7 @@ local hl = require('gitsigns.highlight')
|
|||
local gs_cache = require('gitsigns.cache')
|
||||
local cache = gs_cache.cache
|
||||
local CacheEntry = gs_cache.CacheEntry
|
||||
local Status = require("gitsigns.status")
|
||||
local Status = require('gitsigns.status')
|
||||
|
||||
local gs_config = require('gitsigns.config')
|
||||
local config = gs_config.config
|
||||
|
@ -19,393 +19,376 @@ local config = gs_config.config
|
|||
local void = require('gitsigns.async').void
|
||||
local util = require('gitsigns.util')
|
||||
|
||||
local throttle_by_id = require("gitsigns.debounce").throttle_by_id
|
||||
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
|
||||
|
||||
local api = vim.api
|
||||
local uv = vim.loop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local vimgrep_running = false
|
||||
|
||||
-- @return (string, string) Tuple of buffer name and commit
|
||||
local function parse_fugitive_uri(name)
|
||||
if vim.fn.exists('*FugitiveReal') == 0 then
|
||||
dprint("Fugitive not installed")
|
||||
return
|
||||
end
|
||||
if vim.fn.exists('*FugitiveReal') == 0 then
|
||||
dprint('Fugitive not installed')
|
||||
return
|
||||
end
|
||||
|
||||
local path = vim.fn.FugitiveReal(name)
|
||||
local commit = vim.fn.FugitiveParse(name)[1]:match('([^:]+):.*')
|
||||
if commit == '0' then
|
||||
-- '0' means the index so clear commit so we attach normally
|
||||
commit = nil
|
||||
end
|
||||
return path, commit
|
||||
local path = vim.fn.FugitiveReal(name)
|
||||
local commit = vim.fn.FugitiveParse(name)[1]:match('([^:]+):.*')
|
||||
if commit == '0' then
|
||||
-- '0' means the index so clear commit so we attach normally
|
||||
commit = nil
|
||||
end
|
||||
return path, commit
|
||||
end
|
||||
|
||||
local function parse_gitsigns_uri(name)
|
||||
-- TODO(lewis6991): Support submodules
|
||||
local _, _, root_path, commit, rel_path =
|
||||
name:find([[^gitsigns://(.*)/%.git/(.*):(.*)]])
|
||||
if commit == ':0' then
|
||||
-- ':0' means the index so clear commit so we attach normally
|
||||
commit = nil
|
||||
end
|
||||
if root_path then
|
||||
name = root_path .. '/' .. rel_path
|
||||
end
|
||||
return name, commit
|
||||
-- TODO(lewis6991): Support submodules
|
||||
local _, _, root_path, commit, rel_path = name:find([[^gitsigns://(.*)/%.git/(.*):(.*)]])
|
||||
if commit == ':0' then
|
||||
-- ':0' means the index so clear commit so we attach normally
|
||||
commit = nil
|
||||
end
|
||||
if root_path then
|
||||
name = root_path .. '/' .. rel_path
|
||||
end
|
||||
return name, commit
|
||||
end
|
||||
|
||||
local function get_buf_path(bufnr)
|
||||
local file =
|
||||
uv.fs_realpath(api.nvim_buf_get_name(bufnr)) or
|
||||
|
||||
api.nvim_buf_call(bufnr, function()
|
||||
local file = uv.fs_realpath(api.nvim_buf_get_name(bufnr))
|
||||
or api.nvim_buf_call(bufnr, function()
|
||||
return vim.fn.expand('%:p')
|
||||
end)
|
||||
end)
|
||||
|
||||
if not vim.wo.diff then
|
||||
if vim.startswith(file, 'fugitive://') then
|
||||
local path, commit = parse_fugitive_uri(file)
|
||||
dprintf("Fugitive buffer for file '%s' from path '%s'", path, file)
|
||||
path = uv.fs_realpath(path)
|
||||
if path then
|
||||
return path, commit
|
||||
end
|
||||
if not vim.wo.diff then
|
||||
if vim.startswith(file, 'fugitive://') then
|
||||
local path, commit = parse_fugitive_uri(file)
|
||||
dprintf("Fugitive buffer for file '%s' from path '%s'", path, file)
|
||||
path = uv.fs_realpath(path)
|
||||
if path then
|
||||
return path, commit
|
||||
end
|
||||
end
|
||||
|
||||
if vim.startswith(file, 'gitsigns://') then
|
||||
local path, commit = parse_gitsigns_uri(file)
|
||||
dprintf("Gitsigns buffer for file '%s' from path '%s'", path, file)
|
||||
path = uv.fs_realpath(path)
|
||||
if path then
|
||||
return path, commit
|
||||
end
|
||||
if vim.startswith(file, 'gitsigns://') then
|
||||
local path, commit = parse_gitsigns_uri(file)
|
||||
dprintf("Gitsigns buffer for file '%s' from path '%s'", path, file)
|
||||
path = uv.fs_realpath(path)
|
||||
if path then
|
||||
return path, commit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return file
|
||||
return file
|
||||
end
|
||||
|
||||
local function on_lines(_, bufnr, _, first, last_orig, last_new, byte_count)
|
||||
if first == last_orig and last_orig == last_new and byte_count == 0 then
|
||||
-- on_lines can be called twice for undo events; ignore the second
|
||||
-- call which indicates no changes.
|
||||
return
|
||||
end
|
||||
return manager.on_lines(bufnr, first, last_orig, last_new)
|
||||
if first == last_orig and last_orig == last_new and byte_count == 0 then
|
||||
-- on_lines can be called twice for undo events; ignore the second
|
||||
-- call which indicates no changes.
|
||||
return
|
||||
end
|
||||
return manager.on_lines(bufnr, first, last_orig, last_new)
|
||||
end
|
||||
|
||||
local function on_reload(_, bufnr)
|
||||
local __FUNC__ = 'on_reload'
|
||||
dprint('Reload')
|
||||
manager.update_debounced(bufnr)
|
||||
local __FUNC__ = 'on_reload'
|
||||
dprint('Reload')
|
||||
manager.update_debounced(bufnr)
|
||||
end
|
||||
|
||||
local function on_detach(_, bufnr)
|
||||
M.detach(bufnr, true)
|
||||
M.detach(bufnr, true)
|
||||
end
|
||||
|
||||
local function on_attach_pre(bufnr)
|
||||
local gitdir, toplevel
|
||||
if config._on_attach_pre then
|
||||
local res = async.wrap(config._on_attach_pre, 2)(bufnr)
|
||||
dprintf('ran on_attach_pre with result %s', vim.inspect(res))
|
||||
if type(res) == "table" then
|
||||
if type(res.gitdir) == 'string' then
|
||||
gitdir = res.gitdir
|
||||
end
|
||||
if type(res.toplevel) == 'string' then
|
||||
toplevel = res.toplevel
|
||||
end
|
||||
local gitdir, toplevel
|
||||
if config._on_attach_pre then
|
||||
local res = async.wrap(config._on_attach_pre, 2)(bufnr)
|
||||
dprintf('ran on_attach_pre with result %s', vim.inspect(res))
|
||||
if type(res) == 'table' then
|
||||
if type(res.gitdir) == 'string' then
|
||||
gitdir = res.gitdir
|
||||
end
|
||||
end
|
||||
return gitdir, toplevel
|
||||
if type(res.toplevel) == 'string' then
|
||||
toplevel = res.toplevel
|
||||
end
|
||||
end
|
||||
end
|
||||
return gitdir, toplevel
|
||||
end
|
||||
|
||||
local function try_worktrees(_bufnr, file, encoding)
|
||||
if not config.worktrees then
|
||||
return
|
||||
end
|
||||
if not config.worktrees then
|
||||
return
|
||||
end
|
||||
|
||||
for _, wt in ipairs(config.worktrees) do
|
||||
local git_obj = git.Obj.new(file, encoding, wt.gitdir, wt.toplevel)
|
||||
if git_obj and git_obj.object_name then
|
||||
dprintf('Using worktree %s', vim.inspect(wt))
|
||||
return git_obj
|
||||
end
|
||||
end
|
||||
for _, wt in ipairs(config.worktrees) do
|
||||
local git_obj = git.Obj.new(file, encoding, wt.gitdir, wt.toplevel)
|
||||
if git_obj and git_obj.object_name then
|
||||
dprintf('Using worktree %s', vim.inspect(wt))
|
||||
return git_obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local done_setup = false
|
||||
|
||||
function M._setup()
|
||||
if done_setup then
|
||||
return
|
||||
end
|
||||
if done_setup then
|
||||
return
|
||||
end
|
||||
|
||||
done_setup = true
|
||||
done_setup = true
|
||||
|
||||
manager.setup()
|
||||
manager.setup()
|
||||
|
||||
hl.setup_highlights()
|
||||
api.nvim_create_autocmd('ColorScheme', {
|
||||
group = 'gitsigns',
|
||||
callback = hl.setup_highlights,
|
||||
})
|
||||
hl.setup_highlights()
|
||||
api.nvim_create_autocmd('ColorScheme', {
|
||||
group = 'gitsigns',
|
||||
callback = hl.setup_highlights,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('OptionSet', {
|
||||
group = 'gitsigns',
|
||||
pattern = 'fileformat',
|
||||
callback = function()
|
||||
require('gitsigns.actions').refresh()
|
||||
end, })
|
||||
api.nvim_create_autocmd('OptionSet', {
|
||||
group = 'gitsigns',
|
||||
pattern = 'fileformat',
|
||||
callback = function()
|
||||
require('gitsigns.actions').refresh()
|
||||
end,
|
||||
})
|
||||
|
||||
-- vimpgrep creates and deletes lots of buffers so attaching to each one will
|
||||
-- waste lots of resource and even slow down vimgrep.
|
||||
api.nvim_create_autocmd('QuickFixCmdPre', {
|
||||
group = 'gitsigns',
|
||||
pattern = '*vimgrep*',
|
||||
callback = function()
|
||||
vimgrep_running = true
|
||||
end,
|
||||
})
|
||||
|
||||
-- vimpgrep creates and deletes lots of buffers so attaching to each one will
|
||||
-- waste lots of resource and even slow down vimgrep.
|
||||
api.nvim_create_autocmd('QuickFixCmdPre', {
|
||||
group = 'gitsigns',
|
||||
pattern = '*vimgrep*',
|
||||
callback = function()
|
||||
vimgrep_running = true
|
||||
end,
|
||||
})
|
||||
api.nvim_create_autocmd('QuickFixCmdPost', {
|
||||
group = 'gitsigns',
|
||||
pattern = '*vimgrep*',
|
||||
callback = function()
|
||||
vimgrep_running = false
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('QuickFixCmdPost', {
|
||||
group = 'gitsigns',
|
||||
pattern = '*vimgrep*',
|
||||
callback = function()
|
||||
vimgrep_running = false
|
||||
end,
|
||||
})
|
||||
|
||||
require('gitsigns.current_line_blame').setup()
|
||||
|
||||
api.nvim_create_autocmd('VimLeavePre', {
|
||||
group = 'gitsigns',
|
||||
callback = M.detach_all,
|
||||
})
|
||||
require('gitsigns.current_line_blame').setup()
|
||||
|
||||
api.nvim_create_autocmd('VimLeavePre', {
|
||||
group = 'gitsigns',
|
||||
callback = M.detach_all,
|
||||
})
|
||||
end
|
||||
|
||||
-- Ensure attaches cannot be interleaved.
|
||||
-- Since attaches are asynchronous we need to make sure an attach isn't
|
||||
-- performed whilst another one is in progress.
|
||||
local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd)
|
||||
local __FUNC__ = 'attach'
|
||||
local __FUNC__ = 'attach'
|
||||
|
||||
M._setup()
|
||||
M._setup()
|
||||
|
||||
if vimgrep_running then
|
||||
dprint('attaching is disabled')
|
||||
if vimgrep_running then
|
||||
dprint('attaching is disabled')
|
||||
return
|
||||
end
|
||||
|
||||
if cache[cbuf] then
|
||||
dprint('Already attached')
|
||||
return
|
||||
end
|
||||
|
||||
if aucmd then
|
||||
dprintf('Attaching (trigger=%s)', aucmd)
|
||||
else
|
||||
dprint('Attaching')
|
||||
end
|
||||
|
||||
if not api.nvim_buf_is_loaded(cbuf) then
|
||||
dprint('Non-loaded buffer')
|
||||
return
|
||||
end
|
||||
|
||||
local encoding = vim.bo[cbuf].fileencoding
|
||||
if encoding == '' then
|
||||
encoding = 'utf-8'
|
||||
end
|
||||
local file
|
||||
local commit
|
||||
local gitdir_oap
|
||||
local toplevel_oap
|
||||
|
||||
if ctx then
|
||||
gitdir_oap = ctx.gitdir
|
||||
toplevel_oap = ctx.toplevel
|
||||
file = ctx.toplevel .. util.path_sep .. ctx.file
|
||||
commit = ctx.commit
|
||||
else
|
||||
if api.nvim_buf_line_count(cbuf) > config.max_file_length then
|
||||
dprint('Exceeds max_file_length')
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if cache[cbuf] then
|
||||
dprint('Already attached')
|
||||
if vim.bo[cbuf].buftype ~= '' then
|
||||
dprint('Non-normal buffer')
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if aucmd then
|
||||
dprintf('Attaching (trigger=%s)', aucmd)
|
||||
else
|
||||
dprint('Attaching')
|
||||
end
|
||||
file, commit = get_buf_path(cbuf)
|
||||
local file_dir = util.dirname(file)
|
||||
|
||||
if not api.nvim_buf_is_loaded(cbuf) then
|
||||
dprint('Non-loaded buffer')
|
||||
if not file_dir or not util.path_exists(file_dir) then
|
||||
dprint('Not a path')
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local encoding = vim.bo[cbuf].fileencoding
|
||||
if encoding == '' then
|
||||
encoding = 'utf-8'
|
||||
end
|
||||
local file
|
||||
local commit
|
||||
local gitdir_oap
|
||||
local toplevel_oap
|
||||
gitdir_oap, toplevel_oap = on_attach_pre(cbuf)
|
||||
end
|
||||
|
||||
if ctx then
|
||||
gitdir_oap = ctx.gitdir
|
||||
toplevel_oap = ctx.toplevel
|
||||
file = ctx.toplevel .. util.path_sep .. ctx.file
|
||||
commit = ctx.commit
|
||||
else
|
||||
if api.nvim_buf_line_count(cbuf) > config.max_file_length then
|
||||
dprint('Exceeds max_file_length')
|
||||
return
|
||||
end
|
||||
local git_obj = git.Obj.new(file, encoding, gitdir_oap, toplevel_oap)
|
||||
|
||||
if vim.bo[cbuf].buftype ~= '' then
|
||||
dprint('Non-normal buffer')
|
||||
return
|
||||
end
|
||||
if not git_obj and not ctx then
|
||||
git_obj = try_worktrees(cbuf, file, encoding)
|
||||
async.scheduler()
|
||||
end
|
||||
|
||||
file, commit = get_buf_path(cbuf)
|
||||
local file_dir = util.dirname(file)
|
||||
if not git_obj then
|
||||
dprint('Empty git obj')
|
||||
return
|
||||
end
|
||||
local repo = git_obj.repo
|
||||
|
||||
if not file_dir or not util.path_exists(file_dir) then
|
||||
dprint('Not a path')
|
||||
return
|
||||
end
|
||||
async.scheduler()
|
||||
Status:update(cbuf, {
|
||||
head = repo.abbrev_head,
|
||||
root = repo.toplevel,
|
||||
gitdir = repo.gitdir,
|
||||
})
|
||||
|
||||
gitdir_oap, toplevel_oap = on_attach_pre(cbuf)
|
||||
end
|
||||
if vim.startswith(file, repo.gitdir .. util.path_sep) then
|
||||
dprint('In non-standard git dir')
|
||||
return
|
||||
end
|
||||
|
||||
local git_obj = git.Obj.new(file, encoding, gitdir_oap, toplevel_oap)
|
||||
if not ctx and (not util.path_exists(file) or uv.fs_stat(file).type == 'directory') then
|
||||
dprint('Not a file')
|
||||
return
|
||||
end
|
||||
|
||||
if not git_obj and not ctx then
|
||||
git_obj = try_worktrees(cbuf, file, encoding)
|
||||
async.scheduler()
|
||||
end
|
||||
if not git_obj.relpath then
|
||||
dprint('Cannot resolve file in repo')
|
||||
return
|
||||
end
|
||||
|
||||
if not git_obj then
|
||||
dprint('Empty git obj')
|
||||
return
|
||||
end
|
||||
local repo = git_obj.repo
|
||||
if not config.attach_to_untracked and git_obj.object_name == nil then
|
||||
dprint('File is untracked')
|
||||
return
|
||||
end
|
||||
|
||||
async.scheduler()
|
||||
Status:update(cbuf, {
|
||||
head = repo.abbrev_head,
|
||||
root = repo.toplevel,
|
||||
gitdir = repo.gitdir,
|
||||
})
|
||||
-- On windows os.tmpname() crashes in callback threads so initialise this
|
||||
-- variable on the main thread.
|
||||
async.scheduler()
|
||||
|
||||
if vim.startswith(file, repo.gitdir .. util.path_sep) then
|
||||
dprint('In non-standard git dir')
|
||||
return
|
||||
end
|
||||
if config.on_attach and config.on_attach(cbuf) == false then
|
||||
dprint('User on_attach() returned false')
|
||||
return
|
||||
end
|
||||
|
||||
if not ctx and (not util.path_exists(file) or uv.fs_stat(file).type == 'directory') then
|
||||
dprint('Not a file')
|
||||
return
|
||||
end
|
||||
cache[cbuf] = CacheEntry.new({
|
||||
base = ctx and ctx.base or config.base,
|
||||
file = file,
|
||||
commit = commit,
|
||||
gitdir_watcher = manager.watch_gitdir(cbuf, repo.gitdir),
|
||||
git_obj = git_obj,
|
||||
})
|
||||
|
||||
if not git_obj.relpath then
|
||||
dprint('Cannot resolve file in repo')
|
||||
return
|
||||
end
|
||||
if not api.nvim_buf_is_loaded(cbuf) then
|
||||
dprint('Un-loaded buffer')
|
||||
return
|
||||
end
|
||||
|
||||
if not config.attach_to_untracked and git_obj.object_name == nil then
|
||||
dprint('File is untracked')
|
||||
return
|
||||
end
|
||||
-- Make sure to attach before the first update (which is async) so we pick up
|
||||
-- changes from BufReadCmd.
|
||||
api.nvim_buf_attach(cbuf, false, {
|
||||
on_lines = on_lines,
|
||||
on_reload = on_reload,
|
||||
on_detach = on_detach,
|
||||
})
|
||||
|
||||
-- On windows os.tmpname() crashes in callback threads so initialise this
|
||||
-- variable on the main thread.
|
||||
async.scheduler()
|
||||
-- Initial update
|
||||
manager.update(cbuf, cache[cbuf])
|
||||
|
||||
if config.on_attach and config.on_attach(cbuf) == false then
|
||||
dprint('User on_attach() returned false')
|
||||
return
|
||||
end
|
||||
|
||||
cache[cbuf] = CacheEntry.new({
|
||||
base = ctx and ctx.base or config.base,
|
||||
file = file,
|
||||
commit = commit,
|
||||
gitdir_watcher = manager.watch_gitdir(cbuf, repo.gitdir),
|
||||
git_obj = git_obj,
|
||||
})
|
||||
|
||||
if not api.nvim_buf_is_loaded(cbuf) then
|
||||
dprint('Un-loaded buffer')
|
||||
return
|
||||
end
|
||||
|
||||
-- Make sure to attach before the first update (which is async) so we pick up
|
||||
-- changes from BufReadCmd.
|
||||
api.nvim_buf_attach(cbuf, false, {
|
||||
on_lines = on_lines,
|
||||
on_reload = on_reload,
|
||||
on_detach = on_detach,
|
||||
})
|
||||
|
||||
-- Initial update
|
||||
manager.update(cbuf, cache[cbuf])
|
||||
|
||||
if config.keymaps and not vim.tbl_isempty(config.keymaps) then
|
||||
require('gitsigns.mappings')(config.keymaps, cbuf)
|
||||
end
|
||||
if config.keymaps and not vim.tbl_isempty(config.keymaps) then
|
||||
require('gitsigns.mappings')(config.keymaps, cbuf)
|
||||
end
|
||||
end)
|
||||
|
||||
--- Detach Gitsigns from all buffers it is attached to.
|
||||
function M.detach_all()
|
||||
for k, _ in pairs(cache) do
|
||||
M.detach(k)
|
||||
end
|
||||
for k, _ in pairs(cache) do
|
||||
M.detach(k)
|
||||
end
|
||||
end
|
||||
|
||||
--- Detach Gitsigns from the buffer {bufnr}. If {bufnr} is not
|
||||
--- provided then the current buffer is used.
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- {bufnr} (number): Buffer number
|
||||
--- {bufnr} (number): Buffer number
|
||||
function M.detach(bufnr, _keep_signs)
|
||||
-- When this is called interactively (with no arguments) we want to remove all
|
||||
-- the signs, however if called via a detach event (due to nvim_buf_attach)
|
||||
-- then we don't want to clear the signs in case the buffer is just being
|
||||
-- updated due to the file externally changing. When this happens a detach and
|
||||
-- attach event happen in sequence and so we keep the old signs to stop the
|
||||
-- sign column width moving about between updates.
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
dprint('Detached')
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
dprint('Cache was nil')
|
||||
return
|
||||
end
|
||||
-- When this is called interactively (with no arguments) we want to remove all
|
||||
-- the signs, however if called via a detach event (due to nvim_buf_attach)
|
||||
-- then we don't want to clear the signs in case the buffer is just being
|
||||
-- updated due to the file externally changing. When this happens a detach and
|
||||
-- attach event happen in sequence and so we keep the old signs to stop the
|
||||
-- sign column width moving about between updates.
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
dprint('Detached')
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
dprint('Cache was nil')
|
||||
return
|
||||
end
|
||||
|
||||
manager.detach(bufnr, _keep_signs)
|
||||
manager.detach(bufnr, _keep_signs)
|
||||
|
||||
-- Clear status variables
|
||||
Status:clear(bufnr)
|
||||
-- Clear status variables
|
||||
Status:clear(bufnr)
|
||||
|
||||
cache:destroy(bufnr)
|
||||
cache:destroy(bufnr)
|
||||
end
|
||||
|
||||
|
||||
--- Attach Gitsigns to the buffer.
|
||||
---
|
||||
--- Attributes: ~
|
||||
--- {async}
|
||||
--- {async}
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- {bufnr} (number): Buffer number
|
||||
--- {ctx} (table|nil):
|
||||
--- Git context data that may optionally be used to attach to any
|
||||
--- buffer that represents a real git object.
|
||||
--- • {file}: (string)
|
||||
--- Path to the file represented by the buffer, relative to the
|
||||
--- top-level.
|
||||
--- • {toplevel}: (string)
|
||||
--- Path to the top-level of the parent git repository.
|
||||
--- • {gitdir}: (string)
|
||||
--- Path to the git directory of the parent git repository
|
||||
--- (typically the ".git/" directory).
|
||||
--- • {commit}: (string)
|
||||
--- The git revision that the file belongs to.
|
||||
--- • {base}: (string|nil)
|
||||
--- The git revision that the file should be compared to.
|
||||
--- {bufnr} (number): Buffer number
|
||||
--- {ctx} (table|nil):
|
||||
--- Git context data that may optionally be used to attach to any
|
||||
--- buffer that represents a real git object.
|
||||
--- • {file}: (string)
|
||||
--- Path to the file represented by the buffer, relative to the
|
||||
--- top-level.
|
||||
--- • {toplevel}: (string)
|
||||
--- Path to the top-level of the parent git repository.
|
||||
--- • {gitdir}: (string)
|
||||
--- Path to the git directory of the parent git repository
|
||||
--- (typically the ".git/" directory).
|
||||
--- • {commit}: (string)
|
||||
--- The git revision that the file belongs to.
|
||||
--- • {base}: (string|nil)
|
||||
--- The git revision that the file should be compared to.
|
||||
M.attach = void(function(bufnr, ctx, _trigger)
|
||||
attach_throttled(bufnr or api.nvim_get_current_buf(), ctx, _trigger)
|
||||
attach_throttled(bufnr or api.nvim_get_current_buf(), ctx, _trigger)
|
||||
end)
|
||||
|
||||
return M
|
||||
|
|
100
lua/gitsigns/cache.lua
generated
100
lua/gitsigns/cache.lua
generated
|
@ -1,101 +1,67 @@
|
|||
local Hunk = require("gitsigns.hunks").Hunk
|
||||
local Hunk = require('gitsigns.hunks').Hunk
|
||||
local GitObj = require('gitsigns.git').Obj
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
local M = {CacheEntry = {}, CacheObj = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Timer object watching the gitdir
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = { CacheEntry = {}, CacheObj = {} }
|
||||
|
||||
-- Timer object watching the gitdir
|
||||
|
||||
local CacheEntry = M.CacheEntry
|
||||
|
||||
function CacheEntry:get_compare_rev(base)
|
||||
base = base or self.base
|
||||
if base then
|
||||
return base
|
||||
end
|
||||
base = base or self.base
|
||||
if base then
|
||||
return base
|
||||
end
|
||||
|
||||
if self.commit then
|
||||
-- Buffer is a fugitive commit so compare against the parent of the commit
|
||||
if config._signs_staged_enable then
|
||||
return self.commit
|
||||
else
|
||||
return string.format('%s^', self.commit)
|
||||
end
|
||||
end
|
||||
if self.commit then
|
||||
-- Buffer is a fugitive commit so compare against the parent of the commit
|
||||
if config._signs_staged_enable then
|
||||
return self.commit
|
||||
else
|
||||
return string.format('%s^', self.commit)
|
||||
end
|
||||
end
|
||||
|
||||
local stage = self.git_obj.has_conflicts and 1 or 0
|
||||
return string.format(':%d', stage)
|
||||
local stage = self.git_obj.has_conflicts and 1 or 0
|
||||
return string.format(':%d', stage)
|
||||
end
|
||||
|
||||
function CacheEntry:get_staged_compare_rev()
|
||||
return self.commit and string.format('%s^', self.commit) or 'HEAD'
|
||||
return self.commit and string.format('%s^', self.commit) or 'HEAD'
|
||||
end
|
||||
|
||||
function CacheEntry:get_rev_bufname(rev)
|
||||
rev = rev or self:get_compare_rev()
|
||||
return string.format(
|
||||
'gitsigns://%s/%s:%s',
|
||||
self.git_obj.repo.gitdir,
|
||||
rev,
|
||||
self.git_obj.relpath)
|
||||
|
||||
rev = rev or self:get_compare_rev()
|
||||
return string.format('gitsigns://%s/%s:%s', self.git_obj.repo.gitdir, rev, self.git_obj.relpath)
|
||||
end
|
||||
|
||||
function CacheEntry:invalidate()
|
||||
self.compare_text = nil
|
||||
self.compare_text_head = nil
|
||||
self.hunks = nil
|
||||
self.hunks_staged = nil
|
||||
self.compare_text = nil
|
||||
self.compare_text_head = nil
|
||||
self.hunks = nil
|
||||
self.hunks_staged = nil
|
||||
end
|
||||
|
||||
function CacheEntry.new(o)
|
||||
o.staged_diffs = o.staged_diffs or {}
|
||||
return setmetatable(o, { __index = CacheEntry })
|
||||
o.staged_diffs = o.staged_diffs or {}
|
||||
return setmetatable(o, { __index = CacheEntry })
|
||||
end
|
||||
|
||||
function CacheEntry:destroy()
|
||||
local w = self.gitdir_watcher
|
||||
if w and not w:is_closing() then
|
||||
w:close()
|
||||
end
|
||||
local w = self.gitdir_watcher
|
||||
if w and not w:is_closing() then
|
||||
w:close()
|
||||
end
|
||||
end
|
||||
|
||||
function M.CacheObj:destroy(bufnr)
|
||||
self[bufnr]:destroy()
|
||||
self[bufnr] = nil
|
||||
self[bufnr]:destroy()
|
||||
self[bufnr] = nil
|
||||
end
|
||||
|
||||
M.cache = setmetatable({}, {
|
||||
__index = M.CacheObj,
|
||||
__index = M.CacheObj,
|
||||
})
|
||||
|
||||
return M
|
||||
|
|
126
lua/gitsigns/cli.lua
generated
126
lua/gitsigns/cli.lua
generated
|
@ -12,9 +12,9 @@ local attach = require('gitsigns.attach')
|
|||
local gs_debug = require('gitsigns.debug')
|
||||
|
||||
local sources = {
|
||||
[actions] = true,
|
||||
[attach] = false,
|
||||
[gs_debug] = false,
|
||||
[actions] = true,
|
||||
[attach] = false,
|
||||
[gs_debug] = false,
|
||||
}
|
||||
|
||||
-- try to parse each argument as a lua boolean, nil or number, if fails then
|
||||
|
@ -25,85 +25,87 @@ local sources = {
|
|||
-- '100' -> 100
|
||||
-- 'HEAD~300' -> 'HEAD~300'
|
||||
local function parse_to_lua(a)
|
||||
if tonumber(a) then
|
||||
return tonumber(a)
|
||||
elseif a == 'false' or a == 'true' then
|
||||
return a == 'true'
|
||||
elseif a == 'nil' then
|
||||
return nil
|
||||
end
|
||||
return a
|
||||
if tonumber(a) then
|
||||
return tonumber(a)
|
||||
elseif a == 'false' or a == 'true' then
|
||||
return a == 'true'
|
||||
elseif a == 'nil' then
|
||||
return nil
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
function M.complete(arglead, line)
|
||||
local words = vim.split(line, '%s+')
|
||||
local n = #words
|
||||
local words = vim.split(line, '%s+')
|
||||
local n = #words
|
||||
|
||||
local matches = {}
|
||||
if n == 2 then
|
||||
for m, _ in pairs(sources) do
|
||||
for func, _ in pairs(m) do
|
||||
if not func:match('^[a-z]') then
|
||||
-- exclude
|
||||
elseif vim.startswith(func, arglead) then
|
||||
table.insert(matches, func)
|
||||
end
|
||||
end
|
||||
local matches = {}
|
||||
if n == 2 then
|
||||
for m, _ in pairs(sources) do
|
||||
for func, _ in pairs(m) do
|
||||
if not func:match('^[a-z]') then
|
||||
-- exclude
|
||||
elseif vim.startswith(func, arglead) then
|
||||
table.insert(matches, func)
|
||||
end
|
||||
end
|
||||
elseif n > 2 then
|
||||
-- Subcommand completion
|
||||
local cmp_func = actions._get_cmp_func(words[2])
|
||||
if cmp_func then
|
||||
return cmp_func(arglead)
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
elseif n > 2 then
|
||||
-- Subcommand completion
|
||||
local cmp_func = actions._get_cmp_func(words[2])
|
||||
if cmp_func then
|
||||
return cmp_func(arglead)
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
local function print_nonnil(x)
|
||||
if x ~= nil then
|
||||
print(vim.inspect(x))
|
||||
end
|
||||
if x ~= nil then
|
||||
print(vim.inspect(x))
|
||||
end
|
||||
end
|
||||
|
||||
M.run = void(function(params)
|
||||
local __FUNC__ = 'cli.run'
|
||||
local pos_args_raw, named_args_raw = parse_args(params.args)
|
||||
local __FUNC__ = 'cli.run'
|
||||
local pos_args_raw, named_args_raw = parse_args(params.args)
|
||||
|
||||
local func = pos_args_raw[1]
|
||||
local func = pos_args_raw[1]
|
||||
|
||||
if not func then
|
||||
func = async.wrap(vim.ui.select, 3)(M.complete('', 'Gitsigns '), {})
|
||||
end
|
||||
if not func then
|
||||
func = async.wrap(vim.ui.select, 3)(M.complete('', 'Gitsigns '), {})
|
||||
end
|
||||
|
||||
local pos_args = vim.tbl_map(parse_to_lua, vim.list_slice(pos_args_raw, 2))
|
||||
local named_args = vim.tbl_map(parse_to_lua, named_args_raw)
|
||||
local args = vim.tbl_extend('error', pos_args, named_args)
|
||||
local pos_args = vim.tbl_map(parse_to_lua, vim.list_slice(pos_args_raw, 2))
|
||||
local named_args = vim.tbl_map(parse_to_lua, named_args_raw)
|
||||
local args = vim.tbl_extend('error', pos_args, named_args)
|
||||
|
||||
dprintf("Running action '%s' with arguments %s", func, vim.inspect(args, { newline = ' ', indent = '' }))
|
||||
dprintf(
|
||||
"Running action '%s' with arguments %s",
|
||||
func,
|
||||
vim.inspect(args, { newline = ' ', indent = '' })
|
||||
)
|
||||
|
||||
local cmd_func = actions._get_cmd_func(func)
|
||||
if cmd_func then
|
||||
-- Action has a specialised mapping function from command form to lua
|
||||
-- function
|
||||
print_nonnil(cmd_func(args, params))
|
||||
local cmd_func = actions._get_cmd_func(func)
|
||||
if cmd_func then
|
||||
-- Action has a specialised mapping function from command form to lua
|
||||
-- function
|
||||
print_nonnil(cmd_func(args, params))
|
||||
return
|
||||
end
|
||||
|
||||
for m, has_named in pairs(sources) do
|
||||
local f = (m)[func]
|
||||
if type(f) == 'function' then
|
||||
-- Note functions here do not have named arguments
|
||||
print_nonnil(f(unpack(pos_args), has_named and named_args or nil))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for m, has_named in pairs(sources) do
|
||||
local f = (m)[func]
|
||||
if type(f) == "function" then
|
||||
-- Note functions here do not have named arguments
|
||||
print_nonnil(f(unpack(pos_args), has_named and named_args or nil))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
message.error('%s is not a valid function or action', func)
|
||||
message.error('%s is not a valid function or action', func)
|
||||
end)
|
||||
|
||||
return M
|
||||
|
|
186
lua/gitsigns/cli/argparse.lua
generated
186
lua/gitsigns/cli/argparse.lua
generated
|
@ -1,115 +1,105 @@
|
|||
local M = {}
|
||||
|
||||
|
||||
|
||||
local function is_char(x)
|
||||
return x:match('[^=\'"%s]') ~= nil
|
||||
return x:match('[^=\'"%s]') ~= nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Return positional arguments and named arguments
|
||||
function M.parse_args(x)
|
||||
local pos_args, named_args = {}, {}
|
||||
local pos_args, named_args = {}, {}
|
||||
|
||||
local state = 'in_arg'
|
||||
local cur_arg = ''
|
||||
local cur_val = ''
|
||||
local cur_quote = ''
|
||||
local state = 'in_arg'
|
||||
local cur_arg = ''
|
||||
local cur_val = ''
|
||||
local cur_quote = ''
|
||||
|
||||
local function peek(idx)
|
||||
return x:sub(idx + 1, idx + 1)
|
||||
end
|
||||
local function peek(idx)
|
||||
return x:sub(idx + 1, idx + 1)
|
||||
end
|
||||
|
||||
local i = 1
|
||||
while i <= #x do
|
||||
local ch = x:sub(i, i)
|
||||
-- dprintf('L(%d)(%s): cur_arg="%s" ch="%s"', i, state, cur_arg, ch)
|
||||
local i = 1
|
||||
while i <= #x do
|
||||
local ch = x:sub(i, i)
|
||||
-- dprintf('L(%d)(%s): cur_arg="%s" ch="%s"', i, state, cur_arg, ch)
|
||||
|
||||
if state == 'in_arg' then
|
||||
if is_char(ch) then
|
||||
if ch == '-' and peek(i) == '-' then
|
||||
state = 'in_flag'
|
||||
cur_arg = ''
|
||||
i = i + 1
|
||||
else
|
||||
cur_arg = cur_arg .. ch
|
||||
end
|
||||
elseif ch:match('%s') then
|
||||
pos_args[#pos_args + 1] = cur_arg
|
||||
state = 'in_ws'
|
||||
elseif ch == '=' then
|
||||
cur_val = ''
|
||||
local next_ch = peek(i)
|
||||
if next_ch == "'" or next_ch == '"' then
|
||||
cur_quote = next_ch
|
||||
i = i + 1
|
||||
state = 'in_quote'
|
||||
else
|
||||
state = 'in_value'
|
||||
end
|
||||
end
|
||||
elseif state == 'in_flag' then
|
||||
if ch:match('%s') then
|
||||
named_args[cur_arg] = true
|
||||
state = 'in_ws'
|
||||
else
|
||||
cur_arg = cur_arg .. ch
|
||||
end
|
||||
elseif state == 'in_ws' then
|
||||
if is_char(ch) then
|
||||
if ch == '-' and peek(i) == '-' then
|
||||
state = 'in_flag'
|
||||
cur_arg = ''
|
||||
i = i + 1
|
||||
else
|
||||
state = 'in_arg'
|
||||
cur_arg = ch
|
||||
end
|
||||
end
|
||||
elseif state == 'in_value' then
|
||||
if is_char(ch) then
|
||||
cur_val = cur_val .. ch
|
||||
elseif ch:match('%s') then
|
||||
named_args[cur_arg] = cur_val
|
||||
cur_arg = ''
|
||||
state = 'in_ws'
|
||||
end
|
||||
elseif state == 'in_quote' then
|
||||
local next_ch = peek(i)
|
||||
if ch == "\\" and next_ch == cur_quote then
|
||||
cur_val = cur_val .. next_ch
|
||||
i = i + 1
|
||||
elseif ch == cur_quote then
|
||||
named_args[cur_arg] = cur_val
|
||||
state = 'in_ws'
|
||||
if next_ch ~= '' and not next_ch:match('%s') then
|
||||
error('malformed argument: ' .. next_ch)
|
||||
end
|
||||
else
|
||||
cur_val = cur_val .. ch
|
||||
end
|
||||
if state == 'in_arg' then
|
||||
if is_char(ch) then
|
||||
if ch == '-' and peek(i) == '-' then
|
||||
state = 'in_flag'
|
||||
cur_arg = ''
|
||||
i = i + 1
|
||||
else
|
||||
cur_arg = cur_arg .. ch
|
||||
end
|
||||
elseif ch:match('%s') then
|
||||
pos_args[#pos_args + 1] = cur_arg
|
||||
state = 'in_ws'
|
||||
elseif ch == '=' then
|
||||
cur_val = ''
|
||||
local next_ch = peek(i)
|
||||
if next_ch == "'" or next_ch == '"' then
|
||||
cur_quote = next_ch
|
||||
i = i + 1
|
||||
state = 'in_quote'
|
||||
else
|
||||
state = 'in_value'
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if #cur_arg > 0 then
|
||||
if state == 'in_arg' then
|
||||
pos_args[#pos_args + 1] = cur_arg
|
||||
elseif state == 'in_flag' then
|
||||
named_args[cur_arg] = true
|
||||
elseif state == 'in_value' then
|
||||
named_args[cur_arg] = cur_val
|
||||
elseif state == 'in_flag' then
|
||||
if ch:match('%s') then
|
||||
named_args[cur_arg] = true
|
||||
state = 'in_ws'
|
||||
else
|
||||
cur_arg = cur_arg .. ch
|
||||
end
|
||||
end
|
||||
elseif state == 'in_ws' then
|
||||
if is_char(ch) then
|
||||
if ch == '-' and peek(i) == '-' then
|
||||
state = 'in_flag'
|
||||
cur_arg = ''
|
||||
i = i + 1
|
||||
else
|
||||
state = 'in_arg'
|
||||
cur_arg = ch
|
||||
end
|
||||
end
|
||||
elseif state == 'in_value' then
|
||||
if is_char(ch) then
|
||||
cur_val = cur_val .. ch
|
||||
elseif ch:match('%s') then
|
||||
named_args[cur_arg] = cur_val
|
||||
cur_arg = ''
|
||||
state = 'in_ws'
|
||||
end
|
||||
elseif state == 'in_quote' then
|
||||
local next_ch = peek(i)
|
||||
if ch == '\\' and next_ch == cur_quote then
|
||||
cur_val = cur_val .. next_ch
|
||||
i = i + 1
|
||||
elseif ch == cur_quote then
|
||||
named_args[cur_arg] = cur_val
|
||||
state = 'in_ws'
|
||||
if next_ch ~= '' and not next_ch:match('%s') then
|
||||
error('malformed argument: ' .. next_ch)
|
||||
end
|
||||
else
|
||||
cur_val = cur_val .. ch
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return pos_args, named_args
|
||||
if #cur_arg > 0 then
|
||||
if state == 'in_arg' then
|
||||
pos_args[#pos_args + 1] = cur_arg
|
||||
elseif state == 'in_flag' then
|
||||
named_args[cur_arg] = true
|
||||
elseif state == 'in_value' then
|
||||
named_args[cur_arg] = cur_val
|
||||
end
|
||||
end
|
||||
|
||||
return pos_args, named_args
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
907
lua/gitsigns/config.lua
generated
907
lua/gitsigns/config.lua
generated
File diff suppressed because it is too large
Load diff
274
lua/gitsigns/current_line_blame.lua
generated
274
lua/gitsigns/current_line_blame.lua
generated
|
@ -19,196 +19,198 @@ local timer = uv.new_timer(true)
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
local wait_timer = wrap(vim.loop.timer_start, 4)
|
||||
|
||||
local function set_extmark(bufnr, row, opts)
|
||||
opts = opts or {}
|
||||
opts.id = 1
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, row - 1, 0, opts)
|
||||
opts = opts or {}
|
||||
opts.id = 1
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, row - 1, 0, opts)
|
||||
end
|
||||
|
||||
local function get_extmark(bufnr)
|
||||
local pos = api.nvim_buf_get_extmark_by_id(bufnr, namespace, 1, {})
|
||||
if pos[1] then
|
||||
return pos[1] + 1
|
||||
end
|
||||
return
|
||||
local pos = api.nvim_buf_get_extmark_by_id(bufnr, namespace, 1, {})
|
||||
if pos[1] then
|
||||
return pos[1] + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function reset(bufnr)
|
||||
bufnr = bufnr or current_buf()
|
||||
if not api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
api.nvim_buf_del_extmark(bufnr, namespace, 1)
|
||||
vim.b[bufnr].gitsigns_blame_line_dict = nil
|
||||
bufnr = bufnr or current_buf()
|
||||
if not api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
api.nvim_buf_del_extmark(bufnr, namespace, 1)
|
||||
vim.b[bufnr].gitsigns_blame_line_dict = nil
|
||||
end
|
||||
|
||||
-- TODO: expose as config
|
||||
local max_cache_size = 1000
|
||||
|
||||
local BlameCache = {Elem = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local BlameCache = { Elem = {} }
|
||||
|
||||
BlameCache.contents = {}
|
||||
|
||||
function BlameCache:add(bufnr, lnum, x)
|
||||
if not config._blame_cache then return end
|
||||
local scache = self.contents[bufnr]
|
||||
if scache.size <= max_cache_size then
|
||||
scache.cache[lnum] = x
|
||||
scache.size = scache.size + 1
|
||||
end
|
||||
if not config._blame_cache then
|
||||
return
|
||||
end
|
||||
local scache = self.contents[bufnr]
|
||||
if scache.size <= max_cache_size then
|
||||
scache.cache[lnum] = x
|
||||
scache.size = scache.size + 1
|
||||
end
|
||||
end
|
||||
|
||||
function BlameCache:get(bufnr, lnum)
|
||||
if not config._blame_cache then return end
|
||||
if not config._blame_cache then
|
||||
return
|
||||
end
|
||||
|
||||
-- init and invalidate
|
||||
local tick = vim.b[bufnr].changedtick
|
||||
if not self.contents[bufnr] or self.contents[bufnr].tick ~= tick then
|
||||
self.contents[bufnr] = { tick = tick, cache = {}, size = 0 }
|
||||
end
|
||||
-- init and invalidate
|
||||
local tick = vim.b[bufnr].changedtick
|
||||
if not self.contents[bufnr] or self.contents[bufnr].tick ~= tick then
|
||||
self.contents[bufnr] = { tick = tick, cache = {}, size = 0 }
|
||||
end
|
||||
|
||||
return self.contents[bufnr].cache[lnum]
|
||||
return self.contents[bufnr].cache[lnum]
|
||||
end
|
||||
|
||||
local function expand_blame_format(fmt, name, info)
|
||||
if info.author == name then
|
||||
info.author = 'You'
|
||||
end
|
||||
return util.expand_format(fmt, info, config.current_line_blame_formatter_opts.relative_time)
|
||||
if info.author == name then
|
||||
info.author = 'You'
|
||||
end
|
||||
return util.expand_format(fmt, info, config.current_line_blame_formatter_opts.relative_time)
|
||||
end
|
||||
|
||||
local function flatten_virt_text(virt_text)
|
||||
local res = {}
|
||||
for _, part in ipairs(virt_text) do
|
||||
res[#res + 1] = part[1]
|
||||
end
|
||||
return table.concat(res)
|
||||
local res = {}
|
||||
for _, part in ipairs(virt_text) do
|
||||
res[#res + 1] = part[1]
|
||||
end
|
||||
return table.concat(res)
|
||||
end
|
||||
|
||||
-- Update function, must be called in async context
|
||||
local update = void(function()
|
||||
local bufnr = current_buf()
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
local bufnr = current_buf()
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
|
||||
local old_lnum = get_extmark(bufnr)
|
||||
if old_lnum and lnum == old_lnum and BlameCache:get(bufnr, lnum) then
|
||||
-- Don't update if on the same line and we already have results
|
||||
return
|
||||
end
|
||||
local old_lnum = get_extmark(bufnr)
|
||||
if old_lnum and lnum == old_lnum and BlameCache:get(bufnr, lnum) then
|
||||
-- Don't update if on the same line and we already have results
|
||||
return
|
||||
end
|
||||
|
||||
if api.nvim_get_mode().mode == 'i' then
|
||||
reset(bufnr)
|
||||
return
|
||||
end
|
||||
if api.nvim_get_mode().mode == 'i' then
|
||||
reset(bufnr)
|
||||
return
|
||||
end
|
||||
|
||||
-- Set an empty extmark to save the line number.
|
||||
-- This will also clear virt_text.
|
||||
-- Only do this if there was already an extmark to avoid clearing the intro
|
||||
-- text.
|
||||
if get_extmark(bufnr) then
|
||||
reset(bufnr)
|
||||
set_extmark(bufnr, lnum)
|
||||
end
|
||||
-- Set an empty extmark to save the line number.
|
||||
-- This will also clear virt_text.
|
||||
-- Only do this if there was already an extmark to avoid clearing the intro
|
||||
-- text.
|
||||
if get_extmark(bufnr) then
|
||||
reset(bufnr)
|
||||
set_extmark(bufnr, lnum)
|
||||
end
|
||||
|
||||
-- Can't show extmarks on folded lines so skip
|
||||
if vim.fn.foldclosed(lnum) ~= -1 then
|
||||
return
|
||||
end
|
||||
-- Can't show extmarks on folded lines so skip
|
||||
if vim.fn.foldclosed(lnum) ~= -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local opts = config.current_line_blame_opts
|
||||
local opts = config.current_line_blame_opts
|
||||
|
||||
-- Note because the same timer is re-used, this call has a debouncing effect.
|
||||
wait_timer(timer, opts.delay, 0)
|
||||
scheduler()
|
||||
-- Note because the same timer is re-used, this call has a debouncing effect.
|
||||
wait_timer(timer, opts.delay, 0)
|
||||
scheduler()
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.git_obj.object_name then
|
||||
return
|
||||
end
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.git_obj.object_name then
|
||||
return
|
||||
end
|
||||
|
||||
local result = BlameCache:get(bufnr, lnum)
|
||||
if not result then
|
||||
local buftext = util.buf_lines(bufnr)
|
||||
result = bcache.git_obj:run_blame(buftext, lnum, opts.ignore_whitespace)
|
||||
BlameCache:add(bufnr, lnum, result)
|
||||
scheduler()
|
||||
end
|
||||
local result = BlameCache:get(bufnr, lnum)
|
||||
if not result then
|
||||
local buftext = util.buf_lines(bufnr)
|
||||
result = bcache.git_obj:run_blame(buftext, lnum, opts.ignore_whitespace)
|
||||
BlameCache:add(bufnr, lnum, result)
|
||||
scheduler()
|
||||
end
|
||||
|
||||
local lnum1 = api.nvim_win_get_cursor(0)[1]
|
||||
if bufnr == current_buf() and lnum ~= lnum1 then
|
||||
-- Cursor has moved during events; abort
|
||||
return
|
||||
end
|
||||
local lnum1 = api.nvim_win_get_cursor(0)[1]
|
||||
if bufnr == current_buf() and lnum ~= lnum1 then
|
||||
-- Cursor has moved during events; abort
|
||||
return
|
||||
end
|
||||
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
-- Buffer is no longer loaded; abort
|
||||
return
|
||||
end
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
-- Buffer is no longer loaded; abort
|
||||
return
|
||||
end
|
||||
|
||||
vim.b[bufnr].gitsigns_blame_line_dict = result
|
||||
vim.b[bufnr].gitsigns_blame_line_dict = result
|
||||
|
||||
if result then
|
||||
local virt_text
|
||||
local clb_formatter = result.author == 'Not Committed Yet' and
|
||||
config.current_line_blame_formatter_nc or
|
||||
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 -- function
|
||||
virt_text = clb_formatter(
|
||||
bcache.git_obj.repo.username,
|
||||
result,
|
||||
config.current_line_blame_formatter_opts)
|
||||
if result then
|
||||
local virt_text
|
||||
local clb_formatter = result.author == 'Not Committed Yet'
|
||||
and config.current_line_blame_formatter_nc
|
||||
or 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 -- function
|
||||
virt_text = clb_formatter(
|
||||
bcache.git_obj.repo.username,
|
||||
result,
|
||||
config.current_line_blame_formatter_opts
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
vim.b[bufnr].gitsigns_blame_line = flatten_virt_text(virt_text)
|
||||
|
||||
vim.b[bufnr].gitsigns_blame_line = flatten_virt_text(virt_text)
|
||||
|
||||
if opts.virt_text then
|
||||
set_extmark(bufnr, lnum, {
|
||||
virt_text = virt_text,
|
||||
virt_text_pos = opts.virt_text_pos,
|
||||
priority = opts.virt_text_priority,
|
||||
hl_mode = 'combine',
|
||||
})
|
||||
end
|
||||
end
|
||||
if opts.virt_text then
|
||||
set_extmark(bufnr, lnum, {
|
||||
virt_text = virt_text,
|
||||
virt_text_pos = opts.virt_text_pos,
|
||||
priority = opts.virt_text_priority,
|
||||
hl_mode = 'combine',
|
||||
})
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
M.setup = function()
|
||||
local group = api.nvim_create_augroup('gitsigns_blame', {})
|
||||
local group = api.nvim_create_augroup('gitsigns_blame', {})
|
||||
|
||||
for k, _ in pairs(cache) do
|
||||
reset(k)
|
||||
end
|
||||
for k, _ in pairs(cache) do
|
||||
reset(k)
|
||||
end
|
||||
|
||||
if config.current_line_blame then
|
||||
api.nvim_create_autocmd({ 'FocusGained', 'BufEnter', 'CursorMoved', 'CursorMovedI' }, {
|
||||
group = group, callback = function() update() end,
|
||||
})
|
||||
if config.current_line_blame then
|
||||
api.nvim_create_autocmd({ 'FocusGained', 'BufEnter', 'CursorMoved', 'CursorMovedI' }, {
|
||||
group = group,
|
||||
callback = function()
|
||||
update()
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd({ 'InsertEnter', 'FocusLost', 'BufLeave' }, {
|
||||
group = group, callback = function() reset() end,
|
||||
})
|
||||
api.nvim_create_autocmd({ 'InsertEnter', 'FocusLost', 'BufLeave' }, {
|
||||
group = group,
|
||||
callback = function()
|
||||
reset()
|
||||
end,
|
||||
})
|
||||
|
||||
-- Call via vim.schedule to avoid the debounce timer killing the async
|
||||
-- coroutine
|
||||
vim.schedule(update)
|
||||
end
|
||||
-- Call via vim.schedule to avoid the debounce timer killing the async
|
||||
-- coroutine
|
||||
vim.schedule(update)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
84
lua/gitsigns/debounce.lua
generated
84
lua/gitsigns/debounce.lua
generated
|
@ -2,9 +2,6 @@ local uv = require('gitsigns.uv')
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
--- Debounces a function on the trailing edge.
|
||||
---
|
||||
--- @generic F: function
|
||||
|
@ -12,17 +9,16 @@ local M = {}
|
|||
--- @param fn F Function to debounce
|
||||
--- @return F Debounced function.
|
||||
function M.debounce_trailing(ms, fn)
|
||||
local timer = uv.new_timer(true)
|
||||
return function(...)
|
||||
local argv = { ... }
|
||||
timer:start(ms, 0, function()
|
||||
timer:stop()
|
||||
fn(unpack(argv))
|
||||
end)
|
||||
end
|
||||
local timer = uv.new_timer(true)
|
||||
return function(...)
|
||||
local argv = { ... }
|
||||
timer:start(ms, 0, function()
|
||||
timer:stop()
|
||||
fn(unpack(argv))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Throttles a function on the leading edge.
|
||||
---
|
||||
--- @generic F: function
|
||||
|
@ -30,18 +26,18 @@ end
|
|||
--- @param fn F Function to throttle
|
||||
--- @return F throttled function.
|
||||
function M.throttle_leading(ms, fn)
|
||||
local timer = uv.new_timer(true)
|
||||
local running = false
|
||||
return function(...)
|
||||
if not running then
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
timer:stop()
|
||||
end)
|
||||
running = true
|
||||
fn(...)
|
||||
end
|
||||
end
|
||||
local timer = uv.new_timer(true)
|
||||
local running = false
|
||||
return function(...)
|
||||
if not running then
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
timer:stop()
|
||||
end)
|
||||
running = true
|
||||
fn(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Throttles a function using the first argument as an ID
|
||||
|
@ -61,26 +57,26 @@ end
|
|||
--- @param schedule boolean
|
||||
--- @return F throttled function.
|
||||
function M.throttle_by_id(fn, schedule)
|
||||
local scheduled = {} --- @type table<any,boolean>
|
||||
local running = {} --- @type table<any,boolean>
|
||||
return function(id, ...)
|
||||
if scheduled[id] then
|
||||
-- If fn is already scheduled, then drop
|
||||
return
|
||||
end
|
||||
if not running[id] or schedule then
|
||||
scheduled[id] = true
|
||||
end
|
||||
if running[id] then
|
||||
return
|
||||
end
|
||||
while scheduled[id] do
|
||||
scheduled[id] = nil
|
||||
running[id] = true
|
||||
fn(id, ...)
|
||||
running[id] = nil
|
||||
end
|
||||
end
|
||||
local scheduled = {} --- @type table<any,boolean>
|
||||
local running = {} --- @type table<any,boolean>
|
||||
return function(id, ...)
|
||||
if scheduled[id] then
|
||||
-- If fn is already scheduled, then drop
|
||||
return
|
||||
end
|
||||
if not running[id] or schedule then
|
||||
scheduled[id] = true
|
||||
end
|
||||
if running[id] then
|
||||
return
|
||||
end
|
||||
while scheduled[id] do
|
||||
scheduled[id] = nil
|
||||
running[id] = true
|
||||
fn(id, ...)
|
||||
running[id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
54
lua/gitsigns/debug.lua
generated
54
lua/gitsigns/debug.lua
generated
|
@ -6,45 +6,45 @@ local M = {}
|
|||
--- @param path string[]
|
||||
--- @return any
|
||||
local function process(raw_item, path)
|
||||
if path[#path] == vim.inspect.METATABLE then
|
||||
return nil
|
||||
elseif type(raw_item) == "function" then
|
||||
return nil
|
||||
elseif type(raw_item) == "table" then
|
||||
local key = path[#path]
|
||||
if key == 'compare_text' or key == 'compare_text_head' then
|
||||
local item = raw_item
|
||||
return { '...', length = #item, head = item[1] }
|
||||
elseif not vim.tbl_isempty(raw_item) and key == 'staged_diffs' then
|
||||
return { '...', length = #vim.tbl_keys(raw_item) }
|
||||
end
|
||||
end
|
||||
return raw_item
|
||||
if path[#path] == vim.inspect.METATABLE then
|
||||
return nil
|
||||
elseif type(raw_item) == 'function' then
|
||||
return nil
|
||||
elseif type(raw_item) == 'table' then
|
||||
local key = path[#path]
|
||||
if key == 'compare_text' or key == 'compare_text_head' then
|
||||
local item = raw_item
|
||||
return { '...', length = #item, head = item[1] }
|
||||
elseif not vim.tbl_isempty(raw_item) and key == 'staged_diffs' then
|
||||
return { '...', length = #vim.tbl_keys(raw_item) }
|
||||
end
|
||||
end
|
||||
return raw_item
|
||||
end
|
||||
|
||||
--- @return any
|
||||
function M.dump_cache()
|
||||
-- TODO(lewis6991): hack: use package.loaded to avoid circular deps
|
||||
local cache = (require('gitsigns.cache')).cache
|
||||
local text = vim.inspect(cache, { process = process })
|
||||
vim.api.nvim_echo({ { text } }, false, {})
|
||||
return cache
|
||||
-- TODO(lewis6991): hack: use package.loaded to avoid circular deps
|
||||
local cache = (require('gitsigns.cache')).cache
|
||||
local text = vim.inspect(cache, { process = process })
|
||||
vim.api.nvim_echo({ { text } }, false, {})
|
||||
return cache
|
||||
end
|
||||
|
||||
--- @param noecho boolean
|
||||
--- @return string[]
|
||||
function M.debug_messages(noecho)
|
||||
if noecho then
|
||||
return log.messages
|
||||
else
|
||||
for _, m in ipairs(log.messages) do
|
||||
vim.api.nvim_echo({ { m } }, false, {})
|
||||
end
|
||||
end
|
||||
if noecho then
|
||||
return log.messages
|
||||
else
|
||||
for _, m in ipairs(log.messages) do
|
||||
vim.api.nvim_echo({ { m } }, false, {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.clear_debug()
|
||||
log.messages = {}
|
||||
log.messages = {}
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
154
lua/gitsigns/debug/log.lua
generated
154
lua/gitsigns/debug/log.lua
generated
|
@ -1,109 +1,125 @@
|
|||
local M = {
|
||||
debug_mode = false,
|
||||
verbose = false,
|
||||
messages = {},
|
||||
debug_mode = false,
|
||||
verbose = false,
|
||||
messages = {},
|
||||
}
|
||||
|
||||
local function getvarvalue(name, lvl)
|
||||
lvl = lvl + 1
|
||||
local value
|
||||
local found
|
||||
lvl = lvl + 1
|
||||
local value
|
||||
local found
|
||||
|
||||
-- try local variables
|
||||
local i = 1
|
||||
while true do
|
||||
local n, v = debug.getlocal(lvl, i)
|
||||
if not n then break end
|
||||
if n == name then
|
||||
value = v
|
||||
found = true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
if found then return value end
|
||||
-- try local variables
|
||||
local i = 1
|
||||
while true do
|
||||
local n, v = debug.getlocal(lvl, i)
|
||||
if not n then
|
||||
break
|
||||
end
|
||||
if n == name then
|
||||
value = v
|
||||
found = true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
if found then
|
||||
return value
|
||||
end
|
||||
|
||||
-- try upvalues
|
||||
local func = debug.getinfo(lvl).func
|
||||
i = 1
|
||||
while true do
|
||||
local n, v = debug.getupvalue(func, i)
|
||||
if not n then break end
|
||||
if n == name then return v end
|
||||
i = i + 1
|
||||
end
|
||||
-- try upvalues
|
||||
local func = debug.getinfo(lvl).func
|
||||
i = 1
|
||||
while true do
|
||||
local n, v = debug.getupvalue(func, i)
|
||||
if not n then
|
||||
break
|
||||
end
|
||||
if n == name then
|
||||
return v
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- not found; get global
|
||||
return getfenv(func)[name]
|
||||
-- not found; get global
|
||||
return getfenv(func)[name]
|
||||
end
|
||||
|
||||
local function get_context(lvl)
|
||||
lvl = lvl + 1
|
||||
local ret = {}
|
||||
ret.name = getvarvalue('__FUNC__', lvl)
|
||||
if not ret.name then
|
||||
local name0 = debug.getinfo(lvl, 'n').name or ''
|
||||
ret.name = name0:gsub('(.*)%d+$', '%1')
|
||||
end
|
||||
ret.bufnr = getvarvalue('bufnr', lvl) or
|
||||
getvarvalue('_bufnr', lvl) or
|
||||
getvarvalue('cbuf', lvl) or
|
||||
getvarvalue('buf', lvl)
|
||||
lvl = lvl + 1
|
||||
local ret = {}
|
||||
ret.name = getvarvalue('__FUNC__', lvl)
|
||||
if not ret.name then
|
||||
local name0 = debug.getinfo(lvl, 'n').name or ''
|
||||
ret.name = name0:gsub('(.*)%d+$', '%1')
|
||||
end
|
||||
ret.bufnr = getvarvalue('bufnr', lvl)
|
||||
or getvarvalue('_bufnr', lvl)
|
||||
or getvarvalue('cbuf', lvl)
|
||||
or getvarvalue('buf', lvl)
|
||||
|
||||
return ret
|
||||
return ret
|
||||
end
|
||||
|
||||
-- If called in a callback then make sure the callback defines a __FUNC__
|
||||
-- variable which can be used to identify the name of the function.
|
||||
local function cprint(obj, lvl)
|
||||
lvl = lvl + 1
|
||||
local msg = type(obj) == "string" and obj or vim.inspect(obj)
|
||||
local ctx = get_context(lvl)
|
||||
local msg2
|
||||
if ctx.bufnr then
|
||||
msg2 = string.format('%s(%s): %s', ctx.name, ctx.bufnr, msg)
|
||||
else
|
||||
msg2 = string.format('%s: %s', ctx.name, msg)
|
||||
end
|
||||
table.insert(M.messages, msg2)
|
||||
lvl = lvl + 1
|
||||
local msg = type(obj) == 'string' and obj or vim.inspect(obj)
|
||||
local ctx = get_context(lvl)
|
||||
local msg2
|
||||
if ctx.bufnr then
|
||||
msg2 = string.format('%s(%s): %s', ctx.name, ctx.bufnr, msg)
|
||||
else
|
||||
msg2 = string.format('%s: %s', ctx.name, msg)
|
||||
end
|
||||
table.insert(M.messages, msg2)
|
||||
end
|
||||
|
||||
function M.dprint(obj)
|
||||
if not M.debug_mode then return end
|
||||
cprint(obj, 2)
|
||||
if not M.debug_mode then
|
||||
return
|
||||
end
|
||||
cprint(obj, 2)
|
||||
end
|
||||
|
||||
function M.dprintf(obj, ...)
|
||||
if not M.debug_mode then return end
|
||||
cprint(obj:format(...), 2)
|
||||
if not M.debug_mode then
|
||||
return
|
||||
end
|
||||
cprint(obj:format(...), 2)
|
||||
end
|
||||
|
||||
function M.vprint(obj)
|
||||
if not (M.debug_mode and M.verbose) then return end
|
||||
cprint(obj, 2)
|
||||
if not (M.debug_mode and M.verbose) then
|
||||
return
|
||||
end
|
||||
cprint(obj, 2)
|
||||
end
|
||||
|
||||
function M.vprintf(obj, ...)
|
||||
if not (M.debug_mode and M.verbose) then return end
|
||||
cprint(obj:format(...), 2)
|
||||
if not (M.debug_mode and M.verbose) then
|
||||
return
|
||||
end
|
||||
cprint(obj:format(...), 2)
|
||||
end
|
||||
|
||||
local function eprint(msg, level)
|
||||
local info = debug.getinfo(level + 2, 'Sl')
|
||||
if info then
|
||||
msg = string.format('(ERROR) %s(%d): %s', info.short_src, info.currentline, msg)
|
||||
end
|
||||
M.messages[#M.messages + 1] = msg
|
||||
if M.debug_mode then
|
||||
error(msg)
|
||||
end
|
||||
local info = debug.getinfo(level + 2, 'Sl')
|
||||
if info then
|
||||
msg = string.format('(ERROR) %s(%d): %s', info.short_src, info.currentline, msg)
|
||||
end
|
||||
M.messages[#M.messages + 1] = msg
|
||||
if M.debug_mode then
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function M.eprint(msg)
|
||||
eprint(msg, 1)
|
||||
eprint(msg, 1)
|
||||
end
|
||||
|
||||
function M.eprintf(fmt, ...)
|
||||
eprint(fmt:format(...), 1)
|
||||
eprint(fmt:format(...), 1)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
25
lua/gitsigns/diff.lua
generated
25
lua/gitsigns/diff.lua
generated
|
@ -1,18 +1,17 @@
|
|||
local config = require('gitsigns.config').config
|
||||
local Hunk = require('gitsigns.hunks').Hunk
|
||||
|
||||
return function(a, b, linematch)
|
||||
local diff_opts = config.diff_opts
|
||||
local f
|
||||
if diff_opts.internal then
|
||||
f = require('gitsigns.diff_int').run_diff
|
||||
else
|
||||
f = require('gitsigns.diff_ext').run_diff
|
||||
end
|
||||
local diff_opts = config.diff_opts
|
||||
local f
|
||||
if diff_opts.internal then
|
||||
f = require('gitsigns.diff_int').run_diff
|
||||
else
|
||||
f = require('gitsigns.diff_ext').run_diff
|
||||
end
|
||||
|
||||
local linematch0
|
||||
if linematch ~= false then
|
||||
linematch0 = diff_opts.linematch
|
||||
end
|
||||
return f(a, b, diff_opts.algorithm, diff_opts.indent_heuristic, linematch0)
|
||||
local linematch0 --- @type boolean?
|
||||
if linematch ~= false then
|
||||
linematch0 = diff_opts.linematch
|
||||
end
|
||||
return f(a, b, diff_opts.algorithm, diff_opts.indent_heuristic, linematch0)
|
||||
end
|
||||
|
|
109
lua/gitsigns/diff_ext.lua
generated
109
lua/gitsigns/diff_ext.lua
generated
|
@ -1,80 +1,73 @@
|
|||
local git_diff = require('gitsigns.git').diff
|
||||
|
||||
local gs_hunks = require("gitsigns.hunks")
|
||||
local gs_hunks = require('gitsigns.hunks')
|
||||
local Hunk = gs_hunks.Hunk
|
||||
local util = require('gitsigns.util')
|
||||
local scheduler = require('gitsigns.async').scheduler
|
||||
|
||||
local M = {}
|
||||
-- Async function
|
||||
|
||||
|
||||
-- Async function
|
||||
|
||||
local function write_to_file(path, text)
|
||||
local f, err = io.open(path, 'wb')
|
||||
if f == nil then
|
||||
error(err)
|
||||
end
|
||||
for _, l in ipairs(text) do
|
||||
f:write(l)
|
||||
f:write('\n')
|
||||
end
|
||||
f:close()
|
||||
local f, err = io.open(path, 'wb')
|
||||
if f == nil then
|
||||
error(err)
|
||||
end
|
||||
for _, l in ipairs(text) do
|
||||
f:write(l)
|
||||
f:write('\n')
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
|
||||
M.run_diff = function(
|
||||
text_cmp,
|
||||
text_buf,
|
||||
diff_algo,
|
||||
indent_heuristic)
|
||||
M.run_diff = function(text_cmp, text_buf, diff_algo, indent_heuristic)
|
||||
local results = {}
|
||||
|
||||
local results = {}
|
||||
-- tmpname must not be called in a callback
|
||||
if vim.in_fast_event() then
|
||||
scheduler()
|
||||
end
|
||||
|
||||
-- tmpname must not be called in a callback
|
||||
if vim.in_fast_event() then
|
||||
scheduler()
|
||||
end
|
||||
local file_buf = util.tmpname()
|
||||
local file_cmp = util.tmpname()
|
||||
|
||||
local file_buf = util.tmpname()
|
||||
local file_cmp = util.tmpname()
|
||||
write_to_file(file_buf, text_buf)
|
||||
write_to_file(file_cmp, text_cmp)
|
||||
|
||||
write_to_file(file_buf, text_buf)
|
||||
write_to_file(file_cmp, text_cmp)
|
||||
-- Taken from gitgutter, diff.vim:
|
||||
--
|
||||
-- If a file has CRLF line endings and git's core.autocrlf is true, the file
|
||||
-- in git's object store will have LF line endings. Writing it out via
|
||||
-- git-show will produce a file with LF line endings.
|
||||
--
|
||||
-- If this last file is one of the files passed to git-diff, git-diff will
|
||||
-- convert its line endings to CRLF before diffing -- which is what we want
|
||||
-- but also by default outputs a warning on stderr.
|
||||
--
|
||||
-- warning: LF will be replace by CRLF in <temp file>.
|
||||
-- The file will have its original line endings in your working directory.
|
||||
--
|
||||
-- We can safely ignore the warning, we turn it off by passing the '-c
|
||||
-- "core.safecrlf=false"' argument to git-diff.
|
||||
|
||||
-- Taken from gitgutter, diff.vim:
|
||||
--
|
||||
-- If a file has CRLF line endings and git's core.autocrlf is true, the file
|
||||
-- in git's object store will have LF line endings. Writing it out via
|
||||
-- git-show will produce a file with LF line endings.
|
||||
--
|
||||
-- If this last file is one of the files passed to git-diff, git-diff will
|
||||
-- convert its line endings to CRLF before diffing -- which is what we want
|
||||
-- but also by default outputs a warning on stderr.
|
||||
--
|
||||
-- warning: LF will be replace by CRLF in <temp file>.
|
||||
-- The file will have its original line endings in your working directory.
|
||||
--
|
||||
-- We can safely ignore the warning, we turn it off by passing the '-c
|
||||
-- "core.safecrlf=false"' argument to git-diff.
|
||||
local out = git_diff(file_cmp, file_buf, indent_heuristic, diff_algo)
|
||||
|
||||
local out = git_diff(file_cmp, file_buf, indent_heuristic, diff_algo)
|
||||
|
||||
for _, line in ipairs(out) do
|
||||
if vim.startswith(line, '@@') then
|
||||
results[#results + 1] = gs_hunks.parse_diff_line(line)
|
||||
elseif #results > 0 then
|
||||
local r = results[#results]
|
||||
if line:sub(1, 1) == '-' then
|
||||
r.removed.lines[#r.removed.lines + 1] = line:sub(2)
|
||||
elseif line:sub(1, 1) == '+' then
|
||||
r.added.lines[#r.added.lines + 1] = line:sub(2)
|
||||
end
|
||||
for _, line in ipairs(out) do
|
||||
if vim.startswith(line, '@@') then
|
||||
results[#results + 1] = gs_hunks.parse_diff_line(line)
|
||||
elseif #results > 0 then
|
||||
local r = results[#results]
|
||||
if line:sub(1, 1) == '-' then
|
||||
r.removed.lines[#r.removed.lines + 1] = line:sub(2)
|
||||
elseif line:sub(1, 1) == '+' then
|
||||
r.added.lines[#r.added.lines + 1] = line:sub(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
os.remove(file_buf)
|
||||
os.remove(file_cmp)
|
||||
return results
|
||||
os.remove(file_buf)
|
||||
os.remove(file_cmp)
|
||||
return results
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
208
lua/gitsigns/diff_int.lua
generated
208
lua/gitsigns/diff_int.lua
generated
|
@ -1,149 +1,133 @@
|
|||
local create_hunk = require("gitsigns.hunks").create_hunk
|
||||
local create_hunk = require('gitsigns.hunks').create_hunk
|
||||
local Hunk = require('gitsigns.hunks').Hunk
|
||||
local config = require('gitsigns.config').config
|
||||
local async = require('gitsigns.async')
|
||||
|
||||
local M = {}
|
||||
|
||||
local run_diff_xdl = function(fa, fb, algorithm, indent_heuristic, linematch)
|
||||
local a = vim.tbl_isempty(fa) and '' or table.concat(fa, '\n') .. '\n'
|
||||
local b = vim.tbl_isempty(fb) and '' or table.concat(fb, '\n') .. '\n'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local run_diff_xdl = function(
|
||||
fa, fb,
|
||||
algorithm, indent_heuristic,
|
||||
linematch)
|
||||
|
||||
|
||||
local a = vim.tbl_isempty(fa) and '' or table.concat(fa, '\n') .. '\n'
|
||||
local b = vim.tbl_isempty(fb) and '' or table.concat(fb, '\n') .. '\n'
|
||||
|
||||
return vim.diff(a, b, {
|
||||
result_type = 'indices',
|
||||
algorithm = algorithm,
|
||||
indent_heuristic = indent_heuristic,
|
||||
linematch = linematch,
|
||||
})
|
||||
return vim.diff(a, b, {
|
||||
result_type = 'indices',
|
||||
algorithm = algorithm,
|
||||
indent_heuristic = indent_heuristic,
|
||||
linematch = linematch,
|
||||
})
|
||||
end
|
||||
|
||||
local run_diff_xdl_async = async.wrap(function(
|
||||
fa, fb,
|
||||
algorithm, indent_heuristic,
|
||||
linematch,
|
||||
callback)
|
||||
local run_diff_xdl_async = async.wrap(
|
||||
function(fa, fb, algorithm, indent_heuristic, linematch, callback)
|
||||
local a = vim.tbl_isempty(fa) and '' or table.concat(fa, '\n') .. '\n'
|
||||
local b = vim.tbl_isempty(fb) and '' or table.concat(fb, '\n') .. '\n'
|
||||
|
||||
vim.loop
|
||||
.new_work(function(a0, b0, algorithm0, indent_heuristic0, linematch0)
|
||||
return vim.mpack.encode(vim.diff(a0, b0, {
|
||||
result_type = 'indices',
|
||||
algorithm = algorithm0,
|
||||
indent_heuristic = indent_heuristic0,
|
||||
linematch = linematch0,
|
||||
}))
|
||||
end, function(r)
|
||||
callback(vim.mpack.decode(r))
|
||||
end)
|
||||
:queue(a, b, algorithm, indent_heuristic, linematch)
|
||||
end,
|
||||
6
|
||||
)
|
||||
|
||||
local a = vim.tbl_isempty(fa) and '' or table.concat(fa, '\n') .. '\n'
|
||||
local b = vim.tbl_isempty(fb) and '' or table.concat(fb, '\n') .. '\n'
|
||||
M.run_diff = async.void(function(fa, fb, diff_algo, indent_heuristic, linematch)
|
||||
local run_diff0
|
||||
if config._threaded_diff and vim.is_thread then
|
||||
run_diff0 = run_diff_xdl_async
|
||||
else
|
||||
run_diff0 = run_diff_xdl
|
||||
end
|
||||
|
||||
vim.loop.new_work(function(
|
||||
a0, b0,
|
||||
algorithm0, indent_heuristic0,
|
||||
linematch0)
|
||||
local results = run_diff0(fa, fb, diff_algo, indent_heuristic, linematch)
|
||||
|
||||
return vim.mpack.encode(vim.diff(a0, b0, {
|
||||
result_type = 'indices',
|
||||
algorithm = algorithm0,
|
||||
indent_heuristic = indent_heuristic0,
|
||||
linematch = linematch0,
|
||||
}))
|
||||
end, function(r)
|
||||
callback(vim.mpack.decode(r))
|
||||
end):queue(a, b, algorithm, indent_heuristic, linematch)
|
||||
end, 6)
|
||||
local hunks = {}
|
||||
|
||||
M.run_diff = async.void(function(
|
||||
fa, fb,
|
||||
diff_algo, indent_heuristic,
|
||||
linematch)
|
||||
|
||||
local run_diff0
|
||||
if config._threaded_diff and vim.is_thread then
|
||||
run_diff0 = run_diff_xdl_async
|
||||
else
|
||||
run_diff0 = run_diff_xdl
|
||||
end
|
||||
|
||||
local results = run_diff0(fa, fb, diff_algo, indent_heuristic, linematch)
|
||||
|
||||
local hunks = {}
|
||||
|
||||
for _, r in ipairs(results) do
|
||||
local rs, rc, as, ac = unpack(r)
|
||||
local hunk = create_hunk(rs, rc, as, ac)
|
||||
if rc > 0 then
|
||||
for i = rs, rs + rc - 1 do
|
||||
hunk.removed.lines[#hunk.removed.lines + 1] = fa[i] or ''
|
||||
end
|
||||
for _, r in ipairs(results) do
|
||||
local rs, rc, as, ac = unpack(r)
|
||||
local hunk = create_hunk(rs, rc, as, ac)
|
||||
if rc > 0 then
|
||||
for i = rs, rs + rc - 1 do
|
||||
hunk.removed.lines[#hunk.removed.lines + 1] = fa[i] or ''
|
||||
end
|
||||
if ac > 0 then
|
||||
for i = as, as + ac - 1 do
|
||||
hunk.added.lines[#hunk.added.lines + 1] = fb[i] or ''
|
||||
end
|
||||
end
|
||||
if ac > 0 then
|
||||
for i = as, as + ac - 1 do
|
||||
hunk.added.lines[#hunk.added.lines + 1] = fb[i] or ''
|
||||
end
|
||||
hunks[#hunks + 1] = hunk
|
||||
end
|
||||
end
|
||||
hunks[#hunks + 1] = hunk
|
||||
end
|
||||
|
||||
return hunks
|
||||
return hunks
|
||||
end)
|
||||
|
||||
|
||||
|
||||
local gaps_between_regions = 5
|
||||
|
||||
local function denoise_hunks(hunks)
|
||||
-- Denoise the hunks
|
||||
local ret = { hunks[1] }
|
||||
for j = 2, #hunks do
|
||||
local h, n = ret[#ret], hunks[j]
|
||||
if not h or not n then break end
|
||||
if n.added.start - h.added.start - h.added.count < gaps_between_regions then
|
||||
h.added.count = n.added.start + n.added.count - h.added.start
|
||||
h.removed.count = n.removed.start + n.removed.count - h.removed.start
|
||||
-- Denoise the hunks
|
||||
local ret = { hunks[1] }
|
||||
for j = 2, #hunks do
|
||||
local h, n = ret[#ret], hunks[j]
|
||||
if not h or not n then
|
||||
break
|
||||
end
|
||||
if n.added.start - h.added.start - h.added.count < gaps_between_regions then
|
||||
h.added.count = n.added.start + n.added.count - h.added.start
|
||||
h.removed.count = n.removed.start + n.removed.count - h.removed.start
|
||||
|
||||
if h.added.count > 0 or h.removed.count > 0 then
|
||||
h.type = 'change'
|
||||
end
|
||||
else
|
||||
ret[#ret + 1] = n
|
||||
if h.added.count > 0 or h.removed.count > 0 then
|
||||
h.type = 'change'
|
||||
end
|
||||
end
|
||||
return ret
|
||||
else
|
||||
ret[#ret + 1] = n
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.run_word_diff(removed, added)
|
||||
local adds = {}
|
||||
local rems = {}
|
||||
local adds = {}
|
||||
local rems = {}
|
||||
|
||||
if #removed ~= #added then
|
||||
return rems, adds
|
||||
end
|
||||
if #removed ~= #added then
|
||||
return rems, adds
|
||||
end
|
||||
|
||||
for i = 1, #removed do
|
||||
-- pair lines by position
|
||||
local a, b = vim.split(removed[i], ''), vim.split(added[i], '')
|
||||
for i = 1, #removed do
|
||||
-- pair lines by position
|
||||
local a, b = vim.split(removed[i], ''), vim.split(added[i], '')
|
||||
|
||||
local hunks = {}
|
||||
for _, r in ipairs(run_diff_xdl(a, b)) do
|
||||
local rs, rc, as, ac = unpack(r)
|
||||
local hunks = {}
|
||||
for _, r in ipairs(run_diff_xdl(a, b)) do
|
||||
local rs, rc, as, ac = unpack(r)
|
||||
|
||||
-- Balance of the unknown offset done in hunk_func
|
||||
if rc == 0 then rs = rs + 1 end
|
||||
if ac == 0 then as = as + 1 end
|
||||
|
||||
hunks[#hunks + 1] = create_hunk(rs, rc, as, ac)
|
||||
-- Balance of the unknown offset done in hunk_func
|
||||
if rc == 0 then
|
||||
rs = rs + 1
|
||||
end
|
||||
if ac == 0 then
|
||||
as = as + 1
|
||||
end
|
||||
|
||||
hunks = denoise_hunks(hunks)
|
||||
hunks[#hunks + 1] = create_hunk(rs, rc, as, ac)
|
||||
end
|
||||
|
||||
for _, h in ipairs(hunks) do
|
||||
adds[#adds + 1] = { i, h.type, h.added.start, h.added.start + h.added.count }
|
||||
rems[#rems + 1] = { i, h.type, h.removed.start, h.removed.start + h.removed.count }
|
||||
end
|
||||
end
|
||||
return rems, adds
|
||||
hunks = denoise_hunks(hunks)
|
||||
|
||||
for _, h in ipairs(hunks) do
|
||||
adds[#adds + 1] = { i, h.type, h.added.start, h.added.start + h.added.count }
|
||||
rems[#rems + 1] = { i, h.type, h.removed.start, h.removed.start + h.removed.count }
|
||||
end
|
||||
end
|
||||
return rems, adds
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
279
lua/gitsigns/diffthis.lua
generated
279
lua/gitsigns/diffthis.lua
generated
|
@ -16,186 +16,179 @@ local throttle_by_id = require('gitsigns.debounce').throttle_by_id
|
|||
|
||||
local input = awrap(vim.ui.input, 2)
|
||||
|
||||
local M = {DiffthisOpts = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = { DiffthisOpts = {} }
|
||||
|
||||
local bufread = void(function(bufnr, dbufnr, base, bcache)
|
||||
local comp_rev = bcache:get_compare_rev(util.calc_base(base))
|
||||
local text
|
||||
if util.calc_base(base) == util.calc_base(bcache.base) then
|
||||
text = bcache.compare_text
|
||||
else
|
||||
local err
|
||||
text, err = bcache.git_obj:get_show_text(comp_rev)
|
||||
if err then
|
||||
error(err, 2)
|
||||
end
|
||||
scheduler()
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
text = util.strip_cr(text)
|
||||
end
|
||||
end
|
||||
local comp_rev = bcache:get_compare_rev(util.calc_base(base))
|
||||
local text
|
||||
if util.calc_base(base) == util.calc_base(bcache.base) then
|
||||
text = bcache.compare_text
|
||||
else
|
||||
local err
|
||||
text, err = bcache.git_obj:get_show_text(comp_rev)
|
||||
if err then
|
||||
error(err, 2)
|
||||
end
|
||||
scheduler()
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
text = util.strip_cr(text)
|
||||
end
|
||||
end
|
||||
|
||||
local modifiable = vim.bo[dbufnr].modifiable
|
||||
vim.bo[dbufnr].modifiable = true
|
||||
util.set_lines(dbufnr, 0, -1, text)
|
||||
local modifiable = vim.bo[dbufnr].modifiable
|
||||
vim.bo[dbufnr].modifiable = true
|
||||
util.set_lines(dbufnr, 0, -1, text)
|
||||
|
||||
vim.bo[dbufnr].modifiable = modifiable
|
||||
vim.bo[dbufnr].modified = false
|
||||
vim.bo[dbufnr].filetype = vim.bo[bufnr].filetype
|
||||
vim.bo[dbufnr].bufhidden = 'wipe'
|
||||
vim.bo[dbufnr].modifiable = modifiable
|
||||
vim.bo[dbufnr].modified = false
|
||||
vim.bo[dbufnr].filetype = vim.bo[bufnr].filetype
|
||||
vim.bo[dbufnr].bufhidden = 'wipe'
|
||||
end)
|
||||
|
||||
local bufwrite = void(function(bufnr, dbufnr, base, bcache)
|
||||
local buftext = util.buf_lines(dbufnr)
|
||||
bcache.git_obj:stage_lines(buftext)
|
||||
scheduler()
|
||||
vim.bo[dbufnr].modified = false
|
||||
-- If diff buffer base matches the bcache base then also update the
|
||||
-- signs.
|
||||
if util.calc_base(base) == util.calc_base(bcache.base) then
|
||||
bcache.compare_text = buftext
|
||||
manager.update(bufnr, bcache)
|
||||
end
|
||||
local buftext = util.buf_lines(dbufnr)
|
||||
bcache.git_obj:stage_lines(buftext)
|
||||
scheduler()
|
||||
vim.bo[dbufnr].modified = false
|
||||
-- If diff buffer base matches the bcache base then also update the
|
||||
-- signs.
|
||||
if util.calc_base(base) == util.calc_base(bcache.base) then
|
||||
bcache.compare_text = buftext
|
||||
manager.update(bufnr, bcache)
|
||||
end
|
||||
end)
|
||||
|
||||
local function run(base, diffthis, opts)
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
opts = opts or {}
|
||||
|
||||
local comp_rev = bcache:get_compare_rev(util.calc_base(base))
|
||||
local bufname = bcache:get_rev_bufname(comp_rev)
|
||||
local comp_rev = bcache:get_compare_rev(util.calc_base(base))
|
||||
local bufname = bcache:get_rev_bufname(comp_rev)
|
||||
|
||||
local dbuf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(dbuf, bufname)
|
||||
local dbuf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(dbuf, bufname)
|
||||
|
||||
local ok, err = pcall(bufread, bufnr, dbuf, base, bcache)
|
||||
if not ok then
|
||||
message.error(err)
|
||||
scheduler()
|
||||
vim.cmd('bdelete')
|
||||
if diffthis then
|
||||
vim.cmd('diffoff')
|
||||
end
|
||||
return
|
||||
end
|
||||
local ok, err = pcall(bufread, bufnr, dbuf, base, bcache)
|
||||
if not ok then
|
||||
message.error(err)
|
||||
scheduler()
|
||||
vim.cmd('bdelete')
|
||||
if diffthis then
|
||||
vim.cmd('diffoff')
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if comp_rev == ':0' then
|
||||
vim.bo[dbuf].buftype = 'acwrite'
|
||||
if comp_rev == ':0' then
|
||||
vim.bo[dbuf].buftype = 'acwrite'
|
||||
|
||||
api.nvim_create_autocmd('BufReadCmd', {
|
||||
group = 'gitsigns',
|
||||
buffer = dbuf,
|
||||
callback = function()
|
||||
bufread(bufnr, dbuf, base, bcache)
|
||||
if diffthis then
|
||||
vim.cmd('diffthis')
|
||||
end
|
||||
end,
|
||||
})
|
||||
api.nvim_create_autocmd('BufReadCmd', {
|
||||
group = 'gitsigns',
|
||||
buffer = dbuf,
|
||||
callback = function()
|
||||
bufread(bufnr, dbuf, base, bcache)
|
||||
if diffthis then
|
||||
vim.cmd('diffthis')
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('BufWriteCmd', {
|
||||
group = 'gitsigns',
|
||||
buffer = dbuf,
|
||||
callback = function()
|
||||
bufwrite(bufnr, dbuf, base, bcache)
|
||||
end,
|
||||
})
|
||||
else
|
||||
vim.bo[dbuf].buftype = 'nowrite'
|
||||
vim.bo[dbuf].modifiable = false
|
||||
end
|
||||
api.nvim_create_autocmd('BufWriteCmd', {
|
||||
group = 'gitsigns',
|
||||
buffer = dbuf,
|
||||
callback = function()
|
||||
bufwrite(bufnr, dbuf, base, bcache)
|
||||
end,
|
||||
})
|
||||
else
|
||||
vim.bo[dbuf].buftype = 'nowrite'
|
||||
vim.bo[dbuf].modifiable = false
|
||||
end
|
||||
|
||||
if diffthis then
|
||||
vim.cmd(table.concat({
|
||||
'keepalt', opts.split or 'aboveleft',
|
||||
opts.vertical and 'vertical' or '',
|
||||
'diffsplit', bufname,
|
||||
}, ' '))
|
||||
else
|
||||
vim.cmd('edit ' .. bufname)
|
||||
end
|
||||
if diffthis then
|
||||
vim.cmd(table.concat({
|
||||
'keepalt',
|
||||
opts.split or 'aboveleft',
|
||||
opts.vertical and 'vertical' or '',
|
||||
'diffsplit',
|
||||
bufname,
|
||||
}, ' '))
|
||||
else
|
||||
vim.cmd('edit ' .. bufname)
|
||||
end
|
||||
end
|
||||
|
||||
M.diffthis = void(function(base, opts)
|
||||
if vim.wo.diff then
|
||||
return
|
||||
end
|
||||
if vim.wo.diff then
|
||||
return
|
||||
end
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
|
||||
local cwin = api.nvim_get_current_win()
|
||||
if not base and bcache.git_obj.has_conflicts then
|
||||
run(':2', true, opts)
|
||||
api.nvim_set_current_win(cwin)
|
||||
opts.split = 'belowright'
|
||||
run(':3', true, opts)
|
||||
else
|
||||
run(base, true, opts)
|
||||
end
|
||||
api.nvim_set_current_win(cwin)
|
||||
local cwin = api.nvim_get_current_win()
|
||||
if not base and bcache.git_obj.has_conflicts then
|
||||
run(':2', true, opts)
|
||||
api.nvim_set_current_win(cwin)
|
||||
opts.split = 'belowright'
|
||||
run(':3', true, opts)
|
||||
else
|
||||
run(base, true, opts)
|
||||
end
|
||||
api.nvim_set_current_win(cwin)
|
||||
end)
|
||||
|
||||
M.show = void(function(base)
|
||||
run(base, false)
|
||||
run(base, false)
|
||||
end)
|
||||
|
||||
local function should_reload(bufnr)
|
||||
if not vim.bo[bufnr].modified then
|
||||
return true
|
||||
end
|
||||
local response
|
||||
while not vim.tbl_contains({ 'O', 'L' }, response) do
|
||||
response = input({
|
||||
prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:',
|
||||
})
|
||||
end
|
||||
return response == 'L'
|
||||
if not vim.bo[bufnr].modified then
|
||||
return true
|
||||
end
|
||||
local response
|
||||
while not vim.tbl_contains({ 'O', 'L' }, response) do
|
||||
response = input({
|
||||
prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:',
|
||||
})
|
||||
end
|
||||
return response == 'L'
|
||||
end
|
||||
|
||||
-- This function needs to be throttled as there is a call to vim.ui.input
|
||||
M.update = throttle_by_id(void(function(bufnr)
|
||||
if not vim.wo.diff then
|
||||
return
|
||||
end
|
||||
if not vim.wo.diff then
|
||||
return
|
||||
end
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
-- Note this will be the bufname for the currently set base
|
||||
-- which are the only ones we want to update
|
||||
local bufname = bcache:get_rev_bufname()
|
||||
-- Note this will be the bufname for the currently set base
|
||||
-- which are the only ones we want to update
|
||||
local bufname = bcache:get_rev_bufname()
|
||||
|
||||
for _, w in ipairs(api.nvim_list_wins()) do
|
||||
if api.nvim_win_is_valid(w) then
|
||||
local b = api.nvim_win_get_buf(w)
|
||||
local bname = api.nvim_buf_get_name(b)
|
||||
if bname == bufname or vim.startswith(bname, 'fugitive://') then
|
||||
if should_reload(b) then
|
||||
api.nvim_buf_call(b, function()
|
||||
vim.cmd('doautocmd BufReadCmd')
|
||||
vim.cmd('diffthis')
|
||||
end)
|
||||
end
|
||||
end
|
||||
for _, w in ipairs(api.nvim_list_wins()) do
|
||||
if api.nvim_win_is_valid(w) then
|
||||
local b = api.nvim_win_get_buf(w)
|
||||
local bname = api.nvim_buf_get_name(b)
|
||||
if bname == bufname or vim.startswith(bname, 'fugitive://') then
|
||||
if should_reload(b) then
|
||||
api.nvim_buf_call(b, function()
|
||||
vim.cmd('doautocmd BufReadCmd')
|
||||
vim.cmd('diffthis')
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end))
|
||||
|
||||
return M
|
||||
|
|
953
lua/gitsigns/git.lua
generated
953
lua/gitsigns/git.lua
generated
File diff suppressed because it is too large
Load diff
414
lua/gitsigns/highlight.lua
generated
414
lua/gitsigns/highlight.lua
generated
|
@ -1,223 +1,307 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
-- Use array of dict so we can iterate deterministically
|
||||
-- Export for docgen
|
||||
M.hls = {
|
||||
{ GitSignsAdd = { 'GitGutterAdd', 'SignifySignAdd', 'DiffAddedGutter', 'diffAdded', 'DiffAdd',
|
||||
desc = "Used for the text of 'add' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsAdd = {
|
||||
'GitGutterAdd',
|
||||
'SignifySignAdd',
|
||||
'DiffAddedGutter',
|
||||
'diffAdded',
|
||||
'DiffAdd',
|
||||
desc = "Used for the text of 'add' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChange = { 'GitGutterChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange',
|
||||
desc = "Used for the text of 'change' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChange = {
|
||||
'GitGutterChange',
|
||||
'SignifySignChange',
|
||||
'DiffModifiedGutter',
|
||||
'diffChanged',
|
||||
'DiffChange',
|
||||
desc = "Used for the text of 'change' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsDelete = { 'GitGutterDelete', 'SignifySignDelete', 'DiffRemovedGutter', 'diffRemoved', 'DiffDelete',
|
||||
desc = "Used for the text of 'delete' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsDelete = {
|
||||
'GitGutterDelete',
|
||||
'SignifySignDelete',
|
||||
'DiffRemovedGutter',
|
||||
'diffRemoved',
|
||||
'DiffDelete',
|
||||
desc = "Used for the text of 'delete' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangedelete = { 'GitSignsChange',
|
||||
desc = "Used for the text of 'changedelete' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangedelete = {
|
||||
'GitSignsChange',
|
||||
desc = "Used for the text of 'changedelete' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsTopdelete = { 'GitSignsDelete',
|
||||
desc = "Used for the text of 'topdelete' signs.",
|
||||
}, },
|
||||
{ GitSignsTopdelete = { 'GitSignsDelete', desc = "Used for the text of 'topdelete' signs." } },
|
||||
|
||||
{ GitSignsUntracked = { 'GitSignsAdd',
|
||||
desc = "Used for the text of 'untracked' signs.",
|
||||
}, },
|
||||
{ GitSignsUntracked = { 'GitSignsAdd', desc = "Used for the text of 'untracked' signs." } },
|
||||
|
||||
{ GitSignsAddNr = { 'GitGutterAddLineNr', 'GitSignsAdd',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'add' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsAddNr = {
|
||||
'GitGutterAddLineNr',
|
||||
'GitSignsAdd',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'add' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangeNr = { 'GitGutterChangeLineNr', 'GitSignsChange',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'change' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangeNr = {
|
||||
'GitGutterChangeLineNr',
|
||||
'GitSignsChange',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'change' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsDeleteNr = { 'GitGutterDeleteLineNr', 'GitSignsDelete',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'delete' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsDeleteNr = {
|
||||
'GitGutterDeleteLineNr',
|
||||
'GitSignsDelete',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'delete' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangedeleteNr = { 'GitSignsChangeNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'changedelete' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangedeleteNr = {
|
||||
'GitSignsChangeNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'changedelete' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsTopdeleteNr = { 'GitSignsDeleteNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'topdelete' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsTopdeleteNr = {
|
||||
'GitSignsDeleteNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'topdelete' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsUntrackedNr = { 'GitSignsAddNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'untracked' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsUntrackedNr = {
|
||||
'GitSignsAddNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'untracked' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsAddLn = { 'GitGutterAddLine', 'SignifyLineAdd', 'DiffAdd',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'add' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsAddLn = {
|
||||
'GitGutterAddLine',
|
||||
'SignifyLineAdd',
|
||||
'DiffAdd',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'add' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangeLn = { 'GitGutterChangeLine', 'SignifyLineChange', 'DiffChange',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'change' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangeLn = {
|
||||
'GitGutterChangeLine',
|
||||
'SignifyLineChange',
|
||||
'DiffChange',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'change' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangedeleteLn = { 'GitSignsChangeLn',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'changedelete' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangedeleteLn = {
|
||||
'GitSignsChangeLn',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'changedelete' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsUntrackedLn = { 'GitSignsAddLn',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'untracked' signs.",
|
||||
}, },
|
||||
{
|
||||
GitSignsUntrackedLn = {
|
||||
'GitSignsAddLn',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'untracked' signs.",
|
||||
},
|
||||
},
|
||||
|
||||
-- Don't set GitSignsDeleteLn by default
|
||||
-- {GitSignsDeleteLn = {}},
|
||||
-- Don't set GitSignsDeleteLn by default
|
||||
-- {GitSignsDeleteLn = {}},
|
||||
|
||||
{ GitSignsStagedAdd = { 'GitSignsAdd', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChange = { 'GitSignsChange', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedDelete = { 'GitSignsDelete', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangedelete = { 'GitSignsChangedelete', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedTopdelete = { 'GitSignsTopdelete', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedAddNr = { 'GitSignsAddNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangeNr = { 'GitSignsChangeNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedDeleteNr = { 'GitSignsDeleteNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangedeleteNr = { 'GitSignsChangedeleteNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedTopdeleteNr = { 'GitSignsTopdeleteNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedAddLn = { 'GitSignsAddLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangeLn = { 'GitSignsChangeLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedDeleteLn = { 'GitSignsDeleteLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangedeleteLn = { 'GitSignsChangedeleteLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedTopdeleteLn = { 'GitSignsTopdeleteLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedAdd = { 'GitSignsAdd', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChange = { 'GitSignsChange', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedDelete = { 'GitSignsDelete', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangedelete = { 'GitSignsChangedelete', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedTopdelete = { 'GitSignsTopdelete', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedAddNr = { 'GitSignsAddNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangeNr = { 'GitSignsChangeNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedDeleteNr = { 'GitSignsDeleteNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangedeleteNr = { 'GitSignsChangedeleteNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedTopdeleteNr = { 'GitSignsTopdeleteNr', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedAddLn = { 'GitSignsAddLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangeLn = { 'GitSignsChangeLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedDeleteLn = { 'GitSignsDeleteLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedChangedeleteLn = { 'GitSignsChangedeleteLn', fg_factor = 0.5, hidden = true } },
|
||||
{ GitSignsStagedTopdeleteLn = { 'GitSignsTopdeleteLn', fg_factor = 0.5, hidden = true } },
|
||||
|
||||
{ GitSignsAddPreview = { 'GitGutterAddLine', 'SignifyLineAdd', 'DiffAdd',
|
||||
desc = "Used for added lines in previews.",
|
||||
}, },
|
||||
{
|
||||
GitSignsAddPreview = {
|
||||
'GitGutterAddLine',
|
||||
'SignifyLineAdd',
|
||||
'DiffAdd',
|
||||
desc = 'Used for added lines in previews.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsDeletePreview = { 'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete',
|
||||
desc = "Used for deleted lines in previews.",
|
||||
}, },
|
||||
{
|
||||
GitSignsDeletePreview = {
|
||||
'GitGutterDeleteLine',
|
||||
'SignifyLineDelete',
|
||||
'DiffDelete',
|
||||
desc = 'Used for deleted lines in previews.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsCurrentLineBlame = { 'NonText',
|
||||
desc = "Used for current line blame.",
|
||||
}, },
|
||||
{ GitSignsCurrentLineBlame = { 'NonText', desc = 'Used for current line blame.' } },
|
||||
|
||||
{ GitSignsAddInline = { 'TermCursor',
|
||||
desc = "Used for added word diff regions in inline previews.",
|
||||
}, },
|
||||
{
|
||||
GitSignsAddInline = {
|
||||
'TermCursor',
|
||||
desc = 'Used for added word diff regions in inline previews.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsDeleteInline = { 'TermCursor',
|
||||
desc = "Used for deleted word diff regions in inline previews.",
|
||||
}, },
|
||||
{
|
||||
GitSignsDeleteInline = {
|
||||
'TermCursor',
|
||||
desc = 'Used for deleted word diff regions in inline previews.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangeInline = { 'TermCursor',
|
||||
desc = "Used for changed word diff regions in inline previews.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangeInline = {
|
||||
'TermCursor',
|
||||
desc = 'Used for changed word diff regions in inline previews.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsAddLnInline = { 'GitSignsAddInline',
|
||||
desc = "Used for added word diff regions when `config.word_diff == true`.",
|
||||
}, },
|
||||
{
|
||||
GitSignsAddLnInline = {
|
||||
'GitSignsAddInline',
|
||||
desc = 'Used for added word diff regions when `config.word_diff == true`.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsChangeLnInline = { 'GitSignsChangeInline',
|
||||
desc = "Used for changed word diff regions when `config.word_diff == true`.",
|
||||
}, },
|
||||
{
|
||||
GitSignsChangeLnInline = {
|
||||
'GitSignsChangeInline',
|
||||
desc = 'Used for changed word diff regions when `config.word_diff == true`.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsDeleteLnInline = { 'GitSignsDeleteInline',
|
||||
desc = "Used for deleted word diff regions when `config.word_diff == true`.",
|
||||
}, },
|
||||
{
|
||||
GitSignsDeleteLnInline = {
|
||||
'GitSignsDeleteInline',
|
||||
desc = 'Used for deleted word diff regions when `config.word_diff == true`.',
|
||||
},
|
||||
},
|
||||
|
||||
-- Currently unused
|
||||
-- {GitSignsAddLnVirtLn = {'GitSignsAddLn'}},
|
||||
-- {GitSignsChangeVirtLn = {'GitSignsChangeLn'}},
|
||||
-- {GitSignsAddLnVirtLnInLine = {'GitSignsAddLnInline', }},
|
||||
-- {GitSignsChangeVirtLnInLine = {'GitSignsChangeLnInline', }},
|
||||
-- Currently unused
|
||||
-- {GitSignsAddLnVirtLn = {'GitSignsAddLn'}},
|
||||
-- {GitSignsChangeVirtLn = {'GitSignsChangeLn'}},
|
||||
-- {GitSignsAddLnVirtLnInLine = {'GitSignsAddLnInline', }},
|
||||
-- {GitSignsChangeVirtLnInLine = {'GitSignsChangeLnInline', }},
|
||||
|
||||
{ GitSignsDeleteVirtLn = { 'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete',
|
||||
desc = "Used for deleted lines shown by inline `preview_hunk_inline()` or `show_deleted()`.",
|
||||
}, },
|
||||
{
|
||||
GitSignsDeleteVirtLn = {
|
||||
'GitGutterDeleteLine',
|
||||
'SignifyLineDelete',
|
||||
'DiffDelete',
|
||||
desc = 'Used for deleted lines shown by inline `preview_hunk_inline()` or `show_deleted()`.',
|
||||
},
|
||||
},
|
||||
|
||||
{ GitSignsDeleteVirtLnInLine = { 'GitSignsDeleteLnInline',
|
||||
desc = "Used for word diff regions in lines shown by inline `preview_hunk_inline()` or `show_deleted()`.",
|
||||
}, },
|
||||
|
||||
{ GitSignsVirtLnum = { 'GitSignsDeleteVirtLn',
|
||||
desc = 'Used for line numbers in inline hunks previews.',
|
||||
}, },
|
||||
{
|
||||
GitSignsDeleteVirtLnInLine = {
|
||||
'GitSignsDeleteLnInline',
|
||||
desc = 'Used for word diff regions in lines shown by inline `preview_hunk_inline()` or `show_deleted()`.',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
GitSignsVirtLnum = {
|
||||
'GitSignsDeleteVirtLn',
|
||||
desc = 'Used for line numbers in inline hunks previews.',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local function is_hl_set(hl_name)
|
||||
-- TODO: this only works with `set termguicolors`
|
||||
local exists, hl = pcall(vim.api.nvim_get_hl_by_name, hl_name, true)
|
||||
local color = hl.foreground or hl.background or hl.reverse
|
||||
return exists and color ~= nil
|
||||
-- TODO: this only works with `set termguicolors`
|
||||
local exists, hl = pcall(vim.api.nvim_get_hl_by_name, hl_name, true)
|
||||
local color = hl.foreground or hl.background or hl.reverse
|
||||
return exists and color ~= nil
|
||||
end
|
||||
|
||||
local function cmul(x, factor)
|
||||
if not x or factor == 1 then
|
||||
return x
|
||||
end
|
||||
if not x or factor == 1 then
|
||||
return x
|
||||
end
|
||||
|
||||
local r = math.floor(x / 2 ^ 16)
|
||||
local x1 = x - (r * 2 ^ 16)
|
||||
local g = math.floor(x1 / 2 ^ 8)
|
||||
local b = math.floor(x1 - (g * 2 ^ 8))
|
||||
return math.floor(math.floor(r * factor) * 2 ^ 16 + math.floor(g * factor) * 2 ^ 8 + math.floor(b * factor))
|
||||
local r = math.floor(x / 2 ^ 16)
|
||||
local x1 = x - (r * 2 ^ 16)
|
||||
local g = math.floor(x1 / 2 ^ 8)
|
||||
local b = math.floor(x1 - (g * 2 ^ 8))
|
||||
return math.floor(
|
||||
math.floor(r * factor) * 2 ^ 16 + math.floor(g * factor) * 2 ^ 8 + math.floor(b * factor)
|
||||
)
|
||||
end
|
||||
|
||||
local function dprintf(fmt, ...)
|
||||
require('gitsigns.debug.log').dprintf(fmt, ...)
|
||||
require('gitsigns.debug.log').dprintf(fmt, ...)
|
||||
end
|
||||
|
||||
local function derive(hl, hldef)
|
||||
for _, d in ipairs(hldef) do
|
||||
if is_hl_set(d) then
|
||||
dprintf('Deriving %s from %s', hl, d)
|
||||
if hldef.fg_factor or hldef.bg_factor then
|
||||
hldef.fg_factor = hldef.fg_factor or 1
|
||||
hldef.bg_factor = hldef.bg_factor or 1
|
||||
local dh = vim.api.nvim_get_hl_by_name(d, true)
|
||||
vim.api.nvim_set_hl(0, hl, {
|
||||
default = true,
|
||||
fg = cmul(dh.foreground, hldef.fg_factor),
|
||||
bg = cmul(dh.background, hldef.bg_factor),
|
||||
})
|
||||
else
|
||||
vim.api.nvim_set_hl(0, hl, { default = true, link = d })
|
||||
end
|
||||
return
|
||||
for _, d in ipairs(hldef) do
|
||||
if is_hl_set(d) then
|
||||
dprintf('Deriving %s from %s', hl, d)
|
||||
if hldef.fg_factor or hldef.bg_factor then
|
||||
hldef.fg_factor = hldef.fg_factor or 1
|
||||
hldef.bg_factor = hldef.bg_factor or 1
|
||||
local dh = vim.api.nvim_get_hl_by_name(d, true)
|
||||
vim.api.nvim_set_hl(0, hl, {
|
||||
default = true,
|
||||
fg = cmul(dh.foreground, hldef.fg_factor),
|
||||
bg = cmul(dh.background, hldef.bg_factor),
|
||||
})
|
||||
else
|
||||
vim.api.nvim_set_hl(0, hl, { default = true, link = d })
|
||||
end
|
||||
end
|
||||
if hldef[1] and not hldef.bg_factor and not hldef.fg_factor then
|
||||
-- No fallback found which is set. Just link to the first fallback
|
||||
-- if there are no modifiers
|
||||
dprintf('Deriving %s from %s', hl, hldef[1])
|
||||
vim.api.nvim_set_hl(0, hl, { default = true, link = hldef[1] })
|
||||
else
|
||||
dprintf('Could not derive %s', hl)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
if hldef[1] and not hldef.bg_factor and not hldef.fg_factor then
|
||||
-- No fallback found which is set. Just link to the first fallback
|
||||
-- if there are no modifiers
|
||||
dprintf('Deriving %s from %s', hl, hldef[1])
|
||||
vim.api.nvim_set_hl(0, hl, { default = true, link = hldef[1] })
|
||||
else
|
||||
dprintf('Could not derive %s', hl)
|
||||
end
|
||||
end
|
||||
|
||||
-- Setup a GitSign* highlight by deriving it from other potentially present
|
||||
-- highlights.
|
||||
M.setup_highlights = function()
|
||||
for _, hlg in ipairs(M.hls) do
|
||||
for hl, hldef in pairs(hlg) do
|
||||
if is_hl_set(hl) then
|
||||
-- Already defined
|
||||
dprintf('Highlight %s is already defined', hl)
|
||||
else
|
||||
derive(hl, hldef)
|
||||
end
|
||||
for _, hlg in ipairs(M.hls) do
|
||||
for hl, hldef in pairs(hlg) do
|
||||
if is_hl_set(hl) then
|
||||
-- Already defined
|
||||
dprintf('Highlight %s is already defined', hl)
|
||||
else
|
||||
derive(hl, hldef)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
540
lua/gitsigns/hunks.lua
generated
540
lua/gitsigns/hunks.lua
generated
|
@ -29,36 +29,9 @@ local min, max = math.min, math.max
|
|||
--- @field added Gitsigns.Hunk.Node
|
||||
--- @field removed Gitsigns.Hunk.Node
|
||||
|
||||
local M = {Node = {}, Hunk = {}, Hunk_Public = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- For internal use
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = { Node = {}, Hunk = {}, Hunk_Public = {} }
|
||||
|
||||
-- For internal use
|
||||
|
||||
local Hunk = M.Hunk
|
||||
|
||||
|
@ -68,18 +41,19 @@ local Hunk = M.Hunk
|
|||
--- @param new_count integer
|
||||
--- @return Gitsigns.Hunk.Hunk
|
||||
function M.create_hunk(old_start, old_count, new_start, new_count)
|
||||
return {
|
||||
removed = { start = old_start, count = old_count, lines = {} },
|
||||
added = { start = new_start, count = new_count, lines = {} },
|
||||
head = ('@@ -%d%s +%d%s @@'):format(
|
||||
old_start, old_count > 0 and ',' .. old_count or '',
|
||||
new_start, new_count > 0 and ',' .. new_count or ''),
|
||||
return {
|
||||
removed = { start = old_start, count = old_count, lines = {} },
|
||||
added = { start = new_start, count = new_count, lines = {} },
|
||||
head = ('@@ -%d%s +%d%s @@'):format(
|
||||
old_start,
|
||||
old_count > 0 and ',' .. old_count or '',
|
||||
new_start,
|
||||
new_count > 0 and ',' .. new_count or ''
|
||||
),
|
||||
|
||||
vend = new_start + math.max(new_count - 1, 0),
|
||||
type = new_count == 0 and 'delete' or
|
||||
old_count == 0 and 'add' or
|
||||
'change',
|
||||
}
|
||||
vend = new_start + math.max(new_count - 1, 0),
|
||||
type = new_count == 0 and 'delete' or old_count == 0 and 'add' or 'change',
|
||||
}
|
||||
end
|
||||
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
|
@ -87,97 +61,100 @@ end
|
|||
--- @param bot integer
|
||||
--- @return Gitsigns.Hunk.Hunk
|
||||
function M.create_partial_hunk(hunks, top, bot)
|
||||
local pretop, precount = top, bot - top + 1
|
||||
for _, h in ipairs(hunks) do
|
||||
local added_in_hunk = h.added.count - h.removed.count
|
||||
local pretop, precount = top, bot - top + 1
|
||||
for _, h in ipairs(hunks) do
|
||||
local added_in_hunk = h.added.count - h.removed.count
|
||||
|
||||
local added_in_range = 0
|
||||
if h.added.start >= top and h.vend <= bot then
|
||||
-- Range contains hunk
|
||||
added_in_range = added_in_hunk
|
||||
else
|
||||
local added_above_bot = max(0, bot + 1 - (h.added.start + h.removed.count))
|
||||
local added_above_top = max(0, top - (h.added.start + h.removed.count))
|
||||
local added_in_range = 0
|
||||
if h.added.start >= top and h.vend <= bot then
|
||||
-- Range contains hunk
|
||||
added_in_range = added_in_hunk
|
||||
else
|
||||
local added_above_bot = max(0, bot + 1 - (h.added.start + h.removed.count))
|
||||
local added_above_top = max(0, top - (h.added.start + h.removed.count))
|
||||
|
||||
if h.added.start >= top and h.added.start <= bot then
|
||||
-- Range top intersects hunk
|
||||
added_in_range = added_above_bot
|
||||
elseif h.vend >= top and h.vend <= bot then
|
||||
-- Range bottom intersects hunk
|
||||
added_in_range = added_in_hunk - added_above_top
|
||||
pretop = pretop - added_above_top
|
||||
elseif h.added.start <= top and h.vend >= bot then
|
||||
-- Range within hunk
|
||||
added_in_range = added_above_bot - added_above_top
|
||||
pretop = pretop - added_above_top
|
||||
end
|
||||
|
||||
if top > h.vend then
|
||||
pretop = pretop - added_in_hunk
|
||||
end
|
||||
if h.added.start >= top and h.added.start <= bot then
|
||||
-- Range top intersects hunk
|
||||
added_in_range = added_above_bot
|
||||
elseif h.vend >= top and h.vend <= bot then
|
||||
-- Range bottom intersects hunk
|
||||
added_in_range = added_in_hunk - added_above_top
|
||||
pretop = pretop - added_above_top
|
||||
elseif h.added.start <= top and h.vend >= bot then
|
||||
-- Range within hunk
|
||||
added_in_range = added_above_bot - added_above_top
|
||||
pretop = pretop - added_above_top
|
||||
end
|
||||
|
||||
precount = precount - added_in_range
|
||||
end
|
||||
if top > h.vend then
|
||||
pretop = pretop - added_in_hunk
|
||||
end
|
||||
end
|
||||
|
||||
if precount == 0 then
|
||||
pretop = pretop - 1
|
||||
end
|
||||
precount = precount - added_in_range
|
||||
end
|
||||
|
||||
return M.create_hunk(pretop, precount, top, bot - top + 1)
|
||||
if precount == 0 then
|
||||
pretop = pretop - 1
|
||||
end
|
||||
|
||||
return M.create_hunk(pretop, precount, top, bot - top + 1)
|
||||
end
|
||||
|
||||
--- @param hunk Gitsigns.Hunk.Hunk
|
||||
--- @param fileformat string
|
||||
--- @return string[]
|
||||
function M.patch_lines(hunk, fileformat)
|
||||
local lines = {} --- @type string[]
|
||||
for _, l in ipairs(hunk.removed.lines) do
|
||||
lines[#lines + 1] = '-' .. l
|
||||
end
|
||||
for _, l in ipairs(hunk.added.lines) do
|
||||
lines[#lines + 1] = '+' .. l
|
||||
end
|
||||
local lines = {} --- @type string[]
|
||||
for _, l in ipairs(hunk.removed.lines) do
|
||||
lines[#lines + 1] = '-' .. l
|
||||
end
|
||||
for _, l in ipairs(hunk.added.lines) do
|
||||
lines[#lines + 1] = '+' .. l
|
||||
end
|
||||
|
||||
if fileformat == 'dos' then
|
||||
lines = util.strip_cr(lines)
|
||||
end
|
||||
return lines
|
||||
if fileformat == 'dos' then
|
||||
lines = util.strip_cr(lines)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
--- @param line string
|
||||
--- @return Gitsigns.Hunk.Hunk
|
||||
function M.parse_diff_line(line)
|
||||
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
|
||||
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
|
||||
|
||||
-- diffKey: "-xx,n +yy"
|
||||
-- pre: {xx, n}, now: {yy}
|
||||
local pre, now = unpack(vim.tbl_map(function(s)
|
||||
return vim.split(string.sub(s, 2), ',')
|
||||
end, vim.split(diffkey, ' ')))
|
||||
-- diffKey: "-xx,n +yy"
|
||||
-- pre: {xx, n}, now: {yy}
|
||||
local pre, now = unpack(vim.tbl_map(function(s)
|
||||
return vim.split(string.sub(s, 2), ',')
|
||||
end, vim.split(diffkey, ' ')))
|
||||
|
||||
local hunk = M.create_hunk(
|
||||
tonumber(pre[1]), (tonumber(pre[2]) or 1),
|
||||
tonumber(now[1]), (tonumber(now[2]) or 1))
|
||||
local hunk = M.create_hunk(
|
||||
tonumber(pre[1]),
|
||||
(tonumber(pre[2]) or 1),
|
||||
tonumber(now[1]),
|
||||
(tonumber(now[2]) or 1)
|
||||
)
|
||||
|
||||
hunk.head = line
|
||||
hunk.head = line
|
||||
|
||||
return hunk
|
||||
return hunk
|
||||
end
|
||||
|
||||
--- @param hunk Gitsigns.Hunk.Hunk
|
||||
--- @return integer
|
||||
local function change_end(hunk)
|
||||
if hunk.added.count == 0 then
|
||||
-- delete
|
||||
return hunk.added.start
|
||||
elseif hunk.removed.count == 0 then
|
||||
-- add
|
||||
return hunk.added.start + hunk.added.count - 1
|
||||
else
|
||||
-- change
|
||||
return hunk.added.start + min(hunk.added.count, hunk.removed.count) - 1
|
||||
end
|
||||
if hunk.added.count == 0 then
|
||||
-- delete
|
||||
return hunk.added.start
|
||||
elseif hunk.removed.count == 0 then
|
||||
-- add
|
||||
return hunk.added.start + hunk.added.count - 1
|
||||
else
|
||||
-- change
|
||||
return hunk.added.start + min(hunk.added.count, hunk.removed.count) - 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate signs needed to be applied from a hunk for a specified line range.
|
||||
|
@ -187,47 +164,45 @@ end
|
|||
--- @param untracked boolean
|
||||
--- @return Gitsigns.Sign[]
|
||||
function M.calc_signs(hunk, min_lnum, max_lnum, untracked)
|
||||
assert(not untracked or hunk.type == 'add')
|
||||
min_lnum = min_lnum or 1
|
||||
max_lnum = max_lnum or math.huge
|
||||
local start, added, removed = hunk.added.start, hunk.added.count, hunk.removed.count
|
||||
assert(not untracked or hunk.type == 'add')
|
||||
min_lnum = min_lnum or 1
|
||||
max_lnum = max_lnum or math.huge
|
||||
local start, added, removed = hunk.added.start, hunk.added.count, hunk.removed.count
|
||||
|
||||
if hunk.type == 'delete' and start == 0 then
|
||||
if min_lnum <= 1 then
|
||||
-- topdelete signs get placed one row lower
|
||||
return { { type = 'topdelete', count = removed, lnum = 1 } }
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
if hunk.type == 'delete' and start == 0 then
|
||||
if min_lnum <= 1 then
|
||||
-- topdelete signs get placed one row lower
|
||||
return { { type = 'topdelete', count = removed, lnum = 1 } }
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
local signs = {}
|
||||
local signs = {}
|
||||
|
||||
local cend = change_end(hunk)
|
||||
local cend = change_end(hunk)
|
||||
|
||||
for lnum = max(start, min_lnum), min(cend, max_lnum) do
|
||||
local changedelete = hunk.type == 'change' and removed > added and lnum == cend
|
||||
for lnum = max(start, min_lnum), min(cend, max_lnum) do
|
||||
local changedelete = hunk.type == 'change' and removed > added and lnum == cend
|
||||
|
||||
signs[#signs + 1] = {
|
||||
type = changedelete and 'changedelete' or untracked and 'untracked' or hunk.type,
|
||||
count = lnum == start and (hunk.type == 'add' and added or removed),
|
||||
lnum = lnum,
|
||||
}
|
||||
end
|
||||
|
||||
if hunk.type == 'change' and added > removed and hunk.vend >= min_lnum and cend <= max_lnum then
|
||||
for lnum = max(cend, min_lnum), min(hunk.vend, max_lnum) do
|
||||
signs[#signs + 1] = {
|
||||
type = changedelete and 'changedelete' or
|
||||
untracked and 'untracked' or hunk.type,
|
||||
count = lnum == start and (hunk.type == 'add' and added or removed),
|
||||
lnum = lnum,
|
||||
type = 'add',
|
||||
count = lnum == hunk.vend and (added - removed),
|
||||
lnum = lnum,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if hunk.type == "change" and added > removed and
|
||||
hunk.vend >= min_lnum and cend <= max_lnum then
|
||||
for lnum = max(cend, min_lnum), min(hunk.vend, max_lnum) do
|
||||
signs[#signs + 1] = {
|
||||
type = 'add',
|
||||
count = lnum == hunk.vend and (added - removed),
|
||||
lnum = lnum,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return signs
|
||||
return signs
|
||||
end
|
||||
|
||||
--- @param relpath string
|
||||
|
@ -236,83 +211,86 @@ end
|
|||
--- @param invert boolean
|
||||
--- @return string[]
|
||||
function M.create_patch(relpath, hunks, mode_bits, invert)
|
||||
invert = invert or false
|
||||
invert = invert or false
|
||||
|
||||
local results = {
|
||||
string.format('diff --git a/%s b/%s', relpath, relpath),
|
||||
'index 000000..000000 ' .. mode_bits,
|
||||
'--- a/' .. relpath,
|
||||
'+++ b/' .. relpath,
|
||||
}
|
||||
local results = {
|
||||
string.format('diff --git a/%s b/%s', relpath, relpath),
|
||||
'index 000000..000000 ' .. mode_bits,
|
||||
'--- a/' .. relpath,
|
||||
'+++ b/' .. relpath,
|
||||
}
|
||||
|
||||
local offset = 0
|
||||
local offset = 0
|
||||
|
||||
for _, process_hunk in ipairs(hunks) do
|
||||
local start, pre_count, now_count =
|
||||
for _, process_hunk in ipairs(hunks) do
|
||||
local start, pre_count, now_count =
|
||||
process_hunk.removed.start, process_hunk.removed.count, process_hunk.added.count
|
||||
|
||||
if process_hunk.type == 'add' then
|
||||
start = start + 1
|
||||
end
|
||||
if process_hunk.type == 'add' then
|
||||
start = start + 1
|
||||
end
|
||||
|
||||
local pre_lines = process_hunk.removed.lines
|
||||
local now_lines = process_hunk.added.lines
|
||||
local pre_lines = process_hunk.removed.lines
|
||||
local now_lines = process_hunk.added.lines
|
||||
|
||||
if invert then
|
||||
pre_count, now_count = now_count, pre_count
|
||||
pre_lines, now_lines = now_lines, pre_lines
|
||||
end
|
||||
if invert then
|
||||
pre_count, now_count = now_count, pre_count
|
||||
pre_lines, now_lines = now_lines, pre_lines
|
||||
end
|
||||
|
||||
table.insert(results, string.format('@@ -%s,%s +%s,%s @@', start, pre_count, start + offset, now_count))
|
||||
for _, l in ipairs(pre_lines) do
|
||||
results[#results + 1] = '-' .. l
|
||||
end
|
||||
for _, l in ipairs(now_lines) do
|
||||
results[#results + 1] = '+' .. l
|
||||
end
|
||||
table.insert(
|
||||
results,
|
||||
string.format('@@ -%s,%s +%s,%s @@', start, pre_count, start + offset, now_count)
|
||||
)
|
||||
for _, l in ipairs(pre_lines) do
|
||||
results[#results + 1] = '-' .. l
|
||||
end
|
||||
for _, l in ipairs(now_lines) do
|
||||
results[#results + 1] = '+' .. l
|
||||
end
|
||||
|
||||
process_hunk.removed.start = start + offset
|
||||
offset = offset + (now_count - pre_count)
|
||||
end
|
||||
process_hunk.removed.start = start + offset
|
||||
offset = offset + (now_count - pre_count)
|
||||
end
|
||||
|
||||
return results
|
||||
return results
|
||||
end
|
||||
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @return Gitsigns.StatusObj
|
||||
function M.get_summary(hunks)
|
||||
local status = { added = 0, changed = 0, removed = 0 }
|
||||
local status = { added = 0, changed = 0, removed = 0 }
|
||||
|
||||
for _, hunk in ipairs(hunks or {}) do
|
||||
if hunk.type == 'add' then
|
||||
status.added = status.added + hunk.added.count
|
||||
elseif hunk.type == 'delete' then
|
||||
status.removed = status.removed + hunk.removed.count
|
||||
elseif hunk.type == 'change' then
|
||||
local add, remove = hunk.added.count, hunk.removed.count
|
||||
local delta = min(add, remove)
|
||||
status.changed = status.changed + delta
|
||||
status.added = status.added + add - delta
|
||||
status.removed = status.removed + remove - delta
|
||||
end
|
||||
end
|
||||
for _, hunk in ipairs(hunks or {}) do
|
||||
if hunk.type == 'add' then
|
||||
status.added = status.added + hunk.added.count
|
||||
elseif hunk.type == 'delete' then
|
||||
status.removed = status.removed + hunk.removed.count
|
||||
elseif hunk.type == 'change' then
|
||||
local add, remove = hunk.added.count, hunk.removed.count
|
||||
local delta = min(add, remove)
|
||||
status.changed = status.changed + delta
|
||||
status.added = status.added + add - delta
|
||||
status.removed = status.removed + remove - delta
|
||||
end
|
||||
end
|
||||
|
||||
return status
|
||||
return status
|
||||
end
|
||||
|
||||
--- @param lnum integer
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @return Gitsigns.Hunk.Hunk?, integer?
|
||||
function M.find_hunk(lnum, hunks)
|
||||
for i, hunk in ipairs(hunks or {}) do
|
||||
if lnum == 1 and hunk.added.start == 0 and hunk.vend == 0 then
|
||||
return hunk, i
|
||||
end
|
||||
for i, hunk in ipairs(hunks or {}) do
|
||||
if lnum == 1 and hunk.added.start == 0 and hunk.vend == 0 then
|
||||
return hunk, i
|
||||
end
|
||||
|
||||
if hunk.added.start <= lnum and hunk.vend >= lnum then
|
||||
return hunk, i
|
||||
end
|
||||
end
|
||||
if hunk.added.start <= lnum and hunk.vend >= lnum then
|
||||
return hunk, i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param lnum integer
|
||||
|
@ -321,73 +299,73 @@ end
|
|||
--- @param wrap boolean
|
||||
--- @return Gitsigns.Hunk.Hunk, integer
|
||||
function M.find_nearest_hunk(lnum, hunks, forwards, wrap)
|
||||
local ret --- @type Gitsigns.Hunk.Hunk
|
||||
local index --- @type integer
|
||||
local distance = math.huge
|
||||
if forwards then
|
||||
for i = 1, #hunks do
|
||||
local hunk = hunks[i]
|
||||
local dist = hunk.added.start - lnum
|
||||
if dist > 0 and dist < distance then
|
||||
distance = dist
|
||||
ret = hunk
|
||||
index = i
|
||||
end
|
||||
local ret --- @type Gitsigns.Hunk.Hunk
|
||||
local index --- @type integer
|
||||
local distance = math.huge
|
||||
if forwards then
|
||||
for i = 1, #hunks do
|
||||
local hunk = hunks[i]
|
||||
local dist = hunk.added.start - lnum
|
||||
if dist > 0 and dist < distance then
|
||||
distance = dist
|
||||
ret = hunk
|
||||
index = i
|
||||
end
|
||||
else
|
||||
for i = #hunks, 1, -1 do
|
||||
local hunk = hunks[i]
|
||||
local dist = lnum - hunk.vend
|
||||
if dist > 0 and dist < distance then
|
||||
distance = dist
|
||||
ret = hunk
|
||||
index = i
|
||||
end
|
||||
end
|
||||
else
|
||||
for i = #hunks, 1, -1 do
|
||||
local hunk = hunks[i]
|
||||
local dist = lnum - hunk.vend
|
||||
if dist > 0 and dist < distance then
|
||||
distance = dist
|
||||
ret = hunk
|
||||
index = i
|
||||
end
|
||||
end
|
||||
if not ret and wrap then
|
||||
index = forwards and 1 or #hunks
|
||||
ret = hunks[index]
|
||||
end
|
||||
return ret, index
|
||||
end
|
||||
end
|
||||
if not ret and wrap then
|
||||
index = forwards and 1 or #hunks
|
||||
ret = hunks[index]
|
||||
end
|
||||
return ret, index
|
||||
end
|
||||
|
||||
--- @param a Gitsigns.Hunk.Hunk[]
|
||||
--- @param b Gitsigns.Hunk.Hunk[]
|
||||
--- @return boolean
|
||||
function M.compare_heads(a, b)
|
||||
if (a == nil) ~= (b == nil) then
|
||||
if (a == nil) ~= (b == nil) then
|
||||
return true
|
||||
elseif a and #a ~= #b then
|
||||
return true
|
||||
end
|
||||
for i, ah in ipairs(a or {}) do
|
||||
if b[i].head ~= ah.head then
|
||||
return true
|
||||
elseif a and #a ~= #b then
|
||||
return true
|
||||
end
|
||||
for i, ah in ipairs(a or {}) do
|
||||
if b[i].head ~= ah.head then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- @param a Gitsigns.Hunk.Hunk
|
||||
--- @param b Gitsigns.Hunk.Hunk
|
||||
--- @return boolean
|
||||
local function compare_new(a, b)
|
||||
if a.added.start ~= b.added.start then
|
||||
if a.added.start ~= b.added.start then
|
||||
return false
|
||||
end
|
||||
|
||||
if a.added.count ~= b.added.count then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, a.added.count do
|
||||
if a.added.lines[i] ~= b.added.lines[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if a.added.count ~= b.added.count then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, a.added.count do
|
||||
if a.added.lines[i] ~= b.added.lines[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
return true
|
||||
end
|
||||
|
||||
--- Return hunks in a using b's hunks as a filter. Only compare the 'new' section
|
||||
|
@ -417,56 +395,56 @@ end
|
|||
--- @param b Gitsigns.Hunk.Hunk[]
|
||||
--- @return Gitsigns.Hunk.Hunk[]?
|
||||
function M.filter_common(a, b)
|
||||
if not a and not b then
|
||||
return
|
||||
end
|
||||
if not a and not b then
|
||||
return
|
||||
end
|
||||
|
||||
a, b = a or {}, b or {}
|
||||
local max_iter = math.max(#a, #b)
|
||||
a, b = a or {}, b or {}
|
||||
local max_iter = math.max(#a, #b)
|
||||
|
||||
local a_i = 1
|
||||
local b_i = 1
|
||||
local a_i = 1
|
||||
local b_i = 1
|
||||
|
||||
--- @type Gitsigns.Hunk.Hunk[]
|
||||
local ret = {}
|
||||
--- @type Gitsigns.Hunk.Hunk[]
|
||||
local ret = {}
|
||||
|
||||
for _ = 1, max_iter do
|
||||
local a_h, b_h = a[a_i], b[b_i]
|
||||
for _ = 1, max_iter do
|
||||
local a_h, b_h = a[a_i], b[b_i]
|
||||
|
||||
if not a_h then
|
||||
-- Reached the end of a
|
||||
break
|
||||
if not a_h then
|
||||
-- Reached the end of a
|
||||
break
|
||||
end
|
||||
|
||||
if not b_h then
|
||||
-- Reached the end of b, add remainder of a
|
||||
for i = a_i, #a do
|
||||
ret[#ret + 1] = a[i]
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
if not b_h then
|
||||
-- Reached the end of b, add remainder of a
|
||||
for i = a_i, #a do
|
||||
ret[#ret + 1] = a[i]
|
||||
end
|
||||
break
|
||||
if a_h.added.start > b_h.added.start then
|
||||
-- a pointer is ahead of b; increment b pointer
|
||||
b_i = b_i + 1
|
||||
elseif a_h.added.start < b_h.added.start then
|
||||
-- b pointer is ahead of a; add a_h to ret and increment a pointer
|
||||
ret[#ret + 1] = a_h
|
||||
a_i = a_i + 1
|
||||
else -- a_h.start == b_h.start
|
||||
-- a_h and b_h start on the same line, if hunks have the same changes then
|
||||
-- skip (filtered) otherwise add a_h to ret. Increment both hunk
|
||||
-- pointers
|
||||
-- TODO(lewis6991): Be smarter; if bh intercepts then break down ah.
|
||||
if not compare_new(a_h, b_h) then
|
||||
ret[#ret + 1] = a_h
|
||||
end
|
||||
a_i = a_i + 1
|
||||
b_i = b_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
if a_h.added.start > b_h.added.start then
|
||||
-- a pointer is ahead of b; increment b pointer
|
||||
b_i = b_i + 1
|
||||
elseif a_h.added.start < b_h.added.start then
|
||||
-- b pointer is ahead of a; add a_h to ret and increment a pointer
|
||||
ret[#ret + 1] = a_h
|
||||
a_i = a_i + 1
|
||||
else -- a_h.start == b_h.start
|
||||
-- a_h and b_h start on the same line, if hunks have the same changes then
|
||||
-- skip (filtered) otherwise add a_h to ret. Increment both hunk
|
||||
-- pointers
|
||||
-- TODO(lewis6991): Be smarter; if bh intercepts then break down ah.
|
||||
if not compare_new(a_h, b_h) then
|
||||
ret[#ret + 1] = a_h
|
||||
end
|
||||
a_i = a_i + 1
|
||||
b_i = b_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
return ret
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
760
lua/gitsigns/manager.lua
generated
760
lua/gitsigns/manager.lua
generated
|
@ -7,7 +7,7 @@ local CacheEntry = gs_cache.CacheEntry
|
|||
local cache = gs_cache.cache
|
||||
|
||||
local Signs = require('gitsigns.signs')
|
||||
local Status = require("gitsigns.status")
|
||||
local Status = require('gitsigns.status')
|
||||
|
||||
local debounce_trailing = require('gitsigns.debounce').debounce_trailing
|
||||
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
|
||||
|
@ -22,7 +22,7 @@ local util = require('gitsigns.util')
|
|||
local run_diff = require('gitsigns.diff')
|
||||
local uv = require('gitsigns.uv')
|
||||
|
||||
local gs_hunks = require("gitsigns.hunks")
|
||||
local gs_hunks = require('gitsigns.hunks')
|
||||
local Hunk = gs_hunks.Hunk
|
||||
|
||||
local config = require('gitsigns.config').config
|
||||
|
@ -34,155 +34,150 @@ local signs_staged
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local scheduler_if_buf_valid = awrap(function(buf, cb)
|
||||
vim.schedule(function()
|
||||
if vim.api.nvim_buf_is_valid(buf) then
|
||||
cb()
|
||||
end
|
||||
end)
|
||||
vim.schedule(function()
|
||||
if vim.api.nvim_buf_is_valid(buf) then
|
||||
cb()
|
||||
end
|
||||
end)
|
||||
end, 2)
|
||||
|
||||
local function apply_win_signs0(bufnr, signs, hunks, top, bot, clear, untracked)
|
||||
if clear then
|
||||
signs:remove(bufnr) -- Remove all signs
|
||||
end
|
||||
if clear then
|
||||
signs:remove(bufnr) -- Remove all signs
|
||||
end
|
||||
|
||||
for i, hunk in ipairs(hunks or {}) do
|
||||
-- To stop the sign column width changing too much, if there are signs to be
|
||||
-- added but none of them are visible in the window, then make sure to add at
|
||||
-- least one sign. Only do this on the first call after an update when we all
|
||||
-- the signs have been cleared.
|
||||
if clear and i == 1 then
|
||||
signs:add(bufnr, gs_hunks.calc_signs(hunk, hunk.added.start, hunk.added.start, untracked))
|
||||
end
|
||||
for i, hunk in ipairs(hunks or {}) do
|
||||
-- To stop the sign column width changing too much, if there are signs to be
|
||||
-- added but none of them are visible in the window, then make sure to add at
|
||||
-- least one sign. Only do this on the first call after an update when we all
|
||||
-- the signs have been cleared.
|
||||
if clear and i == 1 then
|
||||
signs:add(bufnr, gs_hunks.calc_signs(hunk, hunk.added.start, hunk.added.start, untracked))
|
||||
end
|
||||
|
||||
if top <= hunk.vend and bot >= hunk.added.start then
|
||||
signs:add(bufnr, gs_hunks.calc_signs(hunk, top, bot, untracked))
|
||||
end
|
||||
if hunk.added.start > bot then
|
||||
break
|
||||
end
|
||||
end
|
||||
if top <= hunk.vend and bot >= hunk.added.start then
|
||||
signs:add(bufnr, gs_hunks.calc_signs(hunk, top, bot, untracked))
|
||||
end
|
||||
if hunk.added.start > bot then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_win_signs(bufnr, top, bot, clear, untracked)
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
apply_win_signs0(bufnr, signs_normal, bcache.hunks, top, bot, clear, untracked)
|
||||
if signs_staged then
|
||||
apply_win_signs0(bufnr, signs_staged, bcache.hunks_staged, top, bot, clear, false)
|
||||
end
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
apply_win_signs0(bufnr, signs_normal, bcache.hunks, top, bot, clear, untracked)
|
||||
if signs_staged then
|
||||
apply_win_signs0(bufnr, signs_staged, bcache.hunks_staged, top, bot, clear, false)
|
||||
end
|
||||
end
|
||||
|
||||
M.on_lines = function(buf, first, last_orig, last_new)
|
||||
local bcache = cache[buf]
|
||||
if not bcache then
|
||||
dprint('Cache for buffer was nil. Detaching')
|
||||
return true
|
||||
end
|
||||
local bcache = cache[buf]
|
||||
if not bcache then
|
||||
dprint('Cache for buffer was nil. Detaching')
|
||||
return true
|
||||
end
|
||||
|
||||
signs_normal:on_lines(buf, first, last_orig, last_new)
|
||||
if signs_staged then
|
||||
signs_staged:on_lines(buf, first, last_orig, last_new)
|
||||
end
|
||||
signs_normal:on_lines(buf, first, last_orig, last_new)
|
||||
if signs_staged then
|
||||
signs_staged:on_lines(buf, first, last_orig, last_new)
|
||||
end
|
||||
|
||||
-- Signs in changed regions get invalidated so we need to force a redraw if
|
||||
-- any signs get removed.
|
||||
if bcache.hunks and signs_normal:contains(buf, first, last_new) then
|
||||
-- Signs in changed regions get invalidated so we need to force a redraw if
|
||||
-- any signs get removed.
|
||||
if bcache.hunks and signs_normal:contains(buf, first, last_new) then
|
||||
-- Force a sign redraw on the next update (fixes #521)
|
||||
bcache.force_next_update = true
|
||||
end
|
||||
|
||||
if signs_staged then
|
||||
if bcache.hunks_staged and signs_staged:contains(buf, first, last_new) then
|
||||
-- Force a sign redraw on the next update (fixes #521)
|
||||
bcache.force_next_update = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if signs_staged then
|
||||
if bcache.hunks_staged and signs_staged:contains(buf, first, last_new) then
|
||||
-- Force a sign redraw on the next update (fixes #521)
|
||||
bcache.force_next_update = true
|
||||
end
|
||||
end
|
||||
|
||||
M.update_debounced(buf, cache[buf])
|
||||
M.update_debounced(buf, cache[buf])
|
||||
end
|
||||
|
||||
local ns = api.nvim_create_namespace('gitsigns')
|
||||
|
||||
local function apply_word_diff(bufnr, row)
|
||||
-- Don't run on folded lines
|
||||
if vim.fn.foldclosed(row + 1) ~= -1 then
|
||||
return
|
||||
end
|
||||
-- Don't run on folded lines
|
||||
if vim.fn.foldclosed(row + 1) ~= -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
if not bcache or not bcache.hunks then
|
||||
return
|
||||
end
|
||||
if not bcache or not bcache.hunks then
|
||||
return
|
||||
end
|
||||
|
||||
local line = api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1]
|
||||
if not line then
|
||||
-- Invalid line
|
||||
return
|
||||
end
|
||||
local line = api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1]
|
||||
if not line then
|
||||
-- Invalid line
|
||||
return
|
||||
end
|
||||
|
||||
local lnum = row + 1
|
||||
local lnum = row + 1
|
||||
|
||||
local hunk = gs_hunks.find_hunk(lnum, bcache.hunks)
|
||||
if not hunk then
|
||||
-- No hunk at line
|
||||
return
|
||||
end
|
||||
local hunk = gs_hunks.find_hunk(lnum, bcache.hunks)
|
||||
if not hunk then
|
||||
-- No hunk at line
|
||||
return
|
||||
end
|
||||
|
||||
if hunk.added.count ~= hunk.removed.count then
|
||||
-- Only word diff if added count == removed
|
||||
return
|
||||
end
|
||||
if hunk.added.count ~= hunk.removed.count then
|
||||
-- Only word diff if added count == removed
|
||||
return
|
||||
end
|
||||
|
||||
local pos = lnum - hunk.added.start + 1
|
||||
local pos = lnum - hunk.added.start + 1
|
||||
|
||||
local added_line = hunk.added.lines[pos]
|
||||
local removed_line = hunk.removed.lines[pos]
|
||||
local added_line = hunk.added.lines[pos]
|
||||
local removed_line = hunk.removed.lines[pos]
|
||||
|
||||
local _, added_regions = require('gitsigns.diff_int').run_word_diff({ removed_line }, { added_line })
|
||||
local _, added_regions = require('gitsigns.diff_int').run_word_diff(
|
||||
{ removed_line },
|
||||
{ added_line }
|
||||
)
|
||||
|
||||
local cols = #line
|
||||
local cols = #line
|
||||
|
||||
for _, region in ipairs(added_regions) do
|
||||
local rtype, scol, ecol = region[2], region[3] - 1, region[4] - 1
|
||||
if ecol == scol then
|
||||
-- Make sure region is at least 1 column wide so deletes can be shown
|
||||
ecol = scol + 1
|
||||
end
|
||||
for _, region in ipairs(added_regions) do
|
||||
local rtype, scol, ecol = region[2], region[3] - 1, region[4] - 1
|
||||
if ecol == scol then
|
||||
-- Make sure region is at least 1 column wide so deletes can be shown
|
||||
ecol = scol + 1
|
||||
end
|
||||
|
||||
local hl_group = rtype == 'add' and 'GitSignsAddLnInline' or
|
||||
rtype == 'change' and 'GitSignsChangeLnInline' or
|
||||
'GitSignsDeleteLnInline'
|
||||
local hl_group = rtype == 'add' and 'GitSignsAddLnInline'
|
||||
or rtype == 'change' and 'GitSignsChangeLnInline'
|
||||
or 'GitSignsDeleteLnInline'
|
||||
|
||||
local opts = {
|
||||
ephemeral = true,
|
||||
priority = 1000,
|
||||
}
|
||||
local opts = {
|
||||
ephemeral = true,
|
||||
priority = 1000,
|
||||
}
|
||||
|
||||
if ecol > cols and ecol == scol + 1 then
|
||||
-- delete on last column, use virtual text instead
|
||||
opts.virt_text = { { ' ', hl_group } }
|
||||
opts.virt_text_pos = 'overlay'
|
||||
else
|
||||
opts.end_col = ecol
|
||||
opts.hl_group = hl_group
|
||||
end
|
||||
if ecol > cols and ecol == scol + 1 then
|
||||
-- delete on last column, use virtual text instead
|
||||
opts.virt_text = { { ' ', hl_group } }
|
||||
opts.virt_text_pos = 'overlay'
|
||||
else
|
||||
opts.end_col = ecol
|
||||
opts.hl_group = hl_group
|
||||
end
|
||||
|
||||
api.nvim_buf_set_extmark(bufnr, ns, row, scol, opts)
|
||||
api.nvim__buf_redraw_range(bufnr, row, row + 1)
|
||||
end
|
||||
api.nvim_buf_set_extmark(bufnr, ns, row, scol, opts)
|
||||
api.nvim__buf_redraw_range(bufnr, row, row + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local ns_rm = api.nvim_create_namespace('gitsigns_removed')
|
||||
|
@ -190,182 +185,185 @@ local ns_rm = api.nvim_create_namespace('gitsigns_removed')
|
|||
local VIRT_LINE_LEN = 300
|
||||
|
||||
local function clear_deleted(bufnr)
|
||||
local marks = api.nvim_buf_get_extmarks(bufnr, ns_rm, 0, -1, {})
|
||||
for _, mark in ipairs(marks) do
|
||||
api.nvim_buf_del_extmark(bufnr, ns_rm, mark[1])
|
||||
end
|
||||
local marks = api.nvim_buf_get_extmarks(bufnr, ns_rm, 0, -1, {})
|
||||
for _, mark in ipairs(marks) do
|
||||
api.nvim_buf_del_extmark(bufnr, ns_rm, mark[1])
|
||||
end
|
||||
end
|
||||
|
||||
function M.show_deleted(bufnr, nsd, hunk)
|
||||
local virt_lines = {}
|
||||
local virt_lines = {}
|
||||
|
||||
for i, line in ipairs(hunk.removed.lines) do
|
||||
local vline = {}
|
||||
local last_ecol = 1
|
||||
for i, line in ipairs(hunk.removed.lines) do
|
||||
local vline = {}
|
||||
local last_ecol = 1
|
||||
|
||||
if config.word_diff then
|
||||
local regions = require('gitsigns.diff_int').run_word_diff(
|
||||
{ hunk.removed.lines[i] }, { hunk.added.lines[i] })
|
||||
if config.word_diff then
|
||||
local regions = require('gitsigns.diff_int').run_word_diff(
|
||||
{ hunk.removed.lines[i] },
|
||||
{ hunk.added.lines[i] }
|
||||
)
|
||||
|
||||
for _, region in ipairs(regions) do
|
||||
local rline, scol, ecol = region[1], region[3], region[4]
|
||||
if rline > 1 then
|
||||
break
|
||||
end
|
||||
vline[#vline + 1] = { line:sub(last_ecol, scol - 1), 'GitSignsDeleteVirtLn' }
|
||||
vline[#vline + 1] = { line:sub(scol, ecol - 1), 'GitSignsDeleteVirtLnInline' }
|
||||
last_ecol = ecol
|
||||
end
|
||||
for _, region in ipairs(regions) do
|
||||
local rline, scol, ecol = region[1], region[3], region[4]
|
||||
if rline > 1 then
|
||||
break
|
||||
end
|
||||
vline[#vline + 1] = { line:sub(last_ecol, scol - 1), 'GitSignsDeleteVirtLn' }
|
||||
vline[#vline + 1] = { line:sub(scol, ecol - 1), 'GitSignsDeleteVirtLnInline' }
|
||||
last_ecol = ecol
|
||||
end
|
||||
end
|
||||
|
||||
if #line > 0 then
|
||||
vline[#vline + 1] = { line:sub(last_ecol, -1), 'GitSignsDeleteVirtLn' }
|
||||
end
|
||||
if #line > 0 then
|
||||
vline[#vline + 1] = { line:sub(last_ecol, -1), 'GitSignsDeleteVirtLn' }
|
||||
end
|
||||
|
||||
-- Add extra padding so the entire line is highlighted
|
||||
local padding = string.rep(' ', VIRT_LINE_LEN - #line)
|
||||
vline[#vline + 1] = { padding, 'GitSignsDeleteVirtLn' }
|
||||
-- Add extra padding so the entire line is highlighted
|
||||
local padding = string.rep(' ', VIRT_LINE_LEN - #line)
|
||||
vline[#vline + 1] = { padding, 'GitSignsDeleteVirtLn' }
|
||||
|
||||
virt_lines[i] = vline
|
||||
end
|
||||
virt_lines[i] = vline
|
||||
end
|
||||
|
||||
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
|
||||
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
|
||||
|
||||
local row = topdelete and 0 or hunk.added.start - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
|
||||
virt_lines = virt_lines,
|
||||
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
|
||||
virt_lines_above = hunk.type ~= 'delete' or topdelete,
|
||||
})
|
||||
local row = topdelete and 0 or hunk.added.start - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
|
||||
virt_lines = virt_lines,
|
||||
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
|
||||
virt_lines_above = hunk.type ~= 'delete' or topdelete,
|
||||
})
|
||||
end
|
||||
|
||||
function M.show_deleted_in_float(bufnr, nsd, hunk)
|
||||
local virt_lines = {}
|
||||
for i = 1, hunk.removed.count do
|
||||
virt_lines[i] = { { '', 'Normal' } }
|
||||
end
|
||||
local virt_lines = {}
|
||||
for i = 1, hunk.removed.count do
|
||||
virt_lines[i] = { { '', 'Normal' } }
|
||||
end
|
||||
|
||||
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
|
||||
local virt_lines_above = hunk.type ~= 'delete' or topdelete
|
||||
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
|
||||
local virt_lines_above = hunk.type ~= 'delete' or topdelete
|
||||
|
||||
local row = topdelete and 0 or hunk.added.start - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
|
||||
virt_lines = virt_lines,
|
||||
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
|
||||
virt_lines_above = virt_lines_above,
|
||||
})
|
||||
local row = topdelete and 0 or hunk.added.start - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
|
||||
virt_lines = virt_lines,
|
||||
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
|
||||
virt_lines_above = virt_lines_above,
|
||||
})
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
local pbufnr = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_lines(pbufnr, 0, -1, false, bcache.compare_text)
|
||||
local bcache = cache[bufnr]
|
||||
local pbufnr = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_lines(pbufnr, 0, -1, false, bcache.compare_text)
|
||||
|
||||
local cwin = api.nvim_get_current_win()
|
||||
local width = api.nvim_win_get_width(0)
|
||||
local cwin = api.nvim_get_current_win()
|
||||
local width = api.nvim_win_get_width(0)
|
||||
|
||||
local opts = {
|
||||
relative = 'win',
|
||||
win = cwin,
|
||||
width = width,
|
||||
height = hunk.removed.count,
|
||||
anchor = 'SW',
|
||||
bufpos = { hunk.added.start, 0 },
|
||||
}
|
||||
local opts = {
|
||||
relative = 'win',
|
||||
win = cwin,
|
||||
width = width,
|
||||
height = hunk.removed.count,
|
||||
anchor = 'SW',
|
||||
bufpos = { hunk.added.start, 0 },
|
||||
}
|
||||
|
||||
local bufpos_offset = virt_lines_above and not topdelete and 1 or 0
|
||||
opts.bufpos[1] = opts.bufpos[1] - bufpos_offset
|
||||
local bufpos_offset = virt_lines_above and not topdelete and 1 or 0
|
||||
opts.bufpos[1] = opts.bufpos[1] - bufpos_offset
|
||||
|
||||
local winid = api.nvim_open_win(pbufnr, false, opts)
|
||||
local winid = api.nvim_open_win(pbufnr, false, opts)
|
||||
|
||||
-- Align buffer text by accounting for differences in the statuscolumn
|
||||
local textoff = vim.fn.getwininfo(cwin)[1].textoff
|
||||
local ptextoff = vim.fn.getwininfo(winid)[1].textoff
|
||||
local col_offset = textoff - ptextoff
|
||||
-- Align buffer text by accounting for differences in the statuscolumn
|
||||
local textoff = vim.fn.getwininfo(cwin)[1].textoff
|
||||
local ptextoff = vim.fn.getwininfo(winid)[1].textoff
|
||||
local col_offset = textoff - ptextoff
|
||||
|
||||
if col_offset ~= 0 then
|
||||
opts.width = opts.width - col_offset
|
||||
opts.bufpos[2] = opts.bufpos[2] + col_offset
|
||||
api.nvim_win_set_config(winid, opts)
|
||||
end
|
||||
if col_offset ~= 0 then
|
||||
opts.width = opts.width - col_offset
|
||||
opts.bufpos[2] = opts.bufpos[2] + col_offset
|
||||
api.nvim_win_set_config(winid, opts)
|
||||
end
|
||||
|
||||
vim.bo[pbufnr].filetype = vim.bo[bufnr].filetype
|
||||
vim.bo[pbufnr].bufhidden = 'wipe'
|
||||
vim.wo[winid].scrolloff = 0
|
||||
vim.wo[winid].relativenumber = false
|
||||
vim.bo[pbufnr].filetype = vim.bo[bufnr].filetype
|
||||
vim.bo[pbufnr].bufhidden = 'wipe'
|
||||
vim.wo[winid].scrolloff = 0
|
||||
vim.wo[winid].relativenumber = false
|
||||
|
||||
api.nvim_win_call(winid, function()
|
||||
-- Expand folds
|
||||
vim.cmd('normal ' .. 'zR')
|
||||
api.nvim_win_call(winid, function()
|
||||
-- Expand folds
|
||||
vim.cmd('normal ' .. 'zR')
|
||||
|
||||
-- Navigate to hunk
|
||||
vim.cmd('normal ' .. tostring(hunk.removed.start) .. 'gg')
|
||||
vim.cmd('normal ' .. vim.api.nvim_replace_termcodes('z<CR>', true, false, true))
|
||||
end)
|
||||
-- Navigate to hunk
|
||||
vim.cmd('normal ' .. tostring(hunk.removed.start) .. 'gg')
|
||||
vim.cmd('normal ' .. vim.api.nvim_replace_termcodes('z<CR>', true, false, true))
|
||||
end)
|
||||
|
||||
-- Apply highlights
|
||||
-- Apply highlights
|
||||
|
||||
for i = hunk.removed.start, hunk.removed.start + hunk.removed.count do
|
||||
api.nvim_buf_set_extmark(pbufnr, nsd, i - 1, 0, {
|
||||
hl_group = 'GitSignsDeleteVirtLn',
|
||||
hl_eol = true,
|
||||
end_row = i,
|
||||
priority = 1000,
|
||||
})
|
||||
end
|
||||
for i = hunk.removed.start, hunk.removed.start + hunk.removed.count do
|
||||
api.nvim_buf_set_extmark(pbufnr, nsd, i - 1, 0, {
|
||||
hl_group = 'GitSignsDeleteVirtLn',
|
||||
hl_eol = true,
|
||||
end_row = i,
|
||||
priority = 1000,
|
||||
})
|
||||
end
|
||||
|
||||
local removed_regions =
|
||||
require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
|
||||
local removed_regions =
|
||||
require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
|
||||
|
||||
for _, region in ipairs(removed_regions) do
|
||||
local start_row = (hunk.removed.start - 1) + (region[1] - 1)
|
||||
local start_col = region[3] - 1
|
||||
local end_col = region[4] - 1
|
||||
api.nvim_buf_set_extmark(pbufnr, nsd, start_row, start_col, {
|
||||
hl_group = 'GitSignsDeleteVirtLnInline',
|
||||
end_col = end_col,
|
||||
end_row = start_row,
|
||||
priority = 1001,
|
||||
})
|
||||
end
|
||||
for _, region in ipairs(removed_regions) do
|
||||
local start_row = (hunk.removed.start - 1) + (region[1] - 1)
|
||||
local start_col = region[3] - 1
|
||||
local end_col = region[4] - 1
|
||||
api.nvim_buf_set_extmark(pbufnr, nsd, start_row, start_col, {
|
||||
hl_group = 'GitSignsDeleteVirtLnInline',
|
||||
end_col = end_col,
|
||||
end_row = start_row,
|
||||
priority = 1001,
|
||||
})
|
||||
end
|
||||
|
||||
return winid
|
||||
return winid
|
||||
end
|
||||
|
||||
function M.show_added(bufnr, nsw, hunk)
|
||||
local start_row = hunk.added.start - 1
|
||||
local start_row = hunk.added.start - 1
|
||||
|
||||
for offset = 0, hunk.added.count - 1 do
|
||||
local row = start_row + offset
|
||||
api.nvim_buf_set_extmark(bufnr, nsw, row, 0, {
|
||||
end_row = row + 1,
|
||||
hl_group = 'GitSignsAddPreview',
|
||||
hl_eol = true,
|
||||
priority = 1000,
|
||||
})
|
||||
end
|
||||
for offset = 0, hunk.added.count - 1 do
|
||||
local row = start_row + offset
|
||||
api.nvim_buf_set_extmark(bufnr, nsw, row, 0, {
|
||||
end_row = row + 1,
|
||||
hl_group = 'GitSignsAddPreview',
|
||||
hl_eol = true,
|
||||
priority = 1000,
|
||||
})
|
||||
end
|
||||
|
||||
local _, added_regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
|
||||
local _, added_regions =
|
||||
require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
|
||||
|
||||
for _, region in ipairs(added_regions) do
|
||||
local offset, rtype, scol, ecol = region[1] - 1, region[2], region[3] - 1, region[4] - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsw, start_row + offset, scol, {
|
||||
end_col = ecol,
|
||||
hl_group = rtype == 'add' and 'GitSignsAddInline' or
|
||||
rtype == 'change' and 'GitSignsChangeInline' or
|
||||
'GitSignsDeleteInline',
|
||||
priority = 1001,
|
||||
})
|
||||
end
|
||||
for _, region in ipairs(added_regions) do
|
||||
local offset, rtype, scol, ecol = region[1] - 1, region[2], region[3] - 1, region[4] - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsw, start_row + offset, scol, {
|
||||
end_col = ecol,
|
||||
hl_group = rtype == 'add' and 'GitSignsAddInline'
|
||||
or rtype == 'change' and 'GitSignsChangeInline'
|
||||
or 'GitSignsDeleteInline',
|
||||
priority = 1001,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function update_show_deleted(bufnr)
|
||||
local bcache = cache[bufnr]
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
clear_deleted(bufnr)
|
||||
if config.show_deleted then
|
||||
for _, hunk in ipairs(bcache.hunks or {}) do
|
||||
M.show_deleted(bufnr, ns_rm, hunk)
|
||||
end
|
||||
end
|
||||
clear_deleted(bufnr)
|
||||
if config.show_deleted then
|
||||
for _, hunk in ipairs(bcache.hunks or {}) do
|
||||
M.show_deleted(bufnr, ns_rm, hunk)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local update_cnt = 0
|
||||
|
@ -375,137 +373,142 @@ local update_cnt = 0
|
|||
-- whilst another one is in progress. If this happens then schedule another
|
||||
-- update after the current one has completed.
|
||||
M.update = throttle_by_id(function(bufnr, bcache)
|
||||
local __FUNC__ = 'update'
|
||||
bcache = bcache or cache[bufnr]
|
||||
if not bcache then
|
||||
eprint('Cache for buffer ' .. bufnr .. ' was nil')
|
||||
return
|
||||
end
|
||||
local old_hunks, old_hunks_staged = bcache.hunks, bcache.hunks_staged
|
||||
bcache.hunks, bcache.hunks_staged = nil, nil
|
||||
local __FUNC__ = 'update'
|
||||
bcache = bcache or cache[bufnr]
|
||||
if not bcache then
|
||||
eprint('Cache for buffer ' .. bufnr .. ' was nil')
|
||||
return
|
||||
end
|
||||
local old_hunks, old_hunks_staged = bcache.hunks, bcache.hunks_staged
|
||||
bcache.hunks, bcache.hunks_staged = nil, nil
|
||||
|
||||
scheduler_if_buf_valid(bufnr)
|
||||
local buftext = util.buf_lines(bufnr)
|
||||
local git_obj = bcache.git_obj
|
||||
scheduler_if_buf_valid(bufnr)
|
||||
local buftext = util.buf_lines(bufnr)
|
||||
local git_obj = bcache.git_obj
|
||||
|
||||
if not bcache.compare_text or config._refresh_staged_on_update then
|
||||
bcache.compare_text = git_obj:get_show_text(bcache:get_compare_rev())
|
||||
end
|
||||
if not bcache.compare_text or config._refresh_staged_on_update then
|
||||
bcache.compare_text = git_obj:get_show_text(bcache:get_compare_rev())
|
||||
end
|
||||
|
||||
bcache.hunks = run_diff(bcache.compare_text, buftext)
|
||||
bcache.hunks = run_diff(bcache.compare_text, buftext)
|
||||
|
||||
if config._signs_staged_enable then
|
||||
if not bcache.compare_text_head or config._refresh_staged_on_update then
|
||||
bcache.compare_text_head = git_obj:get_show_text(bcache:get_staged_compare_rev())
|
||||
end
|
||||
local hunks_head = run_diff(bcache.compare_text_head, buftext)
|
||||
bcache.hunks_staged = gs_hunks.filter_common(hunks_head, bcache.hunks)
|
||||
end
|
||||
if config._signs_staged_enable then
|
||||
if not bcache.compare_text_head or config._refresh_staged_on_update then
|
||||
bcache.compare_text_head = git_obj:get_show_text(bcache:get_staged_compare_rev())
|
||||
end
|
||||
local hunks_head = run_diff(bcache.compare_text_head, buftext)
|
||||
bcache.hunks_staged = gs_hunks.filter_common(hunks_head, bcache.hunks)
|
||||
end
|
||||
|
||||
scheduler_if_buf_valid(bufnr)
|
||||
scheduler_if_buf_valid(bufnr)
|
||||
|
||||
-- Note the decoration provider may have invalidated bcache.hunks at this
|
||||
-- point
|
||||
if bcache.force_next_update or gs_hunks.compare_heads(bcache.hunks, old_hunks) or
|
||||
gs_hunks.compare_heads(bcache.hunks_staged, old_hunks_staged) then
|
||||
-- Apply signs to the window. Other signs will be added by the decoration
|
||||
-- provider as they are drawn.
|
||||
apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true, git_obj.object_name == nil)
|
||||
-- Note the decoration provider may have invalidated bcache.hunks at this
|
||||
-- point
|
||||
if
|
||||
bcache.force_next_update
|
||||
or gs_hunks.compare_heads(bcache.hunks, old_hunks)
|
||||
or gs_hunks.compare_heads(bcache.hunks_staged, old_hunks_staged)
|
||||
then
|
||||
-- Apply signs to the window. Other signs will be added by the decoration
|
||||
-- provider as they are drawn.
|
||||
apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true, git_obj.object_name == nil)
|
||||
|
||||
update_show_deleted(bufnr)
|
||||
bcache.force_next_update = false
|
||||
update_show_deleted(bufnr)
|
||||
bcache.force_next_update = false
|
||||
|
||||
api.nvim_exec_autocmds('User', {
|
||||
pattern = 'GitSignsUpdate',
|
||||
modeline = false,
|
||||
})
|
||||
end
|
||||
api.nvim_exec_autocmds('User', {
|
||||
pattern = 'GitSignsUpdate',
|
||||
modeline = false,
|
||||
})
|
||||
end
|
||||
|
||||
local summary = gs_hunks.get_summary(bcache.hunks)
|
||||
summary.head = git_obj.repo.abbrev_head
|
||||
Status:update(bufnr, summary)
|
||||
local summary = gs_hunks.get_summary(bcache.hunks)
|
||||
summary.head = git_obj.repo.abbrev_head
|
||||
Status:update(bufnr, summary)
|
||||
|
||||
update_cnt = update_cnt + 1
|
||||
update_cnt = update_cnt + 1
|
||||
|
||||
dprintf('updates: %s, jobs: %s', update_cnt, subprocess.job_cnt)
|
||||
dprintf('updates: %s, jobs: %s', update_cnt, subprocess.job_cnt)
|
||||
end, true)
|
||||
|
||||
M.detach = function(bufnr, keep_signs)
|
||||
if not keep_signs then
|
||||
-- Remove all signs
|
||||
signs_normal:remove(bufnr)
|
||||
if signs_staged then
|
||||
signs_staged:remove(bufnr)
|
||||
end
|
||||
end
|
||||
if not keep_signs then
|
||||
-- Remove all signs
|
||||
signs_normal:remove(bufnr)
|
||||
if signs_staged then
|
||||
signs_staged:remove(bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_moved(bufnr, bcache, old_relpath)
|
||||
local git_obj = bcache.git_obj
|
||||
local do_update = false
|
||||
local git_obj = bcache.git_obj
|
||||
local do_update = false
|
||||
|
||||
local new_name = git_obj:has_moved()
|
||||
if new_name then
|
||||
dprintf('File moved to %s', new_name)
|
||||
git_obj.relpath = new_name
|
||||
if not git_obj.orig_relpath then
|
||||
git_obj.orig_relpath = old_relpath
|
||||
end
|
||||
local new_name = git_obj:has_moved()
|
||||
if new_name then
|
||||
dprintf('File moved to %s', new_name)
|
||||
git_obj.relpath = new_name
|
||||
if not git_obj.orig_relpath then
|
||||
git_obj.orig_relpath = old_relpath
|
||||
end
|
||||
do_update = true
|
||||
elseif git_obj.orig_relpath then
|
||||
local orig_file = git_obj.repo.toplevel .. util.path_sep .. git_obj.orig_relpath
|
||||
if git_obj:file_info(orig_file).relpath then
|
||||
dprintf('Moved file reset')
|
||||
git_obj.relpath = git_obj.orig_relpath
|
||||
git_obj.orig_relpath = nil
|
||||
do_update = true
|
||||
elseif git_obj.orig_relpath then
|
||||
local orig_file = git_obj.repo.toplevel .. util.path_sep .. git_obj.orig_relpath
|
||||
if git_obj:file_info(orig_file).relpath then
|
||||
dprintf('Moved file reset')
|
||||
git_obj.relpath = git_obj.orig_relpath
|
||||
git_obj.orig_relpath = nil
|
||||
do_update = true
|
||||
end
|
||||
else
|
||||
-- File removed from index, do nothing
|
||||
end
|
||||
end
|
||||
else
|
||||
-- File removed from index, do nothing
|
||||
end
|
||||
|
||||
if do_update then
|
||||
git_obj.file = git_obj.repo.toplevel .. util.path_sep .. git_obj.relpath
|
||||
bcache.file = git_obj.file
|
||||
git_obj:update_file_info()
|
||||
scheduler()
|
||||
if do_update then
|
||||
git_obj.file = git_obj.repo.toplevel .. util.path_sep .. git_obj.relpath
|
||||
bcache.file = git_obj.file
|
||||
git_obj:update_file_info()
|
||||
scheduler()
|
||||
|
||||
local bufexists = vim.fn.bufexists(bcache.file) == 1
|
||||
local old_name = api.nvim_buf_get_name(bufnr)
|
||||
local bufexists = vim.fn.bufexists(bcache.file) == 1
|
||||
local old_name = api.nvim_buf_get_name(bufnr)
|
||||
|
||||
if not bufexists then
|
||||
util.buf_rename(bufnr, bcache.file)
|
||||
end
|
||||
if not bufexists then
|
||||
util.buf_rename(bufnr, bcache.file)
|
||||
end
|
||||
|
||||
local msg = bufexists and 'Cannot rename' or 'Renamed'
|
||||
dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file)
|
||||
end
|
||||
local msg = bufexists and 'Cannot rename' or 'Renamed'
|
||||
dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M.watch_gitdir(bufnr, gitdir)
|
||||
if not config.watch_gitdir.enable then
|
||||
return
|
||||
end
|
||||
if not config.watch_gitdir.enable then
|
||||
return
|
||||
end
|
||||
|
||||
dprintf('Watching git dir')
|
||||
local w = uv.new_fs_poll(true)
|
||||
w:start(gitdir, config.watch_gitdir.interval, void(function(err)
|
||||
dprintf('Watching git dir')
|
||||
local w = uv.new_fs_poll(true)
|
||||
w:start(
|
||||
gitdir,
|
||||
config.watch_gitdir.interval,
|
||||
void(function(err)
|
||||
local __FUNC__ = 'watcher_cb'
|
||||
if err then
|
||||
dprintf('Git dir update error: %s', err)
|
||||
return
|
||||
dprintf('Git dir update error: %s', err)
|
||||
return
|
||||
end
|
||||
dprint('Git dir update')
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
if not bcache then
|
||||
-- Very occasionally an external git operation may cause the buffer to
|
||||
-- detach and update the git dir simultaneously. When this happens this
|
||||
-- handler will trigger but there will be no cache.
|
||||
dprint('Has detached, aborting')
|
||||
return
|
||||
-- Very occasionally an external git operation may cause the buffer to
|
||||
-- detach and update the git dir simultaneously. When this happens this
|
||||
-- handler will trigger but there will be no cache.
|
||||
dprint('Has detached, aborting')
|
||||
return
|
||||
end
|
||||
|
||||
local git_obj = bcache.git_obj
|
||||
|
@ -521,62 +524,63 @@ function M.watch_gitdir(bufnr, gitdir)
|
|||
git_obj:update_file_info()
|
||||
|
||||
if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then
|
||||
-- File was tracked but is no longer tracked. Must of been removed or
|
||||
-- moved. Check if it was moved and switch to it.
|
||||
handle_moved(bufnr, bcache, old_relpath)
|
||||
-- File was tracked but is no longer tracked. Must of been removed or
|
||||
-- moved. Check if it was moved and switch to it.
|
||||
handle_moved(bufnr, bcache, old_relpath)
|
||||
end
|
||||
|
||||
bcache:invalidate()
|
||||
|
||||
M.update(bufnr, bcache)
|
||||
end))
|
||||
return w
|
||||
end)
|
||||
)
|
||||
return w
|
||||
end
|
||||
|
||||
function M.reset_signs()
|
||||
-- Remove all signs
|
||||
if signs_normal then
|
||||
signs_normal:reset()
|
||||
end
|
||||
if signs_staged then
|
||||
signs_staged:reset()
|
||||
end
|
||||
-- Remove all signs
|
||||
if signs_normal then
|
||||
signs_normal:reset()
|
||||
end
|
||||
if signs_staged then
|
||||
signs_staged:reset()
|
||||
end
|
||||
end
|
||||
|
||||
local function on_win(_, _, bufnr, topline, botline_guess)
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.hunks then
|
||||
return false
|
||||
end
|
||||
local botline = math.min(botline_guess, api.nvim_buf_line_count(bufnr))
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.hunks then
|
||||
return false
|
||||
end
|
||||
local botline = math.min(botline_guess, api.nvim_buf_line_count(bufnr))
|
||||
|
||||
local untracked = bcache.git_obj.object_name == nil
|
||||
local untracked = bcache.git_obj.object_name == nil
|
||||
|
||||
apply_win_signs(bufnr, topline + 1, botline + 1, false, untracked)
|
||||
apply_win_signs(bufnr, topline + 1, botline + 1, false, untracked)
|
||||
|
||||
if not (config.word_diff and config.diff_opts.internal) then
|
||||
return false
|
||||
end
|
||||
if not (config.word_diff and config.diff_opts.internal) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function on_line(_, _, bufnr, row)
|
||||
apply_word_diff(bufnr, row)
|
||||
apply_word_diff(bufnr, row)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
-- Calling this before any await calls will stop nvim's intro messages being
|
||||
-- displayed
|
||||
api.nvim_set_decoration_provider(ns, {
|
||||
on_win = on_win,
|
||||
on_line = on_line,
|
||||
})
|
||||
-- Calling this before any await calls will stop nvim's intro messages being
|
||||
-- displayed
|
||||
api.nvim_set_decoration_provider(ns, {
|
||||
on_win = on_win,
|
||||
on_line = on_line,
|
||||
})
|
||||
|
||||
signs_normal = Signs.new(config.signs)
|
||||
if config._signs_staged_enable then
|
||||
signs_staged = Signs.new(config._signs_staged, 'staged')
|
||||
end
|
||||
signs_normal = Signs.new(config.signs)
|
||||
if config._signs_staged_enable then
|
||||
signs_staged = Signs.new(config._signs_staged, 'staged')
|
||||
end
|
||||
|
||||
M.update_debounced = debounce_trailing(config.update_debounce, void(M.update))
|
||||
M.update_debounced = debounce_trailing(config.update_debounce, void(M.update))
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
124
lua/gitsigns/mappings.lua
generated
124
lua/gitsigns/mappings.lua
generated
|
@ -5,83 +5,91 @@ local validate = vim.validate
|
|||
local api = vim.api
|
||||
|
||||
local valid_modes = {
|
||||
n = 'n', v = 'v', x = 'x', i = 'i', o = 'o', t = 't', c = 'c', s = 's',
|
||||
-- :map! and :map
|
||||
['!'] = '!', [' '] = '',
|
||||
n = 'n',
|
||||
v = 'v',
|
||||
x = 'x',
|
||||
i = 'i',
|
||||
o = 'o',
|
||||
t = 't',
|
||||
c = 'c',
|
||||
s = 's',
|
||||
-- :map! and :map
|
||||
['!'] = '!',
|
||||
[' '] = '',
|
||||
}
|
||||
|
||||
local valid_options = {
|
||||
buffer = 'boolean',
|
||||
expr = 'boolean',
|
||||
noremap = 'boolean',
|
||||
nowait = 'boolean',
|
||||
script = 'boolean',
|
||||
silent = 'boolean',
|
||||
unique = 'boolean',
|
||||
buffer = 'boolean',
|
||||
expr = 'boolean',
|
||||
noremap = 'boolean',
|
||||
nowait = 'boolean',
|
||||
script = 'boolean',
|
||||
silent = 'boolean',
|
||||
unique = 'boolean',
|
||||
}
|
||||
|
||||
local function validate_option_keywords(options)
|
||||
for option_name, expected_type in pairs(valid_options) do
|
||||
local value = options[option_name]
|
||||
if value then
|
||||
validate({
|
||||
[option_name] = { value, expected_type },
|
||||
})
|
||||
end
|
||||
end
|
||||
for option_name, expected_type in pairs(valid_options) do
|
||||
local value = options[option_name]
|
||||
if value then
|
||||
validate({
|
||||
[option_name] = { value, expected_type },
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_mappings(mappings, bufnr)
|
||||
validate({
|
||||
mappings = { mappings, 'table' },
|
||||
})
|
||||
validate({
|
||||
mappings = { mappings, 'table' },
|
||||
})
|
||||
|
||||
local default_options = {}
|
||||
for key, val in pairs(mappings) do
|
||||
local default_options = {}
|
||||
for key, val in pairs(mappings) do
|
||||
-- Skip any inline default keywords.
|
||||
if valid_options[key] then
|
||||
default_options[key] = val
|
||||
end
|
||||
end
|
||||
|
||||
for key, opts in pairs(mappings) do
|
||||
repeat
|
||||
-- Skip any inline default keywords.
|
||||
if valid_options[key] then
|
||||
default_options[key] = val
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for key, opts in pairs(mappings) do
|
||||
repeat
|
||||
-- Skip any inline default keywords.
|
||||
if valid_options[key] then
|
||||
break
|
||||
end
|
||||
local rhs
|
||||
local options
|
||||
if type(opts) == 'string' then
|
||||
rhs = opts
|
||||
options = {}
|
||||
elseif type(opts) == 'table' then
|
||||
rhs = opts[1]
|
||||
local boptions = {}
|
||||
for k in pairs(valid_options) do
|
||||
boptions[k] = opts[k]
|
||||
end
|
||||
options = boptions
|
||||
else
|
||||
error(('Invalid type for option rhs: %q = %s'):format(type(opts), vim.inspect(opts)))
|
||||
end
|
||||
options = vim.tbl_extend('keep', default_options, options)
|
||||
|
||||
local rhs
|
||||
local options
|
||||
if type(opts) == "string" then
|
||||
rhs = opts
|
||||
options = {}
|
||||
elseif type(opts) == "table" then
|
||||
rhs = opts[1]
|
||||
local boptions = {}
|
||||
for k in pairs(valid_options) do
|
||||
boptions[k] = opts[k]
|
||||
end
|
||||
options = boptions
|
||||
else
|
||||
error(("Invalid type for option rhs: %q = %s"):format(type(opts), vim.inspect(opts)))
|
||||
end
|
||||
options = vim.tbl_extend('keep', default_options, options)
|
||||
validate_option_keywords(options)
|
||||
|
||||
validate_option_keywords(options)
|
||||
local mode, mapping = key:match('^(.)[ ]*(.+)$')
|
||||
|
||||
local mode, mapping = key:match("^(.)[ ]*(.+)$")
|
||||
if not mode or not valid_modes[mode] then
|
||||
error('Invalid mode specified for keymapping. mode=' .. mode)
|
||||
end
|
||||
|
||||
if not mode or not valid_modes[mode] then
|
||||
error("Invalid mode specified for keymapping. mode=" .. mode)
|
||||
end
|
||||
-- In case users haven't updated their config.
|
||||
options.buffer = nil
|
||||
|
||||
-- In case users haven't updated their config.
|
||||
options.buffer = nil
|
||||
|
||||
api.nvim_buf_set_keymap(bufnr, mode, mapping, rhs, options)
|
||||
until true
|
||||
end
|
||||
api.nvim_buf_set_keymap(bufnr, mode, mapping, rhs, options)
|
||||
until true
|
||||
end
|
||||
end
|
||||
|
||||
return apply_mappings
|
||||
|
|
9
lua/gitsigns/message.lua
generated
9
lua/gitsigns/message.lua
generated
|
@ -1,16 +1,11 @@
|
|||
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
M.warn = vim.schedule_wrap(function(s, ...)
|
||||
vim.notify(s:format(...), vim.log.levels.WARN, { title = 'gitsigns' })
|
||||
vim.notify(s:format(...), vim.log.levels.WARN, { title = 'gitsigns' })
|
||||
end)
|
||||
|
||||
M.error = vim.schedule_wrap(function(s, ...)
|
||||
vim.notify(s:format(...), vim.log.levels.ERROR, { title = 'gitsigns' })
|
||||
vim.notify(s:format(...), vim.log.levels.ERROR, { title = 'gitsigns' })
|
||||
end)
|
||||
|
||||
return M
|
||||
|
|
358
lua/gitsigns/popup.lua
generated
358
lua/gitsigns/popup.lua
generated
|
@ -1,245 +1,237 @@
|
|||
local popup = {HlMark = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local popup = { HlMark = {} }
|
||||
|
||||
local HlMark = popup.HlMark
|
||||
|
||||
local api = vim.api
|
||||
|
||||
local function bufnr_calc_width(bufnr, lines)
|
||||
return api.nvim_buf_call(bufnr, function()
|
||||
local width = 0
|
||||
for _, l in ipairs(lines) do
|
||||
if vim.fn.type(l) == vim.v.t_string then
|
||||
local len = vim.fn.strdisplaywidth(l)
|
||||
if len > width then
|
||||
width = len
|
||||
end
|
||||
end
|
||||
return api.nvim_buf_call(bufnr, function()
|
||||
local width = 0
|
||||
for _, l in ipairs(lines) do
|
||||
if vim.fn.type(l) == vim.v.t_string then
|
||||
local len = vim.fn.strdisplaywidth(l)
|
||||
if len > width then
|
||||
width = len
|
||||
end
|
||||
end
|
||||
return width + 1 -- Add 1 for some miinor padding
|
||||
end)
|
||||
end
|
||||
return width + 1 -- Add 1 for some miinor padding
|
||||
end)
|
||||
end
|
||||
|
||||
-- Expand height until all lines are visible to account for wrapped lines.
|
||||
local function expand_height(winid, nlines)
|
||||
local newheight = 0
|
||||
for _ = 0, 50 do
|
||||
local winheight = api.nvim_win_get_height(winid)
|
||||
if newheight > winheight then
|
||||
-- Window must be max height
|
||||
break
|
||||
end
|
||||
local wd = api.nvim_win_call(winid, function()
|
||||
return vim.fn.line('w$')
|
||||
end)
|
||||
if wd >= nlines then
|
||||
break
|
||||
end
|
||||
newheight = winheight + nlines - wd
|
||||
api.nvim_win_set_height(winid, newheight)
|
||||
end
|
||||
local newheight = 0
|
||||
for _ = 0, 50 do
|
||||
local winheight = api.nvim_win_get_height(winid)
|
||||
if newheight > winheight then
|
||||
-- Window must be max height
|
||||
break
|
||||
end
|
||||
local wd = api.nvim_win_call(winid, function()
|
||||
return vim.fn.line('w$')
|
||||
end)
|
||||
if wd >= nlines then
|
||||
break
|
||||
end
|
||||
newheight = winheight + nlines - wd
|
||||
api.nvim_win_set_height(winid, newheight)
|
||||
end
|
||||
end
|
||||
|
||||
local function offset_hlmarks(hlmarks, row_offset)
|
||||
for _, h in ipairs(hlmarks) do
|
||||
if h.start_row then
|
||||
h.start_row = h.start_row + row_offset
|
||||
end
|
||||
if h.end_row then
|
||||
h.end_row = h.end_row + row_offset
|
||||
end
|
||||
end
|
||||
for _, h in ipairs(hlmarks) do
|
||||
if h.start_row then
|
||||
h.start_row = h.start_row + row_offset
|
||||
end
|
||||
if h.end_row then
|
||||
h.end_row = h.end_row + row_offset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function process_linesspec(fmt)
|
||||
local lines = {}
|
||||
local hls = {}
|
||||
local lines = {}
|
||||
local hls = {}
|
||||
|
||||
local row = 0
|
||||
for _, section in ipairs(fmt) do
|
||||
local sec = {}
|
||||
local pos = 0
|
||||
for _, part in ipairs(section) do
|
||||
local text = part[1]
|
||||
local hl = part[2]
|
||||
local row = 0
|
||||
for _, section in ipairs(fmt) do
|
||||
local sec = {}
|
||||
local pos = 0
|
||||
for _, part in ipairs(section) do
|
||||
local text = part[1]
|
||||
local hl = part[2]
|
||||
|
||||
sec[#sec + 1] = text
|
||||
sec[#sec + 1] = text
|
||||
|
||||
local srow = row
|
||||
local scol = pos
|
||||
local srow = row
|
||||
local scol = pos
|
||||
|
||||
local ts = vim.split(text, '\n')
|
||||
local ts = vim.split(text, '\n')
|
||||
|
||||
if #ts > 1 then
|
||||
pos = 0
|
||||
row = row + #ts - 1
|
||||
else
|
||||
pos = pos + #text
|
||||
end
|
||||
|
||||
if type(hl) == "string" then
|
||||
hls[#hls + 1] = {
|
||||
hl_group = hl,
|
||||
start_row = srow,
|
||||
end_row = row,
|
||||
start_col = scol,
|
||||
end_col = pos,
|
||||
}
|
||||
else -- hl is {HlMark}
|
||||
offset_hlmarks(hl, srow)
|
||||
vim.list_extend(hls, hl)
|
||||
end
|
||||
if #ts > 1 then
|
||||
pos = 0
|
||||
row = row + #ts - 1
|
||||
else
|
||||
pos = pos + #text
|
||||
end
|
||||
for _, l in ipairs(vim.split(table.concat(sec, ''), '\n')) do
|
||||
lines[#lines + 1] = l
|
||||
end
|
||||
row = row + 1
|
||||
end
|
||||
|
||||
return lines, hls
|
||||
if type(hl) == 'string' then
|
||||
hls[#hls + 1] = {
|
||||
hl_group = hl,
|
||||
start_row = srow,
|
||||
end_row = row,
|
||||
start_col = scol,
|
||||
end_col = pos,
|
||||
}
|
||||
else -- hl is {HlMark}
|
||||
offset_hlmarks(hl, srow)
|
||||
vim.list_extend(hls, hl)
|
||||
end
|
||||
end
|
||||
for _, l in ipairs(vim.split(table.concat(sec, ''), '\n')) do
|
||||
lines[#lines + 1] = l
|
||||
end
|
||||
row = row + 1
|
||||
end
|
||||
|
||||
return lines, hls
|
||||
end
|
||||
|
||||
local function close_all_but(id)
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview ~= nil and
|
||||
vim.w[winid].gitsigns_preview ~= id then
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
end
|
||||
end
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview ~= nil and vim.w[winid].gitsigns_preview ~= id then
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function popup.close(id)
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview == id then
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
end
|
||||
end
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview == id then
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function popup.create0(lines, opts, id)
|
||||
-- Close any popups not matching id
|
||||
close_all_but(id)
|
||||
-- Close any popups not matching id
|
||||
close_all_but(id)
|
||||
|
||||
local ts = vim.bo.tabstop
|
||||
local bufnr = api.nvim_create_buf(false, true)
|
||||
assert(bufnr, "Failed to create buffer")
|
||||
local ts = vim.bo.tabstop
|
||||
local bufnr = api.nvim_create_buf(false, true)
|
||||
assert(bufnr, 'Failed to create buffer')
|
||||
|
||||
-- In case nvim was opened with '-M'
|
||||
vim.bo[bufnr].modifiable = true
|
||||
api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
||||
vim.bo[bufnr].modifiable = false
|
||||
-- In case nvim was opened with '-M'
|
||||
vim.bo[bufnr].modifiable = true
|
||||
api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
||||
vim.bo[bufnr].modifiable = false
|
||||
|
||||
-- Set tabstop before calculating the buffer width so that the correct width
|
||||
-- is calculated
|
||||
vim.bo[bufnr].tabstop = ts
|
||||
-- Set tabstop before calculating the buffer width so that the correct width
|
||||
-- is calculated
|
||||
vim.bo[bufnr].tabstop = ts
|
||||
|
||||
local opts1 = vim.deepcopy(opts or {})
|
||||
opts1.height = opts1.height or #lines -- Guess, adjust later
|
||||
opts1.width = opts1.width or bufnr_calc_width(bufnr, lines)
|
||||
local opts1 = vim.deepcopy(opts or {})
|
||||
opts1.height = opts1.height or #lines -- Guess, adjust later
|
||||
opts1.width = opts1.width or bufnr_calc_width(bufnr, lines)
|
||||
|
||||
local winid = api.nvim_open_win(bufnr, false, opts1)
|
||||
local winid = api.nvim_open_win(bufnr, false, opts1)
|
||||
|
||||
vim.w[winid].gitsigns_preview = id or true
|
||||
vim.w[winid].gitsigns_preview = id or true
|
||||
|
||||
if not opts.height then
|
||||
expand_height(winid, #lines)
|
||||
end
|
||||
if not opts.height then
|
||||
expand_height(winid, #lines)
|
||||
end
|
||||
|
||||
if opts1.style == 'minimal' then
|
||||
-- If 'signcolumn' = auto:1-2, then a empty signcolumn will appear and cause
|
||||
-- line wrapping.
|
||||
vim.wo[winid].signcolumn = 'no'
|
||||
end
|
||||
if opts1.style == 'minimal' then
|
||||
-- If 'signcolumn' = auto:1-2, then a empty signcolumn will appear and cause
|
||||
-- line wrapping.
|
||||
vim.wo[winid].signcolumn = 'no'
|
||||
end
|
||||
|
||||
-- Close the popup when navigating to any window which is not the preview
|
||||
-- itself.
|
||||
local group = 'gitsigns_popup'
|
||||
local group_id = api.nvim_create_augroup(group, {})
|
||||
local old_cursor = api.nvim_win_get_cursor(0)
|
||||
-- Close the popup when navigating to any window which is not the preview
|
||||
-- itself.
|
||||
local group = 'gitsigns_popup'
|
||||
local group_id = api.nvim_create_augroup(group, {})
|
||||
local old_cursor = api.nvim_win_get_cursor(0)
|
||||
|
||||
api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
|
||||
group = group_id,
|
||||
callback = function()
|
||||
local cursor = api.nvim_win_get_cursor(0)
|
||||
-- Did the cursor REALLY change (neovim/neovim#12923)
|
||||
if (old_cursor[1] ~= cursor[1] or old_cursor[2] ~= cursor[2]) and
|
||||
api.nvim_get_current_win() ~= winid then
|
||||
-- Clear the augroup
|
||||
api.nvim_create_augroup(group, {})
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
return
|
||||
end
|
||||
old_cursor = cursor
|
||||
end,
|
||||
})
|
||||
api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
|
||||
group = group_id,
|
||||
callback = function()
|
||||
local cursor = api.nvim_win_get_cursor(0)
|
||||
-- Did the cursor REALLY change (neovim/neovim#12923)
|
||||
if
|
||||
(old_cursor[1] ~= cursor[1] or old_cursor[2] ~= cursor[2])
|
||||
and api.nvim_get_current_win() ~= winid
|
||||
then
|
||||
-- Clear the augroup
|
||||
api.nvim_create_augroup(group, {})
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
return
|
||||
end
|
||||
old_cursor = cursor
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('WinClosed', {
|
||||
pattern = tostring(winid),
|
||||
group = group_id,
|
||||
callback = function()
|
||||
-- Clear the augroup
|
||||
api.nvim_create_augroup(group, {})
|
||||
end,
|
||||
})
|
||||
api.nvim_create_autocmd('WinClosed', {
|
||||
pattern = tostring(winid),
|
||||
group = group_id,
|
||||
callback = function()
|
||||
-- Clear the augroup
|
||||
api.nvim_create_augroup(group, {})
|
||||
end,
|
||||
})
|
||||
|
||||
-- update window position to follow the cursor when scrolling
|
||||
api.nvim_create_autocmd('WinScrolled', {
|
||||
buffer = api.nvim_get_current_buf(),
|
||||
group = group_id,
|
||||
callback = function()
|
||||
if api.nvim_win_is_valid(winid) then
|
||||
api.nvim_win_set_config(winid, opts1)
|
||||
end
|
||||
end,
|
||||
})
|
||||
-- update window position to follow the cursor when scrolling
|
||||
api.nvim_create_autocmd('WinScrolled', {
|
||||
buffer = api.nvim_get_current_buf(),
|
||||
group = group_id,
|
||||
callback = function()
|
||||
if api.nvim_win_is_valid(winid) then
|
||||
api.nvim_win_set_config(winid, opts1)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return winid, bufnr
|
||||
return winid, bufnr
|
||||
end
|
||||
|
||||
local ns = api.nvim_create_namespace('gitsigns_popup')
|
||||
|
||||
function popup.create(lines_spec, opts, id)
|
||||
local lines, highlights = process_linesspec(lines_spec)
|
||||
local winid, bufnr = popup.create0(lines, opts, id)
|
||||
local lines, highlights = process_linesspec(lines_spec)
|
||||
local winid, bufnr = popup.create0(lines, opts, id)
|
||||
|
||||
for _, hl in ipairs(highlights) do
|
||||
local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, ns, hl.start_row, hl.start_col or 0, {
|
||||
hl_group = hl.hl_group,
|
||||
end_row = hl.end_row,
|
||||
end_col = hl.end_col,
|
||||
hl_eol = true,
|
||||
})
|
||||
if not ok then
|
||||
error(vim.inspect(hl) .. '\n' .. err)
|
||||
end
|
||||
end
|
||||
for _, hl in ipairs(highlights) do
|
||||
local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, ns, hl.start_row, hl.start_col or 0, {
|
||||
hl_group = hl.hl_group,
|
||||
end_row = hl.end_row,
|
||||
end_col = hl.end_col,
|
||||
hl_eol = true,
|
||||
})
|
||||
if not ok then
|
||||
error(vim.inspect(hl) .. '\n' .. err)
|
||||
end
|
||||
end
|
||||
|
||||
return winid, bufnr
|
||||
return winid, bufnr
|
||||
end
|
||||
|
||||
function popup.is_open(id)
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview == id then
|
||||
return winid
|
||||
end
|
||||
end
|
||||
return nil
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview == id then
|
||||
return winid
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function popup.focus_open(id)
|
||||
local winid = popup.is_open(id)
|
||||
if winid then
|
||||
api.nvim_set_current_win(winid)
|
||||
end
|
||||
return winid
|
||||
local winid = popup.is_open(id)
|
||||
if winid then
|
||||
api.nvim_set_current_win(winid)
|
||||
end
|
||||
return winid
|
||||
end
|
||||
|
||||
return popup
|
||||
|
|
34
lua/gitsigns/repeat.lua
generated
34
lua/gitsigns/repeat.lua
generated
|
@ -2,27 +2,27 @@ local api = vim.api
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
function M.mk_repeatable(fn)
|
||||
return function(...)
|
||||
local args = { ... }
|
||||
local nargs = select('#', ...)
|
||||
vim.go.operatorfunc = "v:lua.require'gitsigns.repeat'.repeat_action"
|
||||
return function(...)
|
||||
local args = { ... }
|
||||
local nargs = select('#', ...)
|
||||
vim.go.operatorfunc = "v:lua.require'gitsigns.repeat'.repeat_action"
|
||||
|
||||
M.repeat_action = function()
|
||||
fn(unpack(args, 1, nargs))
|
||||
if vim.fn.exists('*repeat#set') == 1 then
|
||||
local action = api.nvim_replace_termcodes(
|
||||
string.format('<cmd>call %s()<cr>', vim.go.operatorfunc),
|
||||
true, true, true)
|
||||
vim.fn['repeat#set'](action, -1)
|
||||
end
|
||||
M.repeat_action = function()
|
||||
fn(unpack(args, 1, nargs))
|
||||
if vim.fn.exists('*repeat#set') == 1 then
|
||||
local action = api.nvim_replace_termcodes(
|
||||
string.format('<cmd>call %s()<cr>', vim.go.operatorfunc),
|
||||
true,
|
||||
true,
|
||||
true
|
||||
)
|
||||
vim.fn['repeat#set'](action, -1)
|
||||
end
|
||||
end
|
||||
|
||||
vim.cmd('normal! g@l')
|
||||
end
|
||||
vim.cmd('normal! g@l')
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
55
lua/gitsigns/signs.lua
generated
55
lua/gitsigns/signs.lua
generated
|
@ -1,5 +1,4 @@
|
|||
local config = require('gitsigns.config').config
|
||||
local SignsConfig = require('gitsigns.config').Config.SignsConfig
|
||||
|
||||
local dprint = require('gitsigns.debug.log').dprint
|
||||
|
||||
|
@ -10,34 +9,34 @@ local B = require('gitsigns.signs.base')
|
|||
-- end
|
||||
|
||||
function B.new(cfg, name)
|
||||
local __FUNC__ = 'signs.init'
|
||||
local C
|
||||
if config._extmark_signs then
|
||||
dprint('Using extmark signs')
|
||||
C = require('gitsigns.signs.extmarks')
|
||||
else
|
||||
dprint('Using vimfn signs')
|
||||
C = require('gitsigns.signs.vimfn')
|
||||
end
|
||||
local __FUNC__ = 'signs.init'
|
||||
local C
|
||||
if config._extmark_signs then
|
||||
dprint('Using extmark signs')
|
||||
C = require('gitsigns.signs.extmarks')
|
||||
else
|
||||
dprint('Using vimfn signs')
|
||||
C = require('gitsigns.signs.vimfn')
|
||||
end
|
||||
|
||||
local hls = (name == 'staged' and config._signs_staged or config.signs)
|
||||
-- Add when config.signs.*.[hl,numhl,linehl] are removed
|
||||
-- for _, t in ipairs {
|
||||
-- 'add',
|
||||
-- 'change',
|
||||
-- 'delete',
|
||||
-- 'topdelete',
|
||||
-- 'changedelete',
|
||||
-- 'untracked',
|
||||
-- } do
|
||||
-- local hl = string.format('GitSigns%s%s', name, capitalise_word(t))
|
||||
-- obj.hls[t] = {
|
||||
-- hl = hl,
|
||||
-- numhl = hl..'Nr',
|
||||
-- linehl = hl..'Ln',
|
||||
-- }
|
||||
-- end
|
||||
return C._new(cfg, hls, name)
|
||||
local hls = (name == 'staged' and config._signs_staged or config.signs)
|
||||
-- Add when config.signs.*.[hl,numhl,linehl] are removed
|
||||
-- for _, t in ipairs {
|
||||
-- 'add',
|
||||
-- 'change',
|
||||
-- 'delete',
|
||||
-- 'topdelete',
|
||||
-- 'changedelete',
|
||||
-- 'untracked',
|
||||
-- } do
|
||||
-- local hl = string.format('GitSigns%s%s', name, capitalise_word(t))
|
||||
-- obj.hls[t] = {
|
||||
-- hl = hl,
|
||||
-- numhl = hl..'Nr',
|
||||
-- linehl = hl..'Ln',
|
||||
-- }
|
||||
-- end
|
||||
return C._new(cfg, hls, name)
|
||||
end
|
||||
|
||||
return B
|
||||
|
|
43
lua/gitsigns/signs/base.lua
generated
43
lua/gitsigns/signs/base.lua
generated
|
@ -1,46 +1,9 @@
|
|||
local SignsConfig = require('gitsigns.config').Config.SignsConfig
|
||||
|
||||
local M = {Sign = {}, HlDef = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Used by signs/extmarks.tl
|
||||
|
||||
|
||||
-- Used by signs/vimfn.tl
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = { Sign = {}, HlDef = {} }
|
||||
|
||||
-- Used by signs/extmarks.tl
|
||||
|
||||
-- Used by signs/vimfn.tl
|
||||
|
||||
return M
|
||||
|
|
119
lua/gitsigns/signs/extmarks.lua
generated
119
lua/gitsigns/signs/extmarks.lua
generated
|
@ -10,80 +10,85 @@ local M = {}
|
|||
local group_base = 'gitsigns_extmark_signs_'
|
||||
|
||||
function M._new(cfg, hls, name)
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.config = cfg
|
||||
self.hls = hls
|
||||
self.group = group_base .. (name or '')
|
||||
self.ns = api.nvim_create_namespace(self.group)
|
||||
return self
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.config = cfg
|
||||
self.hls = hls
|
||||
self.group = group_base .. (name or '')
|
||||
self.ns = api.nvim_create_namespace(self.group)
|
||||
return self
|
||||
end
|
||||
|
||||
function M:on_lines(buf, _, last_orig, last_new)
|
||||
-- Remove extmarks on line deletions to mimic
|
||||
-- the behaviour of vim signs.
|
||||
if last_orig > last_new then
|
||||
self:remove(buf, last_new + 1, last_orig)
|
||||
end
|
||||
-- Remove extmarks on line deletions to mimic
|
||||
-- the behaviour of vim signs.
|
||||
if last_orig > last_new then
|
||||
self:remove(buf, last_new + 1, last_orig)
|
||||
end
|
||||
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)
|
||||
else
|
||||
api.nvim_buf_clear_namespace(bufnr, self.ns, 0, -1)
|
||||
end
|
||||
if start_lnum then
|
||||
api.nvim_buf_clear_namespace(bufnr, self.ns, start_lnum - 1, end_lnum or start_lnum)
|
||||
else
|
||||
api.nvim_buf_clear_namespace(bufnr, self.ns, 0, -1)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
local count = s.count
|
||||
local cc = config.count_chars
|
||||
local count_char = cc[count] or cc['+'] or ''
|
||||
text = cs.text .. count_char
|
||||
end
|
||||
|
||||
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,
|
||||
sign_text = config.signcolumn and text or '',
|
||||
priority = config.sign_priority,
|
||||
sign_hl_group = hls.hl,
|
||||
number_hl_group = config.numhl and hls.numhl or nil,
|
||||
line_hl_group = config.linehl and hls.linehl or nil,
|
||||
})
|
||||
|
||||
if not ok and config.debug_mode then
|
||||
vim.schedule(function()
|
||||
error(table.concat({
|
||||
string.format('Error placing extmark on line %d', s.lnum),
|
||||
err,
|
||||
}, '\n'))
|
||||
end)
|
||||
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
|
||||
local count = s.count
|
||||
local cc = config.count_chars
|
||||
local count_char = cc[count] or cc['+'] or ''
|
||||
text = cs.text .. count_char
|
||||
end
|
||||
end
|
||||
|
||||
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,
|
||||
sign_text = config.signcolumn and text or '',
|
||||
priority = config.sign_priority,
|
||||
sign_hl_group = hls.hl,
|
||||
number_hl_group = config.numhl and hls.numhl or nil,
|
||||
line_hl_group = config.linehl and hls.linehl or nil,
|
||||
})
|
||||
|
||||
if not ok and config.debug_mode then
|
||||
vim.schedule(function()
|
||||
error(table.concat({
|
||||
string.format('Error placing extmark on line %d', s.lnum),
|
||||
err,
|
||||
}, '\n'))
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:contains(bufnr, start, last)
|
||||
local marks = api.nvim_buf_get_extmarks(
|
||||
bufnr, self.ns, { start - 1, 0 }, { last or start, 0 }, { limit = 1 })
|
||||
return #marks > 0
|
||||
local marks = api.nvim_buf_get_extmarks(
|
||||
bufnr,
|
||||
self.ns,
|
||||
{ start - 1, 0 },
|
||||
{ last or start, 0 },
|
||||
{ limit = 1 }
|
||||
)
|
||||
return #marks > 0
|
||||
end
|
||||
|
||||
function M:reset()
|
||||
for _, buf in ipairs(api.nvim_list_bufs()) do
|
||||
self:remove(buf)
|
||||
end
|
||||
for _, buf in ipairs(api.nvim_list_bufs()) do
|
||||
self:remove(buf)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
195
lua/gitsigns/signs/vimfn.lua
generated
195
lua/gitsigns/signs/vimfn.lua
generated
|
@ -18,146 +18,145 @@ local M = {}
|
|||
-- - skip adding a sign if it has already been placed.
|
||||
|
||||
local function capitalise_word(x)
|
||||
return x:sub(1, 1):upper() .. x:sub(2)
|
||||
return x:sub(1, 1):upper() .. x:sub(2)
|
||||
end
|
||||
|
||||
local sign_define_cache = {}
|
||||
local sign_name_cache = {}
|
||||
|
||||
local function get_sign_name(name, stype)
|
||||
local key = name .. stype
|
||||
if not sign_name_cache[key] then
|
||||
sign_name_cache[key] = string.format(
|
||||
'%s%s%s', 'GitSigns', capitalise_word(key), capitalise_word(stype))
|
||||
end
|
||||
local key = name .. stype
|
||||
if not sign_name_cache[key] then
|
||||
sign_name_cache[key] =
|
||||
string.format('%s%s%s', 'GitSigns', capitalise_word(key), capitalise_word(stype))
|
||||
end
|
||||
|
||||
return sign_name_cache[key]
|
||||
return sign_name_cache[key]
|
||||
end
|
||||
|
||||
local function sign_get(name)
|
||||
if not sign_define_cache[name] then
|
||||
local s = fn.sign_getdefined(name)
|
||||
if not vim.tbl_isempty(s) then
|
||||
sign_define_cache[name] = s
|
||||
end
|
||||
end
|
||||
return sign_define_cache[name]
|
||||
if not sign_define_cache[name] then
|
||||
local s = fn.sign_getdefined(name)
|
||||
if not vim.tbl_isempty(s) then
|
||||
sign_define_cache[name] = s
|
||||
end
|
||||
end
|
||||
return sign_define_cache[name]
|
||||
end
|
||||
|
||||
local function define_sign(name, opts, redefine)
|
||||
if redefine then
|
||||
sign_define_cache[name] = nil
|
||||
fn.sign_undefine(name)
|
||||
fn.sign_define(name, opts)
|
||||
elseif not sign_get(name) then
|
||||
fn.sign_define(name, opts)
|
||||
end
|
||||
if redefine then
|
||||
sign_define_cache[name] = nil
|
||||
fn.sign_undefine(name)
|
||||
fn.sign_define(name, opts)
|
||||
elseif not sign_get(name) then
|
||||
fn.sign_define(name, opts)
|
||||
end
|
||||
end
|
||||
|
||||
local function define_signs(obj, redefine)
|
||||
-- Define signs
|
||||
for stype, cs in pairs(obj.config) do
|
||||
local hls = obj.hls[stype]
|
||||
define_sign(get_sign_name(obj.name, stype), {
|
||||
texthl = hls.hl,
|
||||
text = config.signcolumn and cs.text or nil,
|
||||
numhl = config.numhl and hls.numhl or nil,
|
||||
linehl = config.linehl and hls.linehl or nil,
|
||||
}, redefine)
|
||||
end
|
||||
-- Define signs
|
||||
for stype, cs in pairs(obj.config) do
|
||||
local hls = obj.hls[stype]
|
||||
define_sign(get_sign_name(obj.name, stype), {
|
||||
texthl = hls.hl,
|
||||
text = config.signcolumn and cs.text or nil,
|
||||
numhl = config.numhl and hls.numhl or nil,
|
||||
linehl = config.linehl and hls.linehl or nil,
|
||||
}, redefine)
|
||||
end
|
||||
end
|
||||
|
||||
local group_base = 'gitsigns_vimfn_signs_'
|
||||
|
||||
function M._new(cfg, hls, name)
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.name = name or ''
|
||||
self.group = group_base .. (name or '')
|
||||
self.config = cfg
|
||||
self.hls = hls
|
||||
self.placed = emptytable()
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.name = name or ''
|
||||
self.group = group_base .. (name or '')
|
||||
self.config = cfg
|
||||
self.hls = hls
|
||||
self.placed = emptytable()
|
||||
|
||||
define_signs(self, false)
|
||||
define_signs(self, false)
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
function M:on_lines(_, _, _, _)
|
||||
end
|
||||
function M:on_lines(_, _, _, _) end
|
||||
|
||||
function M:remove(bufnr, start_lnum, end_lnum)
|
||||
end_lnum = end_lnum or start_lnum
|
||||
end_lnum = end_lnum or start_lnum
|
||||
|
||||
if start_lnum then
|
||||
for lnum = start_lnum, end_lnum do
|
||||
self.placed[bufnr][lnum] = nil
|
||||
fn.sign_unplace(self.group, { buffer = bufnr, id = lnum })
|
||||
end
|
||||
else
|
||||
self.placed[bufnr] = nil
|
||||
fn.sign_unplace(self.group, { buffer = bufnr })
|
||||
end
|
||||
if start_lnum then
|
||||
for lnum = start_lnum, end_lnum do
|
||||
self.placed[bufnr][lnum] = nil
|
||||
fn.sign_unplace(self.group, { buffer = bufnr, id = lnum })
|
||||
end
|
||||
else
|
||||
self.placed[bufnr] = nil
|
||||
fn.sign_unplace(self.group, { buffer = bufnr })
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
local to_place = {}
|
||||
local to_place = {}
|
||||
|
||||
for _, s in ipairs(signs) do
|
||||
local sign_name = get_sign_name(self.name, s.type)
|
||||
for _, s in ipairs(signs) do
|
||||
local sign_name = get_sign_name(self.name, s.type)
|
||||
|
||||
local cs = self.config[s.type]
|
||||
if config.signcolumn and cs.show_count and s.count then
|
||||
local count = s.count
|
||||
local cc = config.count_chars
|
||||
local count_suffix = cc[count] and tostring(count) or (cc['+'] and 'Plus') or ''
|
||||
local count_char = cc[count] or cc['+'] or ''
|
||||
local hls = self.hls[s.type]
|
||||
sign_name = sign_name .. count_suffix
|
||||
define_sign(sign_name, {
|
||||
texthl = hls.hl,
|
||||
text = config.signcolumn and cs.text .. count_char or '',
|
||||
numhl = config.numhl and hls.numhl or nil,
|
||||
linehl = config.linehl and hls.linehl or nil,
|
||||
})
|
||||
end
|
||||
local cs = self.config[s.type]
|
||||
if config.signcolumn and cs.show_count and s.count then
|
||||
local count = s.count
|
||||
local cc = config.count_chars
|
||||
local count_suffix = cc[count] and tostring(count) or (cc['+'] and 'Plus') or ''
|
||||
local count_char = cc[count] or cc['+'] or ''
|
||||
local hls = self.hls[s.type]
|
||||
sign_name = sign_name .. count_suffix
|
||||
define_sign(sign_name, {
|
||||
texthl = hls.hl,
|
||||
text = config.signcolumn and cs.text .. count_char or '',
|
||||
numhl = config.numhl and hls.numhl or nil,
|
||||
linehl = config.linehl and hls.linehl or nil,
|
||||
})
|
||||
end
|
||||
|
||||
if not self.placed[bufnr][s.lnum] then
|
||||
local sign = {
|
||||
id = s.lnum,
|
||||
group = self.group,
|
||||
name = sign_name,
|
||||
buffer = bufnr,
|
||||
lnum = s.lnum,
|
||||
priority = config.sign_priority,
|
||||
}
|
||||
self.placed[bufnr][s.lnum] = s
|
||||
to_place[#to_place + 1] = sign
|
||||
end
|
||||
end
|
||||
if not self.placed[bufnr][s.lnum] then
|
||||
local sign = {
|
||||
id = s.lnum,
|
||||
group = self.group,
|
||||
name = sign_name,
|
||||
buffer = bufnr,
|
||||
lnum = s.lnum,
|
||||
priority = config.sign_priority,
|
||||
}
|
||||
self.placed[bufnr][s.lnum] = s
|
||||
to_place[#to_place + 1] = sign
|
||||
end
|
||||
end
|
||||
|
||||
if #to_place > 0 then
|
||||
fn.sign_placelist(to_place)
|
||||
end
|
||||
if #to_place > 0 then
|
||||
fn.sign_placelist(to_place)
|
||||
end
|
||||
end
|
||||
|
||||
function M:contains(bufnr, start, last)
|
||||
for i = start + 1, last + 1 do
|
||||
if self.placed[bufnr][i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
for i = start + 1, last + 1 do
|
||||
if self.placed[bufnr][i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M:reset()
|
||||
self.placed = emptytable()
|
||||
fn.sign_unplace(self.group)
|
||||
define_signs(self, true)
|
||||
self.placed = emptytable()
|
||||
fn.sign_unplace(self.group)
|
||||
define_signs(self, true)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
46
lua/gitsigns/status.lua
generated
46
lua/gitsigns/status.lua
generated
|
@ -1,46 +1,38 @@
|
|||
local api = vim.api
|
||||
|
||||
|
||||
local StatusObj = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local Status = {
|
||||
StatusObj = StatusObj,
|
||||
StatusObj = StatusObj,
|
||||
}
|
||||
|
||||
function Status:update(bufnr, status)
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
local bstatus = vim.b[bufnr].gitsigns_status_dict
|
||||
if bstatus then
|
||||
status = vim.tbl_extend('force', bstatus, status)
|
||||
end
|
||||
vim.b[bufnr].gitsigns_head = status.head or ''
|
||||
vim.b[bufnr].gitsigns_status_dict = status
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
local bstatus = vim.b[bufnr].gitsigns_status_dict
|
||||
if bstatus then
|
||||
status = vim.tbl_extend('force', bstatus, status)
|
||||
end
|
||||
vim.b[bufnr].gitsigns_head = status.head or ''
|
||||
vim.b[bufnr].gitsigns_status_dict = status
|
||||
|
||||
local config = require('gitsigns.config').config
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
vim.b[bufnr].gitsigns_status = config.status_formatter(status)
|
||||
vim.b[bufnr].gitsigns_status = config.status_formatter(status)
|
||||
end
|
||||
|
||||
function Status:clear(bufnr)
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
vim.b[bufnr].gitsigns_head = nil
|
||||
vim.b[bufnr].gitsigns_status_dict = nil
|
||||
vim.b[bufnr].gitsigns_status = nil
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
vim.b[bufnr].gitsigns_head = nil
|
||||
vim.b[bufnr].gitsigns_status_dict = nil
|
||||
vim.b[bufnr].gitsigns_status = nil
|
||||
end
|
||||
|
||||
function Status:clear_diff(bufnr)
|
||||
self:update(bufnr, { added = 0, removed = 0, changed = 0 })
|
||||
self:update(bufnr, { added = 0, removed = 0, changed = 0 })
|
||||
end
|
||||
|
||||
return Status
|
||||
|
|
157
lua/gitsigns/subprocess.lua
generated
157
lua/gitsigns/subprocess.lua
generated
|
@ -1,119 +1,108 @@
|
|||
local log = require("gitsigns.debug.log")
|
||||
local guv = require("gitsigns.uv")
|
||||
local log = require('gitsigns.debug.log')
|
||||
local guv = require('gitsigns.uv')
|
||||
local uv = vim.loop
|
||||
|
||||
local M = {JobSpec = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = { JobSpec = {} }
|
||||
|
||||
M.job_cnt = 0
|
||||
|
||||
--- @param ... uv_pipe_t
|
||||
local function try_close(...)
|
||||
for i = 1, select('#', ...) do
|
||||
local pipe = select(i, ...)
|
||||
if pipe and not pipe:is_closing() then
|
||||
pipe:close()
|
||||
end
|
||||
end
|
||||
for i = 1, select('#', ...) do
|
||||
local pipe = select(i, ...)
|
||||
if pipe and not pipe:is_closing() then
|
||||
pipe:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param pipe uv_pipe_t
|
||||
--- @param x string[]|string
|
||||
local function handle_writer(pipe, x)
|
||||
if type(x) == "table" then
|
||||
for i, v in ipairs(x) do
|
||||
pipe:write(v)
|
||||
if i ~= #(x) then
|
||||
pipe:write("\n")
|
||||
else
|
||||
pipe:write("\n", function()
|
||||
try_close(pipe)
|
||||
end)
|
||||
end
|
||||
if type(x) == 'table' then
|
||||
for i, v in ipairs(x) do
|
||||
pipe:write(v)
|
||||
if i ~= #x then
|
||||
pipe:write('\n')
|
||||
else
|
||||
pipe:write('\n', function()
|
||||
try_close(pipe)
|
||||
end)
|
||||
end
|
||||
elseif x then
|
||||
-- write is string
|
||||
pipe:write(x, function()
|
||||
try_close(pipe)
|
||||
end)
|
||||
end
|
||||
end
|
||||
elseif x then
|
||||
-- write is string
|
||||
pipe:write(x, function()
|
||||
try_close(pipe)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param pipe uv_pipe_t
|
||||
--- @param output string[]
|
||||
local function handle_reader(pipe, output)
|
||||
pipe:read_start(function(err, data)
|
||||
if err then
|
||||
log.eprint(err)
|
||||
end
|
||||
if data then
|
||||
output[#output + 1] = data
|
||||
else
|
||||
try_close(pipe)
|
||||
end
|
||||
end)
|
||||
pipe:read_start(function(err, data)
|
||||
if err then
|
||||
log.eprint(err)
|
||||
end
|
||||
if data then
|
||||
output[#output + 1] = data
|
||||
else
|
||||
try_close(pipe)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- @param obj table
|
||||
--- @param callback fun(_: integer, _: integer, _: string?, _: string?)
|
||||
function M.run_job(obj, callback)
|
||||
local __FUNC__ = 'run_job'
|
||||
if log.debug_mode then
|
||||
local cmd = obj.command .. ' ' .. table.concat(obj.args, ' ')
|
||||
log.dprint(cmd)
|
||||
end
|
||||
local __FUNC__ = 'run_job'
|
||||
if log.debug_mode then
|
||||
local cmd = obj.command .. ' ' .. table.concat(obj.args, ' ')
|
||||
log.dprint(cmd)
|
||||
end
|
||||
|
||||
local stdout_data = {}
|
||||
local stderr_data = {}
|
||||
local stdout_data = {}
|
||||
local stderr_data = {}
|
||||
|
||||
local stdout = guv.new_pipe(false)
|
||||
local stderr = guv.new_pipe(false)
|
||||
local stdin
|
||||
if obj.writer then
|
||||
stdin = guv.new_pipe(false)
|
||||
end
|
||||
local stdout = guv.new_pipe(false)
|
||||
local stderr = guv.new_pipe(false)
|
||||
local stdin
|
||||
if obj.writer then
|
||||
stdin = guv.new_pipe(false)
|
||||
end
|
||||
|
||||
--- @type uv_process_t?, integer|string
|
||||
local handle, _pid
|
||||
handle, _pid = vim.loop.spawn(obj.command, {
|
||||
args = obj.args,
|
||||
stdio = { stdin, stdout, stderr },
|
||||
cwd = obj.cwd,
|
||||
},
|
||||
function(code, signal)
|
||||
if handle then
|
||||
handle:close()
|
||||
end
|
||||
stdout:read_stop()
|
||||
stderr:read_stop()
|
||||
--- @type uv_process_t?, integer|string
|
||||
local handle, _pid
|
||||
handle, _pid = vim.loop.spawn(obj.command, {
|
||||
args = obj.args,
|
||||
stdio = { stdin, stdout, stderr },
|
||||
cwd = obj.cwd,
|
||||
}, function(code, signal)
|
||||
if handle then
|
||||
handle:close()
|
||||
end
|
||||
stdout:read_stop()
|
||||
stderr:read_stop()
|
||||
|
||||
try_close(stdin, stdout, stderr)
|
||||
try_close(stdin, stdout, stderr)
|
||||
|
||||
local stdout_result = #stdout_data > 0 and table.concat(stdout_data) or nil
|
||||
local stderr_result = #stderr_data > 0 and table.concat(stderr_data) or nil
|
||||
local stdout_result = #stdout_data > 0 and table.concat(stdout_data) or nil
|
||||
local stderr_result = #stderr_data > 0 and table.concat(stderr_data) or nil
|
||||
|
||||
callback(code, signal, stdout_result, stderr_result)
|
||||
end)
|
||||
callback(code, signal, stdout_result, stderr_result)
|
||||
end)
|
||||
|
||||
if not handle then
|
||||
try_close(stdin, stdout, stderr)
|
||||
error(debug.traceback('Failed to spawn process: ' .. vim.inspect(obj)))
|
||||
end
|
||||
|
||||
if not handle then
|
||||
try_close(stdin, stdout, stderr)
|
||||
error(debug.traceback("Failed to spawn process: " .. vim.inspect(obj)))
|
||||
end
|
||||
handle_reader(stdout, stdout_data)
|
||||
handle_reader(stderr, stderr_data)
|
||||
handle_writer(stdin, obj.writer)
|
||||
|
||||
handle_reader(stdout, stdout_data)
|
||||
handle_reader(stderr, stderr_data)
|
||||
handle_writer(stdin, obj.writer)
|
||||
|
||||
M.job_cnt = M.job_cnt + 1
|
||||
M.job_cnt = M.job_cnt + 1
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
33
lua/gitsigns/test.lua
generated
33
lua/gitsigns/test.lua
generated
|
@ -1,35 +1,34 @@
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
local function eq(act, exp)
|
||||
assert(act == exp, string.format('%s != %s', act, exp))
|
||||
assert(act == exp, string.format('%s != %s', act, exp))
|
||||
end
|
||||
|
||||
M._tests = {}
|
||||
|
||||
M._tests.expand_format = function()
|
||||
local util = require('gitsigns.util')
|
||||
assert('hello % world % 2021' == util.expand_format('<var1> % <var2> % <var_time:%Y>', {
|
||||
var1 = 'hello', var2 = 'world', var_time = 1616838297, }))
|
||||
local util = require('gitsigns.util')
|
||||
assert('hello % world % 2021' == util.expand_format('<var1> % <var2> % <var_time:%Y>', {
|
||||
var1 = 'hello',
|
||||
var2 = 'world',
|
||||
var_time = 1616838297,
|
||||
}))
|
||||
end
|
||||
|
||||
|
||||
M._tests.test_args = function()
|
||||
local parse_args = require('gitsigns.cli.argparse').parse_args
|
||||
local parse_args = require('gitsigns.cli.argparse').parse_args
|
||||
|
||||
local pos_args, named_args = parse_args('hello there key=value, key1="a b c"')
|
||||
local pos_args, named_args = parse_args('hello there key=value, key1="a b c"')
|
||||
|
||||
eq(pos_args[1], 'hello')
|
||||
eq(pos_args[2], 'there')
|
||||
eq(named_args.key, 'value,')
|
||||
eq(named_args.key1, 'a b c')
|
||||
eq(pos_args[1], 'hello')
|
||||
eq(pos_args[2], 'there')
|
||||
eq(named_args.key, 'value,')
|
||||
eq(named_args.key1, 'a b c')
|
||||
|
||||
pos_args, named_args = parse_args('base=HEAD~1 posarg')
|
||||
pos_args, named_args = parse_args('base=HEAD~1 posarg')
|
||||
|
||||
eq(named_args.base, 'HEAD~1')
|
||||
eq(pos_args[1], 'posarg')
|
||||
eq(named_args.base, 'HEAD~1')
|
||||
eq(pos_args[1], 'posarg')
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
252
lua/gitsigns/util.lua
generated
252
lua/gitsigns/util.lua
generated
|
@ -1,41 +1,37 @@
|
|||
local M = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function M.path_exists(path)
|
||||
return vim.loop.fs_stat(path) and true or false
|
||||
return vim.loop.fs_stat(path) and true or false
|
||||
end
|
||||
|
||||
local jit_os --- @type string
|
||||
|
||||
if jit then
|
||||
jit_os = jit.os:lower()
|
||||
jit_os = jit.os:lower()
|
||||
end
|
||||
|
||||
local is_unix = false
|
||||
if jit_os then
|
||||
is_unix = jit_os == 'linux' or jit_os == 'osx' or jit_os == 'bsd'
|
||||
is_unix = jit_os == 'linux' or jit_os == 'osx' or jit_os == 'bsd'
|
||||
else
|
||||
local binfmt = package.cpath:match("%p[\\|/]?%p(%a+)")
|
||||
is_unix = binfmt ~= "dll"
|
||||
local binfmt = package.cpath:match('%p[\\|/]?%p(%a+)')
|
||||
is_unix = binfmt ~= 'dll'
|
||||
end
|
||||
|
||||
--- @param file string
|
||||
--- @return string
|
||||
function M.dirname(file)
|
||||
return file:match(string.format('^(.+)%s[^%s]+', M.path_sep, M.path_sep))
|
||||
return file:match(string.format('^(.+)%s[^%s]+', M.path_sep, M.path_sep))
|
||||
end
|
||||
|
||||
--- @param file string
|
||||
--- @return string[]
|
||||
function M.file_lines(file)
|
||||
local text = {} --- @type string[]
|
||||
for line in io.lines(file) do
|
||||
text[#text + 1] = line
|
||||
end
|
||||
return text
|
||||
local text = {} --- @type string[]
|
||||
for line in io.lines(file) do
|
||||
text[#text + 1] = line
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
M.path_sep = package.config:sub(1, 1)
|
||||
|
@ -43,31 +39,31 @@ M.path_sep = package.config:sub(1, 1)
|
|||
--- @param bufnr integer
|
||||
--- @return string[]
|
||||
function M.buf_lines(bufnr)
|
||||
-- nvim_buf_get_lines strips carriage returns if fileformat==dos
|
||||
local buftext = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
for i = 1, #buftext do
|
||||
buftext[i] = buftext[i] .. '\r'
|
||||
end
|
||||
end
|
||||
return buftext
|
||||
-- nvim_buf_get_lines strips carriage returns if fileformat==dos
|
||||
local buftext = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
for i = 1, #buftext do
|
||||
buftext[i] = buftext[i] .. '\r'
|
||||
end
|
||||
end
|
||||
return buftext
|
||||
end
|
||||
|
||||
--- @param buf integer
|
||||
local function delete_alt(buf)
|
||||
local alt = vim.api.nvim_buf_call(buf, function()
|
||||
return vim.fn.bufnr('#')
|
||||
end)
|
||||
if alt ~= buf and alt ~= -1 then
|
||||
pcall(vim.api.nvim_buf_delete, alt, { force = true })
|
||||
end
|
||||
local alt = vim.api.nvim_buf_call(buf, function()
|
||||
return vim.fn.bufnr('#')
|
||||
end)
|
||||
if alt ~= buf and alt ~= -1 then
|
||||
pcall(vim.api.nvim_buf_delete, alt, { force = true })
|
||||
end
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @param name string
|
||||
function M.buf_rename(bufnr, name)
|
||||
vim.api.nvim_buf_set_name(bufnr, name)
|
||||
delete_alt(bufnr)
|
||||
vim.api.nvim_buf_set_name(bufnr, name)
|
||||
delete_alt(bufnr)
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
|
@ -75,112 +71,112 @@ end
|
|||
--- @param end_row integer
|
||||
--- @param lines string[]
|
||||
function M.set_lines(bufnr, start_row, end_row, lines)
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
for i = 1, #lines do
|
||||
lines[i] = lines[i]:gsub('\r$', '')
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(bufnr, start_row, end_row, false, lines)
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
for i = 1, #lines do
|
||||
lines[i] = lines[i]:gsub('\r$', '')
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(bufnr, start_row, end_row, false, lines)
|
||||
end
|
||||
|
||||
--- @return string
|
||||
function M.tmpname()
|
||||
if is_unix then
|
||||
return os.tmpname()
|
||||
end
|
||||
return vim.fn.tempname()
|
||||
if is_unix then
|
||||
return os.tmpname()
|
||||
end
|
||||
return vim.fn.tempname()
|
||||
end
|
||||
|
||||
--- @param timestamp number
|
||||
--- @return string
|
||||
function M.get_relative_time(timestamp)
|
||||
local current_timestamp = os.time()
|
||||
local elapsed = current_timestamp - timestamp
|
||||
local current_timestamp = os.time()
|
||||
local elapsed = current_timestamp - timestamp
|
||||
|
||||
if elapsed == 0 then
|
||||
return 'a while ago'
|
||||
end
|
||||
if elapsed == 0 then
|
||||
return 'a while ago'
|
||||
end
|
||||
|
||||
local minute_seconds = 60
|
||||
local hour_seconds = minute_seconds * 60
|
||||
local day_seconds = hour_seconds * 24
|
||||
local month_seconds = day_seconds * 30
|
||||
local year_seconds = month_seconds * 12
|
||||
local minute_seconds = 60
|
||||
local hour_seconds = minute_seconds * 60
|
||||
local day_seconds = hour_seconds * 24
|
||||
local month_seconds = day_seconds * 30
|
||||
local year_seconds = month_seconds * 12
|
||||
|
||||
local to_relative_string = function(time, divisor, time_word)
|
||||
local num = math.floor(time / divisor)
|
||||
if num > 1 then
|
||||
time_word = time_word .. 's'
|
||||
end
|
||||
local to_relative_string = function(time, divisor, time_word)
|
||||
local num = math.floor(time / divisor)
|
||||
if num > 1 then
|
||||
time_word = time_word .. 's'
|
||||
end
|
||||
|
||||
return num .. ' ' .. time_word .. ' ago'
|
||||
end
|
||||
return num .. ' ' .. time_word .. ' ago'
|
||||
end
|
||||
|
||||
if elapsed < minute_seconds then
|
||||
return to_relative_string(elapsed, 1, 'second')
|
||||
elseif elapsed < hour_seconds then
|
||||
return to_relative_string(elapsed, minute_seconds, 'minute')
|
||||
elseif elapsed < day_seconds then
|
||||
return to_relative_string(elapsed, hour_seconds, 'hour')
|
||||
elseif elapsed < month_seconds then
|
||||
return to_relative_string(elapsed, day_seconds, 'day')
|
||||
elseif elapsed < year_seconds then
|
||||
return to_relative_string(elapsed, month_seconds, 'month')
|
||||
else
|
||||
return to_relative_string(elapsed, year_seconds, 'year')
|
||||
end
|
||||
if elapsed < minute_seconds then
|
||||
return to_relative_string(elapsed, 1, 'second')
|
||||
elseif elapsed < hour_seconds then
|
||||
return to_relative_string(elapsed, minute_seconds, 'minute')
|
||||
elseif elapsed < day_seconds then
|
||||
return to_relative_string(elapsed, hour_seconds, 'hour')
|
||||
elseif elapsed < month_seconds then
|
||||
return to_relative_string(elapsed, day_seconds, 'day')
|
||||
elseif elapsed < year_seconds then
|
||||
return to_relative_string(elapsed, month_seconds, 'month')
|
||||
else
|
||||
return to_relative_string(elapsed, year_seconds, 'year')
|
||||
end
|
||||
end
|
||||
|
||||
--- @generic T
|
||||
--- @param x T[]
|
||||
--- @return T[]
|
||||
function M.copy_array(x)
|
||||
local r = {}
|
||||
for i, e in ipairs(x) do
|
||||
r[i] = e
|
||||
end
|
||||
return r
|
||||
local r = {}
|
||||
for i, e in ipairs(x) do
|
||||
r[i] = e
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- Strip '\r' from the EOL of each line only if all lines end with '\r'
|
||||
--- @param xs0 string[]
|
||||
--- @return string[]
|
||||
function M.strip_cr(xs0)
|
||||
for i = 1, #xs0 do
|
||||
if xs0[i]:sub(-1) ~= '\r' then
|
||||
-- don't strip, return early
|
||||
return xs0
|
||||
end
|
||||
end
|
||||
-- all lines end with '\r', need to strip
|
||||
local xs = vim.deepcopy(xs0)
|
||||
for i = 1, #xs do
|
||||
xs[i] = xs[i]:sub(1, -2)
|
||||
end
|
||||
return xs
|
||||
for i = 1, #xs0 do
|
||||
if xs0[i]:sub(-1) ~= '\r' then
|
||||
-- don't strip, return early
|
||||
return xs0
|
||||
end
|
||||
end
|
||||
-- all lines end with '\r', need to strip
|
||||
local xs = vim.deepcopy(xs0)
|
||||
for i = 1, #xs do
|
||||
xs[i] = xs[i]:sub(1, -2)
|
||||
end
|
||||
return xs
|
||||
end
|
||||
|
||||
function M.calc_base(base)
|
||||
if base and base:sub(1, 1):match('[~\\^]') then
|
||||
base = 'HEAD' .. base
|
||||
end
|
||||
return base
|
||||
if base and base:sub(1, 1):match('[~\\^]') then
|
||||
base = 'HEAD' .. base
|
||||
end
|
||||
return base
|
||||
end
|
||||
|
||||
function M.emptytable()
|
||||
return setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
t[k] = {}
|
||||
return t[k]
|
||||
end,
|
||||
})
|
||||
return setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
t[k] = {}
|
||||
return t[k]
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local function expand_date(fmt, time)
|
||||
if fmt == '%R' then
|
||||
return M.get_relative_time(time)
|
||||
end
|
||||
return os.date(fmt, time)
|
||||
if fmt == '%R' then
|
||||
return M.get_relative_time(time)
|
||||
end
|
||||
return os.date(fmt, time)
|
||||
end
|
||||
|
||||
---@param fmt string
|
||||
|
@ -188,36 +184,36 @@ end
|
|||
---@param reltime boolean Use relative time as the default date format
|
||||
---@return string
|
||||
function M.expand_format(fmt, info, reltime)
|
||||
local ret = {} --- @type string[]
|
||||
local ret = {} --- @type string[]
|
||||
|
||||
for _ = 1, 20 do -- loop protection
|
||||
-- Capture <name> or <name:format>
|
||||
local scol, ecol, match, key, time_fmt = fmt:find('(<([^:>]+):?([^>]*)>)')
|
||||
if not match then
|
||||
break
|
||||
for _ = 1, 20 do -- loop protection
|
||||
-- Capture <name> or <name:format>
|
||||
local scol, ecol, match, key, time_fmt = fmt:find('(<([^:>]+):?([^>]*)>)')
|
||||
if not match then
|
||||
break
|
||||
end
|
||||
|
||||
ret[#ret + 1], fmt = fmt:sub(1, scol - 1), fmt:sub(ecol + 1)
|
||||
|
||||
local v = info[key]
|
||||
|
||||
if v then
|
||||
if type(v) == 'table' then
|
||||
v = table.concat(v, '\n')
|
||||
end
|
||||
|
||||
ret[#ret + 1], fmt = fmt:sub(1, scol - 1), fmt:sub(ecol + 1)
|
||||
|
||||
local v = info[key]
|
||||
|
||||
if v then
|
||||
if type(v) == "table" then
|
||||
v = table.concat(v, '\n')
|
||||
end
|
||||
if vim.endswith(key, '_time') then
|
||||
if time_fmt == '' then
|
||||
time_fmt = reltime and '%R' or '%Y-%m-%d'
|
||||
end
|
||||
v = expand_date(time_fmt, v)
|
||||
end
|
||||
match = tostring(v)
|
||||
if vim.endswith(key, '_time') then
|
||||
if time_fmt == '' then
|
||||
time_fmt = reltime and '%R' or '%Y-%m-%d'
|
||||
end
|
||||
v = expand_date(time_fmt, v)
|
||||
end
|
||||
ret[#ret + 1] = match
|
||||
end
|
||||
match = tostring(v)
|
||||
end
|
||||
ret[#ret + 1] = match
|
||||
end
|
||||
|
||||
ret[#ret + 1] = fmt
|
||||
return table.concat(ret, '')
|
||||
ret[#ret + 1] = fmt
|
||||
return table.concat(ret, '')
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
80
lua/gitsigns/uv.lua
generated
80
lua/gitsigns/uv.lua
generated
|
@ -2,67 +2,65 @@ local uv = vim.loop
|
|||
|
||||
local M = {}
|
||||
|
||||
|
||||
|
||||
--- @type table<integer,{[1]: uv_handle_t, [2]: boolean, [3]: string}>
|
||||
local handles = {}
|
||||
|
||||
M.handles = handles
|
||||
|
||||
function M.print_handles()
|
||||
local none = true
|
||||
for _, e in pairs(handles) do
|
||||
local handle, longlived, tr = e[1], e[2], e[3]
|
||||
if handle and not longlived and not handle:is_closing() then
|
||||
print('')
|
||||
print(tr)
|
||||
none = false
|
||||
end
|
||||
end
|
||||
if none then
|
||||
print('No active handles')
|
||||
end
|
||||
local none = true
|
||||
for _, e in pairs(handles) do
|
||||
local handle, longlived, tr = e[1], e[2], e[3]
|
||||
if handle and not longlived and not handle:is_closing() then
|
||||
print('')
|
||||
print(tr)
|
||||
none = false
|
||||
end
|
||||
end
|
||||
if none then
|
||||
print('No active handles')
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd('VimLeavePre', {
|
||||
callback = function()
|
||||
for _, e in pairs(handles) do
|
||||
local handle = e[1]
|
||||
if handle and not handle:is_closing() then
|
||||
handle:close()
|
||||
end
|
||||
callback = function()
|
||||
for _, e in pairs(handles) do
|
||||
local handle = e[1]
|
||||
if handle and not handle:is_closing() then
|
||||
handle:close()
|
||||
end
|
||||
end,
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--- @param longlived boolean
|
||||
--- @return uv_timer_t?
|
||||
function M.new_timer(longlived)
|
||||
local r = uv.new_timer()
|
||||
if r then
|
||||
table.insert(handles, { r, longlived, debug.traceback() })
|
||||
end
|
||||
return r
|
||||
local r = uv.new_timer()
|
||||
if r then
|
||||
table.insert(handles, { r, longlived, debug.traceback() })
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param longlived boolean
|
||||
--- @return uv_fs_poll_t?
|
||||
function M.new_fs_poll(longlived)
|
||||
local r = uv.new_fs_poll()
|
||||
if r then
|
||||
table.insert(handles, { r, longlived, debug.traceback() })
|
||||
end
|
||||
return r
|
||||
local r = uv.new_fs_poll()
|
||||
if r then
|
||||
table.insert(handles, { r, longlived, debug.traceback() })
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param ipc boolean
|
||||
--- @return uv_pipe_t?
|
||||
function M.new_pipe(ipc)
|
||||
local r = uv.new_pipe(ipc)
|
||||
if r then
|
||||
table.insert(handles, { r, false, debug.traceback() })
|
||||
end
|
||||
return r
|
||||
local r = uv.new_pipe(ipc)
|
||||
if r then
|
||||
table.insert(handles, { r, false, debug.traceback() })
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param cmd string
|
||||
|
@ -70,11 +68,11 @@ end
|
|||
--- @param on_exit fun(_: integer, _, integer): uv_process_t, integer
|
||||
--- @return uv_process_t?, string|integer
|
||||
function M.spawn(cmd, opts, on_exit)
|
||||
local handle, pid = uv.spawn(cmd, opts, on_exit)
|
||||
if handle then
|
||||
table.insert(handles, { handle, false, cmd .. ' ' .. vim.inspect(opts) })
|
||||
end
|
||||
return handle, pid
|
||||
local handle, pid = uv.spawn(cmd, opts, on_exit)
|
||||
if handle then
|
||||
table.insert(handles, { handle, false, cmd .. ' ' .. vim.inspect(opts) })
|
||||
end
|
||||
return handle, pid
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
206
teal/gitsigns.tl
206
teal/gitsigns.tl
|
@ -1,206 +0,0 @@
|
|||
local void = require('gitsigns.async').void
|
||||
local scheduler = require('gitsigns.async').scheduler
|
||||
|
||||
local gs_config = require('gitsigns.config')
|
||||
local Config = gs_config.Config
|
||||
local config = gs_config.config
|
||||
|
||||
local log = require('gitsigns.debug.log')
|
||||
local dprintf = log.dprintf
|
||||
local dprint = log.dprint
|
||||
|
||||
local api = vim.api
|
||||
local uv = require('gitsigns.uv')
|
||||
|
||||
local record M
|
||||
setup: function(cfg: Config)
|
||||
|
||||
-- from attach.tl
|
||||
attach: function(cbuf: integer, ctx: table, trigger: string)
|
||||
|
||||
_setup_done: boolean
|
||||
end
|
||||
|
||||
local cwd_watcher: vim.loop.FSPollObj
|
||||
|
||||
local update_cwd_head = void(function()
|
||||
local paths = vim.fs.find('.git', {
|
||||
limit = 1,
|
||||
upward = true,
|
||||
type = 'directory'
|
||||
})
|
||||
|
||||
if #paths == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if cwd_watcher then
|
||||
cwd_watcher:stop()
|
||||
else
|
||||
cwd_watcher = uv.new_fs_poll(true)
|
||||
end
|
||||
|
||||
local cwd = vim.loop.cwd()
|
||||
local gitdir, head: string, string
|
||||
|
||||
local gs_cache = require('gitsigns.cache')
|
||||
|
||||
-- Look in the cache first
|
||||
for _, bcache in pairs(gs_cache.cache as {number:gs_cache.CacheEntry}) do
|
||||
local repo = bcache.git_obj.repo
|
||||
if repo.toplevel == cwd then
|
||||
head = repo.abbrev_head
|
||||
gitdir = repo.gitdir
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local git = require('gitsigns.git')
|
||||
|
||||
if not head or not gitdir then
|
||||
local info = git.get_repo_info(cwd)
|
||||
gitdir = info.gitdir
|
||||
head = info.abbrev_head
|
||||
end
|
||||
|
||||
scheduler()
|
||||
vim.g.gitsigns_head = head
|
||||
|
||||
if not gitdir then
|
||||
return
|
||||
end
|
||||
|
||||
local towatch = gitdir..'/HEAD'
|
||||
|
||||
if cwd_watcher:getpath() == towatch then
|
||||
-- Already watching
|
||||
return
|
||||
end
|
||||
|
||||
-- Watch .git/HEAD to detect branch changes
|
||||
cwd_watcher:start(
|
||||
towatch,
|
||||
config.watch_gitdir.interval,
|
||||
void(function(err: string)
|
||||
local __FUNC__ = 'cwd_watcher_cb'
|
||||
if err then
|
||||
dprintf('Git dir update error: %s', err)
|
||||
return
|
||||
end
|
||||
dprint('Git cwd dir update')
|
||||
|
||||
local new_head = git.get_repo_info(cwd).abbrev_head
|
||||
scheduler()
|
||||
vim.g.gitsigns_head = new_head
|
||||
end)
|
||||
)
|
||||
end)
|
||||
|
||||
local function setup_cli()
|
||||
api.nvim_create_user_command('Gitsigns', function(params: api.UserCmdParams)
|
||||
require'gitsigns.cli'.run(params)
|
||||
end, {
|
||||
force = true,
|
||||
nargs = '*',
|
||||
range = true,
|
||||
complete = function(arglead: string, line: string): {string}
|
||||
return require'gitsigns.cli'.complete(arglead, line)
|
||||
end})
|
||||
end
|
||||
|
||||
local exported = {
|
||||
'attach',
|
||||
'actions'
|
||||
}
|
||||
|
||||
local function setup_debug()
|
||||
log.debug_mode = config.debug_mode
|
||||
log.verbose = config._verbose
|
||||
|
||||
if config.debug_mode then
|
||||
exported[#exported+1] = 'debug'
|
||||
end
|
||||
end
|
||||
|
||||
local function setup_attach()
|
||||
scheduler()
|
||||
|
||||
-- Attach to all open buffers
|
||||
for _, buf in ipairs(api.nvim_list_bufs()) do
|
||||
if api.nvim_buf_is_loaded(buf)
|
||||
and api.nvim_buf_get_name(buf) ~= '' then
|
||||
M.attach(buf, nil, 'setup')
|
||||
scheduler()
|
||||
end
|
||||
end
|
||||
|
||||
api.nvim_create_autocmd({'BufRead', 'BufNewFile', 'BufWritePost'}, {
|
||||
group = 'gitsigns',
|
||||
callback = function(data: vim.api.AutoCmdOpts.CallbackData)
|
||||
M.attach(nil, nil, data.event)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function setup_cwd_head()
|
||||
scheduler()
|
||||
update_cwd_head()
|
||||
-- Need to debounce in case some plugin changes the cwd too often
|
||||
-- (like vim-grepper)
|
||||
api.nvim_create_autocmd('DirChanged', {
|
||||
group = 'gitsigns',
|
||||
callback = function()
|
||||
local debounce = require("gitsigns.debounce").debounce_trailing
|
||||
debounce(100, update_cwd_head)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
--- Setup and start Gitsigns.
|
||||
---
|
||||
--- Attributes: ~
|
||||
--- {async}
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- {cfg} Table object containing configuration for
|
||||
--- Gitsigns. See |gitsigns-usage| for more details.
|
||||
M.setup = void(function(cfg: Config)
|
||||
gs_config.build(cfg)
|
||||
|
||||
if vim.fn.executable('git') == 0 then
|
||||
print('gitsigns: git not in path. Aborting setup')
|
||||
return
|
||||
end
|
||||
|
||||
if config.yadm.enable and vim.fn.executable('yadm') == 0 then
|
||||
print("gitsigns: yadm not in path. Ignoring 'yadm.enable' in config")
|
||||
config.yadm.enable = false
|
||||
return
|
||||
end
|
||||
|
||||
setup_debug()
|
||||
setup_cli()
|
||||
|
||||
api.nvim_create_augroup('gitsigns', {})
|
||||
|
||||
if config._test_mode then
|
||||
require'gitsigns.attach'._setup()
|
||||
require'gitsigns.git'._set_version(config._git_version)
|
||||
end
|
||||
|
||||
setup_attach()
|
||||
setup_cwd_head()
|
||||
|
||||
M._setup_done = true
|
||||
end)
|
||||
|
||||
return setmetatable(M, {
|
||||
__index = function(_, f: string): any
|
||||
for _, mod in ipairs(exported) do
|
||||
local m = (require as function)('gitsigns.'..mod) as table
|
||||
if m[f] then
|
||||
return m[f]
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
File diff suppressed because it is too large
Load diff
|
@ -1,204 +0,0 @@
|
|||
local record Async
|
||||
-- Order by highest number of return types
|
||||
|
||||
create: function<T>(T, integer): T
|
||||
|
||||
void: function<F>(F): F
|
||||
|
||||
wrap: function<A1,R1,R2,R3,R4> (function(A1, function(R1,R2,R3,R4)), integer): function(A1 ): R1,R2,R3,R4
|
||||
wrap: function<A1,A2,R1,R2> (function(A1,A2, function(R1,R2) ), integer): function(A1,A2 ): R1,R2
|
||||
wrap: function<A1,A2,A3,A4,R1> (function(A1,A2,A3,A4, function(R1) ), integer): function(A1,A2,A3,A4 ): R1
|
||||
wrap: function<A1,A2,A3,A4,A5,R1>(function(A1,A2,A3,A4,A5,function(R1) ), integer): function(A1,A2,A3,A4,A5): R1
|
||||
wrap: function<A1,A2,A3> (function(A1,A2,A3, function() ), integer): function(A1,A2,A3)
|
||||
|
||||
wait: function<A1,R1,R2,R3,R4> (integer, function(A1, function(R1,R2,R3,R4)), A1 ): R1,R2,R3,R4
|
||||
wait: function<A1,A2,R1,R2> (integer, function(A1,A2, function(R1,R2) ), A1, A2 ): R1,R2
|
||||
wait: function<A1,A2,A3,A4,R1> (integer, function(A1,A2,A3,A4, function(R1) ), A1, A2, A3, A4 ): R1
|
||||
wait: function<A1,A2,A3,A4,A5,R1>(integer, function(A1,A2,A3,A4,A5,function(R1) ), A1, A2, A3, A4, A5): R1
|
||||
wait: function<A1,A2,A3> (integer, function(A1,A2,A3, function() ), A1, A2, A3)
|
||||
|
||||
scheduler: function()
|
||||
end
|
||||
|
||||
local record M
|
||||
scheduler: function()
|
||||
end
|
||||
|
||||
local record Async_T
|
||||
|
||||
-- Handle for an object currently running on the event loop.
|
||||
-- The coroutine is paused while this is active.
|
||||
-- Must provide methods cancel() and is_cancelled()
|
||||
--
|
||||
-- Handle gets updated on each call to a wrapped functions, so provide access
|
||||
-- to it via a proxy
|
||||
_current: Async_T
|
||||
|
||||
cancel: function(Async_T, function)
|
||||
is_cancelled: function(Async_T): boolean
|
||||
end
|
||||
|
||||
-- Coroutine.running() was changed between Lua 5.1 and 5.2:
|
||||
-- - 5.1: Returns the running coroutine, or nil when called by the main thread.
|
||||
-- - 5.2: Returns the running coroutine plus a boolean, true when the running
|
||||
-- coroutine is the main one.
|
||||
--
|
||||
-- For LuaJIT, 5.2 behaviour is enabled with LUAJIT_ENABLE_LUA52COMPAT
|
||||
--
|
||||
-- We need to handle both.
|
||||
|
||||
-- Store all the async threads in a weak table so we don't prevent them from
|
||||
-- being garbage collected
|
||||
local handles = setmetatable({} as {thread:Async_T}, { __mode = 'k' })
|
||||
|
||||
--- Returns whether the current execution context is async.
|
||||
function M.running(): boolean
|
||||
local current = coroutine.running()
|
||||
if current and handles[current] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- hack: teal doesn't know table.maxn exists
|
||||
local function maxn(x: table): integer
|
||||
return ((table as table).maxn as function)(x) as integer
|
||||
end
|
||||
|
||||
local function is_Async_T(handle: Async_T): boolean
|
||||
if handle
|
||||
and type(handle) == 'table'
|
||||
and vim.is_callable(handle.cancel)
|
||||
and vim.is_callable(handle.is_cancelled) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Analogous to uv.close
|
||||
function Async_T:cancel(cb: function)
|
||||
-- Cancel anything running on the event loop
|
||||
if self._current and not self._current:is_cancelled() then
|
||||
self._current:cancel(cb)
|
||||
end
|
||||
end
|
||||
|
||||
function Async_T.new(co: thread): Async_T
|
||||
local handle = setmetatable({} as Async_T, { __index = Async_T })
|
||||
handles[co] = handle
|
||||
return handle
|
||||
end
|
||||
|
||||
-- Analogous to uv.is_closing
|
||||
function Async_T:is_cancelled(): boolean
|
||||
return self._current and self._current:is_cancelled()
|
||||
end
|
||||
|
||||
local function run(func: function, callback: function, ...: any): Async_T
|
||||
local co = coroutine.create(func)
|
||||
local handle = Async_T.new(co)
|
||||
|
||||
local function step(...: any)
|
||||
local ret = {coroutine.resume(co, ...)}
|
||||
local stat = ret[1] as boolean
|
||||
|
||||
if not stat then
|
||||
local err = ret[2] as string
|
||||
error(string.format("The coroutine failed with this message: %s\n%s",
|
||||
err, debug.traceback(co)))
|
||||
end
|
||||
|
||||
if coroutine.status(co) == 'dead' then
|
||||
if callback then
|
||||
callback(unpack(ret, 4, maxn(ret)))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local _, nargs, fn = unpack(ret) as (any, integer, function(...:any): Async_T)
|
||||
|
||||
assert(type(fn) == 'function', "type error :: expected func")
|
||||
|
||||
local args = {select(4, unpack(ret))}
|
||||
args[nargs] = step
|
||||
|
||||
local r = fn(unpack(args, 1, nargs))
|
||||
if is_Async_T(r) then
|
||||
handle._current = r
|
||||
end
|
||||
end
|
||||
|
||||
step(...)
|
||||
return handle
|
||||
end
|
||||
|
||||
function M.wait(argc: integer, func: function, ...): any...
|
||||
-- Always run the wrapped functions in xpcall and re-raise the error in the
|
||||
-- coroutine. This makes pcall work as normal.
|
||||
local function pfunc(...: any)
|
||||
local args = { ... }
|
||||
local cb = args[argc] as function
|
||||
args[argc] = function(...: any)
|
||||
cb(true, ...)
|
||||
end
|
||||
xpcall(func, function(err)
|
||||
cb(false, err, debug.traceback())
|
||||
end, unpack(args, 1, argc))
|
||||
end
|
||||
|
||||
local ret = {coroutine.yield(argc, pfunc, ...)}
|
||||
|
||||
local ok = ret[1]
|
||||
if not ok then
|
||||
local _, err, traceback = unpack(ret)
|
||||
error(string.format("Wrapped function failed: %s\n%s", err, traceback))
|
||||
end
|
||||
|
||||
return unpack(ret, 2, maxn(ret))
|
||||
end
|
||||
|
||||
---Creates an async function with a callback style function.
|
||||
---@param func function: A callback style function to be converted. The last argument must be the callback.
|
||||
---@param argc number: The number of arguments of func. Must be included.
|
||||
---@return function: Returns an async function
|
||||
function M.wrap(func: function, argc: integer): function
|
||||
assert(argc)
|
||||
return function(...): any...
|
||||
if not M.running() then
|
||||
return func(...)
|
||||
end
|
||||
return M.wait(argc, func, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---Use this to create a function which executes in an async context but
|
||||
---called from a non-async context. Inherently this cannot return anything
|
||||
---since it is non-blocking
|
||||
---@param func function
|
||||
function M.create(func: function, argc: integer): function(...: any): Async_T
|
||||
argc = argc or 0
|
||||
return function(...: any): Async_T
|
||||
if M.running() then
|
||||
return func(...) as Async_T
|
||||
end
|
||||
local callback = select(argc+1, ...) as function
|
||||
return run(func, callback, unpack({...}, 1, argc))
|
||||
end
|
||||
end
|
||||
|
||||
---Use this to create a function which executes in an async context but
|
||||
---called from a non-async context. Inherently this cannot return anything
|
||||
---since it is non-blocking
|
||||
---@param func function
|
||||
function M.void(func: function(...:any)): function(...:any): Async_T
|
||||
return function(...: any): Async_T
|
||||
if M.running() then
|
||||
return func(...)
|
||||
end
|
||||
return run(func, nil, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---An async function that when called will yield to the Neovim scheduler to be
|
||||
---able to call the API.
|
||||
M.scheduler = M.wrap(vim.schedule, 1) as function()
|
||||
|
||||
return M as Async
|
|
@ -1,411 +0,0 @@
|
|||
local async = require('gitsigns.async')
|
||||
local git = require('gitsigns.git')
|
||||
|
||||
local log = require("gitsigns.debug.log")
|
||||
local dprintf = log.dprintf
|
||||
local dprint = log.dprint
|
||||
|
||||
local manager = require('gitsigns.manager')
|
||||
local hl = require('gitsigns.highlight')
|
||||
|
||||
local gs_cache = require('gitsigns.cache')
|
||||
local cache = gs_cache.cache
|
||||
local CacheEntry = gs_cache.CacheEntry
|
||||
local Status = require("gitsigns.status")
|
||||
|
||||
local gs_config = require('gitsigns.config')
|
||||
local config = gs_config.config
|
||||
|
||||
local void = require('gitsigns.async').void
|
||||
local util = require('gitsigns.util')
|
||||
|
||||
local throttle_by_id = require("gitsigns.debounce").throttle_by_id
|
||||
|
||||
local api = vim.api
|
||||
local uv = vim.loop
|
||||
|
||||
local record GitContext
|
||||
toplevel: string
|
||||
gitdir: string
|
||||
file: string
|
||||
commit: string
|
||||
base: string
|
||||
end
|
||||
|
||||
local record M
|
||||
attach: function(cbuf: integer, ctx: GitContext, trigger: string)
|
||||
detach: function(bufnr: integer, _keep_signs: boolean)
|
||||
detach_all: function()
|
||||
end
|
||||
|
||||
local vimgrep_running = false
|
||||
|
||||
-- @return (string, string) Tuple of buffer name and commit
|
||||
local function parse_fugitive_uri(name: string): string, string
|
||||
if vim.fn.exists('*FugitiveReal') == 0 then
|
||||
dprint("Fugitive not installed")
|
||||
return
|
||||
end
|
||||
|
||||
local path = vim.fn.FugitiveReal(name)
|
||||
local commit = vim.fn.FugitiveParse(name)[1]:match('([^:]+):.*')
|
||||
if commit == '0' then
|
||||
-- '0' means the index so clear commit so we attach normally
|
||||
commit = nil
|
||||
end
|
||||
return path, commit
|
||||
end
|
||||
|
||||
local function parse_gitsigns_uri(name: string): string, string
|
||||
-- TODO(lewis6991): Support submodules
|
||||
local _, _, root_path, commit, rel_path =
|
||||
name:find([[^gitsigns://(.*)/%.git/(.*):(.*)]])
|
||||
if commit == ':0' then
|
||||
-- ':0' means the index so clear commit so we attach normally
|
||||
commit = nil
|
||||
end
|
||||
if root_path then
|
||||
name = root_path .. '/' .. rel_path
|
||||
end
|
||||
return name, commit
|
||||
end
|
||||
|
||||
local function get_buf_path(bufnr: integer): string, string
|
||||
local file =
|
||||
uv.fs_realpath(api.nvim_buf_get_name(bufnr))
|
||||
or
|
||||
api.nvim_buf_call(bufnr, function(): string
|
||||
return vim.fn.expand('%:p')
|
||||
end)
|
||||
|
||||
if not vim.wo.diff then
|
||||
if vim.startswith(file, 'fugitive://') then
|
||||
local path, commit = parse_fugitive_uri(file)
|
||||
dprintf("Fugitive buffer for file '%s' from path '%s'", path, file)
|
||||
path = uv.fs_realpath(path)
|
||||
if path then
|
||||
return path, commit
|
||||
end
|
||||
end
|
||||
|
||||
if vim.startswith(file, 'gitsigns://') then
|
||||
local path, commit = parse_gitsigns_uri(file)
|
||||
dprintf("Gitsigns buffer for file '%s' from path '%s'", path, file)
|
||||
path = uv.fs_realpath(path)
|
||||
if path then
|
||||
return path, commit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return file
|
||||
end
|
||||
|
||||
local function on_lines(_, bufnr: integer, _, first: integer, last_orig: integer, last_new: integer, byte_count: integer): boolean
|
||||
if first == last_orig and last_orig == last_new and byte_count == 0 then
|
||||
-- on_lines can be called twice for undo events; ignore the second
|
||||
-- call which indicates no changes.
|
||||
return
|
||||
end
|
||||
return manager.on_lines(bufnr, first, last_orig, last_new)
|
||||
end
|
||||
|
||||
local function on_reload(_, bufnr: integer)
|
||||
local __FUNC__ = 'on_reload'
|
||||
dprint('Reload')
|
||||
manager.update_debounced(bufnr)
|
||||
end
|
||||
|
||||
local function on_detach(_, bufnr: integer)
|
||||
M.detach(bufnr, true)
|
||||
end
|
||||
|
||||
local function on_attach_pre(bufnr: integer): string, string
|
||||
local gitdir, toplevel: string, string
|
||||
if config._on_attach_pre then
|
||||
local res: any = async.wrap(config._on_attach_pre, 2)(bufnr)
|
||||
dprintf('ran on_attach_pre with result %s', vim.inspect(res))
|
||||
if res is table then
|
||||
if type(res.gitdir) == 'string' then
|
||||
gitdir = res.gitdir as string
|
||||
end
|
||||
if type(res.toplevel) == 'string' then
|
||||
toplevel = res.toplevel as string
|
||||
end
|
||||
end
|
||||
end
|
||||
return gitdir, toplevel
|
||||
end
|
||||
|
||||
local function try_worktrees(_bufnr: integer, file: string, encoding: string): git.Obj
|
||||
if not config.worktrees then
|
||||
return
|
||||
end
|
||||
|
||||
for _, wt in ipairs(config.worktrees) do
|
||||
local git_obj = git.Obj.new(file, encoding, wt.gitdir, wt.toplevel)
|
||||
if git_obj and git_obj.object_name then
|
||||
dprintf('Using worktree %s', vim.inspect(wt))
|
||||
return git_obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local done_setup = false
|
||||
|
||||
function M._setup()
|
||||
if done_setup then
|
||||
return
|
||||
end
|
||||
|
||||
done_setup = true
|
||||
|
||||
manager.setup()
|
||||
|
||||
hl.setup_highlights()
|
||||
api.nvim_create_autocmd('ColorScheme', {
|
||||
group = 'gitsigns',
|
||||
callback = hl.setup_highlights
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('OptionSet', {
|
||||
group = 'gitsigns',
|
||||
pattern = 'fileformat',
|
||||
callback = function()
|
||||
require('gitsigns.actions').refresh()
|
||||
end}
|
||||
)
|
||||
|
||||
-- vimpgrep creates and deletes lots of buffers so attaching to each one will
|
||||
-- waste lots of resource and even slow down vimgrep.
|
||||
api.nvim_create_autocmd('QuickFixCmdPre', {
|
||||
group = 'gitsigns',
|
||||
pattern ='*vimgrep*',
|
||||
callback = function()
|
||||
vimgrep_running = true
|
||||
end
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('QuickFixCmdPost', {
|
||||
group = 'gitsigns',
|
||||
pattern ='*vimgrep*',
|
||||
callback = function()
|
||||
vimgrep_running = false
|
||||
end
|
||||
})
|
||||
|
||||
require('gitsigns.current_line_blame').setup()
|
||||
|
||||
api.nvim_create_autocmd('VimLeavePre' , {
|
||||
group = 'gitsigns',
|
||||
callback = M.detach_all
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
-- Ensure attaches cannot be interleaved.
|
||||
-- Since attaches are asynchronous we need to make sure an attach isn't
|
||||
-- performed whilst another one is in progress.
|
||||
local attach_throttled = throttle_by_id(function(cbuf: integer, ctx: GitContext, aucmd: string)
|
||||
local __FUNC__ = 'attach'
|
||||
|
||||
M._setup()
|
||||
|
||||
if vimgrep_running then
|
||||
dprint('attaching is disabled')
|
||||
return
|
||||
end
|
||||
|
||||
if cache[cbuf] then
|
||||
dprint('Already attached')
|
||||
return
|
||||
end
|
||||
|
||||
if aucmd then
|
||||
dprintf('Attaching (trigger=%s)', aucmd)
|
||||
else
|
||||
dprint('Attaching')
|
||||
end
|
||||
|
||||
if not api.nvim_buf_is_loaded(cbuf) then
|
||||
dprint('Non-loaded buffer')
|
||||
return
|
||||
end
|
||||
|
||||
local encoding = vim.bo[cbuf].fileencoding
|
||||
if encoding == '' then
|
||||
encoding = 'utf-8'
|
||||
end
|
||||
local file: string
|
||||
local commit: string
|
||||
local gitdir_oap: string
|
||||
local toplevel_oap: string
|
||||
|
||||
if ctx then
|
||||
gitdir_oap = ctx.gitdir
|
||||
toplevel_oap = ctx.toplevel
|
||||
file = ctx.toplevel .. util.path_sep .. ctx.file
|
||||
commit = ctx.commit
|
||||
else
|
||||
if api.nvim_buf_line_count(cbuf) > config.max_file_length then
|
||||
dprint('Exceeds max_file_length')
|
||||
return
|
||||
end
|
||||
|
||||
if vim.bo[cbuf].buftype ~= '' then
|
||||
dprint('Non-normal buffer')
|
||||
return
|
||||
end
|
||||
|
||||
file, commit = get_buf_path(cbuf)
|
||||
local file_dir = util.dirname(file)
|
||||
|
||||
if not file_dir or not util.path_exists(file_dir) then
|
||||
dprint('Not a path')
|
||||
return
|
||||
end
|
||||
|
||||
gitdir_oap, toplevel_oap = on_attach_pre(cbuf)
|
||||
end
|
||||
|
||||
local git_obj = git.Obj.new(file, encoding, gitdir_oap, toplevel_oap)
|
||||
|
||||
if not git_obj and not ctx then
|
||||
git_obj = try_worktrees(cbuf, file, encoding)
|
||||
async.scheduler()
|
||||
end
|
||||
|
||||
if not git_obj then
|
||||
dprint('Empty git obj')
|
||||
return
|
||||
end
|
||||
local repo = git_obj.repo
|
||||
|
||||
async.scheduler()
|
||||
Status:update(cbuf, {
|
||||
head = repo.abbrev_head,
|
||||
root = repo.toplevel,
|
||||
gitdir = repo.gitdir,
|
||||
})
|
||||
|
||||
if vim.startswith(file, repo.gitdir..util.path_sep) then
|
||||
dprint('In non-standard git dir')
|
||||
return
|
||||
end
|
||||
|
||||
if not ctx and (not util.path_exists(file) or uv.fs_stat(file).type == 'directory') then
|
||||
dprint('Not a file')
|
||||
return
|
||||
end
|
||||
|
||||
if not git_obj.relpath then
|
||||
dprint('Cannot resolve file in repo')
|
||||
return
|
||||
end
|
||||
|
||||
if not config.attach_to_untracked and git_obj.object_name == nil then
|
||||
dprint('File is untracked')
|
||||
return
|
||||
end
|
||||
|
||||
-- On windows os.tmpname() crashes in callback threads so initialise this
|
||||
-- variable on the main thread.
|
||||
async.scheduler()
|
||||
|
||||
if config.on_attach and config.on_attach(cbuf) == false then
|
||||
dprint('User on_attach() returned false')
|
||||
return
|
||||
end
|
||||
|
||||
cache[cbuf] = CacheEntry.new {
|
||||
base = ctx and ctx.base or config.base,
|
||||
file = file,
|
||||
commit = commit,
|
||||
gitdir_watcher = manager.watch_gitdir(cbuf, repo.gitdir),
|
||||
git_obj = git_obj
|
||||
}
|
||||
|
||||
if not api.nvim_buf_is_loaded(cbuf) then
|
||||
dprint('Un-loaded buffer')
|
||||
return
|
||||
end
|
||||
|
||||
-- Make sure to attach before the first update (which is async) so we pick up
|
||||
-- changes from BufReadCmd.
|
||||
api.nvim_buf_attach(cbuf, false, {
|
||||
on_lines = on_lines,
|
||||
on_reload = on_reload,
|
||||
on_detach = on_detach
|
||||
})
|
||||
|
||||
-- Initial update
|
||||
manager.update(cbuf, cache[cbuf])
|
||||
|
||||
if config.keymaps and not vim.tbl_isempty(config.keymaps) then
|
||||
require('gitsigns.mappings')(config.keymaps as {string:any}, cbuf)
|
||||
end
|
||||
end)
|
||||
|
||||
--- Detach Gitsigns from all buffers it is attached to.
|
||||
function M.detach_all()
|
||||
for k, _ in pairs(cache as {integer:CacheEntry}) do
|
||||
M.detach(k)
|
||||
end
|
||||
end
|
||||
|
||||
--- Detach Gitsigns from the buffer {bufnr}. If {bufnr} is not
|
||||
--- provided then the current buffer is used.
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- {bufnr} (number): Buffer number
|
||||
function M.detach(bufnr: integer, _keep_signs: boolean)
|
||||
-- When this is called interactively (with no arguments) we want to remove all
|
||||
-- the signs, however if called via a detach event (due to nvim_buf_attach)
|
||||
-- then we don't want to clear the signs in case the buffer is just being
|
||||
-- updated due to the file externally changing. When this happens a detach and
|
||||
-- attach event happen in sequence and so we keep the old signs to stop the
|
||||
-- sign column width moving about between updates.
|
||||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
dprint('Detached')
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
dprint('Cache was nil')
|
||||
return
|
||||
end
|
||||
|
||||
manager.detach(bufnr, _keep_signs)
|
||||
|
||||
-- Clear status variables
|
||||
Status:clear(bufnr)
|
||||
|
||||
cache:destroy(bufnr)
|
||||
end
|
||||
|
||||
|
||||
--- Attach Gitsigns to the buffer.
|
||||
---
|
||||
--- Attributes: ~
|
||||
--- {async}
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- {bufnr} (number): Buffer number
|
||||
--- {ctx} (table|nil):
|
||||
--- Git context data that may optionally be used to attach to any
|
||||
--- buffer that represents a real git object.
|
||||
--- • {file}: (string)
|
||||
--- Path to the file represented by the buffer, relative to the
|
||||
--- top-level.
|
||||
--- • {toplevel}: (string)
|
||||
--- Path to the top-level of the parent git repository.
|
||||
--- • {gitdir}: (string)
|
||||
--- Path to the git directory of the parent git repository
|
||||
--- (typically the ".git/" directory).
|
||||
--- • {commit}: (string)
|
||||
--- The git revision that the file belongs to.
|
||||
--- • {base}: (string|nil)
|
||||
--- The git revision that the file should be compared to.
|
||||
M.attach = void(function(bufnr: integer, ctx: GitContext, _trigger: string)
|
||||
attach_throttled(bufnr or api.nvim_get_current_buf(), ctx, _trigger)
|
||||
end)
|
||||
|
||||
return M
|
|
@ -1,101 +0,0 @@
|
|||
local Hunk = require("gitsigns.hunks").Hunk
|
||||
local GitObj = require('gitsigns.git').Obj
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
local record M
|
||||
record CacheEntry
|
||||
file : string
|
||||
base : string
|
||||
compare_text : {string}
|
||||
hunks : {Hunk}
|
||||
force_next_update: boolean
|
||||
|
||||
compare_text_head : {string}
|
||||
hunks_staged : {Hunk}
|
||||
|
||||
staged_diffs : {Hunk}
|
||||
gitdir_watcher : vim.loop.FSPollObj -- Timer object watching the gitdir
|
||||
git_obj : GitObj
|
||||
commit : string
|
||||
|
||||
get_compare_rev : function(CacheEntry, base: string): string
|
||||
get_staged_compare_rev : function(CacheEntry): string
|
||||
get_rev_bufname : function(CacheEntry, rev: string): string
|
||||
invalidate : function(CacheEntry)
|
||||
new : function(CacheEntry): CacheEntry
|
||||
destroy : function(CacheEntry)
|
||||
end
|
||||
|
||||
record CacheObj
|
||||
{CacheEntry}
|
||||
|
||||
destroy: function(CacheObj, bufnr: integer)
|
||||
end
|
||||
|
||||
cache: CacheObj
|
||||
end
|
||||
|
||||
local CacheEntry = M.CacheEntry
|
||||
|
||||
function CacheEntry:get_compare_rev(base: string): string
|
||||
base = base or self.base
|
||||
if base then
|
||||
return base
|
||||
end
|
||||
|
||||
if self.commit then
|
||||
-- Buffer is a fugitive commit so compare against the parent of the commit
|
||||
if config._signs_staged_enable then
|
||||
return self.commit
|
||||
else
|
||||
return string.format('%s^', self.commit)
|
||||
end
|
||||
end
|
||||
|
||||
local stage = self.git_obj.has_conflicts and 1 or 0
|
||||
return string.format(':%d', stage)
|
||||
end
|
||||
|
||||
function CacheEntry:get_staged_compare_rev(): string
|
||||
return self.commit and string.format('%s^', self.commit) or 'HEAD'
|
||||
end
|
||||
|
||||
function CacheEntry:get_rev_bufname(rev: string): string
|
||||
rev = rev or self:get_compare_rev()
|
||||
return string.format(
|
||||
'gitsigns://%s/%s:%s',
|
||||
self.git_obj.repo.gitdir,
|
||||
rev,
|
||||
self.git_obj.relpath
|
||||
)
|
||||
end
|
||||
|
||||
function CacheEntry:invalidate()
|
||||
self.compare_text = nil
|
||||
self.compare_text_head = nil
|
||||
self.hunks = nil
|
||||
self.hunks_staged = nil
|
||||
end
|
||||
|
||||
function CacheEntry.new(o: CacheEntry): CacheEntry
|
||||
o.staged_diffs = o.staged_diffs or {}
|
||||
return setmetatable(o, {__index = CacheEntry})
|
||||
end
|
||||
|
||||
function CacheEntry:destroy()
|
||||
local w = self.gitdir_watcher
|
||||
if w and not w:is_closing() then
|
||||
w:close()
|
||||
end
|
||||
end
|
||||
|
||||
function M.CacheObj:destroy(bufnr: integer)
|
||||
self[bufnr]:destroy()
|
||||
self[bufnr] = nil
|
||||
end
|
||||
|
||||
M.cache = setmetatable({}, {
|
||||
__index = M.CacheObj,
|
||||
})
|
||||
|
||||
return M
|
|
@ -1,109 +0,0 @@
|
|||
local async = require('gitsigns.async')
|
||||
local void = require('gitsigns.async').void
|
||||
|
||||
local log = require('gitsigns.debug.log')
|
||||
local dprintf = log.dprintf
|
||||
local message = require'gitsigns.message'
|
||||
|
||||
local parse_args = require('gitsigns.cli.argparse').parse_args
|
||||
|
||||
local actions = require('gitsigns.actions')
|
||||
local attach = require('gitsigns.attach')
|
||||
local gs_debug = require('gitsigns.debug')
|
||||
|
||||
local sources = {
|
||||
[actions] = true,
|
||||
[attach] = false,
|
||||
[gs_debug] = false
|
||||
} as {{string:function}:boolean}
|
||||
|
||||
-- try to parse each argument as a lua boolean, nil or number, if fails then
|
||||
-- keep argument as a string:
|
||||
--
|
||||
-- 'false' -> false
|
||||
-- 'nil' -> nil
|
||||
-- '100' -> 100
|
||||
-- 'HEAD~300' -> 'HEAD~300'
|
||||
local function parse_to_lua(a: string): any
|
||||
if tonumber(a) then
|
||||
return tonumber(a)
|
||||
elseif a == 'false' or a == 'true' then
|
||||
return a == 'true'
|
||||
elseif a == 'nil' then
|
||||
return nil
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
local record M
|
||||
run: function(params: vim.api.UserCmdParams)
|
||||
end
|
||||
|
||||
function M.complete(arglead: string, line: string): {string}
|
||||
local words = vim.split(line, '%s+')
|
||||
local n: integer = #words
|
||||
|
||||
local matches: {string} = {}
|
||||
if n == 2 then
|
||||
for m, _ in pairs(sources) do
|
||||
for func, _ in pairs(m as {string:function}) do
|
||||
if not func:match('^[a-z]') then
|
||||
-- exclude
|
||||
elseif vim.startswith(func, arglead) then
|
||||
table.insert(matches, func)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif n > 2 then
|
||||
-- Subcommand completion
|
||||
local cmp_func = actions._get_cmp_func(words[2])
|
||||
if cmp_func then
|
||||
return cmp_func(arglead)
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
local function print_nonnil(x: any)
|
||||
if x ~= nil then
|
||||
print(vim.inspect(x))
|
||||
end
|
||||
end
|
||||
|
||||
M.run = void(function(params: vim.api.UserCmdParams)
|
||||
local __FUNC__ = 'cli.run'
|
||||
local pos_args_raw, named_args_raw = parse_args(params.args)
|
||||
|
||||
local func = pos_args_raw[1]
|
||||
|
||||
if not func then
|
||||
func = async.wrap(vim.ui.select, 3)(M.complete('', 'Gitsigns '), {})
|
||||
end
|
||||
|
||||
local pos_args = vim.tbl_map(parse_to_lua, vim.list_slice(pos_args_raw, 2)) as {any}
|
||||
local named_args = vim.tbl_map(parse_to_lua, named_args_raw) as {string:any}
|
||||
local args = vim.tbl_extend('error', pos_args, named_args)
|
||||
|
||||
dprintf("Running action '%s' with arguments %s", func, vim.inspect(args, {newline=' ', indent=''}))
|
||||
|
||||
local cmd_func = actions._get_cmd_func(func)
|
||||
if cmd_func then
|
||||
-- Action has a specialised mapping function from command form to lua
|
||||
-- function
|
||||
print_nonnil(cmd_func(args, params))
|
||||
return
|
||||
end
|
||||
|
||||
for m, has_named in pairs(sources) do
|
||||
local f = (m as {string:any})[func]
|
||||
if f is function then
|
||||
-- Note functions here do not have named arguments
|
||||
print_nonnil(f(unpack(pos_args), has_named and named_args or nil))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
message.error('%s is not a valid function or action', func)
|
||||
end)
|
||||
|
||||
return M
|
|
@ -1,115 +0,0 @@
|
|||
local record M
|
||||
parse_args: function(x: string): {string}, {string:string|boolean}
|
||||
end
|
||||
|
||||
local function is_char(x: string): boolean
|
||||
return x:match('[^=\'"%s]') ~= nil
|
||||
end
|
||||
|
||||
local enum ArgState
|
||||
"in_arg"
|
||||
"in_ws"
|
||||
"in_value"
|
||||
"in_quote"
|
||||
"in_flag"
|
||||
end
|
||||
|
||||
-- Return positional arguments and named arguments
|
||||
function M.parse_args(x: string): {string}, {string:string|boolean}
|
||||
local pos_args, named_args: {string}, {string:string|boolean} = {}, {}
|
||||
|
||||
local state: ArgState = 'in_arg'
|
||||
local cur_arg = ''
|
||||
local cur_val = ''
|
||||
local cur_quote = ''
|
||||
|
||||
local function peek(idx: integer): string
|
||||
return x:sub(idx+1, idx+1)
|
||||
end
|
||||
|
||||
local i = 1
|
||||
while i <= #x do
|
||||
local ch = x:sub(i, i)
|
||||
-- dprintf('L(%d)(%s): cur_arg="%s" ch="%s"', i, state, cur_arg, ch)
|
||||
|
||||
if state == 'in_arg' then
|
||||
if is_char(ch) then
|
||||
if ch == '-' and peek(i) == '-' then
|
||||
state = 'in_flag'
|
||||
cur_arg = ''
|
||||
i = i + 1
|
||||
else
|
||||
cur_arg = cur_arg .. ch
|
||||
end
|
||||
elseif ch:match('%s') then
|
||||
pos_args[#pos_args+1] = cur_arg
|
||||
state = 'in_ws'
|
||||
elseif ch == '=' then
|
||||
cur_val = ''
|
||||
local next_ch = peek(i)
|
||||
if next_ch == "'" or next_ch == '"' then
|
||||
cur_quote = next_ch
|
||||
i = i + 1
|
||||
state = 'in_quote'
|
||||
else
|
||||
state = 'in_value'
|
||||
end
|
||||
end
|
||||
elseif state == 'in_flag' then
|
||||
if ch:match('%s') then
|
||||
named_args[cur_arg] = true
|
||||
state = 'in_ws'
|
||||
else
|
||||
cur_arg = cur_arg .. ch
|
||||
end
|
||||
elseif state == 'in_ws' then
|
||||
if is_char(ch) then
|
||||
if ch == '-' and peek(i) == '-' then
|
||||
state = 'in_flag'
|
||||
cur_arg = ''
|
||||
i = i + 1
|
||||
else
|
||||
state = 'in_arg'
|
||||
cur_arg = ch
|
||||
end
|
||||
end
|
||||
elseif state == 'in_value' then
|
||||
if is_char(ch) then
|
||||
cur_val = cur_val .. ch
|
||||
elseif ch:match('%s') then
|
||||
named_args[cur_arg] = cur_val
|
||||
cur_arg = ''
|
||||
state = 'in_ws'
|
||||
end
|
||||
elseif state == 'in_quote' then
|
||||
local next_ch = peek(i)
|
||||
if ch == "\\" and next_ch == cur_quote then
|
||||
cur_val = cur_val .. next_ch
|
||||
i = i + 1
|
||||
elseif ch == cur_quote then
|
||||
named_args[cur_arg] = cur_val
|
||||
state = 'in_ws'
|
||||
if next_ch ~= '' and not next_ch:match('%s') then
|
||||
error('malformed argument: '..next_ch)
|
||||
end
|
||||
else
|
||||
cur_val = cur_val .. ch
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if #cur_arg > 0 then
|
||||
if state == 'in_arg' then
|
||||
pos_args[#pos_args+1] = cur_arg
|
||||
elseif state == 'in_flag' then
|
||||
named_args[cur_arg] = true
|
||||
elseif state == 'in_value' then
|
||||
named_args[cur_arg] = cur_val
|
||||
end
|
||||
end
|
||||
|
||||
return pos_args, named_args
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,963 +0,0 @@
|
|||
local warn: function(string, ...: any)
|
||||
do
|
||||
-- this is included in gen_help.lua so don't error if requires fail
|
||||
local ok, ret = pcall(require, 'gitsigns.message')
|
||||
if ok then
|
||||
warn = ret.warn
|
||||
end
|
||||
end
|
||||
|
||||
--- @class Gitsigns.SchemaElem
|
||||
--- @field type string|string[]
|
||||
--- @field deep_extend boolean
|
||||
--- @field default any
|
||||
--- @field deprecated boolean|{new_field:string,message:string,hard:boolean}
|
||||
--- @field default_help string
|
||||
--- @field description string
|
||||
|
||||
--- @class Gitsigns.DiffOpts
|
||||
--- @field algorithm string
|
||||
--- @field internal boolean
|
||||
--- @field indent_heuristic boolean
|
||||
--- @field vertical boolean
|
||||
--- @field linematch integer
|
||||
|
||||
--- @class Gitsign.SignConfig
|
||||
--- @field show_count boolean
|
||||
--- @field hl string
|
||||
--- @field text string
|
||||
--- @field numhl string
|
||||
--- @field linehl string
|
||||
--- @field keymaps table<string,string>
|
||||
|
||||
--- @alias Gitsigns.SignType
|
||||
--- | 'add'
|
||||
--- | 'change'
|
||||
--- | 'delete'
|
||||
--- | 'topdelete'
|
||||
--- | 'changedelete'
|
||||
--- | 'untracked'
|
||||
|
||||
--- @alias Gitsigns.CurrentLineBlameFmtOpts { relative_time: boolean }
|
||||
--- @alias Gitsigns.CurrentLineBlameFmtFun fun(_: string, _: table<string,any>, _: Gitsigns.CurrentLineBlameFmtOpts): {[1]:string,[2]:string}[]
|
||||
|
||||
--- @class Gitsigns.CurrentLineBlameOpts
|
||||
--- @field virt_text boolean
|
||||
--- @field virt_text_pos 'eol'|'overlay'|'right_align'
|
||||
--- @field delay integer
|
||||
--- @field ignore_whitespace boolean
|
||||
--- @field virt_text_priority integer
|
||||
|
||||
--- @class Gitsigns.Config
|
||||
--- @field debug_mode boolean
|
||||
--- @field diff_opts Gitsigns.DiffOpts
|
||||
--- @field base string
|
||||
--- @field signs table<Gitsigns.SignType,Gitsign.SignConfig>
|
||||
--- @field _signs_staged table<Gitsigns.SignType,Gitsign.SignConfig>
|
||||
--- @field _signs_staged_enable boolean
|
||||
--- @field count_chars table<string|integer,string>
|
||||
--- @field signcolumn boolean
|
||||
--- @field numhl boolean
|
||||
--- @field linehl boolean
|
||||
--- @field show_deleted boolean
|
||||
--- @field sign_priority integer
|
||||
--- @field _on_attach_pre fun(bufnr: integer, callback: fun(_: table))
|
||||
--- @field on_attach fun(bufnr: integer)
|
||||
--- @field watch_gitdir { enable: boolean, interval: integer, follow_files: boolean }
|
||||
--- @field max_file_length integer
|
||||
--- @field update_debounce integer
|
||||
--- @field status_formatter fun(_: table<string,any>): string
|
||||
--- @field current_line_blame boolean
|
||||
--- @field current_line_blame_formatter_opts { relative_time: boolean }
|
||||
--- @field current_line_blame_formatter string|Gitsigns.CurrentLineBlameFmtFun
|
||||
--- @field current_line_blame_formatter_nc string|Gitsigns.CurrentLineBlameFmtFun
|
||||
--- @field current_line_blame_opts Gitsigns.CurrentLineBlameOpts
|
||||
--- @field preview_config table<string,any>
|
||||
--- @field attach_to_untracked boolean
|
||||
--- @field yadm { enable: boolean }
|
||||
--- @field worktrees {toplevel: string, gitdir: string}[]
|
||||
--- @field word_diff boolean
|
||||
--- -- Undocumented
|
||||
--- @field _refresh_staged_on_update boolean
|
||||
--- @field _blame_cache boolean
|
||||
--- @field _threaded_diff boolean
|
||||
--- @field _inline2 boolean
|
||||
--- @field _extmark_signs boolean
|
||||
--- @field _git_version string
|
||||
--- @field _verbose boolean
|
||||
--- @field _test_mode boolean
|
||||
|
||||
local record SchemaElem
|
||||
type: string|{string}
|
||||
deep_extend: boolean
|
||||
default: any
|
||||
|
||||
record Deprecated
|
||||
new_field: string
|
||||
message: string
|
||||
hard: boolean
|
||||
end
|
||||
|
||||
deprecated: Deprecated|boolean
|
||||
|
||||
default_help: string
|
||||
description: string
|
||||
end
|
||||
|
||||
local record M
|
||||
record Config
|
||||
debug_mode: boolean
|
||||
|
||||
record DiffOpts
|
||||
algorithm: string
|
||||
internal: boolean
|
||||
indent_heuristic: boolean
|
||||
vertical: boolean
|
||||
linematch: integer
|
||||
end
|
||||
|
||||
diff_opts: DiffOpts
|
||||
base: string
|
||||
|
||||
record SignConfig
|
||||
show_count: boolean
|
||||
hl: string
|
||||
text: string
|
||||
numhl: string
|
||||
linehl: string
|
||||
keymaps: {string:string}
|
||||
end
|
||||
|
||||
enum SignType
|
||||
'add'
|
||||
'change'
|
||||
'delete'
|
||||
'topdelete'
|
||||
'changedelete'
|
||||
'untracked'
|
||||
end
|
||||
|
||||
type SignsConfig = {SignType: SignConfig}
|
||||
|
||||
signs: SignsConfig
|
||||
_signs_staged: SignsConfig
|
||||
_signs_staged_enable: boolean
|
||||
|
||||
count_chars: {string|integer:string}
|
||||
signcolumn: boolean
|
||||
numhl: boolean
|
||||
linehl: boolean
|
||||
show_deleted: boolean
|
||||
sign_priority: integer
|
||||
keymaps: {string:any}
|
||||
_on_attach_pre: function(bufnr: integer, callback: function(table))
|
||||
on_attach: function(bufnr: integer)
|
||||
record watch_gitdir
|
||||
enable: boolean
|
||||
interval: integer
|
||||
follow_files: boolean
|
||||
end
|
||||
max_file_length: integer
|
||||
update_debounce: integer
|
||||
status_formatter: function({string:any}): string
|
||||
|
||||
current_line_blame: boolean
|
||||
|
||||
record current_line_blame_formatter_opts
|
||||
relative_time: boolean
|
||||
end
|
||||
|
||||
current_line_blame_formatter: string|function(string, {string:any}, current_line_blame_formatter_opts): {{string,string}}
|
||||
current_line_blame_formatter_nc: string|function(string, {string:any}, current_line_blame_formatter_opts): {{string,string}}
|
||||
|
||||
record current_line_blame_opts
|
||||
virt_text: boolean
|
||||
|
||||
enum VirtTextPos
|
||||
'eol'
|
||||
'overlay'
|
||||
'right_align'
|
||||
end
|
||||
virt_text_pos: VirtTextPos
|
||||
|
||||
delay: integer
|
||||
ignore_whitespace: boolean
|
||||
virt_text_priority: integer
|
||||
end
|
||||
|
||||
preview_config: {string:any}
|
||||
attach_to_untracked: boolean
|
||||
|
||||
record yadm
|
||||
enable: boolean
|
||||
end
|
||||
|
||||
trouble: boolean
|
||||
|
||||
record Worktree
|
||||
toplevel: string
|
||||
gitdir: string
|
||||
end
|
||||
worktrees: {Worktree}
|
||||
|
||||
-- Undocumented
|
||||
word_diff: boolean
|
||||
_refresh_staged_on_update: boolean
|
||||
_blame_cache: boolean
|
||||
_threaded_diff: boolean
|
||||
_inline2: boolean
|
||||
_extmark_signs: boolean
|
||||
|
||||
_git_version: string
|
||||
_verbose: boolean
|
||||
_test_mode: boolean
|
||||
end
|
||||
|
||||
schema: {string:SchemaElem}
|
||||
config: Config
|
||||
end
|
||||
|
||||
--- @type Gitsigns.Config
|
||||
M.config = {}
|
||||
|
||||
--- @type table<string,Gitsigns.SchemaElem>
|
||||
M.schema = {
|
||||
signs = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
default = {
|
||||
add = {hl = 'GitSignsAdd', text = '┃', numhl = 'GitSignsAddNr', linehl = 'GitSignsAddLn' },
|
||||
change = {hl = 'GitSignsChange', text = '┃', numhl = 'GitSignsChangeNr', linehl = 'GitSignsChangeLn' },
|
||||
delete = {hl = 'GitSignsDelete', text = '▁', numhl = 'GitSignsDeleteNr', linehl = 'GitSignsDeleteLn' },
|
||||
topdelete = {hl = 'GitSignsTopdelete', text = '▔', numhl = 'GitSignsTopdeleteNr', linehl = 'GitSignsTopdeleteLn' },
|
||||
changedelete = {hl = 'GitSignsChangedelete', text = '~', numhl = 'GitSignsChangedeleteNr', linehl = 'GitSignsChangedeleteLn' },
|
||||
untracked = {hl = 'GitSignsUntracked', text = '┆', numhl = 'GitSignsUntrackedNr', linehl = 'GitSignsUntrackedLn' },
|
||||
},
|
||||
default_help = [[{
|
||||
add = { text = '┃' },
|
||||
change = { text = '┃' },
|
||||
delete = { text = '▁' },
|
||||
topdelete = { text = '▔' },
|
||||
changedelete = { text = '~' },
|
||||
untracked = { text = '┆' },
|
||||
}]],
|
||||
description = [[
|
||||
Configuration for signs:
|
||||
• `text` specifies the character to use for the sign.
|
||||
• `show_count` to enable showing count of hunk, e.g. number of deleted
|
||||
lines.
|
||||
|
||||
The highlights `GitSigns[kind][type]` is used for each kind of sign. E.g.
|
||||
'add' signs uses the highlights:
|
||||
• `GitSignsAdd` (for normal text signs)
|
||||
• `GitSignsAddNr` (for signs when `config.numhl == true`)
|
||||
• `GitSignsAddLn `(for signs when `config.linehl == true`)
|
||||
|
||||
See |gitsigns-highlight-groups|.
|
||||
]]
|
||||
},
|
||||
|
||||
_signs_staged = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
default = {
|
||||
add = {hl = 'GitSignsStagedAdd' , text = '┃', numhl='GitSignsStagedAddNr' , linehl='GitSignsStagedAddLn' },
|
||||
change = {hl = 'GitSignsStagedChange' , text = '┃', numhl='GitSignsStagedChangeNr' , linehl='GitSignsStagedChangeLn' },
|
||||
delete = {hl = 'GitSignsStagedDelete' , text = '▁', numhl='GitSignsStagedDeleteNr' , linehl='GitSignsStagedDeleteLn' },
|
||||
topdelete = {hl = 'GitSignsStagedTopdelete' , text = '▔', numhl='GitSignsStagedTopdeleteNr' , linehl='GitSignsStagedTopdeleteLn' },
|
||||
changedelete = {hl = 'GitSignsStagedChangedelete', text = '~', numhl='GitSignsStagedChangedeleteNr', linehl='GitSignsStagedChangedeleteLn' },
|
||||
},
|
||||
default_help = [[{
|
||||
add = { text = '┃' },
|
||||
change = { text = '┃' },
|
||||
delete = { text = '▁' },
|
||||
topdelete = { text = '▔' },
|
||||
changedelete = { text = '~' },
|
||||
}]],
|
||||
description = [[
|
||||
Configuration for signs of staged hunks.
|
||||
|
||||
See |gitsigns-config-signs|.
|
||||
]]
|
||||
},
|
||||
|
||||
_signs_staged_enable = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Show signs for staged hunks.
|
||||
|
||||
When enabled the signs defined in |git-config-signs_staged|` are used.
|
||||
]]
|
||||
},
|
||||
|
||||
keymaps = {
|
||||
deprecated = {
|
||||
message = "config.keymaps is now deprecated. Please define mappings in config.on_attach() instead."
|
||||
},
|
||||
type = 'table',
|
||||
default = {},
|
||||
description = [[
|
||||
Keymaps to set up when attaching to a buffer.
|
||||
|
||||
Each key in the table defines the mode and key (whitespace delimited)
|
||||
for the mapping and the value defines what the key maps to. The value
|
||||
can be a table which can contain keys matching the options defined in
|
||||
|map-arguments| which are: `expr`, `noremap`, `nowait`, `script`, `silent`
|
||||
and `unique`. These options can also be used in the top level of the
|
||||
table to define default options for all mappings.
|
||||
|
||||
Since this field is not extended (unlike |gitsigns-config-signs|),
|
||||
mappings defined in this field can be disabled by setting the whole field
|
||||
to `{}`, and |gitsigns-config-on_attach| can instead be used to define
|
||||
mappings.
|
||||
]]
|
||||
},
|
||||
|
||||
worktrees = {
|
||||
type = 'table',
|
||||
default = nil,
|
||||
description = [[
|
||||
Detached working trees.
|
||||
|
||||
Array of tables with the keys `gitdir` and `toplevel`.
|
||||
|
||||
If normal attaching fails, then each entry in the table is attempted
|
||||
with the work tree details set.
|
||||
|
||||
Example: >
|
||||
worktrees = {
|
||||
{
|
||||
toplevel = vim.env.HOME,
|
||||
gitdir = vim.env.HOME .. '/projects/dotfiles/.git'
|
||||
}
|
||||
}
|
||||
]]
|
||||
},
|
||||
|
||||
_on_attach_pre = {
|
||||
type = 'function',
|
||||
default = nil,
|
||||
description = [[
|
||||
Asynchronous hook called before attaching to a buffer. Mainly used to
|
||||
configure detached worktrees.
|
||||
|
||||
This callback must call its callback argument. The callback argument can
|
||||
accept an optional table argument with the keys: 'gitdir' and 'toplevel'.
|
||||
|
||||
Example: >
|
||||
on_attach_pre = function(bufnr, callback)
|
||||
...
|
||||
callback {
|
||||
gitdir = ...,
|
||||
toplevel = ...
|
||||
}
|
||||
end
|
||||
<
|
||||
]]
|
||||
},
|
||||
|
||||
on_attach = {
|
||||
type = 'function',
|
||||
default = nil,
|
||||
description = [[
|
||||
Callback called when attaching to a buffer. Mainly used to setup keymaps
|
||||
when `config.keymaps` is empty. The buffer number is passed as the first
|
||||
argument.
|
||||
|
||||
This callback can return `false` to prevent attaching to the buffer.
|
||||
|
||||
Example: >
|
||||
on_attach = function(bufnr)
|
||||
if vim.api.nvim_buf_get_name(bufnr):match(<PATTERN>) then
|
||||
-- Don't attach to specific buffers whose name matches a pattern
|
||||
return false
|
||||
end
|
||||
|
||||
-- Setup keymaps
|
||||
vim.api.nvim_buf_set_keymap(bufnr, 'n', 'hs', '<cmd>lua require"gitsigns".stage_hunk()<CR>', {})
|
||||
... -- More keymaps
|
||||
end
|
||||
<
|
||||
]]
|
||||
},
|
||||
|
||||
watch_gitdir = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
default = {
|
||||
enable = true,
|
||||
interval = 1000,
|
||||
follow_files = true
|
||||
},
|
||||
description = [[
|
||||
When opening a file, a libuv watcher is placed on the respective
|
||||
`.git` directory to detect when changes happen to use as a trigger to
|
||||
update signs.
|
||||
|
||||
Fields: ~
|
||||
• `enable`:
|
||||
Whether the watcher is enabled.
|
||||
|
||||
• `interval`:
|
||||
Interval the watcher waits between polls of the gitdir in milliseconds.
|
||||
|
||||
• `follow_files`:
|
||||
If a file is moved with `git mv`, switch the buffer to the new location.
|
||||
]]
|
||||
},
|
||||
|
||||
sign_priority = {
|
||||
type = 'number',
|
||||
default = 6,
|
||||
description = [[
|
||||
Priority to use for signs.
|
||||
]]
|
||||
},
|
||||
|
||||
signcolumn = {
|
||||
type = 'boolean',
|
||||
default = true,
|
||||
description = [[
|
||||
Enable/disable symbols in the sign column.
|
||||
|
||||
When enabled the highlights defined in `signs.*.hl` and symbols defined
|
||||
in `signs.*.text` are used.
|
||||
]]
|
||||
},
|
||||
|
||||
numhl = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Enable/disable line number highlights.
|
||||
|
||||
When enabled the highlights defined in `signs.*.numhl` are used. If
|
||||
the highlight group does not exist, then it is automatically defined
|
||||
and linked to the corresponding highlight group in `signs.*.hl`.
|
||||
]]
|
||||
},
|
||||
|
||||
linehl = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Enable/disable line highlights.
|
||||
|
||||
When enabled the highlights defined in `signs.*.linehl` are used. If
|
||||
the highlight group does not exist, then it is automatically defined
|
||||
and linked to the corresponding highlight group in `signs.*.hl`.
|
||||
]]
|
||||
},
|
||||
|
||||
show_deleted = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Show the old version of hunks inline in the buffer (via virtual lines).
|
||||
|
||||
Note: Virtual lines currently use the highlight `GitSignsDeleteVirtLn`.
|
||||
]]
|
||||
},
|
||||
|
||||
diff_opts = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
default = function(): {string:any}
|
||||
local r: M.Config.DiffOpts = {
|
||||
algorithm = 'myers',
|
||||
internal = false,
|
||||
indent_heuristic = false,
|
||||
vertical = true,
|
||||
linematch = nil
|
||||
}
|
||||
for _, o in ipairs(vim.opt.diffopt:get()) do
|
||||
if o == 'indent-heuristic' then
|
||||
r.indent_heuristic = true
|
||||
elseif o == 'internal' then
|
||||
if vim.diff then
|
||||
r.internal = true
|
||||
end
|
||||
elseif o == 'horizontal' then
|
||||
r.vertical = false
|
||||
elseif vim.startswith(o, 'algorithm:') then
|
||||
r.algorithm = string.sub(o, ('algorithm:'):len() + 1)
|
||||
elseif vim.startswith(o, 'linematch:') then
|
||||
r.linematch = tonumber(string.sub(o, ('linematch:'):len() + 1)) as integer
|
||||
end
|
||||
end
|
||||
return r
|
||||
end,
|
||||
default_help = "derived from 'diffopt'",
|
||||
description = [[
|
||||
Diff options.
|
||||
|
||||
Fields: ~
|
||||
• algorithm: string
|
||||
Diff algorithm to use. Values:
|
||||
• "myers" the default algorithm
|
||||
• "minimal" spend extra time to generate the
|
||||
smallest possible diff
|
||||
• "patience" patience diff algorithm
|
||||
• "histogram" histogram diff algorithm
|
||||
• internal: boolean
|
||||
Use Neovim's built in xdiff library for running diffs.
|
||||
• indent_heuristic: boolean
|
||||
Use the indent heuristic for the internal
|
||||
diff library.
|
||||
• vertical: boolean
|
||||
Start diff mode with vertical splits.
|
||||
• linematch: integer
|
||||
Enable second-stage diff on hunks to align lines.
|
||||
Requires `internal=true`.
|
||||
]]
|
||||
},
|
||||
|
||||
base = {
|
||||
type = 'string',
|
||||
default = nil,
|
||||
default_help = 'index',
|
||||
description = [[
|
||||
The object/revision to diff against.
|
||||
See |gitsigns-revision|.
|
||||
]]
|
||||
},
|
||||
|
||||
count_chars = {
|
||||
type = 'table',
|
||||
default = {
|
||||
[1] = '1', -- '₁',
|
||||
[2] = '2', -- '₂',
|
||||
[3] = '3', -- '₃',
|
||||
[4] = '4', -- '₄',
|
||||
[5] = '5', -- '₅',
|
||||
[6] = '6', -- '₆',
|
||||
[7] = '7', -- '₇',
|
||||
[8] = '8', -- '₈',
|
||||
[9] = '9', -- '₉',
|
||||
['+'] = '>', -- '₊',
|
||||
},
|
||||
description = [[
|
||||
The count characters used when `signs.*.show_count` is enabled. The
|
||||
`+` entry is used as a fallback. With the default, any count outside
|
||||
of 1-9 uses the `>` character in the sign.
|
||||
|
||||
Possible use cases for this field:
|
||||
• to specify unicode characters for the counts instead of 1-9.
|
||||
• to define characters to be used for counts greater than 9.
|
||||
]]
|
||||
},
|
||||
|
||||
status_formatter = {
|
||||
type = 'function',
|
||||
default = function(status: {string:number}): string
|
||||
local added, changed, removed = status.added, status.changed, status.removed
|
||||
local status_txt = {}
|
||||
if added and added > 0 then table.insert(status_txt, '+'..added ) end
|
||||
if changed and changed > 0 then table.insert(status_txt, '~'..changed) end
|
||||
if removed and removed > 0 then table.insert(status_txt, '-'..removed) end
|
||||
return table.concat(status_txt, ' ')
|
||||
end,
|
||||
default_help = [[function(status)
|
||||
local added, changed, removed = status.added, status.changed, status.removed
|
||||
local status_txt = {}
|
||||
if added and added > 0 then table.insert(status_txt, '+'..added ) end
|
||||
if changed and changed > 0 then table.insert(status_txt, '~'..changed) end
|
||||
if removed and removed > 0 then table.insert(status_txt, '-'..removed) end
|
||||
return table.concat(status_txt, ' ')
|
||||
end]],
|
||||
description = [[
|
||||
Function used to format `b:gitsigns_status`.
|
||||
]]
|
||||
},
|
||||
|
||||
max_file_length = {
|
||||
type = 'number',
|
||||
default = 40000,
|
||||
description = [[
|
||||
Max file length (in lines) to attach to.
|
||||
]]
|
||||
},
|
||||
|
||||
preview_config = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
default = {
|
||||
border = 'single',
|
||||
style = 'minimal',
|
||||
relative = 'cursor',
|
||||
row = 0,
|
||||
col = 1
|
||||
},
|
||||
description = [[
|
||||
Option overrides for the Gitsigns preview window. Table is passed directly
|
||||
to `nvim_open_win`.
|
||||
]]
|
||||
},
|
||||
|
||||
attach_to_untracked = {
|
||||
type = 'boolean',
|
||||
default = true,
|
||||
description = [[
|
||||
Attach to untracked files.
|
||||
]]
|
||||
},
|
||||
|
||||
update_debounce = {
|
||||
type = 'number',
|
||||
default = 100,
|
||||
description = [[
|
||||
Debounce time for updates (in milliseconds).
|
||||
]]
|
||||
},
|
||||
|
||||
current_line_blame = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Adds an unobtrusive and customisable blame annotation at the end of
|
||||
the current line.
|
||||
|
||||
The highlight group used for the text is `GitSignsCurrentLineBlame`.
|
||||
]]
|
||||
},
|
||||
|
||||
current_line_blame_opts = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
default = {
|
||||
virt_text = true,
|
||||
virt_text_pos = 'eol',
|
||||
virt_text_priority = 100,
|
||||
delay = 1000
|
||||
},
|
||||
description = [[
|
||||
Options for the current line blame annotation.
|
||||
|
||||
Fields: ~
|
||||
• virt_text: boolean
|
||||
Whether to show a virtual text blame annotation.
|
||||
• virt_text_pos: string
|
||||
Blame annotation position. Available values:
|
||||
`eol` Right after eol character.
|
||||
`overlay` Display over the specified column, without
|
||||
shifting the underlying text.
|
||||
`right_align` Display right aligned in the window.
|
||||
• delay: integer
|
||||
Sets the delay (in milliseconds) before blame virtual text is
|
||||
displayed.
|
||||
• ignore_whitespace: boolean
|
||||
Ignore whitespace when running blame.
|
||||
• virt_text_priority: integer
|
||||
Priority of virtual text.
|
||||
]]
|
||||
},
|
||||
|
||||
current_line_blame_formatter_opts = {
|
||||
type = 'table',
|
||||
deep_extend = true,
|
||||
deprecated = true,
|
||||
default = {
|
||||
relative_time = false
|
||||
},
|
||||
description = [[
|
||||
Options for the current line blame annotation formatter.
|
||||
|
||||
Fields: ~
|
||||
• relative_time: boolean
|
||||
]]
|
||||
},
|
||||
|
||||
current_line_blame_formatter = {
|
||||
type = {'string', 'function'},
|
||||
default = ' <author>, <author_time> - <summary> ',
|
||||
description = [[
|
||||
String or function used to format the virtual text of
|
||||
|gitsigns-config-current_line_blame|.
|
||||
|
||||
When a string, accepts the following format specifiers:
|
||||
|
||||
• `<abbrev_sha>`
|
||||
• `<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>`
|
||||
|
||||
For `<author_time:FORMAT>` and `<committer_time:FORMAT>`, `FORMAT` can
|
||||
be any valid date format that is accepted by `os.date()` with the
|
||||
addition of `%R` (defaults to `%Y-%m-%d`):
|
||||
|
||||
• `%a` abbreviated weekday name (e.g., Wed)
|
||||
• `%A` full weekday name (e.g., Wednesday)
|
||||
• `%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.
|
||||
]]
|
||||
},
|
||||
|
||||
current_line_blame_formatter_nc = {
|
||||
type = {'string', 'function'},
|
||||
default = ' <author>',
|
||||
description = [[
|
||||
String or function used to format the virtual text of
|
||||
|gitsigns-config-current_line_blame| for lines that aren't committed.
|
||||
|
||||
See |gitsigns-config-current_line_blame_formatter| for more information.
|
||||
]]
|
||||
},
|
||||
|
||||
trouble = {
|
||||
type = 'boolean',
|
||||
default = function(): boolean
|
||||
local has_trouble = pcall(require, 'trouble')
|
||||
return has_trouble
|
||||
end,
|
||||
default_help = "true if installed",
|
||||
description = [[
|
||||
When using setqflist() or setloclist(), open Trouble instead of the
|
||||
quickfix/location list window.
|
||||
]]
|
||||
},
|
||||
|
||||
yadm = {
|
||||
type = 'table',
|
||||
default = { enable = false },
|
||||
description = [[
|
||||
yadm configuration.
|
||||
]]
|
||||
},
|
||||
|
||||
_git_version = {
|
||||
type = 'string',
|
||||
default = 'auto',
|
||||
description = [[
|
||||
Version of git available. Set to 'auto' to automatically detect.
|
||||
]]
|
||||
},
|
||||
|
||||
_verbose = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
More verbose debug message. Requires debug_mode=true.
|
||||
]]
|
||||
},
|
||||
|
||||
_test_mode = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
},
|
||||
|
||||
word_diff = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Highlight intra-line word differences in the buffer.
|
||||
Requires `config.diff_opts.internal = true` .
|
||||
|
||||
Uses the highlights:
|
||||
• For word diff in previews:
|
||||
• `GitSignsAddInline`
|
||||
• `GitSignsChangeInline`
|
||||
• `GitSignsDeleteInline`
|
||||
• For word diff in buffer:
|
||||
• `GitSignsAddLnInline`
|
||||
• `GitSignsChangeLnInline`
|
||||
• `GitSignsDeleteLnInline`
|
||||
• For word diff in virtual lines (e.g. show_deleted):
|
||||
• `GitSignsAddVirtLnInline`
|
||||
• `GitSignsChangeVirtLnInline`
|
||||
• `GitSignsDeleteVirtLnInline`
|
||||
]]
|
||||
},
|
||||
|
||||
_refresh_staged_on_update = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Always refresh the staged file on each update. Disabling this will cause
|
||||
the staged file to only be refreshed when an update to the index is
|
||||
detected.
|
||||
]]
|
||||
},
|
||||
|
||||
_blame_cache = {
|
||||
type = 'boolean',
|
||||
default = true,
|
||||
description = [[
|
||||
Cache blame results for current_line_blame
|
||||
]]
|
||||
},
|
||||
|
||||
_threaded_diff = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Run diffs on a separate thread
|
||||
]]
|
||||
},
|
||||
|
||||
_inline2 = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Enable enhanced version of preview_hunk_inline()
|
||||
]]
|
||||
},
|
||||
|
||||
_extmark_signs = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Use extmarks for placing signs.
|
||||
]]
|
||||
},
|
||||
|
||||
debug_mode = {
|
||||
type = 'boolean',
|
||||
default = false,
|
||||
description = [[
|
||||
Enables debug logging and makes the following functions
|
||||
available: `dump_cache`, `debug_messages`, `clear_debug`.
|
||||
]]
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
warn = function(s: string, ...: any)
|
||||
vim.notify(s:format(...), vim.log.levels.WARN, {title = 'gitsigns'})
|
||||
end
|
||||
|
||||
--- @param config Gitsigns.Config
|
||||
local function validate_config(config: {string:any})
|
||||
for k, v in pairs(config) do
|
||||
local kschema = M.schema[k]
|
||||
if kschema == nil then
|
||||
warn("gitsigns: Ignoring invalid configuration field '%s'", k)
|
||||
elseif kschema.type then
|
||||
if type(kschema.type) == 'string' then
|
||||
vim.validate {
|
||||
[k] = { v, kschema.type } as {any};
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function resolve_default(v: SchemaElem): any
|
||||
if type(v.default) == 'function' and v.type ~= 'function' then
|
||||
return (v.default as function)()
|
||||
else
|
||||
return v.default
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_deprecated(cfg: {string:any})
|
||||
for k, v in pairs(M.schema) do
|
||||
local dep = v.deprecated
|
||||
if dep and cfg[k] ~= nil then
|
||||
if dep is SchemaElem.Deprecated then
|
||||
if dep.new_field then
|
||||
local opts_key, field = dep.new_field:match('(.*)%.(.*)')
|
||||
if opts_key and field then
|
||||
-- Field moved to an options table
|
||||
local opts = (cfg[opts_key] or {}) as {string:any}
|
||||
opts[field] = cfg[k]
|
||||
cfg[opts_key] = opts
|
||||
else
|
||||
-- Field renamed
|
||||
cfg[dep.new_field] = cfg[k]
|
||||
end
|
||||
end
|
||||
|
||||
if dep.hard then
|
||||
if dep.message then
|
||||
warn(dep.message)
|
||||
elseif dep.new_field then
|
||||
warn('%s is now deprecated, please use %s', k, dep.new_field)
|
||||
else
|
||||
warn('%s is now deprecated; ignoring', k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param user_config Gitsigns.Config
|
||||
function M.build(user_config: {string:any})
|
||||
user_config = user_config or {}
|
||||
|
||||
handle_deprecated(user_config)
|
||||
|
||||
validate_config(user_config)
|
||||
|
||||
local config = M.config as {string:any}
|
||||
for k, v in pairs(M.schema) do
|
||||
if user_config[k] ~= nil then
|
||||
if v.deep_extend then
|
||||
local d = resolve_default(v)
|
||||
config[k] = vim.tbl_deep_extend('force', d as table, user_config[k] as table)
|
||||
else
|
||||
config[k] = user_config[k]
|
||||
end
|
||||
else
|
||||
config[k] = resolve_default(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,214 +0,0 @@
|
|||
local a = require('gitsigns.async')
|
||||
local wrap = a.wrap
|
||||
local void = a.void
|
||||
local scheduler = a.scheduler
|
||||
|
||||
local cache = require('gitsigns.cache').cache
|
||||
local config = require('gitsigns.config').config
|
||||
local BlameInfo = require('gitsigns.git').BlameInfo
|
||||
local util = require('gitsigns.util')
|
||||
local uv = require('gitsigns.uv')
|
||||
|
||||
local api = vim.api
|
||||
|
||||
local current_buf = api.nvim_get_current_buf
|
||||
|
||||
local namespace = api.nvim_create_namespace('gitsigns_blame')
|
||||
|
||||
local timer = uv.new_timer(true)
|
||||
|
||||
local record M
|
||||
setup: function()
|
||||
end
|
||||
|
||||
local wait_timer = wrap(vim.loop.timer_start, 4)
|
||||
|
||||
local function set_extmark(bufnr: integer, row: integer, opts: {string:any})
|
||||
opts = opts or {}
|
||||
opts.id = 1
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, row-1, 0, opts)
|
||||
end
|
||||
|
||||
local function get_extmark(bufnr: integer): integer
|
||||
local pos = api.nvim_buf_get_extmark_by_id(bufnr, namespace, 1, {}) as {integer, integer}
|
||||
if pos[1] then
|
||||
return pos[1] + 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local function reset(bufnr: integer)
|
||||
bufnr = bufnr or current_buf()
|
||||
if not api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
api.nvim_buf_del_extmark(bufnr, namespace, 1)
|
||||
vim.b[bufnr].gitsigns_blame_line_dict = nil
|
||||
end
|
||||
|
||||
-- TODO: expose as config
|
||||
local max_cache_size = 1000
|
||||
|
||||
local record BlameCache
|
||||
record Elem
|
||||
tick: integer
|
||||
cache: {integer:BlameInfo}
|
||||
size: integer
|
||||
end
|
||||
contents: {integer:Elem}
|
||||
end
|
||||
|
||||
BlameCache.contents = {}
|
||||
|
||||
function BlameCache:add(bufnr: integer, lnum: integer, x: BlameInfo)
|
||||
if not config._blame_cache then return end
|
||||
local scache = self.contents[bufnr]
|
||||
if scache.size <= max_cache_size then
|
||||
scache.cache[lnum] = x
|
||||
scache.size = scache.size + 1
|
||||
end
|
||||
end
|
||||
|
||||
function BlameCache:get(bufnr: integer, lnum: integer): BlameInfo
|
||||
if not config._blame_cache then return end
|
||||
|
||||
-- init and invalidate
|
||||
local tick = vim.b[bufnr].changedtick
|
||||
if not self.contents[bufnr] or self.contents[bufnr].tick ~= tick then
|
||||
self.contents[bufnr] = {tick = tick, cache = {}, size = 0}
|
||||
end
|
||||
|
||||
return self.contents[bufnr].cache[lnum]
|
||||
end
|
||||
|
||||
local function expand_blame_format(fmt: string, name: string, info: BlameInfo): string
|
||||
if info.author == name then
|
||||
info.author = 'You'
|
||||
end
|
||||
return util.expand_format(fmt, info, config.current_line_blame_formatter_opts.relative_time)
|
||||
end
|
||||
|
||||
local function flatten_virt_text(virt_text: {{string, string}}): string
|
||||
local res = {}
|
||||
for _, part in ipairs(virt_text) do
|
||||
res[#res+1] = part[1]
|
||||
end
|
||||
return table.concat(res)
|
||||
end
|
||||
|
||||
-- Update function, must be called in async context
|
||||
local update = void(function()
|
||||
local bufnr = current_buf()
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
|
||||
local old_lnum = get_extmark(bufnr)
|
||||
if old_lnum and lnum == old_lnum and BlameCache:get(bufnr, lnum) then
|
||||
-- Don't update if on the same line and we already have results
|
||||
return
|
||||
end
|
||||
|
||||
if api.nvim_get_mode().mode == 'i' then
|
||||
reset(bufnr)
|
||||
return
|
||||
end
|
||||
|
||||
-- Set an empty extmark to save the line number.
|
||||
-- This will also clear virt_text.
|
||||
-- Only do this if there was already an extmark to avoid clearing the intro
|
||||
-- text.
|
||||
if get_extmark(bufnr) then
|
||||
reset(bufnr)
|
||||
set_extmark(bufnr, lnum)
|
||||
end
|
||||
|
||||
-- Can't show extmarks on folded lines so skip
|
||||
if vim.fn.foldclosed(lnum) ~= -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local opts = config.current_line_blame_opts
|
||||
|
||||
-- Note because the same timer is re-used, this call has a debouncing effect.
|
||||
wait_timer(timer, opts.delay, 0)
|
||||
scheduler()
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.git_obj.object_name then
|
||||
return
|
||||
end
|
||||
|
||||
local result = BlameCache:get(bufnr, lnum)
|
||||
if not result then
|
||||
local buftext = util.buf_lines(bufnr)
|
||||
result = bcache.git_obj:run_blame(buftext, lnum, opts.ignore_whitespace)
|
||||
BlameCache:add(bufnr, lnum, result)
|
||||
scheduler()
|
||||
end
|
||||
|
||||
local lnum1 = api.nvim_win_get_cursor(0)[1]
|
||||
if bufnr == current_buf() and lnum ~= lnum1 then
|
||||
-- Cursor has moved during events; abort
|
||||
return
|
||||
end
|
||||
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
-- Buffer is no longer loaded; abort
|
||||
return
|
||||
end
|
||||
|
||||
vim.b[bufnr].gitsigns_blame_line_dict = result
|
||||
|
||||
if result then
|
||||
local virt_text: {{string, string}}
|
||||
local clb_formatter = result.author == 'Not Committed Yet' and
|
||||
config.current_line_blame_formatter_nc or
|
||||
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,
|
||||
result,
|
||||
config.current_line_blame_formatter_opts
|
||||
)
|
||||
end
|
||||
|
||||
vim.b[bufnr].gitsigns_blame_line = flatten_virt_text(virt_text)
|
||||
|
||||
if opts.virt_text then
|
||||
set_extmark(bufnr, lnum, {
|
||||
virt_text = virt_text,
|
||||
virt_text_pos = opts.virt_text_pos,
|
||||
priority = opts.virt_text_priority,
|
||||
hl_mode = 'combine',
|
||||
})
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
M.setup = function()
|
||||
local group = api.nvim_create_augroup('gitsigns_blame', {})
|
||||
|
||||
for k, _ in pairs(cache as {integer:any}) do
|
||||
reset(k)
|
||||
end
|
||||
|
||||
if config.current_line_blame then
|
||||
api.nvim_create_autocmd({'FocusGained', 'BufEnter', 'CursorMoved', 'CursorMovedI'}, {
|
||||
group = group, callback = function() update() end
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd({'InsertEnter', 'FocusLost', 'BufLeave'}, {
|
||||
group = group, callback = function() reset() end
|
||||
})
|
||||
|
||||
-- Call via vim.schedule to avoid the debounce timer killing the async
|
||||
-- coroutine
|
||||
vim.schedule(update)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,86 +0,0 @@
|
|||
local uv = require('gitsigns.uv')
|
||||
|
||||
local record M
|
||||
throttle_by_id : function<F>(fn: F, schedule: boolean): F
|
||||
debounce_trailing : function<F>(ms: number, fn: F): F
|
||||
end
|
||||
|
||||
--- Debounces a function on the trailing edge.
|
||||
---
|
||||
--- @generic F: function
|
||||
--- @param ms number Timeout in ms
|
||||
--- @param fn F Function to debounce
|
||||
--- @return F Debounced function.
|
||||
function M.debounce_trailing(ms: number, fn: function): function
|
||||
local timer = uv.new_timer(true)
|
||||
return function(...)
|
||||
local argv = {...}
|
||||
timer:start(ms, 0, function()
|
||||
timer:stop()
|
||||
fn(unpack(argv))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Throttles a function on the leading edge.
|
||||
---
|
||||
--- @generic F: function
|
||||
--- @param ms number Timeout in ms
|
||||
--- @param fn F Function to throttle
|
||||
--- @return F throttled function.
|
||||
function M.throttle_leading(ms: number, fn: function): function
|
||||
local timer = uv.new_timer(true)
|
||||
local running = false
|
||||
return function(...)
|
||||
if not running then
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
timer:stop()
|
||||
end)
|
||||
running = true
|
||||
fn(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Throttles a function using the first argument as an ID
|
||||
---
|
||||
--- If function is already running then the function will be scheduled to run
|
||||
--- again once the running call has finished.
|
||||
---
|
||||
--- fn#1 _/‾\__/‾\_/‾\_____________________________
|
||||
--- throttled#1 _/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\/‾‾‾‾‾‾‾‾‾‾\____________
|
||||
--
|
||||
--- fn#2 ______/‾\___________/‾\___________________
|
||||
--- throttled#2 ______/‾‾‾‾‾‾‾‾‾‾\__/‾‾‾‾‾‾‾‾‾‾\__________
|
||||
---
|
||||
---
|
||||
--- @generic F: function
|
||||
--- @param fn F Function to throttle
|
||||
--- @param schedule boolean
|
||||
--- @return F throttled function.
|
||||
function M.throttle_by_id(fn: function, schedule: boolean): function
|
||||
local scheduled: {any:boolean} = {} --- @type table<any,boolean>
|
||||
local running: {any:boolean} = {} --- @type table<any,boolean>
|
||||
return function(id: any, ...)
|
||||
if scheduled[id] then
|
||||
-- If fn is already scheduled, then drop
|
||||
return
|
||||
end
|
||||
if not running[id] or schedule then
|
||||
scheduled[id] = true
|
||||
end
|
||||
if running[id] then
|
||||
return
|
||||
end
|
||||
while scheduled[id] do
|
||||
scheduled[id] = nil
|
||||
running[id] = true
|
||||
fn(id, ...)
|
||||
running[id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,50 +0,0 @@
|
|||
local log = require'gitsigns.debug.log'
|
||||
|
||||
local M = {}
|
||||
|
||||
--- @param raw_item any
|
||||
--- @param path string[]
|
||||
--- @return any
|
||||
local function process(raw_item: any, path: {string}): any
|
||||
if path[#path] == vim.inspect.METATABLE then
|
||||
return nil
|
||||
elseif raw_item is function then
|
||||
return nil
|
||||
elseif raw_item is table then
|
||||
local key = path[#path]
|
||||
if key == 'compare_text' or key == 'compare_text_head' then
|
||||
local item = raw_item as {string}
|
||||
return { '...', length=#item, head=item[1] }
|
||||
elseif not vim.tbl_isempty(raw_item) and key == 'staged_diffs' then
|
||||
return { '...', length=#vim.tbl_keys(raw_item) }
|
||||
end
|
||||
end
|
||||
return raw_item
|
||||
end
|
||||
|
||||
--- @return any
|
||||
function M.dump_cache(): any
|
||||
-- TODO(lewis6991): hack: use package.loaded to avoid circular deps
|
||||
local cache = (require('gitsigns.cache') as table).cache
|
||||
local text = vim.inspect(cache, { process = process })
|
||||
vim.api.nvim_echo({{text}}, false, {})
|
||||
return cache
|
||||
end
|
||||
|
||||
--- @param noecho boolean
|
||||
--- @return string[]
|
||||
function M.debug_messages(noecho: boolean): {string}
|
||||
if noecho then
|
||||
return log.messages
|
||||
else
|
||||
for _, m in ipairs(log.messages) do
|
||||
vim.api.nvim_echo({{m}}, false, {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.clear_debug()
|
||||
log.messages = {}
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,109 +0,0 @@
|
|||
local M = {
|
||||
debug_mode = false,
|
||||
verbose = false,
|
||||
messages: {string} = {},
|
||||
}
|
||||
|
||||
local function getvarvalue(name: string, lvl: integer): any
|
||||
lvl = lvl + 1
|
||||
local value: any
|
||||
local found: boolean
|
||||
|
||||
-- try local variables
|
||||
local i = 1
|
||||
while true do
|
||||
local n, v = debug.getlocal(lvl as function, i) as (string, any)
|
||||
if not n then break end
|
||||
if n == name then
|
||||
value = v
|
||||
found = true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
if found then return value end
|
||||
|
||||
-- try upvalues
|
||||
local func = debug.getinfo(lvl).func as function
|
||||
i = 1
|
||||
while true do
|
||||
local n, v = debug.getupvalue(func, i) as (string, any)
|
||||
if not n then break end
|
||||
if n == name then return v end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- not found; get global
|
||||
return getfenv(func)[name]
|
||||
end
|
||||
|
||||
local function get_context(lvl: integer): table
|
||||
lvl = lvl + 1
|
||||
local ret: table = {}
|
||||
ret.name = getvarvalue('__FUNC__', lvl) as string
|
||||
if not ret.name then
|
||||
local name0 = debug.getinfo(lvl, 'n').name or ''
|
||||
ret.name = name0:gsub('(.*)%d+$', '%1')
|
||||
end
|
||||
ret.bufnr = getvarvalue('bufnr', lvl)
|
||||
or getvarvalue('_bufnr', lvl)
|
||||
or getvarvalue('cbuf', lvl)
|
||||
or getvarvalue('buf', lvl)
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
-- If called in a callback then make sure the callback defines a __FUNC__
|
||||
-- variable which can be used to identify the name of the function.
|
||||
local function cprint(obj: any, lvl: integer)
|
||||
lvl = lvl + 1
|
||||
local msg = obj is string and obj or vim.inspect(obj)
|
||||
local ctx = get_context(lvl)
|
||||
local msg2: string
|
||||
if ctx.bufnr then
|
||||
msg2 = string.format('%s(%s): %s', ctx.name, ctx.bufnr, msg)
|
||||
else
|
||||
msg2 = string.format('%s: %s', ctx.name, msg)
|
||||
end
|
||||
table.insert(M.messages, msg2)
|
||||
end
|
||||
|
||||
function M.dprint(obj: any)
|
||||
if not M.debug_mode then return end
|
||||
cprint(obj, 2)
|
||||
end
|
||||
|
||||
function M.dprintf(obj: string, ...:any)
|
||||
if not M.debug_mode then return end
|
||||
cprint(obj:format(...), 2)
|
||||
end
|
||||
|
||||
function M.vprint(obj: any)
|
||||
if not (M.debug_mode and M.verbose) then return end
|
||||
cprint(obj, 2)
|
||||
end
|
||||
|
||||
function M.vprintf(obj: string, ...:any)
|
||||
if not (M.debug_mode and M.verbose) then return end
|
||||
cprint(obj:format(...), 2)
|
||||
end
|
||||
|
||||
local function eprint(msg: string, level: integer)
|
||||
local info = debug.getinfo(level+2, 'Sl')
|
||||
if info then
|
||||
msg = string.format('(ERROR) %s(%d): %s', info.short_src, info.currentline, msg)
|
||||
end
|
||||
M.messages[#M.messages+1] = msg
|
||||
if M.debug_mode then
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function M.eprint(msg: string)
|
||||
eprint(msg, 1)
|
||||
end
|
||||
|
||||
function M.eprintf(fmt: string, ...:any)
|
||||
eprint(fmt:format(...), 1)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,18 +0,0 @@
|
|||
local config = require('gitsigns.config').config
|
||||
local Hunk = require('gitsigns.hunks').Hunk
|
||||
|
||||
return function(a: {string}, b: {string}, linematch: boolean): {Hunk}
|
||||
local diff_opts = config.diff_opts
|
||||
local f: function({string}, {string}, string, boolean, integer): {Hunk}
|
||||
if diff_opts.internal then
|
||||
f = require('gitsigns.diff_int').run_diff
|
||||
else
|
||||
f = require('gitsigns.diff_ext').run_diff
|
||||
end
|
||||
|
||||
local linematch0: integer
|
||||
if linematch ~= false then
|
||||
linematch0 = diff_opts.linematch
|
||||
end
|
||||
return f(a, b, diff_opts.algorithm, diff_opts.indent_heuristic, linematch0)
|
||||
end
|
|
@ -1,80 +0,0 @@
|
|||
local git_diff = require('gitsigns.git').diff
|
||||
|
||||
local gs_hunks = require("gitsigns.hunks")
|
||||
local Hunk = gs_hunks.Hunk
|
||||
local util = require('gitsigns.util')
|
||||
local scheduler = require('gitsigns.async').scheduler
|
||||
|
||||
local record M
|
||||
-- Async function
|
||||
run_diff: function({string}, {string}, string, boolean): {Hunk}
|
||||
end
|
||||
|
||||
local function write_to_file(path: string, text: {string})
|
||||
local f, err = io.open(path, 'wb')
|
||||
if f == nil then
|
||||
error(err)
|
||||
end
|
||||
for _, l in ipairs(text) do
|
||||
f:write(l)
|
||||
f:write('\n')
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
|
||||
M.run_diff = function(
|
||||
text_cmp: {string},
|
||||
text_buf: {string},
|
||||
diff_algo: string,
|
||||
indent_heuristic: boolean
|
||||
): {Hunk}
|
||||
local results: {Hunk} = {}
|
||||
|
||||
-- tmpname must not be called in a callback
|
||||
if vim.in_fast_event() then
|
||||
scheduler()
|
||||
end
|
||||
|
||||
local file_buf = util.tmpname()
|
||||
local file_cmp = util.tmpname()
|
||||
|
||||
write_to_file(file_buf, text_buf)
|
||||
write_to_file(file_cmp, text_cmp)
|
||||
|
||||
-- Taken from gitgutter, diff.vim:
|
||||
--
|
||||
-- If a file has CRLF line endings and git's core.autocrlf is true, the file
|
||||
-- in git's object store will have LF line endings. Writing it out via
|
||||
-- git-show will produce a file with LF line endings.
|
||||
--
|
||||
-- If this last file is one of the files passed to git-diff, git-diff will
|
||||
-- convert its line endings to CRLF before diffing -- which is what we want
|
||||
-- but also by default outputs a warning on stderr.
|
||||
--
|
||||
-- warning: LF will be replace by CRLF in <temp file>.
|
||||
-- The file will have its original line endings in your working directory.
|
||||
--
|
||||
-- We can safely ignore the warning, we turn it off by passing the '-c
|
||||
-- "core.safecrlf=false"' argument to git-diff.
|
||||
|
||||
local out = git_diff(file_cmp, file_buf, indent_heuristic, diff_algo)
|
||||
|
||||
for _, line in ipairs(out) do
|
||||
if vim.startswith(line, '@@') then
|
||||
results[#results+1] = gs_hunks.parse_diff_line(line)
|
||||
elseif #results > 0 then
|
||||
local r = results[#results]
|
||||
if line:sub(1, 1) == '-' then
|
||||
r.removed.lines[#r.removed.lines+1] = line:sub(2)
|
||||
elseif line:sub(1, 1) == '+' then
|
||||
r.added.lines[#r.added.lines+1] = line:sub(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
os.remove(file_buf)
|
||||
os.remove(file_cmp)
|
||||
return results
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,149 +0,0 @@
|
|||
local create_hunk = require("gitsigns.hunks").create_hunk
|
||||
local Hunk = require('gitsigns.hunks').Hunk
|
||||
local config = require('gitsigns.config').config
|
||||
local async = require('gitsigns.async')
|
||||
|
||||
local record M
|
||||
run_diff: function({string}, {string}, string, boolean, integer): {Hunk}
|
||||
run_word_diff: function({string}, {string}): {Region}, {Region}
|
||||
end
|
||||
|
||||
local type DiffFun = function({string}, {string}, string, boolean, integer): {DiffResult}
|
||||
local type DiffResult = {integer, integer, integer, integer}
|
||||
|
||||
local run_diff_xdl = function(
|
||||
fa: {string}, fb: {string},
|
||||
algorithm: string, indent_heuristic: boolean,
|
||||
linematch: integer
|
||||
): {DiffResult}
|
||||
|
||||
local a = vim.tbl_isempty(fa) and '' or table.concat(fa, '\n')..'\n'
|
||||
local b = vim.tbl_isempty(fb) and '' or table.concat(fb, '\n')..'\n'
|
||||
|
||||
return vim.diff(a, b, {
|
||||
result_type = 'indices',
|
||||
algorithm = algorithm,
|
||||
indent_heuristic = indent_heuristic,
|
||||
linematch = linematch
|
||||
})
|
||||
end
|
||||
|
||||
local run_diff_xdl_async = async.wrap(function(
|
||||
fa: {string}, fb: {string},
|
||||
algorithm: string, indent_heuristic: boolean,
|
||||
linematch: integer,
|
||||
callback: function({DiffResult})
|
||||
)
|
||||
|
||||
local a = vim.tbl_isempty(fa) and '' or table.concat(fa, '\n')..'\n'
|
||||
local b = vim.tbl_isempty(fb) and '' or table.concat(fb, '\n')..'\n'
|
||||
|
||||
vim.loop.new_work(function(
|
||||
a0: string, b0: string,
|
||||
algorithm0: string, indent_heuristic0: boolean,
|
||||
linematch0: integer
|
||||
): string
|
||||
return vim.mpack.encode(vim.diff(a0, b0, {
|
||||
result_type = 'indices',
|
||||
algorithm = algorithm0,
|
||||
indent_heuristic = indent_heuristic0,
|
||||
linematch = linematch0
|
||||
}))
|
||||
end, function(r: string)
|
||||
callback(vim.mpack.decode(r) as {DiffResult})
|
||||
end):queue(a, b, algorithm, indent_heuristic, linematch)
|
||||
end, 6)
|
||||
|
||||
M.run_diff = async.void(function(
|
||||
fa: {string}, fb: {string},
|
||||
diff_algo: string, indent_heuristic: boolean,
|
||||
linematch: integer
|
||||
): {Hunk}
|
||||
local run_diff0: DiffFun
|
||||
if config._threaded_diff and vim.is_thread then
|
||||
run_diff0 = run_diff_xdl_async
|
||||
else
|
||||
run_diff0 = run_diff_xdl
|
||||
end
|
||||
|
||||
local results = run_diff0(fa, fb, diff_algo, indent_heuristic, linematch)
|
||||
|
||||
local hunks: {Hunk} = {}
|
||||
|
||||
for _, r in ipairs(results) do
|
||||
local rs, rc, as, ac = unpack(r)
|
||||
local hunk = create_hunk(rs, rc, as, ac)
|
||||
if rc > 0 then
|
||||
for i = rs, rs+rc-1 do
|
||||
hunk.removed.lines[#hunk.removed.lines+1] = fa[i] or ''
|
||||
end
|
||||
end
|
||||
if ac > 0 then
|
||||
for i = as, as+ac-1 do
|
||||
hunk.added.lines[#hunk.added.lines+1] = fb[i] or ''
|
||||
end
|
||||
end
|
||||
hunks[#hunks+1] = hunk
|
||||
end
|
||||
|
||||
return hunks
|
||||
end)
|
||||
|
||||
local type Region = {integer, string, integer, integer}
|
||||
|
||||
local gaps_between_regions = 5
|
||||
|
||||
local function denoise_hunks(hunks: {Hunk}): {Hunk}
|
||||
-- Denoise the hunks
|
||||
local ret = {hunks[1]}
|
||||
for j = 2, #hunks do
|
||||
local h, n = ret[#ret], hunks[j]
|
||||
if not h or not n then break end
|
||||
if n.added.start - h.added.start - h.added.count < gaps_between_regions then
|
||||
h.added.count = n.added.start + n.added.count - h.added.start
|
||||
h.removed.count = n.removed.start + n.removed.count - h.removed.start
|
||||
|
||||
if h.added.count > 0 or h.removed.count > 0 then
|
||||
h.type = 'change'
|
||||
end
|
||||
else
|
||||
ret[#ret+1] = n
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.run_word_diff(removed: {string}, added: {string}): {Region}, {Region}
|
||||
local adds: {Region} = {}
|
||||
local rems: {Region} = {}
|
||||
|
||||
if #removed ~= #added then
|
||||
return rems, adds
|
||||
end
|
||||
|
||||
for i = 1, #removed do
|
||||
-- pair lines by position
|
||||
local a, b = vim.split(removed[i], ''), vim.split(added[i], '')
|
||||
|
||||
local hunks: {Hunk} = {}
|
||||
for _, r in ipairs(run_diff_xdl(a, b)) do
|
||||
local rs, rc, as, ac = unpack(r)
|
||||
|
||||
-- Balance of the unknown offset done in hunk_func
|
||||
if rc == 0 then rs = rs + 1 end
|
||||
if ac == 0 then as = as + 1 end
|
||||
|
||||
hunks[#hunks+1] = create_hunk(rs, rc, as, ac)
|
||||
end
|
||||
|
||||
hunks = denoise_hunks(hunks)
|
||||
|
||||
for _, h in ipairs(hunks) do
|
||||
adds[#adds+1] = {i, h.type, h.added.start , h.added.start + h.added.count}
|
||||
rems[#rems+1] = {i, h.type, h.removed.start, h.removed.start + h.removed.count}
|
||||
end
|
||||
end
|
||||
return rems, adds
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,201 +0,0 @@
|
|||
local api = vim.api
|
||||
|
||||
local void = require('gitsigns.async').void
|
||||
local scheduler = require('gitsigns.async').scheduler
|
||||
local awrap = require('gitsigns.async').wrap
|
||||
|
||||
local gs_cache = require('gitsigns.cache')
|
||||
local cache = gs_cache.cache
|
||||
local CacheEntry = gs_cache.CacheEntry
|
||||
|
||||
local util = require('gitsigns.util')
|
||||
local manager = require('gitsigns.manager')
|
||||
local message = require('gitsigns.message')
|
||||
|
||||
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
|
||||
|
||||
local input = awrap(vim.ui.input, 2)
|
||||
|
||||
local record M
|
||||
record DiffthisOpts
|
||||
vertical: boolean
|
||||
split: string
|
||||
end
|
||||
|
||||
diffthis: function(base: string, opts: DiffthisOpts)
|
||||
show: function(base: string)
|
||||
update: function(bufnr: integer)
|
||||
end
|
||||
|
||||
local bufread = void(function(bufnr: integer, dbufnr: integer, base: string, bcache: CacheEntry)
|
||||
local comp_rev = bcache:get_compare_rev(util.calc_base(base))
|
||||
local text: {string}
|
||||
if util.calc_base(base) == util.calc_base(bcache.base) then
|
||||
text = bcache.compare_text
|
||||
else
|
||||
local err: string
|
||||
text, err = bcache.git_obj:get_show_text(comp_rev)
|
||||
if err then
|
||||
error(err, 2)
|
||||
end
|
||||
scheduler()
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
text = util.strip_cr(text)
|
||||
end
|
||||
end
|
||||
|
||||
local modifiable = vim.bo[dbufnr].modifiable
|
||||
vim.bo[dbufnr].modifiable = true
|
||||
util.set_lines(dbufnr, 0, -1, text)
|
||||
|
||||
vim.bo[dbufnr].modifiable = modifiable
|
||||
vim.bo[dbufnr].modified = false
|
||||
vim.bo[dbufnr].filetype = vim.bo[bufnr].filetype
|
||||
vim.bo[dbufnr].bufhidden = 'wipe'
|
||||
end)
|
||||
|
||||
local bufwrite = void(function(bufnr: integer, dbufnr: integer, base: string, bcache: CacheEntry)
|
||||
local buftext = util.buf_lines(dbufnr)
|
||||
bcache.git_obj:stage_lines(buftext)
|
||||
scheduler()
|
||||
vim.bo[dbufnr].modified = false
|
||||
-- If diff buffer base matches the bcache base then also update the
|
||||
-- signs.
|
||||
if util.calc_base(base) == util.calc_base(bcache.base) then
|
||||
bcache.compare_text = buftext
|
||||
manager.update(bufnr, bcache)
|
||||
end
|
||||
end)
|
||||
|
||||
local function run(base: string, diffthis: boolean, opts: M.DiffthisOpts)
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
|
||||
local comp_rev = bcache:get_compare_rev(util.calc_base(base))
|
||||
local bufname = bcache:get_rev_bufname(comp_rev)
|
||||
|
||||
local dbuf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(dbuf, bufname)
|
||||
|
||||
local ok, err = pcall(bufread as function, bufnr, dbuf, base, bcache)
|
||||
if not ok then
|
||||
message.error(err as string)
|
||||
scheduler()
|
||||
vim.cmd'bdelete'
|
||||
if diffthis then
|
||||
vim.cmd'diffoff'
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if comp_rev == ':0' then
|
||||
vim.bo[dbuf].buftype = 'acwrite'
|
||||
|
||||
api.nvim_create_autocmd('BufReadCmd', {
|
||||
group = 'gitsigns',
|
||||
buffer = dbuf,
|
||||
callback = function()
|
||||
bufread(bufnr, dbuf, base, bcache)
|
||||
if diffthis then
|
||||
vim.cmd'diffthis'
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('BufWriteCmd', {
|
||||
group = 'gitsigns',
|
||||
buffer = dbuf,
|
||||
callback = function()
|
||||
bufwrite(bufnr, dbuf, base, bcache)
|
||||
end
|
||||
})
|
||||
else
|
||||
vim.bo[dbuf].buftype = 'nowrite'
|
||||
vim.bo[dbuf].modifiable = false
|
||||
end
|
||||
|
||||
if diffthis then
|
||||
vim.cmd(table.concat({
|
||||
'keepalt', opts.split or 'aboveleft',
|
||||
opts.vertical and 'vertical' or '',
|
||||
'diffsplit', bufname
|
||||
}, ' '))
|
||||
else
|
||||
vim.cmd('edit '..bufname)
|
||||
end
|
||||
end
|
||||
|
||||
M.diffthis = void(function(base: string, opts: M.DiffthisOpts)
|
||||
if vim.wo.diff then
|
||||
return
|
||||
end
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
|
||||
local cwin = api.nvim_get_current_win()
|
||||
if not base and bcache.git_obj.has_conflicts then
|
||||
run(':2', true, opts)
|
||||
api.nvim_set_current_win(cwin)
|
||||
opts.split = 'belowright'
|
||||
run(':3', true, opts)
|
||||
else
|
||||
run(base, true, opts)
|
||||
end
|
||||
api.nvim_set_current_win(cwin)
|
||||
end)
|
||||
|
||||
M.show = void(function(base: string)
|
||||
run(base, false)
|
||||
end)
|
||||
|
||||
local function should_reload(bufnr: integer): boolean
|
||||
if not vim.bo[bufnr].modified then
|
||||
return true
|
||||
end
|
||||
local response: string
|
||||
while not vim.tbl_contains({'O', 'L'}, response) do
|
||||
response = input{
|
||||
prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:'
|
||||
}
|
||||
end
|
||||
return response == 'L'
|
||||
end
|
||||
|
||||
-- This function needs to be throttled as there is a call to vim.ui.input
|
||||
M.update = throttle_by_id(void(function(bufnr: integer)
|
||||
if not vim.wo.diff then
|
||||
return
|
||||
end
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
-- Note this will be the bufname for the currently set base
|
||||
-- which are the only ones we want to update
|
||||
local bufname = bcache:get_rev_bufname()
|
||||
|
||||
for _, w in ipairs(api.nvim_list_wins()) do
|
||||
if api.nvim_win_is_valid(w) then
|
||||
local b = api.nvim_win_get_buf(w)
|
||||
local bname = api.nvim_buf_get_name(b)
|
||||
if bname == bufname or vim.startswith(bname, 'fugitive://') then
|
||||
if should_reload(b) then
|
||||
api.nvim_buf_call(b, function(): nil
|
||||
vim.cmd('doautocmd BufReadCmd')
|
||||
vim.cmd('diffthis')
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end))
|
||||
|
||||
return M
|
|
@ -1,725 +0,0 @@
|
|||
local async = require('gitsigns.async')
|
||||
local scheduler = require('gitsigns.async').scheduler
|
||||
|
||||
local log = require("gitsigns.debug.log")
|
||||
local util = require('gitsigns.util')
|
||||
local subprocess = require('gitsigns.subprocess')
|
||||
|
||||
local gs_config = require('gitsigns.config')
|
||||
local config = gs_config.config
|
||||
|
||||
local gs_hunks = require("gitsigns.hunks")
|
||||
local Hunk = gs_hunks.Hunk
|
||||
|
||||
local uv = vim.loop
|
||||
local startswith = vim.startswith
|
||||
|
||||
local dprint = require('gitsigns.debug.log').dprint
|
||||
local eprint = require('gitsigns.debug.log').eprint
|
||||
local err = require('gitsigns.message').error
|
||||
|
||||
local record GJobSpec
|
||||
command: string
|
||||
args: {string}
|
||||
cwd: string
|
||||
writer: {string}
|
||||
|
||||
-- local extensions
|
||||
suppress_stderr: boolean
|
||||
end
|
||||
|
||||
local record M
|
||||
record BlameInfo
|
||||
-- Info in header
|
||||
sha: string
|
||||
abbrev_sha: string
|
||||
orig_lnum: integer
|
||||
final_lnum: integer
|
||||
|
||||
-- Porcelain fields
|
||||
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
|
||||
previous_filename: string
|
||||
previous_sha: string
|
||||
filename: string
|
||||
end
|
||||
|
||||
record Version
|
||||
major: integer
|
||||
minor: integer
|
||||
patch: integer
|
||||
end
|
||||
version: Version
|
||||
|
||||
record RepoInfo
|
||||
gitdir: string
|
||||
toplevel: string
|
||||
detached: boolean
|
||||
abbrev_head: string
|
||||
end
|
||||
|
||||
get_repo_info: function(path: string, cmd: string, gitdir: string, toplevel: string): RepoInfo
|
||||
|
||||
diff: function(file_cmp: string, file_buf: string, indent_heuristic: boolean, diff_algo: string): {string}, string
|
||||
|
||||
record Repo
|
||||
toplevel : string
|
||||
gitdir : string
|
||||
detached : boolean
|
||||
abbrev_head: string
|
||||
username : string
|
||||
|
||||
command : function(Repo, {string}, GJobSpec): {string}, string
|
||||
files_changed : function(Repo): {string}
|
||||
get_show_text : function(Repo, string, string): {string}, string
|
||||
update_abbrev_head : function(Repo)
|
||||
new : function(dir: string, gitdir: string, toplevel: string): Repo
|
||||
end
|
||||
|
||||
record FileProps
|
||||
relpath : string
|
||||
orig_relpath : string -- Use for tracking moved files
|
||||
object_name : string
|
||||
mode_bits : string
|
||||
has_conflicts : boolean
|
||||
i_crlf : boolean -- Object has crlf
|
||||
w_crlf : boolean -- Working copy has crlf
|
||||
end
|
||||
|
||||
record Obj
|
||||
repo : Repo
|
||||
file : string
|
||||
relpath : string
|
||||
orig_relpath : string -- Use for tracking moved files
|
||||
object_name : string
|
||||
mode_bits : string
|
||||
has_conflicts : boolean
|
||||
i_crlf : boolean -- Object has crlf
|
||||
w_crlf : boolean -- Working copy has crlf
|
||||
encoding : string
|
||||
|
||||
command : function(Obj, {string}, GJobSpec): {string}, string
|
||||
update_file_info : function(Obj, boolean, silent: boolean): boolean
|
||||
unstage_file : function(Obj, string, string)
|
||||
run_blame : function(Obj, {string}, number, boolean): BlameInfo
|
||||
file_info : function(Obj, string, silent: boolean): FileProps
|
||||
get_show_text : function(Obj, string): {string}, string
|
||||
ensure_file_in_index : function(Obj)
|
||||
stage_hunks : function(Obj, {Hunk}, boolean)
|
||||
stage_lines : function(Obj, {string})
|
||||
has_moved : function(Obj): string
|
||||
new : function(path: string, enc: string, gitdir: string, toplevel: string): Obj
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local in_git_dir = function(file: string): boolean
|
||||
for _, p in ipairs(vim.split(file, util.path_sep)) do
|
||||
if p == '.git' then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local Obj = M.Obj
|
||||
local Repo = M.Repo
|
||||
|
||||
local function parse_version(version: string): M.Version
|
||||
assert(version:match('%d+%.%d+%.%w+'), 'Invalid git version: '..version)
|
||||
local ret: M.Version = {}
|
||||
local parts = vim.split(version, '%.')
|
||||
ret.major = tonumber(parts[1]) as integer
|
||||
ret.minor = tonumber(parts[2]) as integer
|
||||
|
||||
if parts[3] == 'GIT' then
|
||||
ret.patch = 0
|
||||
else
|
||||
ret.patch = tonumber(parts[3]) as integer
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
-- Usage: check_version{2,3}
|
||||
local function check_version(version: {number,number,number}): boolean
|
||||
if not M.version then
|
||||
return false
|
||||
end
|
||||
if M.version.major < version[1] then
|
||||
return false
|
||||
end
|
||||
if version[2] and M.version.minor < version[2] then
|
||||
return false
|
||||
end
|
||||
if version[3] and M.version.patch < version[3] then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- @async
|
||||
function M._set_version(version: string)
|
||||
if version ~= 'auto' then
|
||||
M.version = parse_version(version)
|
||||
return
|
||||
end
|
||||
|
||||
local _, _, stdout, stderr = async.wait(2, subprocess.run_job, {
|
||||
command = 'git', args = { '--version' }
|
||||
})
|
||||
|
||||
local line = vim.split(stdout or '', '\n', true)[1]
|
||||
if not line then
|
||||
err("Unable to detect git version as 'git --version' failed to return anything")
|
||||
eprint(stderr)
|
||||
return
|
||||
end
|
||||
assert(type(line) == 'string', 'Unexpected output: '..line)
|
||||
assert(startswith(line, 'git version'), 'Unexpected output: '..line)
|
||||
local parts = vim.split(line, '%s+')
|
||||
M.version = parse_version(parts[3])
|
||||
end
|
||||
|
||||
|
||||
--- @async
|
||||
local git_command = async.create(function(args: {string}, spec: GJobSpec): {string}, string
|
||||
if not M.version then
|
||||
M._set_version(config._git_version)
|
||||
end
|
||||
spec = spec or {}
|
||||
spec.command = spec.command or 'git'
|
||||
spec.args = spec.command == 'git' and {
|
||||
'--no-pager',
|
||||
'--literal-pathspecs',
|
||||
'-c', 'gc.auto=0', -- Disable auto-packing which emits messages to stderr
|
||||
unpack(args)
|
||||
} or args
|
||||
|
||||
if not spec.cwd and not uv.cwd() then
|
||||
spec.cwd = vim.env.HOME
|
||||
end
|
||||
|
||||
local _, _, stdout, stderr = async.wait(2, subprocess.run_job, spec as subprocess.JobSpec)
|
||||
|
||||
if not spec.suppress_stderr then
|
||||
if stderr then
|
||||
local cmd_str = table.concat({spec.command, unpack(args)}, ' ')
|
||||
log.eprintf("Received stderr when running command\n'%s':\n%s", cmd_str, stderr)
|
||||
end
|
||||
end
|
||||
|
||||
local stdout_lines = vim.split(stdout or '', '\n', true)
|
||||
|
||||
-- If stdout ends with a newline, then remove the final empty string after
|
||||
-- the split
|
||||
if stdout_lines[#stdout_lines] == '' then
|
||||
stdout_lines[#stdout_lines] = nil
|
||||
end
|
||||
|
||||
if log.verbose then
|
||||
log.vprintf('%d lines:', #stdout_lines)
|
||||
for i = 1, math.min(10, #stdout_lines) do
|
||||
log.vprintf('\t%s', stdout_lines[i])
|
||||
end
|
||||
end
|
||||
|
||||
return stdout_lines, stderr
|
||||
end, 2)
|
||||
|
||||
--- @async
|
||||
function M.diff(file_cmp: string, file_buf: string, indent_heuristic: boolean, diff_algo: string): {string}, string
|
||||
return git_command{
|
||||
'-c', 'core.safecrlf=false',
|
||||
'diff',
|
||||
'--color=never',
|
||||
'--'..(indent_heuristic and '' or 'no-')..'indent-heuristic',
|
||||
'--diff-algorithm='..diff_algo,
|
||||
'--patch-with-raw',
|
||||
'--unified=0',
|
||||
file_cmp,
|
||||
file_buf,
|
||||
}
|
||||
end
|
||||
|
||||
--- @async
|
||||
local function process_abbrev_head(gitdir: string, head_str: string, path: string, cmd: string): string
|
||||
if not gitdir then
|
||||
return head_str
|
||||
end
|
||||
if head_str == 'HEAD' then
|
||||
local short_sha = git_command({'rev-parse', '--short', 'HEAD'}, {
|
||||
command = cmd or 'git',
|
||||
suppress_stderr = true,
|
||||
cwd = path,
|
||||
})[1] or ''
|
||||
if log.debug_mode and short_sha ~= '' then
|
||||
short_sha = 'HEAD'
|
||||
end
|
||||
if util.path_exists(gitdir..'/rebase-merge')
|
||||
or util.path_exists(gitdir..'/rebase-apply') then
|
||||
return short_sha..'(rebasing)'
|
||||
end
|
||||
return short_sha
|
||||
end
|
||||
return head_str
|
||||
end
|
||||
|
||||
local has_cygpath = jit and jit.os == 'Windows' and vim.fn.executable('cygpath') == 1
|
||||
|
||||
--- @async
|
||||
local cygpath_convert: function(path: string): string
|
||||
|
||||
if has_cygpath then
|
||||
cygpath_convert = function(path: string): string
|
||||
return git_command({ '-aw', path }, { command = 'cygpath' })[1]
|
||||
end
|
||||
end
|
||||
|
||||
local function normalize_path(path: string): string
|
||||
if path and has_cygpath and not uv.fs_stat(path) then
|
||||
-- If on windows and path isn't recognizable as a file, try passing it
|
||||
-- through cygpath
|
||||
path = cygpath_convert(path)
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
--- @async
|
||||
function M.get_repo_info(path: string, cmd: string, gitdir: string, toplevel: string): M.RepoInfo
|
||||
-- Does git rev-parse have --absolute-git-dir, added in 2.13:
|
||||
-- https://public-inbox.org/git/20170203024829.8071-16-szeder.dev@gmail.com/
|
||||
local has_abs_gd = check_version{2,13}
|
||||
local git_dir_opt = has_abs_gd and '--absolute-git-dir' or '--git-dir'
|
||||
|
||||
-- Wait for internal scheduler to settle before running command
|
||||
-- https://github.com/lewis6991/gitsigns.nvim/pull/215
|
||||
scheduler()
|
||||
|
||||
local args = {}
|
||||
|
||||
if gitdir then
|
||||
vim.list_extend(args, {'--git-dir', gitdir})
|
||||
end
|
||||
|
||||
if toplevel then
|
||||
vim.list_extend(args, {'--work-tree', toplevel})
|
||||
end
|
||||
|
||||
vim.list_extend(args, {
|
||||
'rev-parse', '--show-toplevel', git_dir_opt, '--abbrev-ref', 'HEAD',
|
||||
})
|
||||
|
||||
local results = git_command(args, {
|
||||
command = cmd or 'git',
|
||||
suppress_stderr = true,
|
||||
cwd = toplevel or path,
|
||||
})
|
||||
|
||||
local ret: M.RepoInfo = {
|
||||
toplevel = normalize_path(results[1]),
|
||||
gitdir = normalize_path(results[2]),
|
||||
}
|
||||
ret.abbrev_head = process_abbrev_head(ret.gitdir, results[3], path, cmd)
|
||||
if ret.gitdir and not has_abs_gd then
|
||||
ret.gitdir = uv.fs_realpath(ret.gitdir)
|
||||
end
|
||||
ret.detached = ret.toplevel and ret.gitdir ~= ret.toplevel..'/.git'
|
||||
return ret
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Git repo object methods
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--- Run git command the with the objects gitdir and toplevel
|
||||
--- @async
|
||||
function Repo:command(args: {string}, spec: GJobSpec): {string}, string
|
||||
spec = spec or {}
|
||||
spec.cwd = self.toplevel
|
||||
|
||||
local args1 = {
|
||||
'--git-dir', self.gitdir,
|
||||
}
|
||||
|
||||
if self.detached then
|
||||
vim.list_extend(args1, {'--work-tree', self.toplevel})
|
||||
end
|
||||
|
||||
vim.list_extend(args1, args)
|
||||
|
||||
return git_command(args1, spec)
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Repo:files_changed(): {string}
|
||||
local results = self:command({ 'status', '--porcelain', '--ignore-submodules' })
|
||||
|
||||
local ret: {string} = {}
|
||||
for _, line in ipairs(results) do
|
||||
if line:sub(1, 2):match('^.M') then
|
||||
ret[#ret+1] = line:sub(4, -1)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function make_bom(...: integer): string
|
||||
local r = {}
|
||||
for i, a in ipairs{...} do
|
||||
r[i] = string.char(a)
|
||||
end
|
||||
return table.concat(r)
|
||||
end
|
||||
|
||||
local BOM_TABLE: {string:string} = {
|
||||
['utf-8'] = make_bom(0xef, 0xbb, 0xbf),
|
||||
['utf-16le'] = make_bom(0xff, 0xfe),
|
||||
['utf-16'] = make_bom(0xfe, 0xff),
|
||||
['utf-16be'] = make_bom(0xfe, 0xff),
|
||||
['utf-32le'] = make_bom(0xff, 0xfe, 0x00, 0x00),
|
||||
['utf-32'] = make_bom(0xff, 0xfe, 0x00, 0x00),
|
||||
['utf-32be'] = make_bom(0x00, 0x00, 0xfe, 0xff),
|
||||
['utf-7'] = make_bom(0x2b, 0x2f, 0x76),
|
||||
['utf-1'] = make_bom(0xf7, 0x54, 0x4c),
|
||||
}
|
||||
|
||||
local function strip_bom(x: string, encoding: string): string
|
||||
local bom = BOM_TABLE[encoding]
|
||||
if bom and vim.startswith(x, bom) then
|
||||
return x:sub(bom:len() + 1)
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
local function iconv_supported(encoding: string): boolean
|
||||
-- TODO(lewis6991): needs https://github.com/neovim/neovim/pull/21924
|
||||
if vim.startswith(encoding, 'utf-16') then
|
||||
return false
|
||||
elseif vim.startswith(encoding, 'utf-32') then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Get version of file in the index, return array lines
|
||||
--- @async
|
||||
function Repo:get_show_text(object: string, encoding: string): {string}, string
|
||||
local stdout, stderr = self:command({'show', object}, {suppress_stderr = true})
|
||||
|
||||
if encoding and encoding ~= 'utf-8' and iconv_supported(encoding) then
|
||||
stdout[1] = strip_bom(stdout[1], encoding)
|
||||
if vim.iconv then
|
||||
for i, l in ipairs(stdout) do
|
||||
stdout[i] = vim.iconv(l, encoding, 'utf-8')
|
||||
end
|
||||
else
|
||||
scheduler()
|
||||
for i, l in ipairs(stdout) do
|
||||
-- vimscript will interpret strings containing NUL as blob type
|
||||
if vim.fn.type(l) == vim.v.t_string then
|
||||
stdout[i] = vim.fn.iconv(l, encoding, 'utf-8')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return stdout, stderr
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Repo:update_abbrev_head()
|
||||
self.abbrev_head = M.get_repo_info(self.toplevel).abbrev_head
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Repo.new(dir: string, gitdir: string, toplevel: string): Repo
|
||||
local self: Repo = setmetatable({}, {__index = Repo})
|
||||
|
||||
self.username = git_command({'config', 'user.name'})[1]
|
||||
local info = M.get_repo_info(dir, nil, gitdir, toplevel)
|
||||
for k, v in pairs(info as table) do
|
||||
(self as table)[k] = v
|
||||
end
|
||||
|
||||
-- Try yadm
|
||||
if config.yadm.enable and not self.gitdir then
|
||||
if vim.startswith(dir, os.getenv('HOME'))
|
||||
and #git_command({'ls-files', dir}, {command = 'yadm'}) ~= 0 then
|
||||
M.get_repo_info(dir, 'yadm', gitdir, toplevel)
|
||||
local yadm_info = M.get_repo_info(dir, 'yadm', gitdir, toplevel)
|
||||
for k, v in pairs(yadm_info as {string:any}) do
|
||||
(self as table)[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Git object methods
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--- Run git command the with the objects gitdir and toplevel
|
||||
--- @async
|
||||
function Obj:command(args: {string}, spec: GJobSpec): {string}, string
|
||||
return self.repo:command(args, spec)
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Obj:update_file_info(update_relpath: boolean, silent: boolean): boolean
|
||||
local old_object_name = self.object_name
|
||||
local props = self:file_info(self.file, silent)
|
||||
|
||||
if update_relpath then
|
||||
self.relpath = props.relpath
|
||||
end
|
||||
self.object_name = props.object_name
|
||||
self.mode_bits = props.mode_bits
|
||||
self.has_conflicts = props.has_conflicts
|
||||
self.i_crlf = props.i_crlf
|
||||
self.w_crlf = props.w_crlf
|
||||
|
||||
return old_object_name ~= self.object_name
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Obj:file_info(file: string, silent: boolean): M.FileProps
|
||||
local results, stderr = self:command({
|
||||
'-c', 'core.quotepath=off',
|
||||
'ls-files',
|
||||
'--stage',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
'--eol',
|
||||
file or self.file
|
||||
}, {suppress_stderr = true})
|
||||
|
||||
if stderr and not silent then
|
||||
-- Suppress_stderr for the cases when we run:
|
||||
-- git ls-files --others exists/nonexist
|
||||
if not stderr:match('^warning: could not open directory .*: No such file or directory') then
|
||||
log.eprint(stderr)
|
||||
end
|
||||
end
|
||||
|
||||
local result: M.FileProps = {}
|
||||
for _, line in ipairs(results) do
|
||||
local parts = vim.split(line, '\t')
|
||||
if #parts > 2 then -- tracked file
|
||||
local eol = vim.split(parts[2], '%s+')
|
||||
result.i_crlf = eol[1] == 'i/crlf'
|
||||
result.w_crlf = eol[2] == 'w/crlf'
|
||||
result.relpath = parts[3]
|
||||
local attrs = vim.split(parts[1], '%s+')
|
||||
local stage = tonumber(attrs[3])
|
||||
if stage <= 1 then
|
||||
result.mode_bits = attrs[1]
|
||||
result.object_name = attrs[2]
|
||||
else
|
||||
result.has_conflicts = true
|
||||
end
|
||||
else -- untracked file
|
||||
result.relpath = parts[2]
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Obj:get_show_text(revision: string): {string}, string
|
||||
if not self.relpath then
|
||||
return {}
|
||||
end
|
||||
|
||||
local stdout, stderr = self.repo:get_show_text(revision..':'..self.relpath, self.encoding)
|
||||
|
||||
if not self.i_crlf and self.w_crlf then
|
||||
-- Add cr
|
||||
for i = 1, #stdout do
|
||||
stdout[i] = stdout[i]..'\r'
|
||||
end
|
||||
end
|
||||
|
||||
return stdout, stderr
|
||||
end
|
||||
|
||||
--- @async
|
||||
Obj.unstage_file = function(self: Obj)
|
||||
self:command{'reset', self.file }
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Obj:run_blame(lines: {string}, lnum: number, ignore_whitespace: boolean): M.BlameInfo
|
||||
local not_committed = {
|
||||
author = 'Not Committed Yet',
|
||||
['author_mail'] = '<not.committed.yet>',
|
||||
committer = 'Not Committed Yet',
|
||||
['committer_mail'] = '<not.committed.yet>',
|
||||
}
|
||||
|
||||
if not self.object_name or self.repo.abbrev_head == '' then
|
||||
-- As we support attaching to untracked files we need to return something if
|
||||
-- the file isn't isn't tracked in git.
|
||||
-- If abbrev_head is empty, then assume the repo has no commits
|
||||
return not_committed
|
||||
end
|
||||
|
||||
local args = {
|
||||
'blame',
|
||||
'--contents', '-',
|
||||
'-L', lnum..',+1',
|
||||
'--line-porcelain',
|
||||
self.file
|
||||
}
|
||||
|
||||
if ignore_whitespace then
|
||||
args[#args+1] = '-w'
|
||||
end
|
||||
|
||||
local ignore_file = self.repo.toplevel ..'/.git-blame-ignore-revs'
|
||||
if uv.fs_stat(ignore_file) then
|
||||
vim.list_extend(args, {'--ignore-revs-file', ignore_file})
|
||||
end
|
||||
|
||||
local results = self:command(args, { writer = lines })
|
||||
if #results == 0 then
|
||||
return
|
||||
end
|
||||
local header = vim.split(table.remove(results, 1), ' ')
|
||||
|
||||
local ret: {string:any} = {}
|
||||
ret.sha = header[1]
|
||||
ret.orig_lnum = tonumber(header[2]) as integer
|
||||
ret.final_lnum = tonumber(header[3]) as integer
|
||||
ret.abbrev_sha = string.sub(ret.sha as string, 1, 8)
|
||||
for _, l in ipairs(results) do
|
||||
if not startswith(l, '\t') then
|
||||
local cols = vim.split(l, ' ')
|
||||
local key = table.remove(cols, 1):gsub('-', '_')
|
||||
ret[key] = table.concat(cols, ' ')
|
||||
if key == 'previous' then
|
||||
ret.previous_sha = cols[1]
|
||||
ret.previous_filename = cols[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- New in git 2.41:
|
||||
-- The output given by "git blame" that attributes a line to contents
|
||||
-- taken from the file specified by the "--contents" option shows it
|
||||
-- differently from a line attributed to the working tree file.
|
||||
if ret.author_mail == '<external.file>' then
|
||||
ret = vim.tbl_extend('force', ret, not_committed as table) as {string:any}
|
||||
end
|
||||
|
||||
return ret as M.BlameInfo
|
||||
end
|
||||
|
||||
--- @async
|
||||
local function ensure_file_in_index(obj: Obj)
|
||||
if obj.object_name and not obj.has_conflicts then
|
||||
return
|
||||
end
|
||||
|
||||
if not obj.object_name then
|
||||
-- If there is no object_name then it is not yet in the index so add it
|
||||
obj:command{'add', '--intent-to-add', obj.file}
|
||||
else
|
||||
-- Update the index with the common ancestor (stage 1) which is what bcache
|
||||
-- stores
|
||||
local info = string.format('%s,%s,%s', obj.mode_bits, obj.object_name, obj.relpath)
|
||||
obj:command{'update-index', '--add', '--cacheinfo', info}
|
||||
end
|
||||
|
||||
obj:update_file_info()
|
||||
end
|
||||
|
||||
-- Stage 'lines' as the entire contents of the file
|
||||
--- @async
|
||||
--- @param lines
|
||||
function Obj:stage_lines(lines: {string})
|
||||
local stdout = self:command({
|
||||
'hash-object', '-w', '--path', self.relpath, '--stdin'
|
||||
}, { writer = lines })
|
||||
|
||||
local new_object = stdout[1]
|
||||
|
||||
self:command{
|
||||
'update-index', '--cacheinfo', string.format('%s,%s,%s', self.mode_bits, new_object, self.relpath)
|
||||
}
|
||||
end
|
||||
|
||||
--- @async
|
||||
Obj.stage_hunks = function(self: Obj, hunks: {Hunk}, invert: boolean)
|
||||
ensure_file_in_index(self)
|
||||
|
||||
local patch = gs_hunks.create_patch(self.relpath, hunks, self.mode_bits, invert)
|
||||
|
||||
if not self.i_crlf and self.w_crlf then
|
||||
-- Remove cr
|
||||
for i = 1, #patch do
|
||||
patch[i] = patch[i]:gsub('\r$', '')
|
||||
end
|
||||
end
|
||||
|
||||
self:command({
|
||||
'apply', '--whitespace=nowarn', '--cached', '--unidiff-zero', '-'
|
||||
}, {
|
||||
writer = patch
|
||||
})
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Obj:has_moved(): string
|
||||
local out = self:command{'diff', '--name-status', '-C', '--cached'}
|
||||
local orig_relpath = self.orig_relpath or self.relpath
|
||||
for _, l in ipairs(out) do
|
||||
local parts = vim.split(l, '%s+')
|
||||
if #parts == 3 then
|
||||
local orig, new = parts[2], parts[3]
|
||||
if orig_relpath == orig then
|
||||
self.orig_relpath = orig_relpath
|
||||
self.relpath = new
|
||||
self.file = self.repo.toplevel..'/'..new
|
||||
return new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @async
|
||||
function Obj.new(file: string, encoding: string, gitdir: string, toplevel: string): Obj
|
||||
if in_git_dir(file) then
|
||||
dprint('In git dir')
|
||||
return nil
|
||||
end
|
||||
local self: Obj = setmetatable({}, {__index = Obj})
|
||||
|
||||
self.file = file
|
||||
self.encoding = encoding
|
||||
self.repo = Repo.new(util.dirname(file), gitdir, toplevel)
|
||||
|
||||
if not self.repo.gitdir then
|
||||
dprint('Not in git repo')
|
||||
return nil
|
||||
end
|
||||
|
||||
-- When passing gitdir and toplevel, suppress stderr when resolving the file
|
||||
local silent = gitdir ~= nil and toplevel ~= nil
|
||||
|
||||
self:update_file_info(true, silent)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,223 +0,0 @@
|
|||
local record Hldef
|
||||
{string}
|
||||
desc: string
|
||||
hidden: boolean
|
||||
fg_factor: number
|
||||
bg_factor: number
|
||||
end
|
||||
|
||||
local record M
|
||||
setup_highlights: function()
|
||||
hls: {{string:Hldef}}
|
||||
end
|
||||
|
||||
-- Use array of dict so we can iterate deterministically
|
||||
-- Export for docgen
|
||||
M.hls = {
|
||||
{GitSignsAdd = { 'GitGutterAdd', 'SignifySignAdd', 'DiffAddedGutter', 'diffAdded', 'DiffAdd',
|
||||
desc = "Used for the text of 'add' signs."
|
||||
}},
|
||||
|
||||
{GitSignsChange = { 'GitGutterChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange',
|
||||
desc = "Used for the text of 'change' signs."
|
||||
}},
|
||||
|
||||
{GitSignsDelete = { 'GitGutterDelete', 'SignifySignDelete', 'DiffRemovedGutter' , 'diffRemoved', 'DiffDelete',
|
||||
desc = "Used for the text of 'delete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsChangedelete = { 'GitSignsChange',
|
||||
desc = "Used for the text of 'changedelete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsTopdelete = { 'GitSignsDelete',
|
||||
desc = "Used for the text of 'topdelete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsUntracked = { 'GitSignsAdd',
|
||||
desc = "Used for the text of 'untracked' signs."
|
||||
}},
|
||||
|
||||
{GitSignsAddNr = {'GitGutterAddLineNr', 'GitSignsAdd',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'add' signs."
|
||||
}},
|
||||
|
||||
{GitSignsChangeNr = {'GitGutterChangeLineNr', 'GitSignsChange',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'change' signs."
|
||||
}},
|
||||
|
||||
{GitSignsDeleteNr = {'GitGutterDeleteLineNr', 'GitSignsDelete',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'delete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsChangedeleteNr = {'GitSignsChangeNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'changedelete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsTopdeleteNr = {'GitSignsDeleteNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'topdelete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsUntrackedNr = {'GitSignsAddNr',
|
||||
desc = "Used for number column (when `config.numhl == true`) of 'untracked' signs."
|
||||
}},
|
||||
|
||||
{GitSignsAddLn = {'GitGutterAddLine', 'SignifyLineAdd', 'DiffAdd',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'add' signs."
|
||||
}},
|
||||
|
||||
{GitSignsChangeLn = {'GitGutterChangeLine', 'SignifyLineChange', 'DiffChange',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'change' signs."
|
||||
}},
|
||||
|
||||
{GitSignsChangedeleteLn = {'GitSignsChangeLn',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'changedelete' signs."
|
||||
}},
|
||||
|
||||
{GitSignsUntrackedLn = {'GitSignsAddLn',
|
||||
desc = "Used for buffer line (when `config.linehl == true`) of 'untracked' signs."
|
||||
}},
|
||||
|
||||
-- Don't set GitSignsDeleteLn by default
|
||||
-- {GitSignsDeleteLn = {}},
|
||||
|
||||
{GitSignsStagedAdd = {'GitSignsAdd' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedChange = {'GitSignsChange' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedDelete = {'GitSignsDelete' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedChangedelete = {'GitSignsChangedelete' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedTopdelete = {'GitSignsTopdelete' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedAddNr = {'GitSignsAddNr' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedChangeNr = {'GitSignsChangeNr' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedDeleteNr = {'GitSignsDeleteNr' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedChangedeleteNr = {'GitSignsChangedeleteNr', fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedTopdeleteNr = {'GitSignsTopdeleteNr' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedAddLn = {'GitSignsAddLn' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedChangeLn = {'GitSignsChangeLn' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedDeleteLn = {'GitSignsDeleteLn' , fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedChangedeleteLn = {'GitSignsChangedeleteLn', fg_factor = 0.5, hidden = true, }},
|
||||
{GitSignsStagedTopdeleteLn = {'GitSignsTopdeleteLn' , fg_factor = 0.5, hidden = true, }},
|
||||
|
||||
{GitSignsAddPreview = {'GitGutterAddLine', 'SignifyLineAdd', 'DiffAdd',
|
||||
desc = "Used for added lines in previews."
|
||||
}},
|
||||
|
||||
{GitSignsDeletePreview = {'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete',
|
||||
desc = "Used for deleted lines in previews."
|
||||
}},
|
||||
|
||||
{GitSignsCurrentLineBlame = {'NonText',
|
||||
desc = "Used for current line blame."
|
||||
}},
|
||||
|
||||
{GitSignsAddInline = {'TermCursor',
|
||||
desc = "Used for added word diff regions in inline previews."
|
||||
}},
|
||||
|
||||
{GitSignsDeleteInline = {'TermCursor',
|
||||
desc = "Used for deleted word diff regions in inline previews."
|
||||
}},
|
||||
|
||||
{GitSignsChangeInline = {'TermCursor',
|
||||
desc = "Used for changed word diff regions in inline previews."
|
||||
}},
|
||||
|
||||
{GitSignsAddLnInline = {'GitSignsAddInline',
|
||||
desc = "Used for added word diff regions when `config.word_diff == true`."
|
||||
}},
|
||||
|
||||
{GitSignsChangeLnInline = {'GitSignsChangeInline',
|
||||
desc = "Used for changed word diff regions when `config.word_diff == true`."
|
||||
}},
|
||||
|
||||
{GitSignsDeleteLnInline = {'GitSignsDeleteInline',
|
||||
desc = "Used for deleted word diff regions when `config.word_diff == true`."
|
||||
}},
|
||||
|
||||
-- Currently unused
|
||||
-- {GitSignsAddLnVirtLn = {'GitSignsAddLn'}},
|
||||
-- {GitSignsChangeVirtLn = {'GitSignsChangeLn'}},
|
||||
-- {GitSignsAddLnVirtLnInLine = {'GitSignsAddLnInline', }},
|
||||
-- {GitSignsChangeVirtLnInLine = {'GitSignsChangeLnInline', }},
|
||||
|
||||
{GitSignsDeleteVirtLn = {'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete',
|
||||
desc = "Used for deleted lines shown by inline `preview_hunk_inline()` or `show_deleted()`."
|
||||
}},
|
||||
|
||||
{GitSignsDeleteVirtLnInLine = {'GitSignsDeleteLnInline',
|
||||
desc = "Used for word diff regions in lines shown by inline `preview_hunk_inline()` or `show_deleted()`."
|
||||
}},
|
||||
|
||||
{GitSignsVirtLnum = {'GitSignsDeleteVirtLn',
|
||||
desc = 'Used for line numbers in inline hunks previews.'
|
||||
}},
|
||||
|
||||
}
|
||||
|
||||
local function is_hl_set(hl_name: string): boolean
|
||||
-- TODO: this only works with `set termguicolors`
|
||||
local exists, hl = pcall(vim.api.nvim_get_hl_by_name, hl_name, true)
|
||||
local color = hl.foreground or hl.background or hl.reverse
|
||||
return exists and color ~= nil
|
||||
end
|
||||
|
||||
local function cmul(x: integer, factor: number): integer
|
||||
if not x or factor == 1 then
|
||||
return x
|
||||
end
|
||||
|
||||
local r = math.floor(x / 2^16)
|
||||
local x1 = x - (r * 2^16)
|
||||
local g = math.floor(x1 / 2^8)
|
||||
local b = math.floor(x1 - (g * 2^8))
|
||||
return math.floor(math.floor(r*factor) * 2^16 + math.floor(g*factor) * 2^8 + math.floor(b*factor))
|
||||
end
|
||||
|
||||
local function dprintf(fmt: string, ...:any)
|
||||
require('gitsigns.debug.log').dprintf(fmt, ...)
|
||||
end
|
||||
|
||||
local function derive(hl: string, hldef: Hldef)
|
||||
for _, d in ipairs(hldef) do
|
||||
if is_hl_set(d) then
|
||||
dprintf('Deriving %s from %s', hl, d)
|
||||
if hldef.fg_factor or hldef.bg_factor then
|
||||
hldef.fg_factor = hldef.fg_factor or 1
|
||||
hldef.bg_factor = hldef.bg_factor or 1
|
||||
local dh = vim.api.nvim_get_hl_by_name(d, true)
|
||||
vim.api.nvim_set_hl(0, hl, {
|
||||
default = true,
|
||||
fg = cmul(dh.foreground, hldef.fg_factor),
|
||||
bg = cmul(dh.background, hldef.bg_factor),
|
||||
})
|
||||
else
|
||||
vim.api.nvim_set_hl(0, hl, {default = true, link = d })
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
if hldef[1] and not hldef.bg_factor and not hldef.fg_factor then
|
||||
-- No fallback found which is set. Just link to the first fallback
|
||||
-- if there are no modifiers
|
||||
dprintf('Deriving %s from %s', hl, hldef[1])
|
||||
vim.api.nvim_set_hl(0, hl, {default = true, link = hldef[1] })
|
||||
else
|
||||
dprintf('Could not derive %s', hl)
|
||||
end
|
||||
end
|
||||
|
||||
-- Setup a GitSign* highlight by deriving it from other potentially present
|
||||
-- highlights.
|
||||
M.setup_highlights = function()
|
||||
for _, hlg in ipairs(M.hls) do
|
||||
for hl, hldef in pairs(hlg) do
|
||||
if is_hl_set(hl) then
|
||||
-- Already defined
|
||||
dprintf('Highlight %s is already defined', hl)
|
||||
else
|
||||
derive(hl, hldef)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,472 +0,0 @@
|
|||
local Sign = require('gitsigns.signs').Sign
|
||||
local StatusObj = require('gitsigns.status').StatusObj
|
||||
|
||||
local util = require('gitsigns.util')
|
||||
|
||||
local min, max = math.min, math.max
|
||||
|
||||
--- @alias Gitsigns.Hunk.Type
|
||||
--- | "add"
|
||||
--- | "change"
|
||||
--- | "delete"
|
||||
|
||||
--- @class Gitsigns.Hunk.Node
|
||||
--- @field start integer
|
||||
--- @field count integer
|
||||
--- @field lines string[]
|
||||
|
||||
--- @class Gitsigns.Hunk.Hunk
|
||||
--- @field type Gitsigns.Hunk.Type
|
||||
--- @field head string
|
||||
--- @field added Gitsigns.Hunk.Node
|
||||
--- @field removed Gitsigns.Hunk.Node
|
||||
--- @field vend integer
|
||||
|
||||
--- @class Gitsigns.Hunk.Hunk_Public
|
||||
--- @field type Gitsigns.Hunk.Type
|
||||
--- @field head string
|
||||
--- @field lines string[]
|
||||
--- @field added Gitsigns.Hunk.Node
|
||||
--- @field removed Gitsigns.Hunk.Node
|
||||
|
||||
local record M
|
||||
enum Type
|
||||
"add"
|
||||
"change"
|
||||
"delete"
|
||||
end
|
||||
|
||||
record Node
|
||||
start: integer
|
||||
count: integer
|
||||
lines: {string}
|
||||
end
|
||||
|
||||
-- For internal use
|
||||
record Hunk
|
||||
type: Type
|
||||
head: string
|
||||
added: Node
|
||||
removed: Node
|
||||
vend: integer
|
||||
end
|
||||
|
||||
record Hunk_Public
|
||||
type: Type
|
||||
head: string
|
||||
lines: {string}
|
||||
added: Node
|
||||
removed: Node
|
||||
end
|
||||
end
|
||||
|
||||
local Hunk = M.Hunk
|
||||
|
||||
--- @param old_start integer
|
||||
--- @param old_count integer
|
||||
--- @param new_start integer
|
||||
--- @param new_count integer
|
||||
--- @return Gitsigns.Hunk.Hunk
|
||||
function M.create_hunk(old_start: integer, old_count: integer, new_start: integer, new_count: integer): Hunk
|
||||
return {
|
||||
removed = { start = old_start, count = old_count, lines = {} },
|
||||
added = { start = new_start, count = new_count, lines = {} },
|
||||
head = ('@@ -%d%s +%d%s @@'):format(
|
||||
old_start, old_count > 0 and ',' .. old_count or '',
|
||||
new_start, new_count > 0 and ',' .. new_count or ''
|
||||
),
|
||||
vend = new_start + math.max(new_count - 1, 0),
|
||||
type = new_count == 0 and 'delete' or
|
||||
old_count == 0 and 'add' or
|
||||
'change'
|
||||
}
|
||||
end
|
||||
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @param top integer
|
||||
--- @param bot integer
|
||||
--- @return Gitsigns.Hunk.Hunk
|
||||
function M.create_partial_hunk(hunks: {Hunk}, top: integer, bot: integer): Hunk
|
||||
local pretop, precount = top, bot - top + 1
|
||||
for _, h in ipairs(hunks) do
|
||||
local added_in_hunk = h.added.count - h.removed.count
|
||||
|
||||
local added_in_range = 0
|
||||
if h.added.start >= top and h.vend <= bot then
|
||||
-- Range contains hunk
|
||||
added_in_range = added_in_hunk
|
||||
else
|
||||
local added_above_bot = max(0, bot + 1 - (h.added.start + h.removed.count))
|
||||
local added_above_top = max(0, top - (h.added.start + h.removed.count))
|
||||
|
||||
if h.added.start >= top and h.added.start <= bot then
|
||||
-- Range top intersects hunk
|
||||
added_in_range = added_above_bot
|
||||
elseif h.vend >= top and h.vend <= bot then
|
||||
-- Range bottom intersects hunk
|
||||
added_in_range = added_in_hunk - added_above_top
|
||||
pretop = pretop - added_above_top
|
||||
elseif h.added.start <= top and h.vend >= bot then
|
||||
-- Range within hunk
|
||||
added_in_range = added_above_bot - added_above_top
|
||||
pretop = pretop - added_above_top
|
||||
end
|
||||
|
||||
if top > h.vend then
|
||||
pretop = pretop - added_in_hunk
|
||||
end
|
||||
end
|
||||
|
||||
precount = precount - added_in_range
|
||||
end
|
||||
|
||||
if precount == 0 then
|
||||
pretop = pretop - 1
|
||||
end
|
||||
|
||||
return M.create_hunk(pretop, precount, top, bot - top + 1)
|
||||
end
|
||||
|
||||
--- @param hunk Gitsigns.Hunk.Hunk
|
||||
--- @param fileformat string
|
||||
--- @return string[]
|
||||
function M.patch_lines(hunk: Hunk, fileformat: string): {string}
|
||||
local lines: {string} = {} --- @type string[]
|
||||
for _, l in ipairs(hunk.removed.lines) do
|
||||
lines[#lines+1] = '-'..l
|
||||
end
|
||||
for _, l in ipairs(hunk.added.lines) do
|
||||
lines[#lines+1] = '+'..l
|
||||
end
|
||||
|
||||
if fileformat == 'dos' then
|
||||
lines = util.strip_cr(lines)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
--- @param line string
|
||||
--- @return Gitsigns.Hunk.Hunk
|
||||
function M.parse_diff_line(line: string): Hunk
|
||||
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
|
||||
|
||||
-- diffKey: "-xx,n +yy"
|
||||
-- pre: {xx, n}, now: {yy}
|
||||
local pre, now = unpack(vim.tbl_map(function(s: string): {string}
|
||||
return vim.split(string.sub(s, 2), ',')
|
||||
end, vim.split(diffkey, ' ')) as {{string}})
|
||||
|
||||
local hunk = M.create_hunk(
|
||||
tonumber(pre[1]) as integer, (tonumber(pre[2]) or 1) as integer,
|
||||
tonumber(now[1]) as integer, (tonumber(now[2]) or 1) as integer
|
||||
)
|
||||
hunk.head = line
|
||||
|
||||
return hunk
|
||||
end
|
||||
|
||||
--- @param hunk Gitsigns.Hunk.Hunk
|
||||
--- @return integer
|
||||
local function change_end(hunk: Hunk): integer
|
||||
if hunk.added.count == 0 then
|
||||
-- delete
|
||||
return hunk.added.start
|
||||
elseif hunk.removed.count == 0 then
|
||||
-- add
|
||||
return hunk.added.start + hunk.added.count - 1
|
||||
else
|
||||
-- change
|
||||
return hunk.added.start + min(hunk.added.count, hunk.removed.count) - 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate signs needed to be applied from a hunk for a specified line range.
|
||||
--- @param hunk Gitsigns.Hunk.Hunk
|
||||
--- @param min_lnum integer
|
||||
--- @param max_lnum integer
|
||||
--- @param untracked boolean
|
||||
--- @return Gitsigns.Sign[]
|
||||
function M.calc_signs(hunk: Hunk, min_lnum: integer, max_lnum: integer, untracked: boolean): {Sign}
|
||||
assert(not untracked or hunk.type == 'add')
|
||||
min_lnum = min_lnum or 1
|
||||
max_lnum = max_lnum or math.huge as integer
|
||||
local start, added, removed = hunk.added.start, hunk.added.count, hunk.removed.count
|
||||
|
||||
if hunk.type == 'delete' and start == 0 then
|
||||
if min_lnum <= 1 then
|
||||
-- topdelete signs get placed one row lower
|
||||
return {{ type = 'topdelete', count = removed, lnum = 1 }}
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
local signs: {Sign} = {}
|
||||
|
||||
local cend = change_end(hunk)
|
||||
|
||||
for lnum = max(start, min_lnum), min(cend, max_lnum) do
|
||||
local changedelete = hunk.type == 'change' and removed > added and lnum == cend
|
||||
|
||||
signs[#signs+1] = {
|
||||
type = changedelete and 'changedelete' or
|
||||
untracked and 'untracked' or hunk.type,
|
||||
count = lnum == start and (hunk.type == 'add' and added or removed),
|
||||
lnum = lnum
|
||||
}
|
||||
end
|
||||
|
||||
if hunk.type == "change" and added > removed and
|
||||
hunk.vend >= min_lnum and cend <= max_lnum then
|
||||
for lnum = max(cend, min_lnum), min(hunk.vend, max_lnum) do
|
||||
signs[#signs+1] = {
|
||||
type = 'add',
|
||||
count = lnum == hunk.vend and (added - removed),
|
||||
lnum = lnum
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return signs
|
||||
end
|
||||
|
||||
--- @param relpath string
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @param mode_bits string
|
||||
--- @param invert boolean
|
||||
--- @return string[]
|
||||
function M.create_patch(relpath: string, hunks: {Hunk}, mode_bits: string, invert: boolean): {string}
|
||||
invert = invert or false
|
||||
|
||||
local results = {
|
||||
string.format('diff --git a/%s b/%s', relpath, relpath),
|
||||
'index 000000..000000 '..mode_bits,
|
||||
'--- a/'..relpath,
|
||||
'+++ b/'..relpath,
|
||||
}
|
||||
|
||||
local offset = 0
|
||||
|
||||
for _, process_hunk in ipairs(hunks) do
|
||||
local start, pre_count, now_count =
|
||||
process_hunk.removed.start, process_hunk.removed.count, process_hunk.added.count
|
||||
|
||||
if process_hunk.type == 'add' then
|
||||
start = start + 1
|
||||
end
|
||||
|
||||
local pre_lines = process_hunk.removed.lines
|
||||
local now_lines = process_hunk.added.lines
|
||||
|
||||
if invert then
|
||||
pre_count, now_count = now_count, pre_count
|
||||
pre_lines, now_lines = now_lines, pre_lines
|
||||
end
|
||||
|
||||
table.insert(results, string.format('@@ -%s,%s +%s,%s @@', start, pre_count, start + offset, now_count))
|
||||
for _, l in ipairs(pre_lines) do
|
||||
results[#results+1] = '-'..l
|
||||
end
|
||||
for _, l in ipairs(now_lines) do
|
||||
results[#results+1] = '+'..l
|
||||
end
|
||||
|
||||
process_hunk.removed.start = start + offset
|
||||
offset = offset + (now_count - pre_count)
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @return Gitsigns.StatusObj
|
||||
function M.get_summary(hunks: {Hunk}): StatusObj
|
||||
local status = { added = 0, changed = 0, removed = 0 }
|
||||
|
||||
for _, hunk in ipairs(hunks or {}) do
|
||||
if hunk.type == 'add' then
|
||||
status.added = status.added + hunk.added.count
|
||||
elseif hunk.type == 'delete' then
|
||||
status.removed = status.removed + hunk.removed.count
|
||||
elseif hunk.type == 'change' then
|
||||
local add, remove = hunk.added.count, hunk.removed.count
|
||||
local delta = min(add, remove)
|
||||
status.changed = status.changed + delta
|
||||
status.added = status.added + add - delta
|
||||
status.removed = status.removed + remove - delta
|
||||
end
|
||||
end
|
||||
|
||||
return status
|
||||
end
|
||||
|
||||
--- @param lnum integer
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @return Gitsigns.Hunk.Hunk?, integer?
|
||||
function M.find_hunk(lnum: integer, hunks: {Hunk}): Hunk, integer
|
||||
for i, hunk in ipairs(hunks or {}) do
|
||||
if lnum == 1 and hunk.added.start == 0 and hunk.vend == 0 then
|
||||
return hunk, i
|
||||
end
|
||||
|
||||
if hunk.added.start <= lnum and hunk.vend >= lnum then
|
||||
return hunk, i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param lnum integer
|
||||
--- @param hunks Gitsigns.Hunk.Hunk[]
|
||||
--- @param forwards boolean
|
||||
--- @param wrap boolean
|
||||
--- @return Gitsigns.Hunk.Hunk, integer
|
||||
function M.find_nearest_hunk(lnum: integer, hunks: {Hunk}, forwards: boolean, wrap: boolean): Hunk, integer
|
||||
local ret: Hunk --- @type Gitsigns.Hunk.Hunk
|
||||
local index: integer --- @type integer
|
||||
local distance: integer = math.huge as integer
|
||||
if forwards then
|
||||
for i = 1, #hunks do
|
||||
local hunk = hunks[i]
|
||||
local dist = hunk.added.start - lnum
|
||||
if dist > 0 and dist < distance then
|
||||
distance = dist
|
||||
ret = hunk
|
||||
index = i
|
||||
end
|
||||
end
|
||||
else
|
||||
for i = #hunks, 1, -1 do
|
||||
local hunk = hunks[i]
|
||||
local dist = lnum - hunk.vend
|
||||
if dist > 0 and dist < distance then
|
||||
distance = dist
|
||||
ret = hunk
|
||||
index = i
|
||||
end
|
||||
end
|
||||
end
|
||||
if not ret and wrap then
|
||||
index = forwards and 1 or #hunks
|
||||
ret = hunks[index as integer]
|
||||
end
|
||||
return ret, index
|
||||
end
|
||||
|
||||
--- @param a Gitsigns.Hunk.Hunk[]
|
||||
--- @param b Gitsigns.Hunk.Hunk[]
|
||||
--- @return boolean
|
||||
function M.compare_heads(a: {Hunk}, b: {Hunk}): boolean
|
||||
if (a == nil) ~= (b == nil) then
|
||||
return true
|
||||
elseif a and #a ~= #b then
|
||||
return true
|
||||
end
|
||||
for i, ah in ipairs(a or {}) do
|
||||
if b[i].head ~= ah.head then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- @param a Gitsigns.Hunk.Hunk
|
||||
--- @param b Gitsigns.Hunk.Hunk
|
||||
--- @return boolean
|
||||
local function compare_new(a: Hunk, b: Hunk): boolean
|
||||
if a.added.start ~= b.added.start then
|
||||
return false
|
||||
end
|
||||
|
||||
if a.added.count ~= b.added.count then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, a.added.count do
|
||||
if a.added.lines[i] ~= b.added.lines[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Return hunks in a using b's hunks as a filter. Only compare the 'new' section
|
||||
--- of the hunk.
|
||||
---
|
||||
--- Eg. Given:
|
||||
---
|
||||
--- a = {
|
||||
--- 1 = '@@ -24 +25,1 @@',
|
||||
--- 2 = '@@ -32 +34,1 @@',
|
||||
--- 3 = '@@ -37 +40,1 @@'
|
||||
--- }
|
||||
---
|
||||
--- b = {
|
||||
--- 1 = '@@ -26 +25,1 @@'
|
||||
--- }
|
||||
---
|
||||
--- Since a[1] and b[1] introduce the same changes to the buffer (both have
|
||||
--- +25,1), we exclude this hunk in the output so we return:
|
||||
---
|
||||
--- {
|
||||
--- 1 = '@@ -32 +34,1 @@',
|
||||
--- 2 = '@@ -37 +40,1 @@'
|
||||
--- }
|
||||
---
|
||||
--- @param a Gitsigns.Hunk.Hunk[]
|
||||
--- @param b Gitsigns.Hunk.Hunk[]
|
||||
--- @return Gitsigns.Hunk.Hunk[]?
|
||||
function M.filter_common(a: {Hunk}, b: {Hunk}): {Hunk}
|
||||
if not a and not b then
|
||||
return
|
||||
end
|
||||
|
||||
a, b = a or {}, b or {}
|
||||
local max_iter = math.max(#a, #b)
|
||||
|
||||
local a_i = 1
|
||||
local b_i = 1
|
||||
|
||||
--- @type Gitsigns.Hunk.Hunk[]
|
||||
local ret: {Hunk} = {}
|
||||
|
||||
for _ = 1, max_iter do
|
||||
local a_h, b_h = a[a_i], b[b_i]
|
||||
|
||||
if not a_h then
|
||||
-- Reached the end of a
|
||||
break
|
||||
end
|
||||
|
||||
if not b_h then
|
||||
-- Reached the end of b, add remainder of a
|
||||
for i = a_i, #a do
|
||||
ret[#ret+1] = a[i]
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
if a_h.added.start > b_h.added.start then
|
||||
-- a pointer is ahead of b; increment b pointer
|
||||
b_i = b_i + 1
|
||||
elseif a_h.added.start < b_h.added.start then
|
||||
-- b pointer is ahead of a; add a_h to ret and increment a pointer
|
||||
ret[#ret+1] = a_h
|
||||
a_i = a_i + 1
|
||||
else -- a_h.start == b_h.start
|
||||
-- a_h and b_h start on the same line, if hunks have the same changes then
|
||||
-- skip (filtered) otherwise add a_h to ret. Increment both hunk
|
||||
-- pointers
|
||||
-- TODO(lewis6991): Be smarter; if bh intercepts then break down ah.
|
||||
if not compare_new(a_h, b_h) then
|
||||
ret[#ret+1] = a_h
|
||||
end
|
||||
a_i = a_i + 1
|
||||
b_i = b_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,582 +0,0 @@
|
|||
local void = require('gitsigns.async').void
|
||||
local awrap = require('gitsigns.async').wrap
|
||||
local scheduler = require('gitsigns.async').scheduler
|
||||
|
||||
local gs_cache = require('gitsigns.cache')
|
||||
local CacheEntry = gs_cache.CacheEntry
|
||||
local cache = gs_cache.cache
|
||||
|
||||
local Signs = require('gitsigns.signs')
|
||||
local Status = require("gitsigns.status")
|
||||
|
||||
local debounce_trailing = require('gitsigns.debounce').debounce_trailing
|
||||
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
|
||||
|
||||
local log = require('gitsigns.debug.log')
|
||||
local dprint = log.dprint
|
||||
local dprintf = log.dprintf
|
||||
local eprint = log.eprint
|
||||
|
||||
local subprocess = require('gitsigns.subprocess')
|
||||
local util = require('gitsigns.util')
|
||||
local run_diff = require('gitsigns.diff')
|
||||
local uv = require('gitsigns.uv')
|
||||
|
||||
local gs_hunks = require("gitsigns.hunks")
|
||||
local Hunk = gs_hunks.Hunk
|
||||
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
local api = vim.api
|
||||
|
||||
local signs_normal: Signs
|
||||
local signs_staged: Signs
|
||||
|
||||
local record M
|
||||
update : function(bufnr: integer, CacheEntry)
|
||||
update_debounced : function(bufnr: integer, CacheEntry)
|
||||
on_lines : function(buf: integer, first: integer, last_orig: integer, last_new: integer): boolean
|
||||
watch_gitdir : function(bufnr: integer, gitdir: string): vim.loop.FSPollObj
|
||||
detach : function(bufnr: integer, keep_signs: boolean)
|
||||
reset_signs : function()
|
||||
setup : function()
|
||||
end
|
||||
|
||||
local scheduler_if_buf_valid = awrap(function(buf: integer, cb: function)
|
||||
vim.schedule(function()
|
||||
if vim.api.nvim_buf_is_valid(buf) then
|
||||
cb()
|
||||
end
|
||||
end)
|
||||
end, 2)
|
||||
|
||||
local function apply_win_signs0(bufnr: integer, signs: Signs, hunks: {Hunk}, top: integer, bot: integer, clear: boolean, untracked: boolean)
|
||||
if clear then
|
||||
signs:remove(bufnr) -- Remove all signs
|
||||
end
|
||||
|
||||
for i, hunk in ipairs(hunks or {}) do
|
||||
-- To stop the sign column width changing too much, if there are signs to be
|
||||
-- added but none of them are visible in the window, then make sure to add at
|
||||
-- least one sign. Only do this on the first call after an update when we all
|
||||
-- the signs have been cleared.
|
||||
if clear and i == 1 then
|
||||
signs:add(bufnr, gs_hunks.calc_signs(hunk, hunk.added.start, hunk.added.start, untracked))
|
||||
end
|
||||
|
||||
if top <= hunk.vend and bot >= hunk.added.start then
|
||||
signs:add(bufnr, gs_hunks.calc_signs(hunk, top, bot, untracked))
|
||||
end
|
||||
if hunk.added.start > bot then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_win_signs(bufnr: integer, top: integer, bot: integer, clear: boolean, untracked: boolean)
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache then
|
||||
return
|
||||
end
|
||||
apply_win_signs0(bufnr, signs_normal, bcache.hunks, top, bot, clear, untracked)
|
||||
if signs_staged then
|
||||
apply_win_signs0(bufnr, signs_staged, bcache.hunks_staged, top, bot, clear, false)
|
||||
end
|
||||
end
|
||||
|
||||
M.on_lines = function(buf: integer, first: integer, last_orig: integer, last_new: integer): boolean
|
||||
local bcache = cache[buf]
|
||||
if not bcache then
|
||||
dprint('Cache for buffer was nil. Detaching')
|
||||
return true
|
||||
end
|
||||
|
||||
signs_normal:on_lines(buf, first, last_orig, last_new)
|
||||
if signs_staged then
|
||||
signs_staged:on_lines(buf, first, last_orig, last_new)
|
||||
end
|
||||
|
||||
-- Signs in changed regions get invalidated so we need to force a redraw if
|
||||
-- any signs get removed.
|
||||
if bcache.hunks and signs_normal:contains(buf, first, last_new) then
|
||||
-- Force a sign redraw on the next update (fixes #521)
|
||||
bcache.force_next_update = true
|
||||
end
|
||||
|
||||
if signs_staged then
|
||||
if bcache.hunks_staged and signs_staged:contains(buf, first, last_new) then
|
||||
-- Force a sign redraw on the next update (fixes #521)
|
||||
bcache.force_next_update = true
|
||||
end
|
||||
end
|
||||
|
||||
M.update_debounced(buf, cache[buf])
|
||||
end
|
||||
|
||||
local ns = api.nvim_create_namespace('gitsigns')
|
||||
|
||||
local function apply_word_diff(bufnr: integer, row: integer)
|
||||
-- Don't run on folded lines
|
||||
if vim.fn.foldclosed(row+1) ~= -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
if not bcache or not bcache.hunks then
|
||||
return
|
||||
end
|
||||
|
||||
local line = api.nvim_buf_get_lines(bufnr, row, row+1, false)[1]
|
||||
if not line then
|
||||
-- Invalid line
|
||||
return
|
||||
end
|
||||
|
||||
local lnum = row + 1
|
||||
|
||||
local hunk = gs_hunks.find_hunk(lnum, bcache.hunks)
|
||||
if not hunk then
|
||||
-- No hunk at line
|
||||
return
|
||||
end
|
||||
|
||||
if hunk.added.count ~= hunk.removed.count then
|
||||
-- Only word diff if added count == removed
|
||||
return
|
||||
end
|
||||
|
||||
local pos = lnum - hunk.added.start + 1
|
||||
|
||||
local added_line = hunk.added.lines[pos]
|
||||
local removed_line = hunk.removed.lines[pos]
|
||||
|
||||
local _, added_regions = require('gitsigns.diff_int').run_word_diff({removed_line}, {added_line})
|
||||
|
||||
local cols = #line
|
||||
|
||||
for _, region in ipairs(added_regions) do
|
||||
local rtype, scol, ecol = region[2], region[3] - 1, region[4] - 1
|
||||
if ecol == scol then
|
||||
-- Make sure region is at least 1 column wide so deletes can be shown
|
||||
ecol = scol + 1
|
||||
end
|
||||
|
||||
local hl_group = rtype == 'add' and 'GitSignsAddLnInline'
|
||||
or rtype == 'change' and 'GitSignsChangeLnInline'
|
||||
or 'GitSignsDeleteLnInline'
|
||||
|
||||
local opts: {string:any} = {
|
||||
ephemeral = true,
|
||||
priority = 1000
|
||||
}
|
||||
|
||||
if ecol > cols and ecol == scol + 1 then
|
||||
-- delete on last column, use virtual text instead
|
||||
opts.virt_text = {{' ', hl_group}}
|
||||
opts.virt_text_pos = 'overlay'
|
||||
else
|
||||
opts.end_col = ecol
|
||||
opts.hl_group = hl_group
|
||||
end
|
||||
|
||||
api.nvim_buf_set_extmark(bufnr, ns, row, scol, opts)
|
||||
api.nvim__buf_redraw_range(bufnr, row, row+1)
|
||||
end
|
||||
end
|
||||
|
||||
local ns_rm = api.nvim_create_namespace('gitsigns_removed')
|
||||
|
||||
local VIRT_LINE_LEN = 300
|
||||
|
||||
local function clear_deleted(bufnr: integer)
|
||||
local marks = api.nvim_buf_get_extmarks(bufnr, ns_rm, 0, -1, {})
|
||||
for _, mark in ipairs(marks as {{integer, integer, integer}}) do
|
||||
api.nvim_buf_del_extmark(bufnr, ns_rm, mark[1])
|
||||
end
|
||||
end
|
||||
|
||||
function M.show_deleted(bufnr: integer, nsd: integer, hunk: Hunk)
|
||||
local virt_lines = {}
|
||||
|
||||
for i, line in ipairs(hunk.removed.lines) do
|
||||
local vline = {}
|
||||
local last_ecol = 1
|
||||
|
||||
if config.word_diff then
|
||||
local regions = require('gitsigns.diff_int').run_word_diff(
|
||||
{hunk.removed.lines[i]}, {hunk.added.lines[i]})
|
||||
|
||||
for _, region in ipairs(regions) do
|
||||
local rline, scol, ecol = region[1], region[3], region[4]
|
||||
if rline > 1 then
|
||||
break
|
||||
end
|
||||
vline[#vline+1] = { line:sub(last_ecol, scol-1), 'GitSignsDeleteVirtLn'}
|
||||
vline[#vline+1] = { line:sub(scol, ecol-1), 'GitSignsDeleteVirtLnInline'}
|
||||
last_ecol = ecol
|
||||
end
|
||||
end
|
||||
|
||||
if #line > 0 then
|
||||
vline[#vline+1] = { line:sub(last_ecol, -1), 'GitSignsDeleteVirtLn'}
|
||||
end
|
||||
|
||||
-- Add extra padding so the entire line is highlighted
|
||||
local padding = string.rep(' ', VIRT_LINE_LEN-#line)
|
||||
vline[#vline+1] = { padding, 'GitSignsDeleteVirtLn'}
|
||||
|
||||
virt_lines[i] = vline
|
||||
end
|
||||
|
||||
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
|
||||
|
||||
local row = topdelete and 0 or hunk.added.start - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
|
||||
virt_lines = virt_lines,
|
||||
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
|
||||
virt_lines_above = hunk.type ~= 'delete' or topdelete,
|
||||
})
|
||||
end
|
||||
|
||||
function M.show_deleted_in_float(bufnr: integer, nsd: integer, hunk: Hunk): integer
|
||||
local virt_lines = {}
|
||||
for i = 1, hunk.removed.count do
|
||||
virt_lines[i] = { { '', 'Normal' } }
|
||||
end
|
||||
|
||||
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
|
||||
local virt_lines_above = hunk.type ~= 'delete' or topdelete
|
||||
|
||||
local row = topdelete and 0 or hunk.added.start - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
|
||||
virt_lines = virt_lines,
|
||||
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
|
||||
virt_lines_above = virt_lines_above
|
||||
})
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
local pbufnr = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_lines(pbufnr, 0, -1, false, bcache.compare_text)
|
||||
|
||||
local cwin = api.nvim_get_current_win()
|
||||
local width = api.nvim_win_get_width(0)
|
||||
|
||||
local opts: vim.api.WinConfig = {
|
||||
relative = 'win',
|
||||
win = cwin,
|
||||
width = width,
|
||||
height = hunk.removed.count,
|
||||
anchor = 'SW',
|
||||
bufpos = { hunk.added.start, 0 }
|
||||
}
|
||||
|
||||
local bufpos_offset = virt_lines_above and not topdelete and 1 or 0
|
||||
opts.bufpos[1] = opts.bufpos[1] - bufpos_offset
|
||||
|
||||
local winid = api.nvim_open_win(pbufnr, false, opts)
|
||||
|
||||
-- Align buffer text by accounting for differences in the statuscolumn
|
||||
local textoff = vim.fn.getwininfo(cwin)[1].textoff
|
||||
local ptextoff = vim.fn.getwininfo(winid)[1].textoff
|
||||
local col_offset = textoff - ptextoff
|
||||
|
||||
if col_offset ~= 0 then
|
||||
opts.width = opts.width - col_offset
|
||||
opts.bufpos[2] = opts.bufpos[2] + col_offset
|
||||
api.nvim_win_set_config(winid, opts)
|
||||
end
|
||||
|
||||
vim.bo[pbufnr].filetype = vim.bo[bufnr].filetype
|
||||
vim.bo[pbufnr].bufhidden = 'wipe'
|
||||
vim.wo[winid].scrolloff = 0
|
||||
vim.wo[winid].relativenumber = false
|
||||
|
||||
api.nvim_win_call(winid, function(): nil
|
||||
-- Expand folds
|
||||
vim.cmd('normal '..'zR')
|
||||
|
||||
-- Navigate to hunk
|
||||
vim.cmd('normal '..tostring(hunk.removed.start)..'gg')
|
||||
vim.cmd('normal '..vim.api.nvim_replace_termcodes('z<CR>', true, false, true))
|
||||
end)
|
||||
|
||||
-- Apply highlights
|
||||
|
||||
for i = hunk.removed.start, hunk.removed.start + hunk.removed.count do
|
||||
api.nvim_buf_set_extmark(pbufnr, nsd, i - 1, 0, {
|
||||
hl_group = 'GitSignsDeleteVirtLn',
|
||||
hl_eol = true,
|
||||
end_row = i,
|
||||
priority = 1000
|
||||
})
|
||||
end
|
||||
|
||||
local removed_regions =
|
||||
require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
|
||||
|
||||
for _, region in ipairs(removed_regions) do
|
||||
local start_row = (hunk.removed.start - 1) + (region[1] - 1)
|
||||
local start_col = region[3] - 1
|
||||
local end_col = region[4] - 1
|
||||
api.nvim_buf_set_extmark(pbufnr, nsd, start_row, start_col, {
|
||||
hl_group = 'GitSignsDeleteVirtLnInline',
|
||||
end_col = end_col,
|
||||
end_row = start_row,
|
||||
priority = 1001
|
||||
})
|
||||
end
|
||||
|
||||
return winid
|
||||
end
|
||||
|
||||
function M.show_added(bufnr: integer, nsw: integer, hunk: Hunk)
|
||||
local start_row = hunk.added.start - 1
|
||||
|
||||
for offset = 0, hunk.added.count - 1 do
|
||||
local row = start_row + offset
|
||||
api.nvim_buf_set_extmark(bufnr, nsw, row, 0, {
|
||||
end_row = row + 1,
|
||||
hl_group = 'GitSignsAddPreview',
|
||||
hl_eol = true,
|
||||
priority = 1000
|
||||
})
|
||||
end
|
||||
|
||||
local _, added_regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
|
||||
|
||||
for _, region in ipairs(added_regions) do
|
||||
local offset, rtype, scol, ecol = region[1] - 1, region[2], region[3] - 1, region[4] - 1
|
||||
api.nvim_buf_set_extmark(bufnr, nsw, start_row + offset, scol, {
|
||||
end_col = ecol,
|
||||
hl_group = rtype == 'add' and 'GitSignsAddInline'
|
||||
or rtype == 'change' and 'GitSignsChangeInline'
|
||||
or 'GitSignsDeleteInline',
|
||||
priority = 1001
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function update_show_deleted(bufnr: integer)
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
clear_deleted(bufnr)
|
||||
if config.show_deleted then
|
||||
for _, hunk in ipairs(bcache.hunks or {}) do
|
||||
M.show_deleted(bufnr, ns_rm, hunk)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local update_cnt = 0
|
||||
|
||||
-- Ensure updates cannot be interleaved.
|
||||
-- Since updates are asynchronous we need to make sure an update isn't performed
|
||||
-- whilst another one is in progress. If this happens then schedule another
|
||||
-- update after the current one has completed.
|
||||
M.update = throttle_by_id(function(bufnr: integer, bcache: CacheEntry)
|
||||
local __FUNC__ = 'update'
|
||||
bcache = bcache or cache[bufnr]
|
||||
if not bcache then
|
||||
eprint('Cache for buffer '..bufnr..' was nil')
|
||||
return
|
||||
end
|
||||
local old_hunks, old_hunks_staged = bcache.hunks, bcache.hunks_staged
|
||||
bcache.hunks, bcache.hunks_staged = nil, nil
|
||||
|
||||
scheduler_if_buf_valid(bufnr)
|
||||
local buftext = util.buf_lines(bufnr)
|
||||
local git_obj = bcache.git_obj
|
||||
|
||||
if not bcache.compare_text or config._refresh_staged_on_update then
|
||||
bcache.compare_text = git_obj:get_show_text(bcache:get_compare_rev())
|
||||
end
|
||||
|
||||
bcache.hunks = run_diff(bcache.compare_text, buftext)
|
||||
|
||||
if config._signs_staged_enable then
|
||||
if not bcache.compare_text_head or config._refresh_staged_on_update then
|
||||
bcache.compare_text_head = git_obj:get_show_text(bcache:get_staged_compare_rev())
|
||||
end
|
||||
local hunks_head = run_diff(bcache.compare_text_head, buftext)
|
||||
bcache.hunks_staged = gs_hunks.filter_common(hunks_head, bcache.hunks)
|
||||
end
|
||||
|
||||
scheduler_if_buf_valid(bufnr)
|
||||
|
||||
-- Note the decoration provider may have invalidated bcache.hunks at this
|
||||
-- point
|
||||
if bcache.force_next_update or gs_hunks.compare_heads(bcache.hunks, old_hunks) or
|
||||
gs_hunks.compare_heads(bcache.hunks_staged, old_hunks_staged) then
|
||||
-- Apply signs to the window. Other signs will be added by the decoration
|
||||
-- provider as they are drawn.
|
||||
apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true, git_obj.object_name == nil)
|
||||
|
||||
update_show_deleted(bufnr)
|
||||
bcache.force_next_update = false
|
||||
|
||||
api.nvim_exec_autocmds('User', {
|
||||
pattern = 'GitSignsUpdate',
|
||||
modeline = false,
|
||||
})
|
||||
end
|
||||
|
||||
local summary = gs_hunks.get_summary(bcache.hunks)
|
||||
summary.head = git_obj.repo.abbrev_head
|
||||
Status:update(bufnr, summary)
|
||||
|
||||
update_cnt = update_cnt + 1
|
||||
|
||||
dprintf('updates: %s, jobs: %s', update_cnt, subprocess.job_cnt)
|
||||
end, true)
|
||||
|
||||
M.detach = function(bufnr: integer, keep_signs: boolean)
|
||||
if not keep_signs then
|
||||
-- Remove all signs
|
||||
signs_normal:remove(bufnr)
|
||||
if signs_staged then
|
||||
signs_staged:remove(bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_moved(bufnr: integer, bcache: CacheEntry, old_relpath: string)
|
||||
local git_obj = bcache.git_obj
|
||||
local do_update = false
|
||||
|
||||
local new_name = git_obj:has_moved()
|
||||
if new_name then
|
||||
dprintf('File moved to %s', new_name)
|
||||
git_obj.relpath = new_name
|
||||
if not git_obj.orig_relpath then
|
||||
git_obj.orig_relpath = old_relpath
|
||||
end
|
||||
do_update = true
|
||||
elseif git_obj.orig_relpath then
|
||||
local orig_file = git_obj.repo.toplevel..util.path_sep..git_obj.orig_relpath
|
||||
if git_obj:file_info(orig_file).relpath then
|
||||
dprintf('Moved file reset')
|
||||
git_obj.relpath = git_obj.orig_relpath
|
||||
git_obj.orig_relpath = nil
|
||||
do_update = true
|
||||
end
|
||||
else
|
||||
-- File removed from index, do nothing
|
||||
end
|
||||
|
||||
if do_update then
|
||||
git_obj.file = git_obj.repo.toplevel..util.path_sep..git_obj.relpath
|
||||
bcache.file = git_obj.file
|
||||
git_obj:update_file_info()
|
||||
scheduler()
|
||||
|
||||
local bufexists = vim.fn.bufexists(bcache.file) == 1
|
||||
local old_name = api.nvim_buf_get_name(bufnr)
|
||||
|
||||
if not bufexists then
|
||||
util.buf_rename(bufnr, bcache.file)
|
||||
end
|
||||
|
||||
local msg = bufexists and 'Cannot rename' or 'Renamed'
|
||||
dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M.watch_gitdir(bufnr: integer, gitdir: string): vim.loop.FSPollObj
|
||||
if not config.watch_gitdir.enable then
|
||||
return
|
||||
end
|
||||
|
||||
dprintf('Watching git dir')
|
||||
local w = uv.new_fs_poll(true)
|
||||
w:start(gitdir, config.watch_gitdir.interval, void(function(err: string)
|
||||
local __FUNC__ = 'watcher_cb'
|
||||
if err then
|
||||
dprintf('Git dir update error: %s', err)
|
||||
return
|
||||
end
|
||||
dprint('Git dir update')
|
||||
|
||||
local bcache = cache[bufnr]
|
||||
|
||||
if not bcache then
|
||||
-- Very occasionally an external git operation may cause the buffer to
|
||||
-- detach and update the git dir simultaneously. When this happens this
|
||||
-- handler will trigger but there will be no cache.
|
||||
dprint('Has detached, aborting')
|
||||
return
|
||||
end
|
||||
|
||||
local git_obj = bcache.git_obj
|
||||
|
||||
git_obj.repo:update_abbrev_head()
|
||||
|
||||
scheduler()
|
||||
Status:update(bufnr, { head = git_obj.repo.abbrev_head})
|
||||
|
||||
local was_tracked = git_obj.object_name ~= nil
|
||||
local old_relpath = git_obj.relpath
|
||||
|
||||
git_obj:update_file_info()
|
||||
|
||||
if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then
|
||||
-- File was tracked but is no longer tracked. Must of been removed or
|
||||
-- moved. Check if it was moved and switch to it.
|
||||
handle_moved(bufnr, bcache, old_relpath)
|
||||
end
|
||||
|
||||
bcache:invalidate()
|
||||
|
||||
M.update(bufnr, bcache)
|
||||
end))
|
||||
return w
|
||||
end
|
||||
|
||||
function M.reset_signs()
|
||||
-- Remove all signs
|
||||
if signs_normal then
|
||||
signs_normal:reset()
|
||||
end
|
||||
if signs_staged then
|
||||
signs_staged:reset()
|
||||
end
|
||||
end
|
||||
|
||||
local function on_win(_, _, bufnr: integer, topline: integer, botline_guess: integer): boolean
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.hunks then
|
||||
return false
|
||||
end
|
||||
local botline = math.min(botline_guess, api.nvim_buf_line_count(bufnr))
|
||||
|
||||
local untracked = bcache.git_obj.object_name == nil
|
||||
|
||||
apply_win_signs(bufnr, topline+1, botline+1, false, untracked)
|
||||
|
||||
if not (config.word_diff and config.diff_opts.internal) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function on_line(_, _, bufnr: integer, row: integer)
|
||||
apply_word_diff(bufnr, row)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
-- Calling this before any await calls will stop nvim's intro messages being
|
||||
-- displayed
|
||||
api.nvim_set_decoration_provider(ns, {
|
||||
on_win = on_win,
|
||||
on_line = on_line,
|
||||
})
|
||||
|
||||
signs_normal = Signs.new(config.signs)
|
||||
if config._signs_staged_enable then
|
||||
signs_staged = Signs.new(config._signs_staged, 'staged')
|
||||
end
|
||||
|
||||
M.update_debounced = debounce_trailing(config.update_debounce, void(M.update)) as function(integer, CacheEntry)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,87 +0,0 @@
|
|||
-- Originated from:
|
||||
-- https://github.com/norcalli/neovim-plugin/blob/master/lua/neovim-plugin/apply_mappings.lua
|
||||
|
||||
local validate = vim.validate
|
||||
local api = vim.api
|
||||
|
||||
local valid_modes: {string:string} = {
|
||||
n = 'n'; v = 'v'; x = 'x'; i = 'i'; o = 'o'; t = 't'; c = 'c'; s = 's';
|
||||
-- :map! and :map
|
||||
['!'] = '!'; [' '] = '';
|
||||
}
|
||||
|
||||
local valid_options: {string:string} = {
|
||||
buffer = 'boolean',
|
||||
expr = 'boolean',
|
||||
noremap = 'boolean',
|
||||
nowait = 'boolean',
|
||||
script = 'boolean',
|
||||
silent = 'boolean',
|
||||
unique = 'boolean',
|
||||
}
|
||||
|
||||
local function validate_option_keywords(options: table)
|
||||
for option_name, expected_type in pairs(valid_options) do
|
||||
local value = options[option_name]
|
||||
if value then
|
||||
validate {
|
||||
[option_name] = { value, expected_type };
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_mappings(mappings: {string:any}, bufnr: integer)
|
||||
validate {
|
||||
mappings = { mappings, 'table' };
|
||||
}
|
||||
|
||||
local default_options = {}
|
||||
for key, val in pairs(mappings) do
|
||||
-- Skip any inline default keywords.
|
||||
if valid_options[key] then
|
||||
default_options[key] = val
|
||||
end
|
||||
end
|
||||
|
||||
for key, opts in pairs(mappings) do
|
||||
repeat
|
||||
-- Skip any inline default keywords.
|
||||
if valid_options[key] then
|
||||
break
|
||||
end
|
||||
|
||||
local rhs: string
|
||||
local options: {string:any}
|
||||
if opts is string then
|
||||
rhs = opts
|
||||
options = {}
|
||||
elseif opts is table then
|
||||
rhs = opts[1] as string
|
||||
local boptions = {}
|
||||
for k in pairs(valid_options) do
|
||||
boptions[k] = opts[k]
|
||||
end
|
||||
options = boptions
|
||||
else
|
||||
error(("Invalid type for option rhs: %q = %s"):format(type(opts), vim.inspect(opts)))
|
||||
end
|
||||
options = vim.tbl_extend('keep', default_options, options) as {string:any}
|
||||
|
||||
validate_option_keywords(options)
|
||||
|
||||
local mode, mapping = key:match("^(.)[ ]*(.+)$")
|
||||
|
||||
if not mode or not valid_modes[mode] then
|
||||
error("Invalid mode specified for keymapping. mode="..mode)
|
||||
end
|
||||
|
||||
-- In case users haven't updated their config.
|
||||
options.buffer = nil
|
||||
|
||||
api.nvim_buf_set_keymap(bufnr, mode, mapping, rhs, options)
|
||||
until true
|
||||
end
|
||||
end
|
||||
|
||||
return apply_mappings
|
|
@ -1,16 +0,0 @@
|
|||
local type MsgFun = function(string, ...: any)
|
||||
|
||||
local record M
|
||||
warn : MsgFun
|
||||
error : MsgFun
|
||||
end
|
||||
|
||||
M.warn = vim.schedule_wrap(function(s: string, ...:any)
|
||||
vim.notify(s:format(...), vim.log.levels.WARN, {title = 'gitsigns'})
|
||||
end) as MsgFun
|
||||
|
||||
M.error = vim.schedule_wrap(function(s: string, ...:any)
|
||||
vim.notify(s:format(...), vim.log.levels.ERROR, {title = 'gitsigns'})
|
||||
end) as MsgFun
|
||||
|
||||
return M
|
|
@ -1,245 +0,0 @@
|
|||
local record popup
|
||||
type LinesSpec = {{{string,string|{HlMark}}}}
|
||||
record HlMark
|
||||
hl_group : string
|
||||
start_row : integer
|
||||
end_row : integer
|
||||
start_col : integer
|
||||
end_col : integer
|
||||
end
|
||||
end
|
||||
|
||||
local HlMark = popup.HlMark
|
||||
|
||||
local api = vim.api
|
||||
|
||||
local function bufnr_calc_width(bufnr: integer, lines: {string}): integer
|
||||
return api.nvim_buf_call(bufnr, function(): integer
|
||||
local width = 0
|
||||
for _, l in ipairs(lines) do
|
||||
if vim.fn.type(l) == vim.v.t_string then
|
||||
local len = vim.fn.strdisplaywidth(l)
|
||||
if len > width then
|
||||
width = len
|
||||
end
|
||||
end
|
||||
end
|
||||
return width + 1 -- Add 1 for some miinor padding
|
||||
end)
|
||||
end
|
||||
|
||||
-- Expand height until all lines are visible to account for wrapped lines.
|
||||
local function expand_height(winid: integer, nlines: integer)
|
||||
local newheight = 0
|
||||
for _ = 0, 50 do
|
||||
local winheight = api.nvim_win_get_height(winid)
|
||||
if newheight > winheight then
|
||||
-- Window must be max height
|
||||
break
|
||||
end
|
||||
local wd = api.nvim_win_call(winid, function(): integer
|
||||
return vim.fn.line('w$')
|
||||
end)
|
||||
if wd >= nlines then
|
||||
break
|
||||
end
|
||||
newheight = winheight+nlines-wd
|
||||
api.nvim_win_set_height(winid, newheight)
|
||||
end
|
||||
end
|
||||
|
||||
local function offset_hlmarks(hlmarks: {HlMark}, row_offset: integer)
|
||||
for _, h in ipairs(hlmarks) do
|
||||
if h.start_row then
|
||||
h.start_row = h.start_row + row_offset
|
||||
end
|
||||
if h.end_row then
|
||||
h.end_row = h.end_row + row_offset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function process_linesspec(fmt: popup.LinesSpec): {string}, {HlMark}
|
||||
local lines: {string} = {}
|
||||
local hls: {HlMark} = {}
|
||||
|
||||
local row = 0
|
||||
for _, section in ipairs(fmt) do
|
||||
local sec = {}
|
||||
local pos = 0
|
||||
for _, part in ipairs(section) do
|
||||
local text = part[1]
|
||||
local hl = part[2]
|
||||
|
||||
sec[#sec+1] = text
|
||||
|
||||
local srow = row
|
||||
local scol = pos
|
||||
|
||||
local ts = vim.split(text, '\n')
|
||||
|
||||
if #ts > 1 then
|
||||
pos = 0
|
||||
row = row + #ts - 1
|
||||
else
|
||||
pos = pos + #text
|
||||
end
|
||||
|
||||
if hl is string then
|
||||
hls[#hls+1] = {
|
||||
hl_group = hl,
|
||||
start_row = srow,
|
||||
end_row = row,
|
||||
start_col = scol,
|
||||
end_col = pos,
|
||||
}
|
||||
else -- hl is {HlMark}
|
||||
offset_hlmarks(hl, srow)
|
||||
vim.list_extend(hls, hl)
|
||||
end
|
||||
end
|
||||
for _, l in ipairs(vim.split(table.concat(sec, ''), '\n')) do
|
||||
lines[#lines+1] = l
|
||||
end
|
||||
row = row + 1
|
||||
end
|
||||
|
||||
return lines, hls
|
||||
end
|
||||
|
||||
local function close_all_but(id: string)
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview ~= nil and
|
||||
vim.w[winid].gitsigns_preview ~= id then
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function popup.close(id: string)
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview == id then
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function popup.create0(lines: {string}, opts: {string:any}, id: string): integer, integer
|
||||
-- Close any popups not matching id
|
||||
close_all_but(id)
|
||||
|
||||
local ts = vim.bo.tabstop
|
||||
local bufnr = api.nvim_create_buf(false, true)
|
||||
assert(bufnr, "Failed to create buffer")
|
||||
|
||||
-- In case nvim was opened with '-M'
|
||||
vim.bo[bufnr].modifiable = true
|
||||
api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
||||
vim.bo[bufnr].modifiable = false
|
||||
|
||||
-- Set tabstop before calculating the buffer width so that the correct width
|
||||
-- is calculated
|
||||
vim.bo[bufnr].tabstop = ts
|
||||
|
||||
local opts1 = vim.deepcopy(opts or {})
|
||||
opts1.height = opts1.height or #lines -- Guess, adjust later
|
||||
opts1.width = opts1.width or bufnr_calc_width(bufnr, lines)
|
||||
|
||||
local winid = api.nvim_open_win(bufnr, false, opts1)
|
||||
|
||||
vim.w[winid].gitsigns_preview = id or true
|
||||
|
||||
if not opts.height then
|
||||
expand_height(winid, #lines)
|
||||
end
|
||||
|
||||
if opts1.style == 'minimal' then
|
||||
-- If 'signcolumn' = auto:1-2, then a empty signcolumn will appear and cause
|
||||
-- line wrapping.
|
||||
vim.wo[winid].signcolumn = 'no'
|
||||
end
|
||||
|
||||
-- Close the popup when navigating to any window which is not the preview
|
||||
-- itself.
|
||||
local group = 'gitsigns_popup'
|
||||
local group_id = api.nvim_create_augroup(group, {})
|
||||
local old_cursor = api.nvim_win_get_cursor(0)
|
||||
|
||||
api.nvim_create_autocmd({'CursorMoved', 'CursorMovedI'}, {
|
||||
group = group_id,
|
||||
callback = function()
|
||||
local cursor = api.nvim_win_get_cursor(0)
|
||||
-- Did the cursor REALLY change (neovim/neovim#12923)
|
||||
if (old_cursor[1] ~= cursor[1] or old_cursor[2] ~= cursor[2])
|
||||
and api.nvim_get_current_win() ~= winid then
|
||||
-- Clear the augroup
|
||||
api.nvim_create_augroup(group, {})
|
||||
pcall(api.nvim_win_close, winid, true)
|
||||
return
|
||||
end
|
||||
old_cursor = cursor
|
||||
end
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('WinClosed', {
|
||||
pattern = tostring(winid),
|
||||
group = group_id,
|
||||
callback = function()
|
||||
-- Clear the augroup
|
||||
api.nvim_create_augroup(group, {})
|
||||
end
|
||||
})
|
||||
|
||||
-- update window position to follow the cursor when scrolling
|
||||
api.nvim_create_autocmd('WinScrolled', {
|
||||
buffer = api.nvim_get_current_buf(),
|
||||
group = group_id,
|
||||
callback = function()
|
||||
if api.nvim_win_is_valid(winid) then
|
||||
api.nvim_win_set_config(winid, opts1)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
return winid, bufnr
|
||||
end
|
||||
|
||||
local ns = api.nvim_create_namespace('gitsigns_popup')
|
||||
|
||||
function popup.create(lines_spec: popup.LinesSpec, opts: {string:any}, id: string): integer, integer
|
||||
local lines, highlights = process_linesspec(lines_spec)
|
||||
local winid, bufnr = popup.create0(lines, opts, id)
|
||||
|
||||
for _, hl in ipairs(highlights) do
|
||||
local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, ns, hl.start_row, hl.start_col or 0, {
|
||||
hl_group = hl.hl_group,
|
||||
end_row = hl.end_row,
|
||||
end_col = hl.end_col,
|
||||
hl_eol = true,
|
||||
})
|
||||
if not ok then
|
||||
error(vim.inspect(hl)..'\n'..err)
|
||||
end
|
||||
end
|
||||
|
||||
return winid, bufnr
|
||||
end
|
||||
|
||||
function popup.is_open(id: string): integer
|
||||
for _, winid in ipairs(api.nvim_list_wins()) do
|
||||
if vim.w[winid].gitsigns_preview == id then
|
||||
return winid
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function popup.focus_open(id: string): integer
|
||||
local winid = popup.is_open(id)
|
||||
if winid then
|
||||
api.nvim_set_current_win(winid)
|
||||
end
|
||||
return winid
|
||||
end
|
||||
|
||||
return popup
|
|
@ -1,28 +0,0 @@
|
|||
local api = vim.api
|
||||
|
||||
local record M
|
||||
mk_repeatable: function<F>(fn: F): F
|
||||
repeat_action: function()
|
||||
end
|
||||
|
||||
function M.mk_repeatable(fn: function): function
|
||||
return function(...: any)
|
||||
local args = {...}
|
||||
local nargs = select('#', ...)
|
||||
vim.go.operatorfunc = "v:lua.require'gitsigns.repeat'.repeat_action"
|
||||
|
||||
M.repeat_action = function()
|
||||
fn(unpack(args, 1, nargs))
|
||||
if vim.fn.exists('*repeat#set') == 1 then
|
||||
local action = api.nvim_replace_termcodes(
|
||||
string.format('<cmd>call %s()<cr>', vim.go.operatorfunc),
|
||||
true, true, true)
|
||||
vim.fn['repeat#set'](action, -1)
|
||||
end
|
||||
end
|
||||
|
||||
vim.cmd'normal! g@l'
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,43 +0,0 @@
|
|||
local config = require('gitsigns.config').config
|
||||
local SignsConfig = require('gitsigns.config').Config.SignsConfig
|
||||
|
||||
local dprint = require('gitsigns.debug.log').dprint
|
||||
|
||||
local B = require('gitsigns.signs.base')
|
||||
|
||||
-- local function capitalise_word(x: string): string
|
||||
-- return x:sub(1, 1):upper()..x:sub(2)
|
||||
-- end
|
||||
|
||||
function B.new(cfg: SignsConfig, name: string): B
|
||||
local __FUNC__ = 'signs.init'
|
||||
local C: B
|
||||
if config._extmark_signs then
|
||||
dprint('Using extmark signs')
|
||||
C = require('gitsigns.signs.extmarks')
|
||||
else
|
||||
dprint('Using vimfn signs')
|
||||
C = require('gitsigns.signs.vimfn')
|
||||
end
|
||||
|
||||
local hls = (name == 'staged' and config._signs_staged or config.signs) as {B.SignType:B.HlDef}
|
||||
-- Add when config.signs.*.[hl,numhl,linehl] are removed
|
||||
-- for _, t in ipairs {
|
||||
-- 'add',
|
||||
-- 'change',
|
||||
-- 'delete',
|
||||
-- 'topdelete',
|
||||
-- 'changedelete',
|
||||
-- 'untracked',
|
||||
-- } do
|
||||
-- local hl = string.format('GitSigns%s%s', name, capitalise_word(t))
|
||||
-- obj.hls[t] = {
|
||||
-- hl = hl,
|
||||
-- numhl = hl..'Nr',
|
||||
-- linehl = hl..'Ln',
|
||||
-- }
|
||||
-- end
|
||||
return C._new(cfg, hls, name)
|
||||
end
|
||||
|
||||
return B
|
|
@ -1,46 +0,0 @@
|
|||
local SignsConfig = require('gitsigns.config').Config.SignsConfig
|
||||
|
||||
local record M
|
||||
enum SignType
|
||||
"add"
|
||||
"delete"
|
||||
"change"
|
||||
"topdelete"
|
||||
"changedelete"
|
||||
"untracked"
|
||||
end
|
||||
|
||||
record Sign
|
||||
type: SignType
|
||||
count: integer
|
||||
lnum: integer
|
||||
end
|
||||
|
||||
record HlDef
|
||||
hl: string
|
||||
numhl: string
|
||||
linehl: string
|
||||
end
|
||||
|
||||
hls: {SignType:HlDef}
|
||||
|
||||
name: string
|
||||
group: string
|
||||
config: SignsConfig
|
||||
|
||||
-- Used by signs/extmarks.tl
|
||||
ns: integer
|
||||
|
||||
-- Used by signs/vimfn.tl
|
||||
placed: {integer:{integer:Sign}}
|
||||
|
||||
new : function(cfg: SignsConfig, name: string): M
|
||||
_new : function(cfg: SignsConfig, hls: {SignType:HlDef}, name: string): M
|
||||
remove : function(M, bufnr: integer, start_lnum: integer, end_lnum: integer)
|
||||
add : function(M, bufnr: integer, signs: {M.Sign})
|
||||
contains : function(M, bufnr: integer, start: integer, last: integer): boolean
|
||||
on_lines : function(M, bufnr: integer, first: integer, last_orig: integer, last_new: integer)
|
||||
reset : function(M)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,89 +0,0 @@
|
|||
local api = vim.api
|
||||
|
||||
local SignsConfig = require('gitsigns.config').Config.SignsConfig
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
local B = require('gitsigns.signs.base')
|
||||
|
||||
local M: B = {}
|
||||
|
||||
local group_base = 'gitsigns_extmark_signs_'
|
||||
|
||||
function M._new(cfg: SignsConfig, hls: {M.SignType:M.HlDef}, name: string): B
|
||||
local self = setmetatable({} as B, {__index = M})
|
||||
self.config = cfg
|
||||
self.hls = hls
|
||||
self.group = group_base..(name or '')
|
||||
self.ns = api.nvim_create_namespace(self.group)
|
||||
return self
|
||||
end
|
||||
|
||||
function M:on_lines(buf: integer, _: integer, last_orig: integer, last_new: integer)
|
||||
-- Remove extmarks on line deletions to mimic
|
||||
-- the behaviour of vim signs.
|
||||
if last_orig > last_new then
|
||||
self:remove(buf, last_new+1, last_orig)
|
||||
end
|
||||
end
|
||||
|
||||
function M:remove(bufnr: integer, start_lnum: integer, end_lnum: integer)
|
||||
if start_lnum then
|
||||
api.nvim_buf_clear_namespace(bufnr, self.ns, start_lnum-1, end_lnum or start_lnum)
|
||||
else
|
||||
api.nvim_buf_clear_namespace(bufnr, self.ns, 0, -1)
|
||||
end
|
||||
end
|
||||
|
||||
function M:add(bufnr: integer, signs: {M.Sign})
|
||||
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
|
||||
local count = s.count
|
||||
local cc = config.count_chars
|
||||
local count_char = cc[count] or cc['+'] or ''
|
||||
text = cs.text..count_char
|
||||
end
|
||||
|
||||
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,
|
||||
sign_text = config.signcolumn and text or '',
|
||||
priority = config.sign_priority,
|
||||
sign_hl_group = hls.hl,
|
||||
number_hl_group = config.numhl and hls.numhl or nil,
|
||||
line_hl_group = config.linehl and hls.linehl or nil,
|
||||
})
|
||||
|
||||
if not ok and config.debug_mode then
|
||||
vim.schedule(function()
|
||||
error(table.concat({
|
||||
string.format('Error placing extmark on line %d', s.lnum),
|
||||
err
|
||||
}, '\n'))
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:contains(bufnr: integer, start: integer, last: integer): boolean
|
||||
local marks = api.nvim_buf_get_extmarks(
|
||||
bufnr, self.ns, {start-1, 0}, {last or start, 0}, {limit=1})
|
||||
return #marks > 0
|
||||
end
|
||||
|
||||
function M:reset()
|
||||
for _, buf in ipairs(api.nvim_list_bufs()) do
|
||||
self:remove(buf)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,163 +0,0 @@
|
|||
local fn = vim.fn
|
||||
|
||||
local SignsConfig = require('gitsigns.config').Config.SignsConfig
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
local emptytable = require('gitsigns.util').emptytable
|
||||
|
||||
local B = require('gitsigns.signs.base')
|
||||
|
||||
local M: B = {}
|
||||
|
||||
-- The internal representation of signs in Neovim is a linked list which is slow
|
||||
-- to index. To improve efficiency we add an abstraction layer to the signs API
|
||||
-- which keeps track of which signs have already been placed in the buffer.
|
||||
--
|
||||
-- This allows us to:
|
||||
-- - efficiently query placed signs.
|
||||
-- - skip adding a sign if it has already been placed.
|
||||
|
||||
local function capitalise_word(x: string): string
|
||||
return x:sub(1, 1):upper()..x:sub(2)
|
||||
end
|
||||
|
||||
local sign_define_cache: {string:table} = {}
|
||||
local sign_name_cache: {string:string} = {}
|
||||
|
||||
local function get_sign_name(name: string, stype: string): string
|
||||
local key = name..stype
|
||||
if not sign_name_cache[key] then
|
||||
sign_name_cache[key] = string.format(
|
||||
'%s%s%s', 'GitSigns', capitalise_word(key), capitalise_word(stype))
|
||||
end
|
||||
|
||||
return sign_name_cache[key]
|
||||
end
|
||||
|
||||
local function sign_get(name: string): table
|
||||
if not sign_define_cache[name] then
|
||||
local s = fn.sign_getdefined(name)
|
||||
if not vim.tbl_isempty(s) then
|
||||
sign_define_cache[name] = s
|
||||
end
|
||||
end
|
||||
return sign_define_cache[name]
|
||||
end
|
||||
|
||||
local function define_sign(name: string, opts: {string:any}, redefine: boolean)
|
||||
if redefine then
|
||||
sign_define_cache[name] = nil
|
||||
fn.sign_undefine(name)
|
||||
fn.sign_define(name, opts)
|
||||
elseif not sign_get(name) then
|
||||
fn.sign_define(name, opts)
|
||||
end
|
||||
end
|
||||
|
||||
local function define_signs(obj: B, redefine: boolean)
|
||||
-- Define signs
|
||||
for stype, cs in pairs(obj.config) do
|
||||
local hls = obj.hls[stype]
|
||||
define_sign(get_sign_name(obj.name, stype), {
|
||||
texthl = hls.hl,
|
||||
text = config.signcolumn and cs.text or nil,
|
||||
numhl = config.numhl and hls.numhl or nil,
|
||||
linehl = config.linehl and hls.linehl or nil
|
||||
}, redefine)
|
||||
end
|
||||
end
|
||||
|
||||
local group_base = 'gitsigns_vimfn_signs_'
|
||||
|
||||
function M._new(cfg: SignsConfig, hls: {M.SignType:M.HlDef}, name: string): B
|
||||
local self = setmetatable({} as B, {__index = M})
|
||||
self.name = name or ''
|
||||
self.group = group_base..(name or '')
|
||||
self.config = cfg
|
||||
self.hls = hls
|
||||
self.placed = emptytable()
|
||||
|
||||
define_signs(self, false)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function M:on_lines(_: integer, _: integer, _: integer, _: integer)
|
||||
end
|
||||
|
||||
function M:remove(bufnr: integer, start_lnum: integer, end_lnum: integer)
|
||||
end_lnum = end_lnum or start_lnum
|
||||
|
||||
if start_lnum then
|
||||
for lnum = start_lnum, end_lnum do
|
||||
self.placed[bufnr][lnum] = nil
|
||||
fn.sign_unplace(self.group, {buffer = bufnr, id = lnum})
|
||||
end
|
||||
else
|
||||
self.placed[bufnr] = nil
|
||||
fn.sign_unplace(self.group, {buffer = bufnr})
|
||||
end
|
||||
end
|
||||
|
||||
function M:add(bufnr: integer, signs: {M.Sign})
|
||||
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
|
||||
|
||||
local to_place = {}
|
||||
|
||||
for _, s in ipairs(signs) do
|
||||
local sign_name = get_sign_name(self.name, s.type)
|
||||
|
||||
local cs = self.config[s.type]
|
||||
if config.signcolumn and cs.show_count and s.count then
|
||||
local count = s.count
|
||||
local cc = config.count_chars
|
||||
local count_suffix = cc[count] and tostring(count) or (cc['+'] and 'Plus') or ''
|
||||
local count_char = cc[count] or cc['+'] or ''
|
||||
local hls = self.hls[s.type]
|
||||
sign_name = sign_name..count_suffix
|
||||
define_sign(sign_name, {
|
||||
texthl = hls.hl,
|
||||
text = config.signcolumn and cs.text..count_char or '',
|
||||
numhl = config.numhl and hls.numhl or nil,
|
||||
linehl = config.linehl and hls.linehl or nil
|
||||
})
|
||||
end
|
||||
|
||||
if not self.placed[bufnr][s.lnum] then
|
||||
local sign = {
|
||||
id = s.lnum,
|
||||
group = self.group,
|
||||
name = sign_name,
|
||||
buffer = bufnr,
|
||||
lnum = s.lnum,
|
||||
priority = config.sign_priority
|
||||
}
|
||||
self.placed[bufnr][s.lnum] = s
|
||||
to_place[#to_place+1] = sign
|
||||
end
|
||||
end
|
||||
|
||||
if #to_place > 0 then
|
||||
fn.sign_placelist(to_place)
|
||||
end
|
||||
end
|
||||
|
||||
function M:contains(bufnr: integer, start: integer, last: integer): boolean
|
||||
for i = start+1, last+1 do
|
||||
if self.placed[bufnr][i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M:reset()
|
||||
self.placed = emptytable()
|
||||
fn.sign_unplace(self.group)
|
||||
define_signs(self, true)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,46 +0,0 @@
|
|||
local api = vim.api
|
||||
|
||||
|
||||
local record StatusObj
|
||||
added : integer
|
||||
removed : integer
|
||||
changed : integer
|
||||
head : string
|
||||
root : string
|
||||
gitdir : string
|
||||
end
|
||||
|
||||
local Status = {
|
||||
StatusObj = StatusObj,
|
||||
}
|
||||
|
||||
function Status:update(bufnr: integer, status: StatusObj)
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
local bstatus = vim.b[bufnr].gitsigns_status_dict
|
||||
if bstatus then
|
||||
status = vim.tbl_extend('force', bstatus as table, status as table) as StatusObj
|
||||
end
|
||||
vim.b[bufnr].gitsigns_head = status.head or ''
|
||||
vim.b[bufnr].gitsigns_status_dict = status
|
||||
|
||||
local config = require('gitsigns.config').config
|
||||
|
||||
vim.b[bufnr].gitsigns_status = config.status_formatter(status)
|
||||
end
|
||||
|
||||
function Status:clear(bufnr: integer)
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
vim.b[bufnr].gitsigns_head = nil
|
||||
vim.b[bufnr].gitsigns_status_dict = nil
|
||||
vim.b[bufnr].gitsigns_status = nil
|
||||
end
|
||||
|
||||
function Status:clear_diff(bufnr: integer)
|
||||
self:update(bufnr, { added = 0, removed = 0, changed = 0 })
|
||||
end
|
||||
|
||||
return Status
|
|
@ -1,119 +0,0 @@
|
|||
local log = require("gitsigns.debug.log")
|
||||
local guv = require("gitsigns.uv")
|
||||
local uv = vim.loop
|
||||
|
||||
local record M
|
||||
job_cnt: integer
|
||||
|
||||
record JobSpec
|
||||
command: string
|
||||
args: {string}
|
||||
cwd: string
|
||||
writer: {string} | string
|
||||
end
|
||||
end
|
||||
|
||||
M.job_cnt = 0
|
||||
|
||||
--- @param ... uv_pipe_t
|
||||
local function try_close(...: uv.Pipe)
|
||||
for i = 1, select('#', ...) do
|
||||
local pipe = select(i, ...)
|
||||
if pipe and not pipe:is_closing() then
|
||||
pipe:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param pipe uv_pipe_t
|
||||
--- @param x string[]|string
|
||||
local function handle_writer(pipe: uv.Pipe, x: {string} | string)
|
||||
if x is {string} then
|
||||
for i, v in ipairs(x) do
|
||||
pipe:write(v)
|
||||
if i ~= #(x as {string}) then
|
||||
pipe:write("\n")
|
||||
else
|
||||
pipe:write("\n", function()
|
||||
try_close(pipe)
|
||||
end)
|
||||
end
|
||||
end
|
||||
elseif x then
|
||||
-- write is string
|
||||
pipe:write(x, function()
|
||||
try_close(pipe)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param pipe uv_pipe_t
|
||||
--- @param output string[]
|
||||
local function handle_reader(pipe: uv.Pipe, output: {string})
|
||||
pipe:read_start(function(err: string, data: string)
|
||||
if err then
|
||||
log.eprint(err)
|
||||
end
|
||||
if data then
|
||||
output[#output+1] = data
|
||||
else
|
||||
try_close(pipe)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- @param obj table
|
||||
--- @param callback fun(_: integer, _: integer, _: string?, _: string?)
|
||||
function M.run_job(obj: M.JobSpec, callback: function(integer, integer, string, string))
|
||||
local __FUNC__ = 'run_job'
|
||||
if log.debug_mode then
|
||||
local cmd: string = obj.command..' '..table.concat(obj.args, ' ')
|
||||
log.dprint(cmd)
|
||||
end
|
||||
|
||||
local stdout_data: {string} = {}
|
||||
local stderr_data: {string} = {}
|
||||
|
||||
local stdout = guv.new_pipe(false)
|
||||
local stderr = guv.new_pipe(false)
|
||||
local stdin: uv.Pipe
|
||||
if obj.writer then
|
||||
stdin = guv.new_pipe(false)
|
||||
end
|
||||
|
||||
--- @type uv_process_t?, integer|string
|
||||
local handle, _pid: uv.Process, integer
|
||||
handle, _pid = vim.loop.spawn(obj.command, {
|
||||
args = obj.args,
|
||||
stdio = { stdin, stdout, stderr },
|
||||
cwd = obj.cwd
|
||||
},
|
||||
function(code: integer, signal: integer)
|
||||
if handle then
|
||||
handle:close()
|
||||
end
|
||||
stdout:read_stop()
|
||||
stderr:read_stop()
|
||||
|
||||
try_close(stdin, stdout, stderr)
|
||||
|
||||
local stdout_result = #stdout_data > 0 and table.concat(stdout_data) or nil
|
||||
local stderr_result = #stderr_data > 0 and table.concat(stderr_data) or nil
|
||||
|
||||
callback(code, signal, stdout_result, stderr_result)
|
||||
end
|
||||
)
|
||||
|
||||
if not handle then
|
||||
try_close(stdin, stdout, stderr)
|
||||
error(debug.traceback("Failed to spawn process: " .. vim.inspect(obj)))
|
||||
end
|
||||
|
||||
handle_reader(stdout, stdout_data)
|
||||
handle_reader(stderr, stderr_data)
|
||||
handle_writer(stdin, obj.writer)
|
||||
|
||||
M.job_cnt = M.job_cnt + 1
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,35 +0,0 @@
|
|||
|
||||
local record M
|
||||
_tests: {string:function}
|
||||
end
|
||||
|
||||
local function eq(act: any, exp: any)
|
||||
assert(act == exp, string.format('%s != %s', act, exp))
|
||||
end
|
||||
|
||||
M._tests = {}
|
||||
|
||||
M._tests.expand_format = function()
|
||||
local util = require'gitsigns.util'
|
||||
assert('hello % world % 2021' == util.expand_format('<var1> % <var2> % <var_time:%Y>' , {
|
||||
var1 = 'hello', var2 = 'world', var_time = 1616838297 }))
|
||||
end
|
||||
|
||||
|
||||
M._tests.test_args = function()
|
||||
local parse_args = require'gitsigns.cli.argparse'.parse_args
|
||||
|
||||
local pos_args, named_args = parse_args('hello there key=value, key1="a b c"')
|
||||
|
||||
eq(pos_args[1], 'hello')
|
||||
eq(pos_args[2], 'there')
|
||||
eq(named_args.key, 'value,')
|
||||
eq(named_args.key1, 'a b c')
|
||||
|
||||
pos_args, named_args = parse_args('base=HEAD~1 posarg')
|
||||
|
||||
eq(named_args.base, 'HEAD~1')
|
||||
eq(pos_args[1], 'posarg')
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,223 +0,0 @@
|
|||
local record M
|
||||
type FmtInfo = {string:string|integer|{string}}
|
||||
|
||||
path_sep: string
|
||||
end
|
||||
|
||||
function M.path_exists(path: string): boolean
|
||||
return vim.loop.fs_stat(path) and true or false
|
||||
end
|
||||
|
||||
local jit_os: string --- @type string
|
||||
|
||||
if jit then
|
||||
jit_os = jit.os:lower()
|
||||
end
|
||||
|
||||
local is_unix: boolean = false
|
||||
if jit_os then
|
||||
is_unix = jit_os == 'linux' or jit_os == 'osx' or jit_os == 'bsd'
|
||||
else
|
||||
local binfmt = package.cpath:match("%p[\\|/]?%p(%a+)")
|
||||
is_unix = binfmt ~= "dll"
|
||||
end
|
||||
|
||||
--- @param file string
|
||||
--- @return string
|
||||
function M.dirname(file: string): string
|
||||
return file:match(string.format('^(.+)%s[^%s]+', M.path_sep, M.path_sep))
|
||||
end
|
||||
|
||||
--- @param file string
|
||||
--- @return string[]
|
||||
function M.file_lines(file: string): {string}
|
||||
local text: {string} = {} --- @type string[]
|
||||
for line in io.lines(file) do
|
||||
text[#text+1] = line
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
M.path_sep = package.config:sub(1, 1)
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @return string[]
|
||||
function M.buf_lines(bufnr: integer): {string}
|
||||
-- nvim_buf_get_lines strips carriage returns if fileformat==dos
|
||||
local buftext: {string} = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
for i = 1, #buftext do
|
||||
buftext[i] = buftext[i]..'\r'
|
||||
end
|
||||
end
|
||||
return buftext
|
||||
end
|
||||
|
||||
--- @param buf integer
|
||||
local function delete_alt(buf: integer)
|
||||
local alt = vim.api.nvim_buf_call(buf, function(): integer
|
||||
return vim.fn.bufnr('#')
|
||||
end) as integer
|
||||
if alt ~= buf and alt ~= -1 then
|
||||
pcall(vim.api.nvim_buf_delete, alt, {force=true})
|
||||
end
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @param name string
|
||||
function M.buf_rename(bufnr: integer, name: string)
|
||||
vim.api.nvim_buf_set_name(bufnr, name)
|
||||
delete_alt(bufnr)
|
||||
end
|
||||
|
||||
--- @param bufnr integer
|
||||
--- @param start_row integer
|
||||
--- @param end_row integer
|
||||
--- @param lines string[]
|
||||
function M.set_lines(bufnr: integer, start_row: integer, end_row: integer, lines: {string})
|
||||
if vim.bo[bufnr].fileformat == 'dos' then
|
||||
for i = 1, #lines do
|
||||
lines[i] = lines[i]:gsub('\r$', '')
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(bufnr, start_row, end_row, false, lines)
|
||||
end
|
||||
|
||||
--- @return string
|
||||
function M.tmpname(): string
|
||||
if is_unix then
|
||||
return os.tmpname()
|
||||
end
|
||||
return vim.fn.tempname()
|
||||
end
|
||||
|
||||
--- @param timestamp number
|
||||
--- @return string
|
||||
function M.get_relative_time(timestamp: number): string
|
||||
local current_timestamp = os.time()
|
||||
local elapsed = current_timestamp - timestamp
|
||||
|
||||
if elapsed == 0 then
|
||||
return 'a while ago'
|
||||
end
|
||||
|
||||
local minute_seconds = 60
|
||||
local hour_seconds = minute_seconds * 60
|
||||
local day_seconds = hour_seconds * 24
|
||||
local month_seconds = day_seconds * 30
|
||||
local year_seconds = month_seconds * 12
|
||||
|
||||
local to_relative_string = function(time: number, divisor: number, time_word: string): string
|
||||
local num = math.floor(time / divisor)
|
||||
if num > 1 then
|
||||
time_word = time_word .. 's'
|
||||
end
|
||||
|
||||
return num .. ' ' .. time_word .. ' ago'
|
||||
end
|
||||
|
||||
if elapsed < minute_seconds then
|
||||
return to_relative_string(elapsed, 1, 'second')
|
||||
elseif elapsed < hour_seconds then
|
||||
return to_relative_string(elapsed, minute_seconds, 'minute')
|
||||
elseif elapsed < day_seconds then
|
||||
return to_relative_string(elapsed, hour_seconds, 'hour')
|
||||
elseif elapsed < month_seconds then
|
||||
return to_relative_string(elapsed, day_seconds, 'day')
|
||||
elseif elapsed < year_seconds then
|
||||
return to_relative_string(elapsed, month_seconds, 'month')
|
||||
else
|
||||
return to_relative_string(elapsed, year_seconds, 'year')
|
||||
end
|
||||
end
|
||||
|
||||
--- @generic T
|
||||
--- @param x T[]
|
||||
--- @return T[]
|
||||
function M.copy_array<T>(x: {T}): {T}
|
||||
local r = {}
|
||||
for i, e in ipairs(x) do
|
||||
r[i] = e
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- Strip '\r' from the EOL of each line only if all lines end with '\r'
|
||||
--- @param xs0 string[]
|
||||
--- @return string[]
|
||||
function M.strip_cr(xs0: {string}): {string}
|
||||
for i = 1, #xs0 do
|
||||
if xs0[i]:sub(-1) ~= '\r' then
|
||||
-- don't strip, return early
|
||||
return xs0
|
||||
end
|
||||
end
|
||||
-- all lines end with '\r', need to strip
|
||||
local xs = vim.deepcopy(xs0)
|
||||
for i = 1, #xs do
|
||||
xs[i] = xs[i]:sub(1, -2)
|
||||
end
|
||||
return xs
|
||||
end
|
||||
|
||||
function M.calc_base(base: string): string
|
||||
if base and base:sub(1, 1):match('[~\\^]') then
|
||||
base = 'HEAD'..base
|
||||
end
|
||||
return base
|
||||
end
|
||||
|
||||
function M.emptytable<T>(): T
|
||||
return setmetatable({} as T, {
|
||||
__index = function(t: table, k: any): any
|
||||
t[k] = {}
|
||||
return t[k]
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function expand_date(fmt: string, time: integer): string
|
||||
if fmt == '%R' then
|
||||
return M.get_relative_time(time)
|
||||
end
|
||||
return os.date(fmt, time)
|
||||
end
|
||||
|
||||
---@param fmt string
|
||||
---@param info table
|
||||
---@param reltime boolean Use relative time as the default date format
|
||||
---@return string
|
||||
function M.expand_format(fmt: string, info: M.FmtInfo, reltime: boolean): string
|
||||
local ret = {} --- @type string[]
|
||||
|
||||
for _ = 1, 20 do -- loop protection
|
||||
-- Capture <name> or <name:format>
|
||||
local scol, ecol, match, key, time_fmt = fmt:find('(<([^:>]+):?([^>]*)>)')
|
||||
if not match then
|
||||
break
|
||||
end
|
||||
|
||||
ret[#ret+1], fmt = fmt:sub(1, scol-1), fmt:sub(ecol+1)
|
||||
|
||||
local v = info[key]
|
||||
|
||||
if v then
|
||||
if v is {string} then
|
||||
v = table.concat(v, '\n')
|
||||
end
|
||||
if vim.endswith(key, '_time') then
|
||||
if time_fmt == '' then
|
||||
time_fmt = reltime and '%R' or '%Y-%m-%d'
|
||||
end
|
||||
v = expand_date(time_fmt, v as integer)
|
||||
end
|
||||
match = tostring(v)
|
||||
end
|
||||
ret[#ret+1] = match
|
||||
end
|
||||
|
||||
ret[#ret+1] = fmt
|
||||
return table.concat(ret, '')
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,80 +0,0 @@
|
|||
local uv = vim.loop
|
||||
|
||||
local record M
|
||||
handles: {integer:{uv.Handle, boolean, string}}
|
||||
end
|
||||
|
||||
--- @type table<integer,{[1]: uv_handle_t, [2]: boolean, [3]: string}>
|
||||
local handles: {integer:{uv.Handle, boolean, string}} = {}
|
||||
|
||||
M.handles = handles
|
||||
|
||||
function M.print_handles()
|
||||
local none = true
|
||||
for _, e in pairs(handles) do
|
||||
local handle, longlived, tr = e[1], e[2], e[3]
|
||||
if handle and not longlived and not handle:is_closing() then
|
||||
print('')
|
||||
print(tr)
|
||||
none = false
|
||||
end
|
||||
end
|
||||
if none then
|
||||
print('No active handles')
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd('VimLeavePre', {
|
||||
callback = function()
|
||||
for _, e in pairs(handles) do
|
||||
local handle = e[1]
|
||||
if handle and not handle:is_closing() then
|
||||
handle:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
--- @param longlived boolean
|
||||
--- @return uv_timer_t?
|
||||
function M.new_timer(longlived: boolean): uv.Timer
|
||||
local r = uv.new_timer()
|
||||
if r then
|
||||
table.insert(handles, {r as uv.Handle, longlived, debug.traceback()})
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param longlived boolean
|
||||
--- @return uv_fs_poll_t?
|
||||
function M.new_fs_poll(longlived: boolean): uv.FSPollObj
|
||||
local r = uv.new_fs_poll()
|
||||
if r then
|
||||
table.insert(handles, {r as uv.Handle, longlived, debug.traceback()})
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param ipc boolean
|
||||
--- @return uv_pipe_t?
|
||||
function M.new_pipe(ipc: boolean): uv.Pipe
|
||||
local r = uv.new_pipe(ipc)
|
||||
if r then
|
||||
table.insert(handles, {r as uv.Handle, false, debug.traceback()})
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
--- @param cmd string
|
||||
--- @param opts uv.aliases.spawn_options
|
||||
--- @param on_exit fun(_: integer, _, integer): uv_process_t, integer
|
||||
--- @return uv_process_t?, string|integer
|
||||
function M.spawn(cmd: string, opts: uv.SpawnOpts, on_exit: function(integer, integer)): uv.Process, integer
|
||||
local handle, pid = uv.spawn(cmd, opts, on_exit)
|
||||
if handle then
|
||||
table.insert(handles, {handle as uv.Handle, false, cmd..' '..vim.inspect(opts)})
|
||||
end
|
||||
return handle, pid
|
||||
end
|
||||
|
||||
return M
|
10
tlconfig.lua
10
tlconfig.lua
|
@ -1,10 +0,0 @@
|
|||
return {
|
||||
gen_target = '5.1',
|
||||
gen_compat = 'off',
|
||||
global_env_def = 'types',
|
||||
include_dir = {
|
||||
'types', 'teal',
|
||||
},
|
||||
source_dir = 'teal',
|
||||
build_dir = "lua",
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
local record Cdefs
|
||||
xdl_diff: (function(...:any): number)
|
||||
end
|
||||
|
||||
-- C callback
|
||||
local record CCB
|
||||
free: function(CCB)
|
||||
set: function(CCB, function)
|
||||
end
|
||||
|
||||
local record ffi
|
||||
cdef: function(string)
|
||||
load: function(string): any
|
||||
new: function(string, ...:any): any
|
||||
string: function(any, number): string
|
||||
gc: function<T>(T, function): T
|
||||
C: Cdefs
|
||||
cast: function(string, any): CCB
|
||||
errno: function(): integer
|
||||
copy: function
|
||||
metatype: function(string, any)
|
||||
end
|
||||
|
||||
return ffi
|
|
@ -1,5 +0,0 @@
|
|||
local record Trouble
|
||||
open: function(mode: string)
|
||||
end
|
||||
|
||||
return Trouble
|
|
@ -1,22 +0,0 @@
|
|||
global _: any
|
||||
|
||||
global _TEST: boolean
|
||||
|
||||
global loadstring: function(string): (function(): any)
|
||||
|
||||
global unpack: function<T>({T}, number, number): T...
|
||||
|
||||
global getfenv: function(function): table
|
||||
|
||||
global record jit
|
||||
arch: string
|
||||
os: string
|
||||
version: string
|
||||
version_num: number
|
||||
off: function()
|
||||
off: function(function)
|
||||
on: function()
|
||||
status: function(): boolean
|
||||
end
|
||||
|
||||
global vim = require('vim')
|
237
types/vim.d.tl
237
types/vim.d.tl
|
@ -1,237 +0,0 @@
|
|||
local api = require 'vim.api'
|
||||
local fn = require 'vim.fn'
|
||||
local uv = require 'vim.uv'
|
||||
|
||||
local record M
|
||||
api: api
|
||||
fn: fn
|
||||
|
||||
call: function(string, ...:any)
|
||||
cmd: function(string): any
|
||||
|
||||
deepcopy: function<T>(T): T
|
||||
|
||||
defer_fn: function(function, integer): uv.Timer
|
||||
|
||||
type DiffResult = {integer, integer, integer, integer}
|
||||
|
||||
-- Assume result_type == 'indices'
|
||||
diff: function(string|{string}, string|{string}, table): {DiffResult}
|
||||
|
||||
env: {string:string}
|
||||
|
||||
iconv: function(string, string, string): string
|
||||
|
||||
is_callable: function(any): boolean
|
||||
|
||||
record go
|
||||
operatorfunc: string
|
||||
end
|
||||
|
||||
record o
|
||||
diffopt: string
|
||||
eventignore: string
|
||||
shortmess: string
|
||||
splitright: boolean
|
||||
updatetime: number
|
||||
wrapscan: boolean
|
||||
end
|
||||
|
||||
record WinOption
|
||||
{WinOption}
|
||||
diff: boolean
|
||||
signcolumn: string
|
||||
scrolloff: integer
|
||||
relativenumber: boolean
|
||||
end
|
||||
|
||||
wo: WinOption
|
||||
|
||||
record BufOption
|
||||
{BufOption}
|
||||
fileformat: string
|
||||
fileencoding: string
|
||||
filetype: string
|
||||
modifiable: boolean
|
||||
modified: boolean
|
||||
tabstop: integer
|
||||
|
||||
enum BufHidden
|
||||
'' 'hide' 'unload' 'delete' 'wipe'
|
||||
end
|
||||
|
||||
bufhidden: BufHidden
|
||||
|
||||
enum BufType
|
||||
'' 'acwrite' 'help' 'nofile' 'nowrite' 'quickfix' 'terminal' 'prompt'
|
||||
end
|
||||
buftype: BufType
|
||||
end
|
||||
|
||||
bo: BufOption
|
||||
|
||||
record BufVar
|
||||
{BufVar}
|
||||
changedtick: integer
|
||||
|
||||
gitsigns_head: string
|
||||
gitsigns_status_dict: {string:any}
|
||||
gitsigns_status: string
|
||||
gitsigns_blame_line_dict: {string:any}
|
||||
gitsigns_blame_line: string
|
||||
end
|
||||
|
||||
b: BufVar
|
||||
|
||||
record WinVar
|
||||
{WinVar}
|
||||
|
||||
gitsigns_preview: string|boolean
|
||||
end
|
||||
|
||||
w: WinVar
|
||||
|
||||
record g
|
||||
gitsigns_head: string
|
||||
loaded_fugitive: integer
|
||||
end
|
||||
|
||||
record v
|
||||
vim_did_enter: integer
|
||||
t_string: integer
|
||||
end
|
||||
|
||||
record opt
|
||||
record Opt<T>
|
||||
get: function<T>(Opt<T>): T
|
||||
end
|
||||
|
||||
diffopt: Opt<{string}>
|
||||
foldopen: Opt<{string}>
|
||||
shortmess: Opt<{string:boolean}>
|
||||
wrapscan: Opt<boolean>
|
||||
end
|
||||
|
||||
record lsp
|
||||
record util
|
||||
close_preview_autocmd: function ({string}, number)
|
||||
end
|
||||
end
|
||||
|
||||
loop: uv
|
||||
|
||||
in_fast_event: function(): boolean
|
||||
|
||||
list_extend: function<T>({T}, {T}, integer, integer): {T}
|
||||
list_slice: function<T>({T}, integer, integer): {T}
|
||||
|
||||
record keymap
|
||||
record Options
|
||||
buffer: boolean|integer
|
||||
expr: boolean
|
||||
end
|
||||
set: function(string|{string}, string, string|function, Options)
|
||||
end
|
||||
|
||||
record log
|
||||
record levels
|
||||
WARN: integer
|
||||
ERROR: integer
|
||||
INFO: integer
|
||||
DEBUG: integer
|
||||
end
|
||||
end
|
||||
notify: function(string, integer, table)
|
||||
pretty_print: function(any)
|
||||
|
||||
split: function(string, string): {string}
|
||||
split: function(string, string, boolean): {string}
|
||||
|
||||
gsplit: function(string, string, boolean): function(): string
|
||||
|
||||
pesc: function(string): string
|
||||
|
||||
record Regex
|
||||
userdata
|
||||
|
||||
match_str: function(Regex, string): integer, integer
|
||||
end
|
||||
|
||||
regex: function(string): Regex
|
||||
|
||||
startswith: function(string, string): boolean
|
||||
endswith: function(string, string): boolean
|
||||
|
||||
schedule_wrap: function(function()): function()
|
||||
schedule_wrap: function(function(...:any): any...): function(...:any): any...
|
||||
|
||||
schedule: function(function)
|
||||
validate: function({string:{any}})
|
||||
trim: function(string): string
|
||||
|
||||
enum ExtendBehavior
|
||||
'error'
|
||||
'keep'
|
||||
'force'
|
||||
end
|
||||
|
||||
tbl_add_reverse_lookup: function<K,I>({K:I}): {I:K}
|
||||
tbl_contains: function(table, any): boolean
|
||||
tbl_count: function(table): integer
|
||||
tbl_deep_extend: function(ExtendBehavior, table, table, ...: table): table
|
||||
tbl_extend: function(ExtendBehavior, table, table, ...: table): table
|
||||
tbl_filter: function<T>((function(any): boolean), {T}): {T}
|
||||
tbl_isempty: function(table): boolean
|
||||
tbl_islist: function(table): boolean
|
||||
tbl_keys: function<K,V>({K:V}): {K}
|
||||
tbl_map: function(function, table): table
|
||||
|
||||
record InspectOptions
|
||||
depth: number
|
||||
newline: string
|
||||
indent: string
|
||||
process: function
|
||||
end
|
||||
record inspect
|
||||
METATABLE: any
|
||||
KEY: any
|
||||
metamethod __call: function(inspect, any, InspectOptions): string
|
||||
metamethod __call: function(inspect, any): string
|
||||
end
|
||||
|
||||
wait: function(number, function, number, boolean)
|
||||
|
||||
record ui
|
||||
input: function({string:any}, function(string))
|
||||
record SelectOpts<T>
|
||||
prompt: string
|
||||
format_item: function(T): string
|
||||
kind: string
|
||||
end
|
||||
select: function<T>({T}, SelectOpts<T>, on_choice: function(T, idx: integer))
|
||||
end
|
||||
|
||||
record VersionDetails
|
||||
api_compatible: integer
|
||||
api_level: integer
|
||||
api_prerelease: boolean
|
||||
major: integer
|
||||
minor: integer
|
||||
patch: integer
|
||||
end
|
||||
|
||||
version: function(): VersionDetails
|
||||
|
||||
record mpack
|
||||
encode: function(any): string
|
||||
decode: function(string): any
|
||||
end
|
||||
|
||||
is_thread: function(): boolean
|
||||
|
||||
record fs
|
||||
find: function(string|{string}, table): {string}
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,288 +0,0 @@
|
|||
local record M
|
||||
record UserCmdParams
|
||||
args: string
|
||||
bang: boolean
|
||||
line1: integer
|
||||
line2: integer
|
||||
range: integer
|
||||
count: number
|
||||
reg: string
|
||||
mods: string
|
||||
|
||||
record Mods
|
||||
browse : boolean
|
||||
confirm : boolean
|
||||
emsg_silent : boolean
|
||||
hide : boolean
|
||||
keepalt : boolean
|
||||
keepjumps : boolean
|
||||
keepmarks : boolean
|
||||
keeppatterns : boolean
|
||||
lockmarks : boolean
|
||||
noautocmd : boolean
|
||||
noswapfile : boolean
|
||||
sandbox : boolean
|
||||
silent : boolean
|
||||
tab : integer
|
||||
verbose : integer
|
||||
vertical : boolean
|
||||
|
||||
enum Split ''
|
||||
'aboveleft'
|
||||
'belowright'
|
||||
'topleft'
|
||||
'botright'
|
||||
end
|
||||
|
||||
split : Split
|
||||
end
|
||||
smods: Mods
|
||||
end
|
||||
|
||||
record UserCmdOpts
|
||||
nargs: string|integer
|
||||
range: boolean|string|integer
|
||||
count: boolean|integer
|
||||
addr: string
|
||||
bang: boolean
|
||||
bar: boolean
|
||||
register: boolean
|
||||
force: boolean
|
||||
complete: string|function(arglead: string, line: string): {string}
|
||||
end
|
||||
nvim_create_user_command : function(string, function(UserCmdParams), UserCmdOpts)
|
||||
nvim_buf_attach : function(integer, boolean, {string:any}): boolean
|
||||
nvim_buf_call : function<T>(integer, function(): T): T
|
||||
nvim_buf_clear_namespace : function(integer, number, number, number)
|
||||
nvim_buf_del_extmark : function(integer, number, number): boolean
|
||||
nvim_buf_delete : function(integer, {string:boolean})
|
||||
nvim_buf_get_extmark_by_id : function(integer, number, number, table): {number}
|
||||
|
||||
record GetExtmarOpts
|
||||
limit: integer
|
||||
details: boolean
|
||||
end
|
||||
|
||||
nvim_buf_get_extmarks: function(
|
||||
buf: integer,
|
||||
ns: integer,
|
||||
start: integer | {integer, integer},
|
||||
eend: integer | {integer, integer},
|
||||
GetExtmarOpts
|
||||
): {{integer, integer, integer}}
|
||||
|
||||
nvim_buf_get_lines : function(integer, number, number, boolean): {string}
|
||||
nvim_buf_get_name : function(integer): string
|
||||
nvim_buf_is_loaded : function(integer): boolean
|
||||
nvim_buf_is_valid : function(integer): boolean
|
||||
nvim_buf_line_count : function(integer): integer
|
||||
nvim_buf_set_extmark : function(integer, integer, integer, integer, {string:any}): integer
|
||||
nvim_buf_set_keymap : function(integer, string, string, string, {string:any})
|
||||
nvim_buf_set_lines : function(integer, number, number, boolean, {string})
|
||||
nvim_buf_set_name : function(integer, string)
|
||||
nvim_create_buf : function(boolean, boolean): integer
|
||||
nvim_create_namespace : function(string): integer
|
||||
|
||||
record AugroupOpts
|
||||
clear: boolean
|
||||
end
|
||||
|
||||
nvim__buf_redraw_range : function(integer, number, number)
|
||||
|
||||
nvim_create_augroup: function(string, AugroupOpts): integer
|
||||
|
||||
record AutoCmdOpts
|
||||
record CallbackData
|
||||
id: integer
|
||||
group: integer
|
||||
event: string
|
||||
match: string
|
||||
buf: integer
|
||||
file: string
|
||||
data: any
|
||||
end
|
||||
callback: function(CallbackData)
|
||||
command: string
|
||||
group: integer|string
|
||||
pattern: string|{string}
|
||||
once: boolean
|
||||
nested: boolean
|
||||
desc: string
|
||||
buffer: integer
|
||||
end
|
||||
|
||||
nvim_create_autocmd: function(string|{string}, AutoCmdOpts): integer
|
||||
nvim_del_current_line: function()
|
||||
nvim_del_keymap: function(string, string)
|
||||
nvim_del_var: function(string)
|
||||
nvim_echo: function({{string}}, boolean, {string:any})
|
||||
nvim_err_write: function(string)
|
||||
nvim_err_writeln: function(string)
|
||||
nvim_eval: function(string): any
|
||||
nvim_exec: function(string, boolean): string
|
||||
|
||||
record ExecAutoCmdOpts
|
||||
group: string|integer
|
||||
pattern: string|{string}
|
||||
buffer: integer
|
||||
modeline: boolean
|
||||
data: any
|
||||
end
|
||||
|
||||
nvim_exec_autocmds: function(string|{string}, ExecAutoCmdOpts): any
|
||||
nvim_exec_lua: function(string, any): any
|
||||
nvim_feedkeys: function(string, string, boolean)
|
||||
nvim_get_api_info: function(): any
|
||||
nvim_get_chan_info: function(number): {string:any}
|
||||
nvim_get_color_by_name: function(string): number
|
||||
nvim_get_color_map: function(): {string:any}
|
||||
nvim_get_commands: function({string:any}): {string:any}
|
||||
nvim_get_context: function({string:any}): {string:any}
|
||||
nvim_get_current_buf: function(): integer
|
||||
nvim_get_current_line: function(): string
|
||||
nvim_get_current_tabpage: function(): any
|
||||
nvim_get_current_win: function(): integer
|
||||
nvim_get_hl_by_id: function(number, boolean): {string:any}
|
||||
|
||||
record HlAttrs
|
||||
foreground: integer
|
||||
background: integer
|
||||
reverse: boolean
|
||||
end
|
||||
|
||||
nvim_get_hl_by_name: function(string, boolean): HlAttrs
|
||||
|
||||
nvim_get_hl_id_by_name: function(string): number
|
||||
nvim_get_keymap: function(string): {{string:any}}
|
||||
nvim_get_mode: function(): {string:any}
|
||||
nvim_get_namespaces: function(): {string:any}
|
||||
nvim_get_option: function(string): any
|
||||
nvim_get_proc: function(number): any
|
||||
nvim_get_proc_children: function(number): any
|
||||
nvim_get_runtime_file: function(string, boolean): {string}
|
||||
nvim_get_var: function(string): any
|
||||
nvim_get_vvar: function(string): any
|
||||
nvim_input: function(string): number
|
||||
nvim_input_mouse: function(string, string, string, number, number, number)
|
||||
nvim_list_bufs: function(): {integer}
|
||||
nvim_list_chans: function(): any
|
||||
nvim_list_runtime_paths: function(): {string}
|
||||
nvim_list_tabpages: function(): {any}
|
||||
nvim_list_uis: function(): any
|
||||
nvim_list_wins: function(): {integer}
|
||||
nvim_load_context: function({string:any}): any
|
||||
nvim_open_win: function(number, boolean, {string:any}): integer
|
||||
nvim_out_write: function(string)
|
||||
nvim_parse_expression: function(string, string, boolean): {string:any}
|
||||
nvim_paste: function(string, boolean, number): boolean
|
||||
nvim_put: function({string}, string, boolean, boolean)
|
||||
nvim_replace_termcodes: function(string, boolean, boolean, boolean): string
|
||||
nvim_select_popupmenu_item: function(number, boolean, boolean, {string:any})
|
||||
nvim_set_client_info: function(string, {string:any}, string, {string:any}, {string:any})
|
||||
nvim_set_current_buf: function(number)
|
||||
nvim_set_current_dir: function(string)
|
||||
nvim_set_current_line: function(string)
|
||||
nvim_set_current_tabpage: function(any)
|
||||
nvim_set_current_win: function(number)
|
||||
nvim_set_decoration_provider: function(number, {string:function})
|
||||
nvim_set_hl: function(integer, string, {string:any})
|
||||
nvim_set_keymap: function(string, string, string, {string:any})
|
||||
nvim_set_option: function(string, any)
|
||||
nvim_set_var: function(string, any)
|
||||
nvim_set_vvar: function(string, any)
|
||||
nvim_strwidth: function(string): number
|
||||
nvim_subscribe: function(string)
|
||||
nvim_tabpage_del_var: function(any, string)
|
||||
nvim_tabpage_get_number: function(any): number
|
||||
nvim_tabpage_get_var: function(any, string): any
|
||||
nvim_tabpage_get_win: function(any): number
|
||||
nvim_tabpage_is_valid: function(any): boolean
|
||||
nvim_tabpage_list_wins: function(any): {number}
|
||||
nvim_tabpage_set_var: function(any, string, any)
|
||||
nvim_ui_attach: function(number, number, {string:any})
|
||||
nvim_ui_detach: function()
|
||||
nvim_ui_pum_set_bounds: function(number, number, number, number)
|
||||
nvim_ui_pum_set_height: function(number)
|
||||
nvim_ui_set_option: function(string, any)
|
||||
nvim_ui_try_resize: function(number, number)
|
||||
nvim_ui_try_resize_grid: function(number, number, number)
|
||||
nvim_unsubscribe: function(string)
|
||||
nvim_win_call: function<T>(number, (function(): T)): T
|
||||
nvim_win_close: function(number, boolean)
|
||||
nvim_win_del_var: function(number, string)
|
||||
nvim_win_get_buf: function(integer): integer
|
||||
nvim_win_get_config: function(number): {string:any}
|
||||
nvim_win_get_cursor: function(number): {integer}
|
||||
nvim_win_get_height: function(integer): integer
|
||||
nvim_win_get_number: function(number): number
|
||||
nvim_win_get_option: function(number, string): any
|
||||
nvim_win_get_position: function(number): {number}
|
||||
nvim_win_get_tabpage: function(number): any
|
||||
nvim_win_get_var: function(number, string): any
|
||||
nvim_win_get_width: function(integer): integer
|
||||
nvim_win_is_valid: function(number): boolean
|
||||
nvim_win_set_buf: function(number, number)
|
||||
|
||||
record WinConfig
|
||||
bufpos: {integer, integer}
|
||||
virt_lines_above: boolean
|
||||
width: integer
|
||||
height: integer
|
||||
relative: string
|
||||
row: integer
|
||||
col: integer
|
||||
win: integer
|
||||
enum Anchor
|
||||
'NW'
|
||||
'NE'
|
||||
'SW'
|
||||
'SE'
|
||||
end
|
||||
anchor: Anchor
|
||||
end
|
||||
|
||||
nvim_win_set_config: function(number, WinConfig)
|
||||
|
||||
nvim_win_set_cursor: function(number, {number})
|
||||
nvim_win_set_height: function(number, number)
|
||||
nvim_win_set_option: function(number, string, any)
|
||||
nvim_win_set_var: function(number, string, any)
|
||||
nvim_win_set_width: function(number, number)
|
||||
|
||||
nvim__buf_redraw_range: function(number, number, number)
|
||||
nvim_create_autocmd : function(string|{string}, AutoCmdOpts): integer
|
||||
nvim_echo : function({{string}}, boolean, {string:any})
|
||||
nvim_get_color_by_name : function(string): number
|
||||
nvim_get_current_buf : function(): integer
|
||||
nvim_get_current_line : function(): string
|
||||
nvim_get_current_tabpage : function(): any
|
||||
nvim_get_current_win : function(): integer
|
||||
nvim_get_hl_by_name : function(string, boolean): {string:any}
|
||||
nvim_get_mode : function(): {string:any}
|
||||
nvim_list_bufs : function(): {integer}
|
||||
nvim_list_wins : function(): {integer}
|
||||
nvim_open_win : function(integer, boolean, {string:any}): integer
|
||||
nvim_replace_termcodes : function(string, boolean, boolean, boolean): string
|
||||
nvim_set_current_buf : function(integer)
|
||||
nvim_set_current_line : function(string)
|
||||
nvim_set_current_win : function(integer)
|
||||
nvim_set_decoration_provider : function(integer, {string:function})
|
||||
nvim_set_hl : function(integer, string, {string:any})
|
||||
nvim_set_keymap : function(string, string, string, {string:any})
|
||||
nvim_strwidth : function(string): number
|
||||
nvim_win_call : function<T>(integer, (function(): T)): T
|
||||
nvim_win_close : function(integer, boolean)
|
||||
nvim_win_get_buf : function(integer): integer
|
||||
nvim_win_get_config : function(integer): {string:any}
|
||||
nvim_win_get_cursor : function(integer): {integer}
|
||||
nvim_win_get_height : function(integer): integer
|
||||
nvim_win_get_width : function(integer): integer
|
||||
nvim_win_is_valid : function(integer): boolean
|
||||
nvim_win_set_buf : function(integer, number)
|
||||
nvim_win_set_config : function(integer, {string:any})
|
||||
nvim_win_set_cursor : function(integer, {number})
|
||||
nvim_win_set_height : function(integer, number)
|
||||
nvim_win_set_width : function(integer, number)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,87 +0,0 @@
|
|||
local record M
|
||||
bufexists: function(string): integer
|
||||
bufnr: function(string): integer
|
||||
iconv: function(string, string, string): string
|
||||
line: function(string): integer
|
||||
join: function({any}, string): string
|
||||
getpos: function(string): {integer}
|
||||
executable: function(string): integer
|
||||
exists: function(string): integer
|
||||
expand: function(string): string
|
||||
foldclosed: function(integer): integer
|
||||
foldclosedend: function(integer): integer
|
||||
getcwd: function(): string
|
||||
input: function(string, string): string
|
||||
|
||||
['repeat#set']: function(string, integer)
|
||||
|
||||
record QFItem
|
||||
bufnr: integer
|
||||
filename: string
|
||||
lnum: integer
|
||||
nr: integer
|
||||
text: string
|
||||
type: string
|
||||
end
|
||||
|
||||
record QFWhat
|
||||
context: any
|
||||
efm: string
|
||||
id: integer
|
||||
idx: integer
|
||||
items: {QFItem}
|
||||
lines: {string}
|
||||
nr: integer
|
||||
quickfixtextfunc: string
|
||||
title: string
|
||||
end
|
||||
|
||||
setqflist: function(list: {QFItem}, action: string, what: QFWhat)
|
||||
setloclist: function(nr: integer, list: {QFItem}, action: string, what: QFWhat)
|
||||
|
||||
sign_unplace: function(string, {string:any})
|
||||
sign_place: function(number, string, string, string | number, {string:any})
|
||||
|
||||
record SignPlaceItem
|
||||
buffer: integer
|
||||
group: string
|
||||
id: integer
|
||||
lnum: integer
|
||||
name: string
|
||||
priority: integer
|
||||
end
|
||||
sign_placelist: function({SignPlaceItem})
|
||||
sign_getdefined: function(string): table
|
||||
|
||||
record SignPlacedInfo
|
||||
bufnr: integer
|
||||
record SignPlacedSigns
|
||||
id: integer
|
||||
name: string
|
||||
group: string
|
||||
lnum: integer
|
||||
priority: integer
|
||||
end
|
||||
signs: {SignPlacedSigns}
|
||||
end
|
||||
sign_getplaced: function(integer, table): {SignPlacedInfo}
|
||||
|
||||
sign_define: function(string, table): number
|
||||
sign_undefine: function(string): number
|
||||
strdisplaywidth: function(string, integer): integer
|
||||
stridx: function(haystack: string, needle: string, start: integer): integer
|
||||
string: function(any): string
|
||||
systemlist: function({string}): {string}
|
||||
tempname: function(): string
|
||||
type: function(any): integer
|
||||
|
||||
FugitiveReal: function(...: any): string
|
||||
FugitiveParse: function(...: any): {string, string}
|
||||
|
||||
record WinInfo
|
||||
textoff: integer
|
||||
end
|
||||
getwininfo: function(integer): {WinInfo}
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,109 +0,0 @@
|
|||
local record M
|
||||
cwd: function(): string
|
||||
record Timer
|
||||
userdata
|
||||
|
||||
start: function(Timer, number, number, function): number
|
||||
stop: function(Timer): number
|
||||
close: function(Timer): number
|
||||
is_closing: function(Timer): boolean
|
||||
again: function(Timer): number
|
||||
set_repeat: function(Timer, number): number
|
||||
get_repeat: function(Timer): number
|
||||
get_due_in: function(Timer): number
|
||||
end
|
||||
hrtime: function(): number
|
||||
new_timer: function(): Timer
|
||||
timer_start: function(Timer, integer, integer, function()): integer | string
|
||||
|
||||
new_fs_event: function()
|
||||
|
||||
record FSPollObj
|
||||
userdata
|
||||
is_closing: function(FSPollObj): boolean | string
|
||||
close: function(FSPollObj)
|
||||
start: function(FSPollObj, string, integer, function)
|
||||
stop: function(FSPollObj)
|
||||
getpath: function(FSPollObj): string
|
||||
end
|
||||
new_fs_poll: function(): FSPollObj
|
||||
|
||||
record FsStatRet
|
||||
dev : number
|
||||
mode : number
|
||||
nlink : number
|
||||
uid : number
|
||||
gid : number
|
||||
rdev : number
|
||||
ino : number
|
||||
size : number
|
||||
blksize : number
|
||||
blocks : number
|
||||
flags : number
|
||||
gen : number
|
||||
record Time
|
||||
sec : number
|
||||
nsec : number
|
||||
end
|
||||
atime: Time
|
||||
mtime: Time
|
||||
ctime: Time
|
||||
birthtime: Time
|
||||
type : string
|
||||
end
|
||||
|
||||
fs_stat: function(string, function): FsStatRet
|
||||
|
||||
fs_realpath: function(string): string
|
||||
|
||||
new_tcp: function()
|
||||
|
||||
sleep: function(integer)
|
||||
|
||||
record Handle
|
||||
userdata
|
||||
|
||||
close: function(Handle)
|
||||
is_closing: function(Handle): boolean | string
|
||||
end
|
||||
|
||||
record Pipe
|
||||
userdata
|
||||
|
||||
close: function(Pipe)
|
||||
is_closing: function(Pipe): boolean | string
|
||||
read_start: function(Pipe, err: any, data: string)
|
||||
read_stop: function(Pipe)
|
||||
write: function(Pipe, string, function())
|
||||
|
||||
open: function(any)
|
||||
end
|
||||
|
||||
record Process
|
||||
userdata
|
||||
|
||||
close: function(Process)
|
||||
end
|
||||
|
||||
record SpawnOpts
|
||||
stdio: {Pipe, Pipe, Pipe}
|
||||
args: {string}
|
||||
cwd: string
|
||||
env: {string}
|
||||
end
|
||||
|
||||
spawn: function(string, SpawnOpts, function(integer, integer)): Process, integer
|
||||
|
||||
read_start: function(Pipe, function)
|
||||
new_pipe: function(boolean): Pipe
|
||||
shutdown: function(any, function)
|
||||
close: function(any, function)
|
||||
|
||||
record WorkCtx
|
||||
queue: function(WorkCtx, ...:any)
|
||||
end
|
||||
|
||||
new_work: function(function, function): WorkCtx
|
||||
end
|
||||
|
||||
return M
|
Loading…
Reference in a new issue