Browse Source

vim: lua config

master
Maxim Likhachev 2 years ago
parent
commit
fd091d1fc2
  1. 2
      etc/soft/nvim/+ftplugin/haskell.vim
  2. 1
      etc/soft/nvim/+ftplugin/help.vim
  3. 1
      etc/soft/nvim/+ftplugin/man.vim
  4. 6
      etc/soft/nvim/+ftplugin/markdown.vim
  5. 57
      etc/soft/nvim/+layouts/coptic.vim
  6. 55
      etc/soft/nvim/+layouts/greek.vim
  7. 70
      etc/soft/nvim/+layouts/russian.vim
  8. 32
      etc/soft/nvim/+plugins/cmd-parser.nvim/README.md
  9. 88
      etc/soft/nvim/+plugins/cmd-parser.nvim/lua/cmd-parser/init.lua
  10. 199
      etc/soft/nvim/+plugins/cmd-parser.nvim/lua/cmd-parser/init.test.lua
  11. 178
      etc/soft/nvim/+plugins/cmdline_increment/plugin/cmdline-increment.vim
  12. 60
      etc/soft/nvim/+plugins/dial.nvim/HISTORY.md
  13. 21
      etc/soft/nvim/+plugins/dial.nvim/LICENSE
  14. 357
      etc/soft/nvim/+plugins/dial.nvim/README.md
  15. 354
      etc/soft/nvim/+plugins/dial.nvim/README_ja.md
  16. 61
      etc/soft/nvim/+plugins/dial.nvim/TROUBLESHOOTING.md
  17. 59
      etc/soft/nvim/+plugins/dial.nvim/TROUBLESHOOTING_ja.md
  18. 23
      etc/soft/nvim/+plugins/dial.nvim/autoload/dial/operator.vim
  19. 1
      etc/soft/nvim/+plugins/dial.nvim/doc/.gitignore
  20. 1417
      etc/soft/nvim/+plugins/dial.nvim/doc/dial.jax
  21. 1477
      etc/soft/nvim/+plugins/dial.nvim/doc/dial.txt
  22. 21
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend.lua
  23. 266
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/case.lua
  24. 76
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/common.lua
  25. 226
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/constant.lua
  26. 728
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/date.lua
  27. 94
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/hexcolor.lua
  28. 180
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/integer.lua
  29. 40
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/misc.lua
  30. 243
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/paren.lua
  31. 107
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/semver.lua
  32. 37
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/user.lua
  33. 224
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/command.lua
  34. 58
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/config.lua
  35. 252
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/handle.lua
  36. 67
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/map.lua
  37. 18
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/types.lua
  38. 189
      etc/soft/nvim/+plugins/dial.nvim/lua/dial/util.lua
  39. 38
      etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/case_spec.lua
  40. 5
      etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/constant_spec.lua
  41. 297
      etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/date_spec.lua
  42. 201
      etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/integer_spec.lua
  43. 29
      etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/misc_spec.lua
  44. 181
      etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/paren_spec.lua
  45. 21
      etc/soft/nvim/+plugins/dial.nvim/plugin/dial.vim
  46. 21
      etc/soft/nvim/+plugins/dressing.nvim/LICENSE
  47. 356
      etc/soft/nvim/+plugins/dressing.nvim/README.md
  48. 12
      etc/soft/nvim/+plugins/dressing.nvim/autoload/dressing.vim
  49. 208
      etc/soft/nvim/+plugins/dressing.nvim/doc/dressing.txt
  50. 213
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/config.lua
  51. 28
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/init.lua
  52. 364
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/input.lua
  53. 38
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/map_util.lua
  54. 42
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/patch.lua
  55. 110
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/builtin.lua
  56. 41
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/fzf.lua
  57. 40
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/fzf_lua.lua
  58. 74
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/init.lua
  59. 63
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/nui.lua
  60. 138
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/telescope.lua
  61. 177
      etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/util.lua
  62. 2
      etc/soft/nvim/+plugins/dressing.nvim/plugin/dressing.lua
  63. 22
      etc/soft/nvim/+plugins/dressing.nvim/run_tests.sh
  64. 181
      etc/soft/nvim/+plugins/dressing.nvim/tests/input_spec.lua
  65. 67
      etc/soft/nvim/+plugins/dressing.nvim/tests/manual/completion.lua
  66. 30
      etc/soft/nvim/+plugins/dressing.nvim/tests/manual/highlight.lua
  67. 21
      etc/soft/nvim/+plugins/dressing.nvim/tests/manual/select.lua
  68. 2
      etc/soft/nvim/+plugins/dressing.nvim/tests/minimal_init.lua
  69. 19
      etc/soft/nvim/+plugins/dressing.nvim/tests/util.lua
  70. 114
      etc/soft/nvim/+plugins/git-conflict.nvim/README.md
  71. 20
      etc/soft/nvim/+plugins/git-conflict.nvim/create_conflict.sh
  72. 734
      etc/soft/nvim/+plugins/git-conflict.nvim/lua/git-conflict.lua
  73. 35
      etc/soft/nvim/+plugins/git-conflict.nvim/lua/git-conflict/colors.lua
  74. 76
      etc/soft/nvim/+plugins/git-conflict.nvim/lua/git-conflict/utils.lua
  75. 6
      etc/soft/nvim/+plugins/git-conflict.nvim/stylua.toml
  76. 85
      etc/soft/nvim/+plugins/gitsigns.nvim/CONTRIBUTING.md
  77. 21
      etc/soft/nvim/+plugins/gitsigns.nvim/LICENSE
  78. 85
      etc/soft/nvim/+plugins/gitsigns.nvim/Makefile
  79. 334
      etc/soft/nvim/+plugins/gitsigns.nvim/README.md
  80. 1023
      etc/soft/nvim/+plugins/gitsigns.nvim/doc/gitsigns.txt
  81. 165
      etc/soft/nvim/+plugins/gitsigns.nvim/etc/doc_template.txt
  82. 327
      etc/soft/nvim/+plugins/gitsigns.nvim/gen_help.lua
  83. 37
      etc/soft/nvim/+plugins/gitsigns.nvim/gitsigns.nvim-scm-1.rockspec
  84. 3
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/README.md
  85. 546
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns.lua
  86. 1269
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/actions.lua
  87. 91
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/argparse.lua
  88. 87
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/async.lua
  89. 85
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/cache.lua
  90. 834
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/config.lua
  91. 213
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/current_line_blame.lua
  92. 84
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/debounce.lua
  93. 150
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/debug.lua
  94. 18
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff.lua
  95. 80
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff_ext.lua
  96. 153
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff_int.lua
  97. 145
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff_int/xdl_diff_ffi.lua
  98. 201
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diffthis.lua
  99. 620
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/git.lua
  100. 17
      etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/health.lua
  101. Some files were not shown because too many files have changed in this diff Show More

2
etc/soft/nvim/+ftplugin/haskell.vim

@ -6,8 +6,6 @@ set shiftwidth=8 @@ -6,8 +6,6 @@ set shiftwidth=8
set formatprg=stylish-haskell
" runtime! syntax/custom/HaskellConceal.vim
" =============================================================================
" Descriptions: Provide a function providing folding information for haskell
" files.

1
etc/soft/nvim/+ftplugin/help.vim

@ -0,0 +1 @@ @@ -0,0 +1 @@
autocmd BufWinEnter <buffer> wincmd L

1
etc/soft/nvim/+ftplugin/man.vim

@ -0,0 +1 @@ @@ -0,0 +1 @@
autocmd BufWinEnter <buffer> wincmd L

6
etc/soft/nvim/+ftplugin/markdown.vim

@ -1,2 +1,4 @@ @@ -1,2 +1,4 @@
" setlocal syntax=off
" set re=1
setlocal spell
setlocal spelllang=ru,en
set filetype=markdown.pandoc

57
etc/soft/nvim/+layouts/coptic.vim

@ -1,57 +0,0 @@ @@ -1,57 +0,0 @@
" Ввод команд в коптской раскладке
map q
map w
map e
map r
map t
map y
map u
map i
map o
map p
map a
map s
map d
map f
map g
map ⲅⲅ gg
map h
map ϫ j
map k
map l
map z
map x
map c
map ϣ v
map b
map n
map m
map ~ ~
map Q
map W
map E
map R
map T
map Y
map U
map I
map O
map P
map A
map S
map D
map F
map G
map H
map Ϫ J
map K
map L
map Z
map X
map C
map Ϣ V
map B
map N
map M

55
etc/soft/nvim/+layouts/greek.vim

@ -1,55 +0,0 @@ @@ -1,55 +0,0 @@
" Ввод команд в древнегреческой раскладке
map ς w
map ε e
map ρ r
map τ t
map υ y
map θ u
map ι i
map ο o
map π p
map α a
map σ s
map δ d
map φ f
map γ g
map γγ gg
map η h
map ξ j
map κ k
map λ l
map ζ z
map χ x
map ψ c
map ω v
map β b
map ν n
map μ m
map ~ ~
map Σ W
map Ε E
map Ρ R
map Τ T
map Υ Y
map Θ U
map Ι I
map Ο O
map Π P
map Α A
map Σ S
map Δ D
map Φ F
map Γ G
map Η H
map Ξ J
map Κ K
map Λ L
map Ζ Z
map Χ X
map Ψ C
map Ω V
map Β B
map Ν N
map Μ M

70
etc/soft/nvim/+layouts/russian.vim

@ -1,70 +0,0 @@ @@ -1,70 +0,0 @@
" Ввод команд в русской раскладке
map ё `
map й q
map ц w
map у e
map к r
map е t
map н y
map г u
map ш i
map щ o
map з p
map х [
map ъ ]
map ф a
map ы s
map в d
map а f
map п g
map р h
map о j
map л k
map д l
map ж ;
map э '
map я z
map ч x
map с c
map м v
map и b
map т n
map ь m
map б ,
map ю .
map Ё ~
map Й Q
map Ц W
map У E
map К R
map Е T
map Н Y
map Г U
map Ш I
map Щ O
map З P
map Х {
map Ъ }
map Ф A
map Ы S
map В D
map А F
map П G
map Р H
map О J
map Л K
map Д L
map Ж :
map Э "
map Я Z
map Ч X
map С C
map М V
map И B
map Т N
map Ь M
map Б <
map Ю >
map , /

32
etc/soft/nvim/+plugins/cmd-parser.nvim/README.md

@ -1,32 +0,0 @@ @@ -1,32 +0,0 @@
# cmd-parser.nvim
I built this plugin to help other plugin authors to easily parse the command inputted by users and do awesome tricks with it.
Input
```lua
local parse_cmd = require'cmd-parser'.parse_cmd
parse_cmd("10+2++,/hello/-3d")
```
Output
```lua
{ ["start_increment_number"] = 4,["end_increment"] = -3,["command"] = d,["start_range"] = 10,["end_increment_number"] = -3,["start_increment"] = +2++,["end_range"] = /hello/,}
```
## Installtion
### `Paq.nvim`
```lua
paq{'winston0410/cmd-parser.nvim'}
```
## Testing
This plugin is well tested. To run the test case or help with testing, you need to install lester
```shell
luarocks install lester
```

88
etc/soft/nvim/+plugins/cmd-parser.nvim/lua/cmd-parser/init.lua

@ -1,88 +0,0 @@ @@ -1,88 +0,0 @@
local number_range = "^(%d+)"
local mark_range = "^('[%l><])"
local forward_search_range = "^(/.*/)"
local backward_search_range = "^(?.*?)"
local special_range = "^([%%%.$])"
local command_pattern = "^(%l+)"
local range_patterns = {
special_range, number_range, mark_range, forward_search_range,
backward_search_range
}
local range_patterns_type = {
"special", "number", "mark", "forward_search", "backward_search"
}
local function get_range(index, cmd)
local range, type
for i = 1, #range_patterns do
local _, end_index, result = string.find(cmd, range_patterns[i], index)
if end_index then
index = end_index + 1
range = result
type = range_patterns_type[i]
break
end
end
if type == "special" then type = range end
return range, type, index
end
local function update_increment(operator, increment, acc_text, acc_num)
local inc_str = acc_text .. operator .. increment
if increment == "" then increment = 1 end
return inc_str, acc_num + tonumber(operator .. increment)
end
local function get_increment(index, cmd)
local pattern, inc_text, total, done = "([+-])(%d*)", "", 0, false
while not done do
local _, end_index, operator, increment =
string.find(cmd, pattern, index)
if not end_index then
done = true
break
end
inc_text, total = update_increment(operator, increment, inc_text, total)
index = end_index + 1
end
return inc_text, total, index
end
local function parse_cmd(cmd)
local result, next_index, comma_index, _ = {}, 1, nil, nil
local start_range_text
result.start_range, result.start_range_type, next_index = get_range(1, cmd)
comma_index, _, result.separator = string.find(cmd, '[(;,)]', next_index)
if comma_index then
if not result.start_range then
result.start_range = "."
result.start_range_type = "."
end
start_range_text = string.sub(cmd, 1, comma_index)
else
start_range_text = cmd
end
result.start_increment, result.start_increment_number, next_index =
get_increment(next_index, start_range_text)
if comma_index then
-- To offset the comma_index
next_index = next_index + 1
result.end_range, result.end_range_type, next_index =
get_range(next_index, cmd)
result.end_increment, result.end_increment_number, next_index =
get_increment(next_index, cmd)
end
_, _, result.command = string.find(cmd, command_pattern, next_index)
return result
end
local function setup() end
return {setup = setup, parse_cmd = parse_cmd}

199
etc/soft/nvim/+plugins/cmd-parser.nvim/lua/cmd-parser/init.test.lua

@ -1,199 +0,0 @@ @@ -1,199 +0,0 @@
local lester = require 'lester'
local describe, it, expect = lester.describe, lester.it, lester.expect
local parse_cmd = require'init'.parse_cmd
-- customize lester configuration.
lester.show_traceback = false
describe('when parse_cmd is called', function()
local result = parse_cmd("")
it('should return a table',
function() expect.equal(type(result), "table") end)
end)
describe('when it is called without range', function()
local result = parse_cmd("d")
it('should return nil start range',
function() expect.equal(result.start_range, nil) end)
it('should return nil start range type',
function() expect.equal(result.start_range_type, nil) end)
end)
describe('when it is called with shorthand range', function ()
local result = parse_cmd(",20d")
it('should return start range as .', function ()
expect.equal(result.start_range, ".")
end)
it('should return start range type', function ()
expect.equal(result.start_range_type, ".")
end)
end)
describe('when it is called with single number range', function()
local result = parse_cmd("23d")
it('should return the start range',
function() expect.equal(result.start_range, "23") end)
it('should return the start range type',
function() expect.equal(result.start_range_type, "number") end)
it('should return the command',
function() expect.equal(result.command, "d") end)
end)
--
describe('when it is called with single number range', function()
describe('when it has increment', function()
local result = parse_cmd("23+5d")
it('should return the command',
function() expect.equal(result.command, "d") end)
it('should return the start increment',
function() expect.equal(result.start_increment, "+5") end)
it('should return the start increment number',
function() expect.equal(result.start_increment_number, 5) end)
end)
end)
describe('when it is called with a complete number range', function()
local result = parse_cmd("10,20d")
it('should return the start range',
function() expect.equal(result.start_range, "10") end)
it('should return the end range',
function() expect.equal(result.end_range, "20") end)
it('should return the end range type',
function() expect.equal(result.end_range_type, "number") end)
it('should return the command',
function() expect.equal(result.command, "d") end)
end)
describe('when it is called with a complete number range', function()
local result = parse_cmd("10+3+++-,20d-8+")
it('should return the start increment',
function() expect.equal(result.start_increment, "+3+++-") end)
it('should return the start increment number',
function() expect.equal(result.start_increment_number, 5) end)
it('should return the end increment',
function() expect.equal(result.end_increment, "-8+") end)
it('should return the end increment number',
function() expect.equal(result.end_increment_number, -7) end)
end)
describe('when it is called with single mark range', function()
local result = parse_cmd("'ad")
it('should return the start range',
function() expect.equal(result.start_range, "'a") end)
it('should return the start range type',
function() expect.equal(result.start_range_type, "mark") end)
it('should return the command',
function() expect.equal(result.command, "d") end)
end)
describe('when it is called with single mark range', function()
describe('when it has increments', function()
local result = parse_cmd("'a+2-5+3d")
it('should return the start range increment',
function() expect.equal(result.start_increment, "+2-5+3") end)
it('should return the start range increment number',
function() expect.equal(result.start_increment_number, 0) end)
end)
end)
describe('when it is called with a complete mark range', function()
local result = parse_cmd("'a,'bt")
it('should return the start range',
function() expect.equal(result.start_range, "'a") end)
it('should return the end range',
function() expect.equal(result.end_range, "'b") end)
it('should return the end range type',
function() expect.equal(result.end_range_type, "mark") end)
it('should return the command',
function() expect.equal(result.command, "t") end)
end)
describe('when it is called with a complete mark range', function()
describe('when it has increment', function()
local result = parse_cmd("'a+++-5,'b+20-t")
it('should return the start range increment',
function() expect.equal(result.start_increment, "+++-5") end)
it('should return the start range increment number',
function() expect.equal(result.start_increment_number, -2) end)
it('should return the end range increment',
function() expect.equal(result.end_increment, "+20-") end)
it('should return the end range increment number',
function() expect.equal(result.end_increment_number, 19) end)
end)
end)
describe('when it is called with single forward search range', function()
local result = parse_cmd("/hello/d")
it('should return the start range',
function() expect.equal(result.start_range, "/hello/") end)
it('should return the start range type',
function() expect.equal(result.start_range_type, "forward_search") end)
it('should return the command',
function() expect.equal(result.command, "d") end)
end)
describe('when it is called with single forward search range', function()
describe('when it has increments', function()
local result = parse_cmd("/hello/+10-2d")
it('should return the start increment',
function() expect.equal(result.start_increment, "+10-2") end)
it('should return the start increment number',
function() expect.equal(result.start_increment_number, 8) end)
end)
end)
describe('when it is called with single backward search range', function()
local result = parse_cmd("?hello?pu")
it('should return the start range',
function() expect.equal(result.start_range, "?hello?") end)
it('should return the start range type',
function() expect.equal(result.start_range_type, "backward_search") end)
it('should return the command',
function() expect.equal(result.command, "pu") end)
end)
describe('when it is called with single backward search range', function()
describe('when it has increments', function()
local result = parse_cmd("?hello?+10-3pu")
it('should return the start increment',
function() expect.equal(result.start_increment, "+10-3") end)
it('should return the start increment number',
function() expect.equal(result.start_increment_number, 7) end)
end)
end)
describe('when it is called with single special range', function()
local result = parse_cmd("%y")
it('should return the start range',
function() expect.equal(result.start_range, "%") end)
it('should return the start range type',
function() expect.equal(result.start_range_type, "%") end)
it('should return the command',
function() expect.equal(result.command, "y") end)
end)
describe('when it is called with single special range', function()
describe('when it has increment', function()
local result = parse_cmd("%y+20---")
it('should return the start increment',
function() expect.equal(result.start_increment, "+20---") end)
it('should return the command',
function() expect.equal(result.start_increment_number, 17) end)
end)
end)
-- Bug test: mark range '<,'> cannot be detected
describe('when it is called with complete mark range', function ()
local result = parse_cmd("'<,'>")
it('should return the start range type', function ()
expect.equal(result.start_range_type, "mark")
end)
it('should return the start range', function()
expect.equal(result.start_range, "'<")
end)
it('should return the end range', function()
expect.equal(result.end_range, "'>")
end)
end)
lester.report() -- Print overall statistic of the tests run.
lester.exit() -- Exit with success if all tests passed.

178
etc/soft/nvim/+plugins/cmdline_increment/plugin/cmdline-increment.vim

@ -1,178 +0,0 @@ @@ -1,178 +0,0 @@
scriptencoding utf-8
if &cp || exists('g:loaded_cmdline_increment')
finish
endif
let g:loaded_cmdline_increment = 1
" escape user configuration
let s:save_cpo = &cpo
set cpo&vim
" mapping
if !hasmapto('<Plug>IncrementCommandLineNumber', 'c')
cmap <c-a> <Plug>IncrementCommandLineNumber
endif
cnoremap <Plug>IncrementCommandLineNumber <c-b>"<cr>:call g:IncrementCommandLineNumbering(1)<cr>:<c-r>=g:IncrementedCommandLine()<cr>
if !hasmapto('<Plug>DecrementCommandLineNumber', 'c')
cmap <c-x> <Plug>DecrementCommandLineNumber
endif
cnoremap <Plug>DecrementCommandLineNumber <c-b>"<cr>:call g:IncrementCommandLineNumbering(-1)<cr>:<c-r>=g:IncrementedCommandLine()<cr>
" script sharing variables.
" updated command line will be stored in g:IncrementCommandLineNumbering().
let s:updatedcommandline = ''
" increment, or decrement last appearing number.
function! g:IncrementCommandLineNumbering(plus)
" when continuous increment is done, because @: is not updated,
" plugin can not increment correctly.
" so add one entry to command history, and use there flag.
" last command is start with '"' is first try,
" last command is not start with '"' is second try.
let l:lastcommand = histget(':', -1)
let l:firstcommandchar = strpart(l:lastcommand, 0, 1)
" l:command is '"' starting text.
if l:firstcommandchar ==# '"'
let l:command = l:lastcommand
else
let l:command = '"' . l:lastcommand
endif
" check input
let l:matchtest = match(l:command, '^".\{-\}-\?\d\+\D*$')
" command do not contain number
if l:matchtest < 0
" remove first char '"'
let s:updatedcommandline = substitute(l:command, '^"\(.*\)$', '\1', '')
return
endif
" update numbering
let l:numpattern = substitute(l:command, '^".\{-\}\(\d\+\)\D*$', '\1', '')
let l:updatednumberpattern = s:IncrementedText(l:numpattern, a:plus)
" create new command line strings
let l:p1 = substitute(l:command, '^"\(.\{-\}\)\d\+\D*$', '\1', '')
let l:p2 = l:updatednumberpattern
let l:p3 = substitute(l:command, '^".\{-\}\d\+\(\D*\)$', '\1', '')
" set l register
let s:updatedcommandline = l:p1 . l:p2 . l:p3
" delete '"' command (dummy command) history
call histdel(':', -1)
" wrong command history is added. limitation.
call histadd(':', s:updatedcommandline)
endfunction
" return incremented command line text.
function! g:IncrementedCommandLine()
return s:updatedcommandline
endfunction
" return incremented pattern text.
"
" number + -
" 0 -> 1, 0
" 0000 -> 0001, 0000
" 127 -> 128, 126
" 0127 -> 0128, 0126
" 00127 -> 00128, 00126
function! s:IncrementedText(pattern, plus)
" 0
if match(a:pattern, '^0$') >= 0
if a:plus > 0
return a:pattern + a:plus
else
" not supported
return a:pattern
endif
endif
" 123
if match(a:pattern, '^[^0]\d*$') >= 0
return a:pattern + a:plus
endif
" 00000
if match(a:pattern, '^0\+$') >= 0
if a:plus > 0
let l:numlength = strlen(a:pattern)
return printf('%0' .l:numlength. 'd', a:plus)
else
" not supported
return a:pattern
endif
endif
" 00123
if match(a:pattern, '^0\d*$') >= 0
echo a:pattern + a:plus
let l:numlength = strlen(a:pattern)
let l:number = substitute(a:pattern, '^0\+\(\d\+\)$', '\1', '')
return printf('%0' .l:numlength. 'd', l:number + a:plus)
endif
throw 'unknow numbering pattern is found.'
endfunction
" recover user configuration
let &cpo = s:save_cpo
finish
==============================================================================
cmdline-increment.vim : increment, decrement for commandline number.
------------------------------------------------------------------------------
$VIMRUNTIMEPATH/plugin/cmdline-increment.vim
==============================================================================
author : OMI TAKU
url : http://nanasi.jp/
email : mail@nanasi.jp
version : 2009/09/19 03:00:00
==============================================================================
Increment last appearing number in commandline-mode command with Control-a ,
and decrement with Control-x .
<C-a> increment commandline last appearing number.
<C-x> decrement commandline last appearing number.
------------------------------------------------------------------------------
[Usage]
1. Enter commandline mode.
2. Enter next command.
:edit workfile_1.txt
3. And Press Control-a , or press Control-x .
------------------------------------------------------------------------------
[Customized Mapping]
If you will customize increment, decrement mapping,
add put these code to your vimrc .
" (for example)
" increment with Shift-Up
cmap <S-Up> <Plug>IncrementCommandLineNumber
" decrement with Shift-Down
cmap <S-Down> <Plug>DecrementCommandLineNumber
------------------------------------------------------------------------------
[history]
2009/09/17
- initial version.
2009/09/18
- plugin is now not using 'l' register.
- below 0 number decrement is newly not supported.
minial value is 0.
- default mapping is switched to <c-a>, <c-x>.
- custom mapping is supported.
==============================================================================
" vim: set ff=unix et ft=vim nowrap :

60
etc/soft/nvim/+plugins/dial.nvim/HISTORY.md

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
# Changelog
## Unreleased
### New Features
* add options to augend 'date'
* `custom_date_elements`
* `clamp`
* `end_sensitive`
## 0.4.0
### New Features
* add augend: paren ([#15](https://github.com/monaqa/dial.nvim/pull/15))
* add augend: case ([#26](https://github.com/monaqa/dial.nvim/pull/26), [#33](https://github.com/monaqa/dial.nvim/pull/33))
* support comma-separated number or other ([#16](https://github.com/monaqa/dial.nvim/pull/16))
* re-implement augend markdown_header ([#21](https://github.com/monaqa/dial.nvim/pull/21))
* add alias: German date formats and weekdays ([#24](https://github.com/monaqa/dial.nvim/pull/24), by @f1rstlady)
* add public config API for 'date' augend ([#35](https://github.com/monaqa/dial.nvim/pull/35)):
* pattern
* default_kind
* only_valid
* word
### Fixes
* Fix document ([#22](https://github.com/monaqa/dial.nvim/pull/22), by @ktakayama)
### Deprecates
* `augend.date.alias["%Y/%m/%d"]`
* `augend.date.alias["%m/%d/%Y"]`
* `augend.date.alias["%d/%m/%Y"]`
* `augend.date.alias["%m/%d/%y"]`
* `augend.date.alias["%d/%m/%y"]`
* `augend.date.alias["%m/%d"]`
* `augend.date.alias["%-m/%-d"]`
* `augend.date.alias["%Y-%m-%d"]`
* `augend.date.alias["%Y年%-m月%-d日"]`
* `augend.date.alias["%Y年%-m月%-d日(%ja)"]`
* `augend.date.alias["%H:%M:%S"]`
* `augend.date.alias["%H:%M"]`
## 0.3.0
* **[BREAKING CHANGE]** change overall interface
* support dot repeating
* support specifying augends with expression register
## 0.2.0
* **[BREAKING CHANGE]** rename all augends
* **[BREAKING CHANGE]** change the directory structure
* add help file
## 0.1.0
* first release

21
etc/soft/nvim/+plugins/dial.nvim/LICENSE

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Mogami Shinichi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

357
etc/soft/nvim/+plugins/dial.nvim/README.md

@ -0,0 +1,357 @@ @@ -0,0 +1,357 @@
# dial.nvim
**NOTICE: This plugin is work-in-progress yet. User interface is subject to change without notice.**
## FOR USERS OF THE PREVIOUS VERSION (v0.2.0)
This plugin was released v0.3.0 on 2022/02/20 and is no longer compatible with the old interface.
If you have configured the settings for previous versions, please refer to [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) and reconfigure them.
## Abstract
Extended increment/decrement plugin for [Neovim](https://github.com/neovim/neovim). Written in Lua.
![demo.gif](https://github.com/monaqa/dial.nvim/wiki/fig/dial-demo-2.gif)
## Features
* Increment/decrement based on various type of rules
* n-ary (`2 <= n <= 36`) integers
* date and time
* constants (an ordered set of specific strings, such as a keyword or operator)
* `true``false`
* `&&``||`
* `a``b` ⇄ ... ⇄ `z`
* hex colors
* semantic version
* Support `<C-a>` / `<C-x>` / `g<C-a>` / `g<C-x>` in VISUAL mode
* Flexible configuration of increment/decrement targets
* Rules that are valid only in specific FileType
* Rules that are valid only in VISUAL mode
* Support counter
* Support dot repeat (without overriding the behavior of `.`)
## Similar plugins
* [tpope/vim-speeddating](https://github.com/tpope/vim-speeddating)
* [Cycle.vim](https://github.com/zef/vim-cycle)
* [AndrewRadev/switch.vim](https://github.com/AndrewRadev/switch.vim)
## Installation
`dial.nvim` requires Neovim `>=0.5.0` (`>=0.6.1` is recommended).
You can install `dial.nvim` by following the instructions of your favorite package manager.
## Usage
This plugin does not provide or override any default key-mappings.
To use this plugin, you need to assign the plugin key-mapping to the key you like, as shown below:
```vim
nmap <C-a> <Plug>(dial-increment)
nmap <C-x> <Plug>(dial-decrement)
vmap <C-a> <Plug>(dial-increment)
vmap <C-x> <Plug>(dial-decrement)
vmap g<C-a> g<Plug>(dial-increment)
vmap g<C-x> g<Plug>(dial-decrement)
```
Or you can configure it with Lua as follows:
```lua
vim.api.nvim_set_keymap("n", "<C-a>", require("dial.map").inc_normal(), {noremap = true})
vim.api.nvim_set_keymap("n", "<C-x>", require("dial.map").dec_normal(), {noremap = true})
vim.api.nvim_set_keymap("v", "<C-a>", require("dial.map").inc_visual(), {noremap = true})
vim.api.nvim_set_keymap("v", "<C-x>", require("dial.map").dec_visual(), {noremap = true})
vim.api.nvim_set_keymap("v", "g<C-a>", require("dial.map").inc_gvisual(), {noremap = true})
vim.api.nvim_set_keymap("v", "g<C-x>", require("dial.map").dec_gvisual(), {noremap = true})
```
## Configuration
In this plugin, flexible increment/decrement rules can be set by using **augend** and **group**,
where **augend** represents the target of the increment/decrement operation,
and **group** represents a group of multiple augends.
```lua
local augend = require("dial.augend")
require("dial.config").augends:register_group{
-- default augends used when no group name is specified
default = {
augend.integer.alias.decimal, -- nonnegative decimal number (0, 1, 2, 3, ...)
augend.integer.alias.hex, -- nonnegative hex number (0x01, 0x1a1f, etc.)
augend.date.alias["%Y/%m/%d"], -- date (2022/02/19, etc.)
},
-- augends used when group with name `mygroup` is specified
mygroup = {
augend.integer.alias.decimal,
augend.constant.alias.bool, -- boolean value (true <-> false)
augend.date.alias["%m/%d/%Y"], -- date (02/19/2022, etc.)
}
}
```
* To define a group, use the `augends:register_group` function in the `"dial.config"` module.
The arguments is a dictionary whose keys are the group names and whose values are the list of augends.
* Various augends are defined `"dial.augend"` by default.
To specify the group of augends, you can use **expression register** ([`:h @=`](https://neovim.io/doc/user/change.html#quote_=)) as follows:
```
"=mygroup<CR><C-a>
```
If it is tedious to specify the expression register for each operation, you can "map" it:
```vim
nmap <Leader>a "=mygroup<CR><Plug>(dial-increment)
```
Alternatively, you can set the same mapping without expression register:
```lua
vim.api.nvim_set_keymap("n", "<Leader>a", require("dial.map").inc_normal("mygroup"), {noremap = true})
```
When you don't specify any group name in the way described above, the addends in the `default` group is used instead.
### Example Configuration
```vim
lua << EOF
local augend = require("dial.augend")
require("dial.config").augends:register_group{
default = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.alias["%Y/%m/%d"],
},
typescript = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.constant.new{ elements = {"let", "const"} },
},
visual = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.alias["%Y/%m/%d"],
augend.constant.alias.alpha,
augend.constant.alias.Alpha,
},
}
-- change augends in VISUAL mode
vim.api.nvim_set_keymap("v", "<C-a>", require("dial.map").inc_normal("visual"), {noremap = true})
vim.api.nvim_set_keymap("v", "<C-x>", require("dial.map").dec_normal("visual"), {noremap = true})
EOF
" enable only for specific FileType
autocmd FileType typescript lua vim.api.nvim_buf_set_keymap(0, "n", "<C-a>", require("dial.map").inc_normal("typescript"), {noremap = true})
```
## List of Augends
For simplicity, we define the variable `augend` as follows.
```lua
local augend = require("dial.augend")
```
### `integer`
`n`-based integer (`2 <= n <= 36`). You can use this rule with `augend.integer.new{ ...opts }`.
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.integer.new{
radix = 16,
prefix = "0x",
natural = true,
case = "upper",
},
},
}
```
## `date`
Date and time.
```lua
require("dial.config").augends:register_group{
default = {
-- date with format `yyyy/mm/dd`
augend.date.new{
pattern = "%Y/%m/%d",
default_kind = "day",
-- if true, it does not match dates which does not exist, such as 2022/05/32
only_valid = true,
-- if true, it only matches dates with word boundary
word = false,
},
},
}
```
In the `pattern` argument, you can use the following escape sequences:
|Sequence|Meaning |
|-----|------------------------------------------------------------------------------|
|`%Y` |4-digit year. (e.g. `2022`) |
|`%y` |Last 2 digits of year. The upper 2 digits are interpreted as `20`. (e.g. `22`)|
|`%m` |2-digit month. (e.g. `09`) |
|`%d` |2-digit day. (e.g. `28`) |
|`%H` |2-digit hour, expressed in 24 hours. (e.g. `15`) |
|`%I` |2-digit hour, expressed in 12 hours. (e.g. `03`) |
|`%M` |2-digit minute. (e.g. `05`) |
|`%S` |2-digit second. (e.g. `08`) |
|`%-y`|1- or 2-digit year. (e.g. `9` represents 2009) |
|`%-m`|1- or 2-digit month. (e.g. `9`) |
|`%-d`|1- or 2-digit day. (e.g. `28`) |
|`%-H`|1- or 2-digit hour, expressed in 24 hours. (e.g. `15`) |
|`%-I`|1- or 2-digit hour, expressed in 12 hours. (e.g. `3`) |
|`%-M`|1- or 2-digit minute. (e.g. `5`) |
|`%-S`|1- or 2-digit second. (e.g. `8`) |
|`%a` |English weekdays (`Sun`, `Mon`, ..., `Sat`) |
|`%A` |English full weekdays (`Sunday`, `Monday`, ..., `Saturday`) |
|`%b` |English month names (`Jan`, ..., `Dec`) |
|`%B` |English month full names (`January`, ..., `December`) |
|`%p` |`AM` or `PM`. |
|`%J` |Japanese weekdays (`日`, `月`, ..., `土`) |
## `constant`
Predefined sequence of strings. You can use this rule with `augend.constant.new{ ...opts }`.
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.constant.new{
elements = {"and", "or"},
word = true, -- if false, "sand" is incremented into "sor", "doctor" into "doctand", etc.
cyclic = true, -- "or" is incremented into "and".
},
augend.constant.new{
elements = {"&&", "||"},
word = false,
cyclic = true,
},
},
}
```
### `hexcolor`
RGB color code such as `#000000` and `#ffffff`.
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.hexcolor.new{
case = "lower",
},
},
}
```
### `semver`
Semantic versions. You can use this rule by augend alias described below.
It differs from a simple nonnegative integer increment/decrement in these ways:
* When the cursor is before the semver string, the patch version is incremented.
* When the minor version is incremented, the patch version is reset to zero.
* When the major version is incremented, the minor and patch versions are reset to zero.
### `user`
Custom augends.
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.user.new{
find = require("dial.augend.common").find_pattern("%d+"),
add = function(text, addend, cursor)
local n = tonumber(text)
n = math.floor(n * (2 ^ addend))
text = tostring(n)
cursor = #text
return {text = text, cursor = cursor}
end
},
},
}
```
## Augend Alias
Some augend rules are defined as alias. It can be used directly without using `new` function.
```lua
require("dial.config").augends:register_group{
default = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.alias["%Y/%m/%d"],
},
}
```
|Alias Name |Explanation |Examples |
|------------------------------------------|-------------------------------------------------|------------------------------------|
|`augend.integer.alias.decimal` |decimal natural number |`0`, `1`, ..., `9`, `10`, `11`, ... |
|`augend.integer.alias.decimal_int` |decimal integer (including negative number) |`0`, `314`, `-1592`, ... |
|`augend.integer.alias.hex` |hex natural number |`0x00`, `0x3f3f`, ... |
|`augend.integer.alias.octal` |octal natural number |`0o00`, `0o11`, `0o24`, ... |
|`augend.integer.alias.binary` |binary natural number |`0b0101`, `0b11001111`, ... |
|`augend.date.alias["%Y/%m/%d"]` |Date in the format `%Y/%m/%d` (`0` padding) |`2021/01/23`, ... |
|`augend.date.alias["%m/%d/%Y"]` |Date in the format `%m/%d/%Y` (`0` padding) |`23/01/2021`, ... |
|`augend.date.alias["%d/%m/%Y"]` |Date in the format `%d/%m/%Y` (`0` padding) |`01/23/2021`, ... |
|`augend.date.alias["%m/%d/%y"]` |Date in the format `%m/%d/%y` (`0` padding) |`01/23/21`, ... |
|`augend.date.alias["%d/%m/%y"]` |Date in the format `%d/%m/%y` (`0` padding) |`23/01/21`, ... |
|`augend.date.alias["%m/%d"]` |Date in the format `%m/%d` (`0` padding) |`01/04`, `02/28`, `12/25`, ... |
|`augend.date.alias["%-m/%-d"]` |Date in the format `%-m/%-d` (no paddings) |`1/4`, `2/28`, `12/25`, ... |
|`augend.date.alias["%Y-%m-%d"]` |Date in the format `%Y-%m-%d` (`0` padding) |`2021-01-04`, ... |
|`augend.date.alias["%d.%m.%Y"]` |Date in the format `%d.%m.%Y` (`0` padding) |`23.01.2021`, ... |
|`augend.date.alias["%d.%m.%y"]` |Date in the format `%d.%m.%y` (`0` padding) |`23.01.21`, ... |
|`augend.date.alias["%d.%m."]` |Date in the format `%d.%m.` (`0` padding) |`04.01.`, `28.02.`, `25.12.`, ... |
|`augend.date.alias["%-d.%-m."]` |Date in the format `%-d.%-m.` (no paddings) |`4.1.`, `28.2.`, `25.12.`, ... |
|`augend.date.alias["%Y年%-m月%-d日"]` |Date in the format `%Y年%-m月%-d日` (no paddings)|`2021年1月4日`, ... |
|`augend.date.alias["%Y年%-m月%-d日(%ja)"]`|Date in the format `%Y年%-m月%-d日(%ja)` |`2021年1月4日(月)`, ... |
|`augend.date.alias["%H:%M:%S"]` |Time in the format `%H:%M:%S` |`14:30:00`, ... |
|`augend.date.alias["%H:%M"]` |Time in the format `%H:%M` |`14:30`, ... |
|`augend.constant.alias.de_weekday` |German weekday |`Mo`, `Di`, ..., `Sa`, `So` |
|`augend.constant.alias.de_weekday_full` |German full weekday |`Montag`, `Dienstag`, ..., `Sonntag`|
|`augend.constant.alias.ja_weekday` |Japanese weekday |`月`, `火`, ..., `土`, `日` |
|`augend.constant.alias.ja_weekday_full` |Japanese full weekday |`月曜日`, `火曜日`, ..., `日曜日` |
|`augend.constant.alias.bool` |elements in boolean algebra (`true` and `false`) |`true`, `false` |
|`augend.constant.alias.alpha` |Lowercase alphabet letter (word) |`a`, `b`, `c`, ..., `z` |
|`augend.constant.alias.Alpha` |Uppercase alphabet letter (word) |`A`, `B`, `C`, ..., `Z` |
|`augend.semver.alias.semver` |Semantic version |`0.3.0`, `1.22.1`, `3.9.1`, ... |
If you don't specify any settings, the following augends is set as the value of the `default` group.
* `augend.integer.alias.decimal`
* `augend.integer.alias.hex`
* `augend.date.alias["%Y/%m/%d"]`
* `augend.date.alias["%Y-%m-%d"]`
* `augend.date.alias["%m/%d"]`
* `augend.date.alias["%H:%M"]`
* `augend.constant.alias.ja_weekday_full`
## Changelog
See [HISTORY](./HISTORY.md).
## Testing
This plugin uses `PlenaryBustedDirectory` in [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim).

354
etc/soft/nvim/+plugins/dial.nvim/README_ja.md

@ -0,0 +1,354 @@ @@ -0,0 +1,354 @@
# dial.nvim
**NOTICE: 本プラグインはまだ開発段階であり、事前告知なくインターフェースが変更となることがあります。**
## 旧バージョン (v0.2.0) を使っていた人へ
2022/02/20 に v0.3.0 がリリースされ、既存のインターフェースとの互換性がなくなりました。
以前のバージョン向けの設定を行っていた方は、[TROUBLESHOOTING.md](./TROUBLESHOOTING_ja.md) を参考に再設定を行ってください。
## 概要
[Neovim](https://github.com/neovim/neovim) の数値増減機能を拡張する Lua 製プラグイン。
既存の `<C-a>``<C-x>` コマンドを拡張し、数値以外も増減・トグルできるようにします。
![demo.gif](https://github.com/monaqa/dial.nvim/wiki/fig/dial-demo-2.gif)
## 特徴
* 数値をはじめとする様々なものの増減
* n 進数 (`2 <= n <= 36`) の整数
* 日付・時刻
* キーワードや演算子など、所定文字列のトグル
* `true``false`
* `&&``||`
* `a``b` ⇄ ... ⇄ `z`
* `日``月` ⇄ ... ⇄ `土``日` ⇄ ...
* Hex color
* SemVer
* VISUAL mode での `<C-a>` / `<C-x>` / `g<C-a>` / `g<C-x>` に対応
* 増減対象の柔軟な設定
* 特定のファイルタイプでのみ有効なルールの設定
* VISUAL モードでのみ有効なルールの設定
* カウンタに対応
* ドットリピートに対応
## 類似プラグイン
* [tpope/vim-speeddating](https://github.com/tpope/vim-speeddating)
* [Cycle.vim](https://github.com/zef/vim-cycle)
* [AndrewRadev/switch.vim](https://github.com/AndrewRadev/switch.vim)
## インストール
本プラグインには Neovim 0.5.0 以上が必要です(Neovim 0.6.1 以降を推奨)。
好きなパッケージマネージャの指示に従うことでインストールできます。
## 使用方法
本プラグインはデフォルトではキーマッピングを設定/上書きしません。
本プラグインを有効にするには、いずれかのキーに以下のような割り当てを行う必要があります。
```vim
nmap <C-a> <Plug>(dial-increment)
nmap <C-x> <Plug>(dial-decrement)
vmap <C-a> <Plug>(dial-increment)
vmap <C-x> <Plug>(dial-decrement)
vmap g<C-a> g<Plug>(dial-increment)
vmap g<C-x> g<Plug>(dial-decrement)
```
または Lua 上で以下のように設定することもできます。
```lua
vim.api.nvim_set_keymap("n", "<C-a>", require("dial.map").inc_normal(), {noremap = true})
vim.api.nvim_set_keymap("n", "<C-x>", require("dial.map").dec_normal(), {noremap = true})
vim.api.nvim_set_keymap("v", "<C-a>", require("dial.map").inc_visual(), {noremap = true})
vim.api.nvim_set_keymap("v", "<C-x>", require("dial.map").dec_visual(), {noremap = true})
vim.api.nvim_set_keymap("v", "g<C-a>", require("dial.map").inc_gvisual(), {noremap = true})
vim.api.nvim_set_keymap("v", "g<C-x>", require("dial.map").dec_gvisual(), {noremap = true})
```
## 設定方法
dial.nvim では操作対象を表す**被加数** (augend) と、複数の被加数をまとめた**グループ**を用いることで、増減させるルールを自由に設定することができます。
```lua
local augend = require("dial.augend")
require("dial.config").augends:register_group{
-- グループ名を指定しない場合に用いられる被加数
default = {
augend.integer.alias.decimal, -- nonnegative decimal number (0, 1, 2, 3, ...)
augend.integer.alias.hex, -- nonnegative hex number (0x01, 0x1a1f, etc.)
augend.date.alias["%Y/%m/%d"], -- date (2022/02/19, etc.)
},
-- `mygroup` というグループ名を使用した際に用いられる被加数
mygroup = {
augend.integer.alias.decimal,
augend.constant.alias.bool, -- boolean value (true <-> false)
augend.date.alias["%m/%d/%Y"], -- date (02/19/2022, etc.)
}
}
```
* `"dial.config"` モジュールに存在する `augends:register_group` 関数を用いてグループを定義することができます。
関数の引数には、グループ名をキー、被加数のリストを値とする辞書を指定します。
* 上の例で `augend` という名前のローカル変数に代入されている `"dial.augend"` モジュールでは、さまざまな被加数が定義されています。
以下のように **expression register** ([`:h @=`](https://neovim.io/doc/user/change.html#quote_=)) を用いると、増減対象のグループを指定できます。
```
"=mygroup<CR><C-a>
```
増減のたびに expression register を指定するのが面倒であれば、以下のようにマッピングすることも可能です。
```vim
nmap <Leader>a "=mygroup<CR><Plug>(dial-increment)
```
また、 Lua 上で以下のように記述すれば expression register を使わずにマッピングを設定できます。
```lua
vim.api.nvim_set_keymap("n", "<Leader>a", require("dial.map").inc_normal("mygroup"), {noremap = true})
```
expression register などでグループ名を指定しなかった場合、`default` グループにある被加数がかわりに用いられます。
### 設定例
```vim
lua << EOF
local augend = require("dial.augend")
require("dial.config").augends:register_group{
default = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.alias["%Y/%m/%d"],
},
typescript = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.constant.new{ elements = {"let", "const"} },
},
visual = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.alias["%Y/%m/%d"],
augend.constant.alias.alpha,
augend.constant.alias.Alpha,
},
}
-- VISUAL モードでの被加数を変更する
vim.api.nvim_set_keymap("v", "<C-a>", require("dial.map").inc_normal("visual"), {noremap = true})
vim.api.nvim_set_keymap("v", "<C-x>", require("dial.map").dec_normal("visual"), {noremap = true})
EOF
" 特定のファイルタイプでのみ有効にする
autocmd FileType typescript lua vim.api.nvim_buf_set_keymap(0, "n", "<C-a>", require("dial.map").inc_normal("typescript"), {noremap = true})
autocmd FileType typescript lua vim.api.nvim_buf_set_keymap(0, "n", "<C-x>", require("dial.map").dec_normal("typescript"), {noremap = true})
```
## 被加数の種類と一覧
以下簡単のため、 `augend` という変数は以下のように定義されているものとします。
```lua
local augend = require("dial.augend")
```
### 整数
n 進数の整数 (`2 <= n <= 36`) を表します。 `augend.integer.new{ ...opts }` で使用できます。
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.integer.new{
radix = 16,
prefix = "0x",
natural = true,
case = "upper",
},
},
}
```
### 日付
日付や時刻を表します。
```lua
require("dial.config").augends:register_group{
default = {
-- date with format `yyyy/mm/dd`
augend.date.new{
pattern = "%Y/%m/%d",
default_kind = "day",
-- if true, it does not match dates which does not exist, such as 2022/05/32
only_valid = true,
-- if true, it only matches dates with word boundary
word = false,
},
},
}
```
`pattern` で指定する文字列には、以下のエスケープシーケンスを使用できます。
|文字列|意味 |
|-----|-------------------------------------------------------------|
|`%Y` |4桁の西暦。 (e.g. `2022`) |
|`%y` |西暦の下2桁。上2桁は `20` として解釈されます。 (e.g. `22`) |
|`%m` |2桁の月。 (e.g. `09`) |
|`%d` |2桁の日。 (e.g. `28`) |
|`%H` |24時間で表示した2桁の時間。 (e.g. `15`) |
|`%I` |12時間で表示した2桁の時間。 (e.g. `03`) |
|`%M` |2桁の分。 (e.g. `05`) |
|`%S` |2桁の秒。 (e.g. `08`) |
|`%-y`|西暦の下2桁を1–2桁で表したもの。(e.g. `9``2009` 年を表す)|
|`%-m`|1–2桁の月。 (e.g. `9`) |
|`%-d`|1–2桁の日。 (e.g. `28`) |
|`%-H`|24時間で表示した1–2桁の時間。 (e.g. `15`) |
|`%-I`|12時間で表示した1–2桁の時間。 (e.g. `3`) |
|`%-M`|1–2桁の分。 (e.g. `5`) |
|`%-S`|1–2桁の秒。 (e.g. `8`) |
|`%a` |英語表記の短い曜日。 (`Sun`, `Mon`, ..., `Sat`) |
|`%A` |英語表記の曜日。 (`Sunday`, `Monday`, ..., `Saturday`) |
|`%b` |英語表記の短い月名。 (`Jan`, ..., `Dec`) |
|`%B` |英語表記の月名。 (`January`, ..., `December`) |
|`%p` |`AM` または `PM`。 |
|`%J` |日本語表記の曜日。 (`日`, `月`, ..., `土`) |
### 定数
キーワードなどの決められた文字列をトグルします。 `augend.constant.new{ ...opts }` で使用できます。
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.constant.new{
elements = {"and", "or"},
word = true, -- if false, "sand" is incremented into "sor", "doctor" into "doctand", etc.
cyclic = true, -- "or" is incremented into "and".
},
augend.constant.new{
elements = {"&&", "||"},
word = false,
cyclic = true,
},
},
}
```
### hex color
`#000000``#ffffff` といった形式の RGB カラーコードを増減します。 `augend.hexcolor.new{ ...opts }` で使用できます。
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.hexcolor.new{
case = "lower",
},
},
}
```
### SemVer
Semantic version を増減します。後述のエイリアスを用います。
単なる非負整数のインクリメントとは以下の点で異なります。
- semver 文字列よりもカーソルが手前にあるときは、パッチバージョンが優先してインクリメントされます。
- マイナーバージョンの値が増加したとき、パッチバージョンの値は0にリセットされます。
- メジャーバージョンの値が増加したとき、マイナー・パッチバージョンの値は0にリセットされます。
### カスタム
ユーザ自身が増減ルールを定義したい場合には `augend.user.new{ ...opts }` を使用できます。
```lua
require("dial.config").augends:register_group{
default = {
-- uppercase hex number (0x1A1A, 0xEEFE, etc.)
augend.user.new{
find = require("dial.augend.common").find_pattern("%d+"),
add = function(text, addend, cursor)
local n = tonumber(text)
n = math.floor(n * (2 ^ addend))
text = tostring(n)
cursor = #text
return {text = text, cursor = cursor}
end
},
},
}
```
### エイリアス
エイリアスはライブラリで予め定義された被加数です。 `new` 関数を用いることなく、そのまま使用できます。
```lua
require("dial.config").augends:register_group{
default = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.alias["%Y/%m/%d"],
},
}
```
エイリアスとして提供されている被加数は以下の通りです。
|Alias Name |Explanation |Examples |
|------------------------------------------|-------------------------------------------------|-----------------------------------|
|`augend.integer.alias.decimal` |decimal natural number |`0`, `1`, ..., `9`, `10`, `11`, ...|
|`augend.integer.alias.decimal_int` |decimal integer (including negative number) |`0`, `314`, `-1592`, ... |
|`augend.integer.alias.hex` |hex natural number |`0x00`, `0x3f3f`, ... |
|`augend.integer.alias.octal` |octal natural number |`0o00`, `0o11`, `0o24`, ... |
|`augend.integer.alias.binary` |binary natural number |`0b0101`, `0b11001111`, ... |
|`augend.date.alias["%Y/%m/%d"]` |Date in the format `%Y/%m/%d` (`0` padding) |`2021/01/23`, ... |
|`augend.date.alias["%m/%d/%Y"]` |Date in the format `%m/%d/%Y` (`0` padding) |`23/01/2021`, ... |
|`augend.date.alias["%d/%m/%Y"]` |Date in the format `%d/%m/%Y` (`0` padding) |`01/23/2021`, ... |
|`augend.date.alias["%m/%d/%y"]` |Date in the format `%m/%d/%y` (`0` padding) |`01/23/21`, ... |
|`augend.date.alias["%d/%m/%y"]` |Date in the format `%d/%m/%y` (`0` padding) |`23/01/21`, ... |
|`augend.date.alias["%m/%d"]` |Date in the format `%m/%d` (`0` padding) |`01/04`, `02/28`, `12/25`, ... |
|`augend.date.alias["%-m/%-d"]` |Date in the format `%-m/%-d` (no paddings) |`1/4`, `2/28`, `12/25`, ... |
|`augend.date.alias["%Y-%m-%d"]` |Date in the format `%Y-%m-%d` (`0` padding) |`2021-01-04`, ... |
|`augend.date.alias["%Y年%-m月%-d日"]` |Date in the format `%Y年%-m月%-d日` (no paddings)|`2021年1月4日`, ... |
|`augend.date.alias["%Y年%-m月%-d日(%ja)"]`|Date in the format `%Y年%-m月%-d日(%ja)` |`2021年1月4日(月)`, ... |
|`augend.date.alias["%H:%M:%S"]` |Time in the format `%H:%M:%S` |`14:30:00`, ... |
|`augend.date.alias["%H:%M"]` |Time in the format `%H:%M` |`14:30`, ... |
|`augend.constant.alias.ja_weekday` |Japanese weekday |`月`, `火`, ..., `土`, `日` |
|`augend.constant.alias.ja_weekday_full` |Japanese full weekday |`月曜日`, `火曜日`, ..., `日曜日` |
|`augend.constant.alias.bool` |elements in boolean algebra (`true` and `false`) |`true`, `false` |
|`augend.constant.alias.alpha` |Lowercase alphabet letter (word) |`a`, `b`, `c`, ..., `z` |
|`augend.constant.alias.Alpha` |Uppercase alphabet letter (word) |`A`, `B`, `C`, ..., `Z` |
|`augend.semver.alias.semver` |Semantic version |`0.3.0`, `1.22.1`, `3.9.1`, ... |
何も設定しなかった場合は以下の被加数が `default` グループの値としてセットされます。
- `augend.integer.alias.decimal`
- `augend.integer.alias.hex`
- `augend.date.alias["%Y/%m/%d"]`
- `augend.date.alias["%Y-%m-%d"]`
- `augend.date.alias["%m/%d"]`
- `augend.date.alias["%H:%M"]`
- `augend.constant.alias.ja_weekday_full`
## 更新履歴
[HISTORY](./HISTORY.md) を参照。
## Testing
[`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim) の `PlenaryBustedDirectory` を用いています。

61
etc/soft/nvim/+plugins/dial.nvim/TROUBLESHOOTING.md

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
# Troubleshooting
## Upgrading from v0.2.0 to v0.3.0
With the update from 0.2.0 to 0.3.0, new features and augend have been implemented,
and at the same time, the configuration scripts are no longer compatible. You need to rewrite it.
Here is an example of rewriting the configuration.
* Example settings (old)
```lua
local dial = require("dial")
dial.config.searchlist.normal = {
"number#decimal",
"date#[%m/%d]",
"char#alph#small#word",
}
```
* Example settings (new)
```lua
local augend = require("dial.augend")
require("dial.config").augends:register_group{
default = {
augend.integer.alias.decimal,
augend.date.alias["%m/%d"],
augend.constant.alias.alpha,
},
}
```
### Correspondence of augend names
|Old (v0.2.0) |New (v0.3.0) |
|----------------------------|------------------------------------------|
|`number#decimal` |`augend.integer.alias.decimal` |
|`number#decimal#int` |`augend.integer.alias.decimal` |
|`number#decimal#fixed#zero` |not implemented |
|`number#decimal#fixed#space`|not implemented |
|`number#hex` |`augend.integer.alias.hex` |
|`number#octal` |`augend.integer.alias.octal` |
|`number#binary` |`augend.integer.alias.binary` |
|`date#[%Y/%m/%d]` |`augend.date.alias["%Y/%m/%d"]` |
|`date#[%m/%d]` |`augend.date.alias["%m/%d"]` |
|`date#[%-m/%-d]` |`augend.date.alias["%-m/%-d"]` |
|`date#[%Y-%m-%d]` |`augend.date.alias["%Y-%m-%d"]` |
|`date#[%Y年%-m月%-d日]` |`augend.date.alias["%Y年%-m月%-d日"]` |
|`date#[%Y年%-m月%-d日(%ja)]`|`augend.date.alias["%Y年%-m月%-d日(%ja)"]`|
|`date#[%H:%M:%S]` |`augend.date.alias["%H:%M:%S"]` |
|`date#[%H:%M]` |`augend.date.alias["%H:%M"]` |
|`date#[%ja]` |`augend.constant.alias.ja_weekday` |
|`date#[%jA]` |`augend.constant.alias.ja_weekday_full` |
|`char#alph#small#word` |`augend.constant.alias.alpha` |
|`char#alph#capital#word` |`augend.constant.alias.Alpha` |
|`char#alph#small#str` |can be defined with `augend.constant.new` |
|`char#alph#capital#str` |can be defined with `augend.constant.new` |
|`color#hex` |`augend.hexcolor.new{}` |
|`markup#markdown#header` |`augend.misc.alias.markdown_header` |

59
etc/soft/nvim/+plugins/dial.nvim/TROUBLESHOOTING_ja.md

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
# トラブルシューティング
## v0.2.0 から v0.3.0 へのアップデート
`0.2.0` から `0.3.0` へのアップデートにあたり、新機能や新たな augend が実装されたと同時に設定方法の互換性がなくなりました。
以下のように設定を書き換える必要があります。
* 設定例(旧)
```lua
local dial = require("dial")
dial.config.searchlist.normal = {
"number#decimal",
"date#[%m/%d]",
"char#alph#small#word",
}
```
* 設定例(新)
```lua
local augend = require("dial.augend")
require("dial.config").augends:register_group{
default = {
augend.integer.alias.decimal,
augend.date.alias["%m/%d"],
augend.constant.alias.alpha,
},
}
```
### 被加数の新旧対応
|旧 |新 |
|----------------------------|------------------------------------------|
|`number#decimal` |`augend.integer.alias.decimal` |
|`number#decimal#int` |`augend.integer.alias.decimal` |
|`number#decimal#fixed#zero` |not implemented |
|`number#decimal#fixed#space`|not implemented |
|`number#hex` |`augend.integer.alias.hex` |
|`number#octal` |`augend.integer.alias.octal` |
|`number#binary` |`augend.integer.alias.binary` |
|`date#[%Y/%m/%d]` |`augend.date.alias["%Y/%m/%d"]` |
|`date#[%m/%d]` |`augend.date.alias["%m/%d"]` |
|`date#[%-m/%-d]` |`augend.date.alias["%-m/%-d"]` |
|`date#[%Y-%m-%d]` |`augend.date.alias["%Y-%m-%d"]` |
|`date#[%Y年%-m月%-d日]` |`augend.date.alias["%Y年%-m月%-d日"]` |
|`date#[%Y年%-m月%-d日(%ja)]`|`augend.date.alias["%Y年%-m月%-d日(%ja)"]`|
|`date#[%H:%M:%S]` |`augend.date.alias["%H:%M:%S"]` |
|`date#[%H:%M]` |`augend.date.alias["%H:%M"]` |
|`date#[%ja]` |`augend.constant.alias.ja_weekday` |
|`date#[%jA]` |`augend.constant.alias.ja_weekday_full` |
|`char#alph#small#word` |`augend.constant.alias.alpha` |
|`char#alph#capital#word` |`augend.constant.alias.Alpha` |
|`char#alph#small#str` |can be defined with `augend.constant.new` |
|`char#alph#capital#str` |can be defined with `augend.constant.new` |
|`color#hex` |`augend.hexcolor.new{}` |
|`markup#markdown#header` |`augend.misc.alias.markdown_header` |

23
etc/soft/nvim/+plugins/dial.nvim/autoload/dial/operator.vim

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
function dial#operator#increment_normal(type, ...)
lua require("dial.command").operator_normal("increment")
endfunction
function dial#operator#decrement_normal(type, ...)
lua require("dial.command").operator_normal("decrement")
endfunction
function dial#operator#increment_visual(type, ...)
lua require("dial.command").operator_visual("increment", false)
endfunction
function dial#operator#decrement_visual(type, ...)
lua require("dial.command").operator_visual("decrement", false)
endfunction
function dial#operator#increment_gvisual(type, ...)
lua require("dial.command").operator_visual("increment", true)
endfunction
function dial#operator#decrement_gvisual(type, ...)
lua require("dial.command").operator_visual("decrement", true)
endfunction

1
etc/soft/nvim/+plugins/dial.nvim/doc/.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
tags*

1417
etc/soft/nvim/+plugins/dial.nvim/doc/dial.jax

File diff suppressed because it is too large Load Diff

1477
etc/soft/nvim/+plugins/dial.nvim/doc/dial.txt

File diff suppressed because it is too large Load Diff

21
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend.lua

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
local case = require "dial.augend.case"
local constant = require "dial.augend.constant"
local date = require "dial.augend.date"
local hexcolor = require "dial.augend.hexcolor"
local integer = require "dial.augend.integer"
local semver = require "dial.augend.semver"
local user = require "dial.augend.user"
local paren = require "dial.augend.paren"
local misc = require "dial.augend.misc"
return {
case = case,
constant = constant,
date = date,
hexcolor = hexcolor,
integer = integer,
semver = semver,
user = user,
paren = paren,
misc = misc,
}

266
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/case.lua

@ -0,0 +1,266 @@ @@ -0,0 +1,266 @@
local common = require "dial.augend.common"
local util = require "dial.util"
local M = {}
---@alias casetype '"PascalCase"' | '"camelCase"' | '"snake_case"' | '"kebab-case"' | '"SCREAMING_SNAKE_CASE"'
---@alias extractf fun(word: string) -> string[] | nil
---@alias constractf fun(terms: string[]) -> string
---@alias casepattern { word_regex: string, extract: extractf, constract: constractf }
---@class AugendCase
---@implement Augend
---@field config { types: casetype[], cyclic: boolean }
---@field patterns casepattern[]
local AugendCase = {}
---@type table<casetype, casepattern>
M.case_patterns = {}
M.case_patterns["camelCase"] = {
word_regex = [[\C\v<([a-z][a-z0-9]*)([A-Z][a-z0-9]*)+>]],
---@param word string
---@return string[] | nil
extract = function(word)
local subwords = {}
local ptr = 1
for i = 1, word:len(), 1 do
local char = word:sub(i, i)
if not (char == char:lower()) then
-- i 番目の文字が大文字の場合は直前で切る
-- 小文字や数字などは切らない
if i == 1 then
-- ただし最初の文字が大文字になることはないはず
return nil
end
table.insert(subwords, word:sub(ptr, i - 1))
ptr = i
end
end
table.insert(subwords, word:sub(ptr, word:len()))
return vim.tbl_map(function(s)
return s:lower()
end, subwords)
end,
---@param terms string[]
---@return string
constract = function(terms)
local result = ""
for index, term in ipairs(terms) do
if index == 1 then
result = result .. term
else
result = result .. term:sub(1, 1):upper() .. term:sub(2)
end
end
return result
end,
}
M.case_patterns["PascalCase"] = {
word_regex = [[\C\v<([A-Z][a-z0-9]*)+>]],
---@param word string
---@return string[] | nil
extract = function(word)
local subwords = {}
local ptr = 1
for i = 2, word:len(), 1 do
local char = word:sub(i, i)
if not (char == char:lower()) then
-- i 番目の文字が大文字の場合は直前で切る
-- 小文字や数字などは切らない
table.insert(subwords, word:sub(ptr, i - 1))
ptr = i
end
end
table.insert(subwords, word:sub(ptr, word:len()))
return vim.tbl_map(function(s)
return s:lower()
end, subwords)
end,
---@param terms string[]
---@return string
constract = function(terms)
local result = ""
for _, term in ipairs(terms) do
result = result .. term:sub(1, 1):upper() .. term:sub(2)
end
return result
end,
}
M.case_patterns["snake_case"] = {
word_regex = [[\C\v<([a-z][a-z0-9]*)(_[a-z0-9]*)+>]],
---@param word string
---@return string[] | nil
extract = function(word)
local subwords = {}
local ptr = 1
for i = 1, word:len(), 1 do
local char = word:sub(i, i)
if char == "_" then
table.insert(subwords, word:sub(ptr, i - 1))
ptr = i + 1
end
end
table.insert(subwords, word:sub(ptr, word:len()))
return subwords
end,
---@param terms string[]
---@return string
constract = function(terms)
return table.concat(terms, "_")
end,
}
M.case_patterns["kebab-case"] = {
word_regex = [[\C\v<([a-z][a-z0-9]*)(-[a-z0-9]*)+>]],
---@param word string
---@return string[] | nil
extract = function(word)
local subwords = {}
local ptr = 1
for i = 1, word:len(), 1 do
local char = word:sub(i, i)
if char == "-" then
table.insert(subwords, word:sub(ptr, i - 1))
ptr = i + 1
end
end
table.insert(subwords, word:sub(ptr, word:len()))
return subwords
end,
---@param terms string[]
---@return string
constract = function(terms)
return table.concat(terms, "-")
end,
}
M.case_patterns["SCREAMING_SNAKE_CASE"] = {
word_regex = [[\C\v<([A-Z][A-Z0-9]*)(_[A-Z0-9]*)+>]],
---@param word string
---@return string[] | nil
extract = function(word)
local subwords = {}
local ptr = 1
for i = 1, word:len(), 1 do
local char = word:sub(i, i)
if char == "_" then
table.insert(subwords, word:sub(ptr, i - 1))
ptr = i + 1
end
end
table.insert(subwords, word:sub(ptr, word:len()))
return vim.tbl_map(function(s)
return s:lower()
end, subwords)
end,
---@param terms string[]
---@return string
constract = function(terms)
return table.concat(terms, "_"):upper()
end,
}
---@param config { types: casetype[], cyclic?: boolean }
---@return Augend
function M.new(config)
vim.validate {
cyclic = { config.cyclic, "boolean", true },
}
if config.cyclic == nil then
config.cyclic = true
end
util.validate_list("types", config.types, function(val)
if
val == "PascalCase"
or val == "camelCase"
or val == "snake_case"
or val == "kebab-case"
or val == "SCREAMING_SNAKE_CASE"
then
return true
end
return false
end)
local patterns = vim.tbl_map(function(type)
return M.case_patterns[type]
end, config.types)
-- local query = prefix .. util.if_expr(natural, "", "-?") .. "[" .. radix_to_query_character(radix) .. delimiter .. "]+"
return setmetatable({
patterns = patterns,
config = config,
}, { __index = AugendCase })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendCase:find(line, cursor)
---@type textrange?
local most_front_range = nil
for _, caseptn in ipairs(self.patterns) do
---@type textrange
local range = common.find_pattern_regex(caseptn.word_regex)(line, cursor)
if range ~= nil then
if most_front_range == nil or range.from < most_front_range.from then
most_front_range = range
end
end
end
return most_front_range
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendCase:add(text, addend, cursor)
local len_patterns = #self.patterns
---@type integer
local index
for i, caseptn in ipairs(self.patterns) do
local range = common.find_pattern_regex(caseptn.word_regex)(text, 1)
if range ~= nil then
index = i
break
end
end
local terms = self.patterns[index].extract(text)
local new_index
if self.config.cyclic then
new_index = (len_patterns + (index - 1 + addend) % len_patterns) % len_patterns + 1
else
new_index = index + addend
if new_index <= 0 then
new_index = 1
end
if new_index > len_patterns then
new_index = len_patterns
end
end
if new_index == index then
return { cursor = text:len() }
end
text = self.patterns[new_index].constract(terms)
return { text = text, cursor = text:len() }
end
return M

76
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/common.lua

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
-- augend で共通して用いられる関数。
local util = require "dial.util"
local M = {}
---augend の find field を簡単に実装する。
---@param ptn string
---@return findf
function M.find_pattern(ptn)
---@param line string
---@param cursor? integer
---@return textrange?
local function f(line, cursor)
local idx_start = 1
while idx_start <= #line do
local s, e = line:find(ptn, idx_start)
if s then
-- 検索結果があったら
if cursor == nil or cursor <= e then
-- cursor が終了文字より後ろにあったら終了
return { from = s, to = e }
else
-- 終了文字の後ろから探し始める
idx_start = e + 1
end
else
-- 検索結果がなければそこで終了
break
end
end
return nil
end
return f
end
-- augend の find field を簡単に実装する。
---@param ptn string
---@return findf
function M.find_pattern_regex(ptn)
---@param line string
---@param cursor? integer
---@return textrange?
local function f(line, cursor)
local idx_start = 1
while idx_start <= #line do
local s, e = vim.regex(ptn):match_str(line:sub(idx_start))
if s then
s = s + idx_start -- 上で得られた s は相対位置なので
e = e + idx_start - 1 -- 上で得られた s は相対位置なので
-- 検索結果があったら
if cursor == nil or cursor <= e then
-- cursor が終了文字より後ろにあったら終了
return { from = s, to = e }
else
-- 終了文字の後ろから探し始める
idx_start = e + 1
end
else
-- 検索結果がなければそこで終了
break
end
end
return nil
end
return f
end
---@param elems string[]
function M.enum_to_regex(elems)
return table.concat(elems, [[\|]])
end
return M

226
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/constant.lua

@ -0,0 +1,226 @@ @@ -0,0 +1,226 @@
local util = require "dial.util"
local common = require "dial.augend.common"
---@alias AugendConstantConfig { elements: string[], cyclic: boolean, pattern_regexp: string, preserve_case: boolean }
---@class AugendConstant
---@implement Augend
---@field config AugendConstantConfig
local AugendConstant = {}
local M = {}
---@param word string
---@return string
local function to_first_upper(word)
local first_letter = word:sub(1, 1)
local rest = word:sub(2)
return first_letter:upper() .. rest:lower()
end
---@param word string
---@return "all-lower" | "all-upper" | "first-upper" | nil
local function preserve_case(word)
if word:lower() == word then
return "all-lower"
end
if word:upper() == word then
return "all-upper"
end
if to_first_upper(word) == word then
return "first-upper"
end
return nil
end
---@param config { elements: string[], word?: boolean, cyclic?: boolean, pattern_regexp?: string, preserve_case?: boolean }
---@return AugendConstant
function M.new(config)
util.validate_list("config.elements", config.elements, "string")
vim.validate {
word = { config.word, "boolean", true },
cyclic = { config.cyclic, "boolean", true },
pattern_regexp = { config.pattern_regexp, "string", true },
preserve_case = { config.preserve_case, "boolean", true },
}
if config.preserve_case == nil then
config.preserve_case = false
end
if config.pattern_regexp == nil then
local case_sensitive_flag = util.if_expr(config.preserve_case, [[\c]], [[\C]])
local word = util.unwrap_or(config.word, true)
if word then
config.pattern_regexp = case_sensitive_flag .. [[\V\<\(%s\)\>]]
else
config.pattern_regexp = case_sensitive_flag .. [[\V\(%s\)]]
end
end
if config.cyclic == nil then
config.cyclic = true
end
return setmetatable({ config = config }, { __index = AugendConstant })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendConstant:find(line, cursor)
local escaped_elements = vim.tbl_map(function(e)
return vim.fn.escape(e, [[/\]])
end, self.config.elements)
local vim_regex_ptn = self.config.pattern_regexp:format(table.concat(escaped_elements, [[\|]]))
return common.find_pattern_regex(vim_regex_ptn)(line, cursor)
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendConstant:add(text, addend, cursor)
local elements = self.config.elements
local n_patterns = #elements
local n = 1
local query
if self.config.preserve_case then
query = function(elem)
return text:lower() == elem:lower()
end
else
query = function(elem)
return text == elem
end
end
for i, elem in ipairs(elements) do
if query(elem) then
n = i
end
end
if self.config.cyclic then
n = (n + addend - 1) % n_patterns + 1
else
n = n + addend
if n < 1 then
n = 1
end
if n > n_patterns then
n = n_patterns
end
end
local new_text = elements[n]
local case = nil
if self.config.preserve_case then
case = preserve_case(text)
end
if case == "all-lower" then
text = new_text:lower()
elseif case == "all-upper" then
text = new_text:upper()
elseif case == "first-upper" then
text = to_first_upper(new_text)
else
text = new_text
end
cursor = #text
return { text = text, cursor = cursor }
end
M.alias = {
bool = M.new { elements = { "true", "false" } },
alpha = M.new {
elements = {
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
},
cyclic = false,
},
Alpha = M.new {
elements = {
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
},
cyclic = false,
},
ja_weekday = M.new {
elements = { "", "", "", "", "", "", "" },
word = true,
cyclic = true,
},
ja_weekday_full = M.new {
elements = { "日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日" },
word = false,
cyclic = true,
},
de_weekday = M.new {
elements = { "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So" },
word = true,
cyclic = true,
},
de_weekday_full = M.new {
elements = {
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
"Sonntag",
},
word = true,
cyclic = true,
},
}
return M

728
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/date.lua

@ -0,0 +1,728 @@ @@ -0,0 +1,728 @@
local util = require "dial.util"
local common = require "dial.augend.common"
local M = {}
---@alias datekind '"year"' | '"month"' | '"day"' | '"hour"' | '"min"' | '"sec"'
---@alias dttable table<datekind, integer>
---@alias dateparser fun(string, osdate): osdate
---@alias dateformatter fun(osdate): string
---@alias dateelement {kind?: datekind, regex: string, update_date: dateparser, format?: dateformatter}
---@param datekind datekind | nil
---@return fun(string, osdate): osdate
local function simple_updater(datekind)
if datekind == nil then
return function(_, date)
return date
end
end
return function(text, date)
date[datekind] = tonumber(text)
return date
end
end
local WEEKDAYS = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
}
local WEEKDAYS_FULL = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
}
local WEEKDAYS_JA = {
"",
"",
"",
"",
"",
"",
"",
}
local MONTHS = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
}
local MONTHS_FULL = {
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
}
---@type table<string, dateelement>
local date_elements = {
["Y"] = {
kind = "year",
regex = [[\d\d\d\d]],
update_date = simple_updater "year",
},
["y"] = {
kind = "year",
regex = [[\d\d]],
update_date = function(text, date)
date.year = 2000 + tonumber(text)
return date
end,
},
["m"] = {
kind = "month",
regex = [[\d\d]],
update_date = simple_updater "month",
},
["d"] = {
kind = "day",
regex = [[\d\d]],
update_date = simple_updater "day",
},
["H"] = {
kind = "hour",
regex = [[\d\d]],
update_date = simple_updater "hour",
},
["I"] = {
kind = "hour",
regex = [[\d\d]],
update_date = function(text, date)
local hour = tonumber(text)
if date.hour < 12 and hour >= 12 then
date.hour = hour - 12
elseif date.hour >= 12 and hour < 12 then
date.hour = hour + 12
else
date.hour = hour
end
return date
end,
},
["M"] = {
kind = "min",
regex = [[\d\d]],
update_date = simple_updater "min",
},
["S"] = {
kind = "sec",
regex = [[\d\d]],
update_date = simple_updater "sec",
},
-- with hyphen
["-y"] = {
kind = "year",
regex = [[\d\{1,2\}]],
update_date = function(text, date)
date.year = 2000 + tonumber(text)
return date
end,
},
["-m"] = {
kind = "month",
regex = [[\d\{1,2\}]],
update_date = simple_updater "month",
},
["-d"] = {
kind = "day",
regex = [[\d\{1,2\}]],
update_date = simple_updater "day",
},
["-H"] = {
kind = "hour",
regex = [[\d\{1,2\}]],
update_date = simple_updater "hour",
},
["-I"] = {
kind = "hour",
regex = [[\d\{1,2\}]],
update_date = function(text, date)
local hour = tonumber(text)
if date.hour < 12 and hour >= 12 then
date.hour = hour - 12
elseif date.hour >= 12 and hour < 12 then
date.hour = hour + 12
else
date.hour = hour
end
return date
end,
},
["-M"] = {
kind = "min",
regex = [[\d\{1,2\}]],
update_date = simple_updater "min",
},
["-S"] = {
kind = "sec",
regex = [[\d\{1,2\}]],
update_date = simple_updater "sec",
},
-- names
["a"] = {
kind = nil,
regex = common.enum_to_regex(WEEKDAYS),
update_date = function(_, date)
return date
end,
format = function(time)
local wday = os.date("*t", time).wday --[[ @as integer ]]
return WEEKDAYS[wday]
end,
},
["A"] = {
kind = nil,
regex = common.enum_to_regex(WEEKDAYS_FULL),
update_date = function(_, date)
return date
end,
format = function(time)
local wday = os.date("*t", time).wday --[[ @as integer ]]
return WEEKDAYS_FULL[wday]
end,
},
["b"] = {
kind = "month",
regex = common.enum_to_regex(MONTHS),
update_date = function(text, date)
for index, value in ipairs(MONTHS) do
if value == text then
date.month = index
end
end
return date
end,
format = function(time)
local month = os.date("*t", time).month --[[ @as integer ]]
return MONTHS[month]
end,
},
["B"] = {
kind = "month",
regex = common.enum_to_regex(MONTHS_FULL),
update_date = function(text, date)
for index, value in ipairs(MONTHS_FULL) do
if value == text then
date.month = index
end
end
return date
end,
format = function(time)
local month = os.date("*t", time).month --[[ @as integer ]]
return MONTHS_FULL[month]
end,
},
["p"] = {
kind = "hour",
regex = common.enum_to_regex { "AM", "PM" },
update_date = function(text, date)
if text == "PM" and date.hour < 12 then
date.hour = date.hour + 12
end
if text == "AM" and date.hour >= 12 then
date.hour = date.hour - 12
end
return date
end,
-- format = function(time)
-- local hour = os.date("*t", time).hour --[[ @as integer ]]
-- if hour < 12 then
-- return "am"
-- end
-- return "pm"
-- end,
},
-- custom
["J"] = {
kind = nil,
regex = common.enum_to_regex(WEEKDAYS_JA),
update_date = simple_updater(),
format = function(time)
local wday = os.date("*t", time).wday --[[ @as integer ]]
return WEEKDAYS_JA[wday]
end,
},
}
---@param pattern string
---@return string[]
---@param custom_date_element_keys string[]
local function parse_date_pattern(pattern, custom_date_element_keys)
local date_elements_keys = vim.tbl_keys(date_elements) --[[@as string[] ]]
local sequences = {}
---@type string
local stack = ""
for c in util.chars(pattern) do
if vim.startswith(stack, "%(") then
if c == ")" then
local custom_element_name = stack:sub(3)
if vim.tbl_contains(custom_date_element_keys, custom_element_name) then
table.insert(sequences, stack .. ")")
stack = ""
else
error(("Unknown custom elements: %s"):format(custom_element_name))
end
else
stack = stack .. c
end
elseif stack == "%-" then
if vim.tbl_contains(date_elements_keys, c) then
table.insert(sequences, "%-" .. c)
stack = ""
else
error("Unsupported special character: %-" .. c)
end
elseif stack == "%" then
-- special character
if c == "-" or c == "(" then
stack = "%" .. c
elseif c == "%" then
table.insert(sequences, "%")
stack = ""
elseif vim.tbl_contains(date_elements_keys, c) then
table.insert(sequences, "%" .. c)
stack = ""
else
error("Unsupported special character: %" .. c)
end
else
-- escape character
if c == "%" then
if stack ~= "" then
table.insert(sequences, stack)
end
stack = "%"
else
stack = stack .. c
end
end
end
if stack ~= "" then
if vim.startswith(stack, "%(") then
error("The end of custom date element was not found:'" .. stack .. "'.")
elseif vim.startswith(stack, "%") then
error("Pattern string cannot end with '" .. stack .. "'.")
else
table.insert(sequences, stack)
stack = ""
end
end
return sequences
end
---@class DateFormat
---@field sequences string[]
---@field default_kind datekind
---@field word boolean
---@field custom_date_elements table<string, dateelement>
local DateFormat = {}
---Parse date pattern string and create new DateFormat.
---@param pattern string
---@param default_kind datekind
---@param word? boolean
---@param custom_date_elements? table<string, dateelement>
---@return DateFormat
function DateFormat.new(pattern, default_kind, word, custom_date_elements)
word = util.unwrap_or(word, false)
custom_date_elements = util.unwrap_or(custom_date_elements, {})
local custom_date_elements_keys = vim.tbl_keys(custom_date_elements) --[[@as string[] ]]
local sequences = parse_date_pattern(pattern, custom_date_elements_keys)
return setmetatable(
{ sequences = sequences, default_kind = default_kind, word = word, custom_date_elements = custom_date_elements },
{ __index = DateFormat }
)
end
---@param pattern string
---@return dateelement
function DateFormat:get_date_elements(pattern)
if vim.startswith(pattern, "%(") and vim.endswith(pattern, ")") then
local custom_element_name = pattern:sub(3, -2)
return self.custom_date_elements[custom_element_name]
elseif vim.startswith(pattern, "%") then
local element_name = pattern:sub(2)
return date_elements[element_name]
else
error(("unknown pattern: '%s'"):format(pattern))
end
end
---returns the regex.
---@return string
function DateFormat:regex()
local regexes = vim.tbl_map(
---@param s string
---@return string
function(s)
if s == "%" then
return [[%]]
elseif vim.startswith(s, "%") then
return [[\(]] .. self:get_date_elements(s).regex .. [[\)]]
else
return vim.fn.escape(s, [[\]])
end
end,
self.sequences
) --[[ @as string[] ]]
if self.word then
return [[\V\C\<]] .. table.concat(regexes, "") .. [[\>]]
else
return [[\V\C]] .. table.concat(regexes, "")
end
end
---@param line string
---@param cursor? integer
---@return {range: textrange, dt_info: osdate, kind: datekind}?
function DateFormat:find(line, cursor)
local range = common.find_pattern_regex(self:regex())(line, cursor)
if range == nil then
return nil
end
-- cursor が nil になるときはカーソルが最初にあるときとみなして良い
if cursor == nil then
cursor = 0
end
local matchlist = vim.fn.matchlist(line:sub(range.from, range.to), self:regex())
local scan_cursor = range.from - 1
local flag_set_status = scan_cursor >= cursor
local dt_info = os.date("*t", os.time()) --[[@as osdate]]
local datekind = self.default_kind
local match_idx = 2
for _, pattern in ipairs(self.sequences) do
---@type string
if pattern ~= "%" and vim.startswith(pattern, "%") then
local substr = matchlist[match_idx]
scan_cursor = scan_cursor + #substr
local date_element = self:get_date_elements(pattern)
dt_info = date_element.update_date(substr, dt_info)
if scan_cursor >= cursor and not flag_set_status and date_element.kind ~= nil then
datekind = date_element.kind
flag_set_status = true
end
match_idx = match_idx + 1
else
scan_cursor = scan_cursor + #pattern
end
end
return { range = range, dt_info = dt_info, kind = datekind }
end
---@param line string
---@param cursor? integer
---@return {range: textrange, dt_info: osdate, kind: datekind}?
function DateFormat:find_with_validity_check(line, cursor)
local find_result = self:find(line, cursor)
if find_result == nil then
return nil
end
local text = line:sub(find_result.range.from, find_result.range.to)
local time = os.time(find_result.dt_info)
local add_result = self:strftime(time, "day")
local correct_text = add_result.text
if correct_text == text then
return find_result
else
return nil
end
end
---@param time integer
---@param datekind? datekind
---@return addresult
function DateFormat:strftime(time, datekind)
local text = ""
local cursor
for _, pattern in ipairs(self.sequences) do
if pattern ~= "%" and vim.startswith(pattern, "%") then
local date_element = self:get_date_elements(pattern)
if date_element.format ~= nil then
text = text .. date_element.format(time)
else
text = text .. os.date(pattern, time)
end
if date_element.kind == datekind then
cursor = #text
end
else
text = text .. pattern
end
end
if datekind == nil then
return { text = text }
else
return { text = text, cursor = cursor }
end
end
---@class AugendDate
---@implement Augend
---@field kind datekind
---@field config {pattern: string, default_kind: datekind, only_valid: boolean, word: boolean, clamp: boolean, end_sensitive: boolean, custom_date_elements: dateelement}
---@field date_format DateFormat
local AugendDate = {}
---@param config {pattern: string, default_kind: datekind, only_valid?: boolean, word?: boolean, clamp?: boolean, end_sensitive?: boolean, custom_date_elements?: table<string, dateelement>}
---@return AugendDate
function M.new(config)
vim.validate {
pattern = { config.pattern, "string" },
default_kind = { config.default_kind, "string" },
only_valid = { config.only_valid, "boolean", true },
word = { config.word, "boolean", true },
clamp = { config.clamp, "boolean", true },
end_sensitive = { config.end_sensitive, "boolean", true },
custom_date_elements = { config.custom_date_elements, "table", true },
}
config.only_valid = util.unwrap_or(config.only_valid, false)
config.word = util.unwrap_or(config.word, false)
config.clamp = util.unwrap_or(config.clamp, false)
config.end_sensitive = util.unwrap_or(config.end_sensitive, false)
local date_format = DateFormat.new(config.pattern, config.default_kind, config.word, config.custom_date_elements)
return setmetatable(
{ config = config, kind = config.default_kind, date_format = date_format },
{ __index = AugendDate }
)
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendDate:find(line, cursor)
local find_result
if self.config.only_valid then
find_result = self.date_format:find_with_validity_check(line, cursor)
else
find_result = self.date_format:find(line, cursor)
end
if find_result == nil then
return nil
end
return find_result.range
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendDate:find_stateful(line, cursor)
local find_result
if self.config.only_valid then
find_result = self.date_format:find_with_validity_check(line, cursor)
else
find_result = self.date_format:find(line, cursor)
end
if find_result == nil then
return nil
end
self.kind = find_result.kind
return find_result.range
end
---@param year integer
---@param month integer
---@return integer
local function calc_end_day(year, month)
if month == 4 or month == 6 or month == 9 or month == 11 then
return 30
elseif month == 2 then
if year % 400 == 0 or (year % 4 == 0 and year % 100 ~= 0) then
return 29
else
return 28
end
else
return 31
end
end
---@param dt_info osdate
---@param kind datekind
---@param addend integer
---@param clamp boolean
---@param end_sensitive boolean
---@return osdate
local function update_dt_info(dt_info, kind, addend, clamp, end_sensitive)
if kind ~= "year" and kind ~= "month" then
dt_info[kind] = dt_info[kind] + addend
return dt_info
end
local end_day_before_add = calc_end_day(dt_info.year, dt_info.month)
local day_before_add = dt_info.day
dt_info.day = 1
dt_info[kind] = dt_info[kind] + addend
-- update date information to existent one
dt_info = os.date("*t", os.time(dt_info)) --[[@as osdate]]
local end_day_after_add = calc_end_day(dt_info.year, dt_info.month)
if end_sensitive and end_day_before_add == day_before_add then
dt_info.day = end_day_after_add
elseif clamp and day_before_add > end_day_after_add then
dt_info.day = end_day_after_add
else
dt_info.day = day_before_add
end
return dt_info
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendDate:add(text, addend, cursor)
local find_result = self.date_format:find(text)
if find_result == nil or self.kind == nil then
return {}
end
local dt_info = find_result.dt_info
dt_info = update_dt_info(dt_info, self.kind, addend, self.config.clamp, self.config.end_sensitive)
-- dt_info[self.kind] = dt_info[self.kind] + addend
local time = os.time(dt_info)
return self.date_format:strftime(time, self.kind)
end
M.alias = {}
M.alias["%Y/%m/%d"] = M.new {
pattern = "%Y/%m/%d",
default_kind = "day",
only_valid = false,
}
M.alias["%d/%m/%Y"] = M.new {
pattern = "%d/%m/%Y",
default_kind = "day",
only_valid = true,
}
M.alias["%d/%m/%y"] = M.new {
pattern = "%d/%m/%y",
default_kind = "day",
only_valid = true,
}
M.alias["%m/%d/%Y"] = M.new {
pattern = "%m/%d/%Y",
default_kind = "day",
only_valid = true,
}
M.alias["%m/%d/%y"] = M.new {
pattern = "%m/%d/%y",
default_kind = "day",
only_valid = true,
}
M.alias["%m/%d"] = M.new {
pattern = "%m/%d",
default_kind = "day",
only_valid = false,
}
M.alias["%Y-%m-%d"] = M.new {
pattern = "%Y-%m-%d",
default_kind = "day",
only_valid = false,
}
M.alias["%-m/%-d"] = M.new {
pattern = "%-m/%-d",
default_kind = "day",
only_valid = true,
}
M.alias["%Y年%-m月%-d日"] = M.new {
pattern = "%Y年%-m月%-d日",
default_kind = "day",
only_valid = false,
}
M.alias["%Y年%-m月%-d日(%ja)"] = M.new {
pattern = "%Y年%-m月%-d日(%J)",
default_kind = "day",
only_valid = false,
}
M.alias["%d.%m.%Y"] = M.new {
pattern = "%d.%m.%Y",
default_kind = "day",
only_valid = true,
}
M.alias["%d.%m.%y"] = M.new {
pattern = "%d.%m.%y",
default_kind = "day",
only_valid = true,
}
M.alias["%d.%m."] = M.new {
pattern = "%d.%m.",
default_kind = "day",
only_valid = true,
}
M.alias["%-d.%-m."] = M.new {
pattern = "%-d.%-m.",
default_kind = "day",
only_valid = true,
}
M.alias["%H:%M:%S"] = M.new {
pattern = "%H:%M:%S",
default_kind = "sec",
only_valid = true,
}
M.alias["%H:%M"] = M.new {
pattern = "%H:%M",
default_kind = "sec",
only_valid = true,
}
return M

94
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/hexcolor.lua

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
local util = require "dial.util"
local common = require "dial.augend.common"
local function cast_u8(n)
if n <= 0 then
return 0
end
if n >= 255 then
return 255
end
return n
end
---@alias colorcase '"upper"' | '"lower"'
---@alias colorkind '"r"' | '"g"' | '"b"' | '"all"'
---@class AugendHexColor
---@implement Augend
---@field datefmt datefmt
---@field kind colorkind
local AugendHexColor = {}
local M = {}
---@param config { case: colorcase }
---@return Augend
function M.new(config)
vim.validate {
case = { config.case, "string", true },
}
return setmetatable({ config = config, kind = "all" }, { __index = AugendHexColor })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendHexColor:find(line, cursor)
return common.find_pattern "#%x%x%x%x%x%x"(line, cursor)
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendHexColor:find_stateful(line, cursor)
local range = common.find_pattern "#%x%x%x%x%x%x"(line, cursor)
if range == nil then
return
end
local relcurpos = cursor - range.from + 1
if relcurpos <= 1 then
self.kind = "all"
elseif relcurpos <= 3 then
self.kind = "r"
elseif relcurpos <= 5 then
self.kind = "g"
else
self.kind = "b"
end
return range
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendHexColor:add(text, addend, cursor)
local r = tonumber(text:sub(2, 3), 16)
local g = tonumber(text:sub(4, 5), 16)
local b = tonumber(text:sub(6, 7), 16)
if cursor == nil then
cursor = 1
end -- default: all
if self.kind == "all" then
-- increment all
r = cast_u8(r + addend)
g = cast_u8(g + addend)
b = cast_u8(b + addend)
cursor = 1
elseif self.kind == "r" then
r = cast_u8(r + addend)
cursor = 3
elseif self.kind == "g" then
g = cast_u8(g + addend)
cursor = 5
else
b = cast_u8(b + addend)
cursor = 7
end
text = "#" .. string.format("%02x", r) .. string.format("%02x", g) .. string.format("%02x", b)
return { text = text, cursor = cursor }
end
return M

180
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/integer.lua

@ -0,0 +1,180 @@ @@ -0,0 +1,180 @@
local common = require "dial.augend.common"
local util = require "dial.util"
---@alias AugendIntegerConfig {}
---@class AugendInteger
---@implement Augend
---@field radix integer
---@field prefix string
---@field natural boolean
---@field query string
---@field case '"upper"' | '"lower"'
---@field delimiter string
---@field delimiter_digits integer
local AugendInteger = {}
local M = {}
---convert integer with given prefix
---@param n integer
---@param radix integer
---@param case '"upper"' | '"lower"'
---@return string
local function tostring_with_radix(n, radix, case)
local floor, insert = math.floor, table.insert
n = floor(n)
if not radix or radix == 10 then
return tostring(n)
end
local digits = "0123456789abcdefghijklmnopqrstuvwxyz"
if case == "upper" then
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
end
local t = {}
local sign = ""
if n < 0 then
sign = "-"
n = -n
end
repeat
local d = (n % radix) + 1
n = floor(n / radix)
insert(t, 1, digits:sub(d, d))
until n == 0
return sign .. table.concat(t, "")
end
---デリミタを挟んだ数字を出力する。
---@param digits string
---@param delimiter string
---@param delimiter_digits integer
---@return string
local function add_delimiter(digits, delimiter, delimiter_digits)
local blocks = {}
if #digits <= delimiter_digits then
return digits
end
for i = 0, #digits - 1, delimiter_digits do
-- 部分文字列の終端は右から i 桁目
local e = #digits - i
-- e から数えて delimiter_digits の数だけ取る
local s = e - delimiter_digits + 1
if s < 1 then
s = 1
end
table.insert(blocks, 1, digits:sub(s, e))
end
return table.concat(blocks, delimiter)
end
---@param radix integer
---@return string
local function radix_to_query_character(radix)
if radix < 2 or radix > 36 then
error(("radix must satisfy 2 <= radix <= 36, got %d"):format(radix))
end
if radix <= 10 then
return "0-" .. tostring(radix - 1)
end
return "0-9a-" .. string.char(86 + radix) .. "A-" .. string.char(54 + radix)
end
---@param config { radix?: integer, prefix?: string, natural?: boolean, case?: '"upper"' | '"lower"', delimiter?: string, delimiter_digits?: number }
---@return Augend
function M.new(config)
vim.validate {
radix = { config.radix, "number", true },
prefix = { config.prefix, "string", true },
natural = { config.natural, "boolean", true },
case = { config.case, "string", true },
delimiter = { config.delimiter, "string", true },
delimiter_digits = { config.delimiter_digits, "number", true },
}
local radix = util.unwrap_or(config.radix, 10)
local prefix = util.unwrap_or(config.prefix, "")
local natural = util.unwrap_or(config.natural, true)
local case = util.unwrap_or(config.case, "lower")
local delimiter = util.unwrap_or(config.delimiter, "")
local delimiter_digits = util.unwrap_or(config.delimiter_digits, 3)
-- local query = prefix .. util.if_expr(natural, "", "-?") .. "[" .. radix_to_query_character(radix) .. delimiter .. "]+"
local query = ([[\V%s%s\(\[%s]\+%s\)\*\[%s]\+]]):format(
prefix,
util.if_expr(natural, "", [[-\?]]),
radix_to_query_character(radix),
vim.fn.escape(delimiter, [[]\/]]),
radix_to_query_character(radix)
)
return setmetatable({
radix = radix,
prefix = prefix,
natural = natural,
query = query,
case = case,
delimiter = delimiter,
delimiter_digits = delimiter_digits,
}, { __index = AugendInteger })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendInteger:find(line, cursor)
return common.find_pattern_regex(self.query)(line, cursor)
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendInteger:add(text, addend, cursor)
local n_prefix = #self.prefix
local subtext = text:sub(n_prefix + 1)
if self.delimiter ~= "" then
local ptn = self.delimiter
if ptn == "." or ptn == "%" or ptn == "^" or ptn == "$" then
ptn = "%" .. ptn
end
subtext = text:gsub(ptn, "")
end
local n = tonumber(subtext, self.radix)
local n_string_digit = subtext:len()
-- local n_actual_digit = tostring(n):len()
local n_actual_digit = tostring_with_radix(n, self.radix, self.case):len()
n = n + addend
if self.natural and n < 0 then
n = 0
end
local digits
if n_string_digit == n_actual_digit then
-- 増減前の数字が0か0始まりでない数字だったら
-- text = ("%d"):format(n)
digits = tostring_with_radix(n, self.radix, self.case)
else
-- 増減前の数字が0始まりの正の数だったら
-- text = ("%0" .. n_string_digit .. "d"):format(n)
local num_string = tostring_with_radix(n, self.radix, self.case)
local pad = ("0"):rep(math.max(n_string_digit - num_string:len(), 0))
digits = pad .. num_string
end
if self.delimiter ~= "" then
digits = add_delimiter(digits, self.delimiter, self.delimiter_digits)
end
text = self.prefix .. digits
cursor = #text
return { text = text, cursor = cursor }
end
M.alias = {
decimal = M.new {},
decimal_int = M.new { natural = false },
binary = M.new { radix = 2, prefix = "0b", natural = true },
octal = M.new { radix = 8, prefix = "0o", natural = true },
hex = M.new { radix = 16, prefix = "0x", natural = true },
}
return M

40
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/misc.lua

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
local util = require "dial.util"
local common = require "dial.augend.common"
local user = require "dial.augend.user"
local M = {}
M.alias = {}
M.alias.markdown_header = user.new {
---@param line string
---@param cursor? integer
---@return textrange?
find = function(line, cursor)
local header_mark_s, header_mark_e = line:find "^#+"
if header_mark_s == nil or header_mark_e >= 7 then
return nil
end
return { from = header_mark_s, to = header_mark_e }
end,
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
add = function(text, addend, cursor)
local n = #text
n = n + addend
if n < 1 then
n = 1
end
if n > 6 then
n = 6
end
text = ("#"):rep(n)
cursor = 1
return { text = text, cursor = cursor }
end,
}
return M

243
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/paren.lua

@ -0,0 +1,243 @@ @@ -0,0 +1,243 @@
local util = require "dial.util"
local common = require "dial.augend.common"
---@class AugendParen
---@implement Augend
---@field config { patterns: string[][], }
---@field find_pattern string
local AugendParen = {}
local M = {}
---text の i 文字目から先が ptn から始まっていたら true を、そうでなければ false を返す。
---@param text string
---@param ptn string
---@param idx integer
---@return boolean
local function precedes(text, ptn, idx)
return text:sub(idx, idx + #ptn - 1) == ptn
end
---括弧のペアを見つける。
---@param line string
---@param open string
---@param close string
---@param nested boolean
---@param cursor_idx integer
---@param escape_char? string
---@return textrange?
local function find_nested_paren(line, open, close, nested, cursor_idx, escape_char)
local depth_at_cursor = nil
local start_idx_stack = {}
local escaped = false
-- idx: 探索している場所
local idx = 1
while idx <= #line do
-- util.dbg{
-- idx = idx,
-- depth_at_cursor = depth_at_cursor,
-- start_idx_stack = start_idx_stack,
-- escaped = escaped,
-- }
-- idx が cursor_idx を超えた瞬間に paren_nested_level_at_cursor を記録
if depth_at_cursor == nil and idx >= cursor_idx then
-- util.dbg"cursor detected!"
depth_at_cursor = #start_idx_stack
end
-- idx を増やしつつ走査。
-- 括弧の open, close または escape char に当たったら特別処理を入れる。
-- open と close が同じパターン列の場合は close を優先。
local from, to = (function()
-- escape 文字: escaped のトグルを行う
if escape_char ~= nil and precedes(line, escape_char, idx) then
-- util.dbg"escape char detected!"
idx = idx + #escape_char
escaped = not escaped
return nil
end
-- close 文字: 括弧の終了
if #start_idx_stack >= 1 and precedes(line, close, idx) then
-- 括弧が閉じきっていないときに close が見つかったら stack を pop
-- util.dbg"close char detected!"
idx = idx + #close
local close_end_idx = idx - 1
-- idx が cursor_idx を超えた瞬間に paren_nested_level_at_cursor を記録
if depth_at_cursor == nil and close_end_idx >= cursor_idx then
-- util.dbg"cursor detected!"
depth_at_cursor = #start_idx_stack
end
if escaped then
escaped = false
return nil
end
escaped = false
local start_idx = table.remove(start_idx_stack, #start_idx_stack)
-- カーソル下の深さの括弧を抜けたらその時点で探索終了
if depth_at_cursor ~= nil and #start_idx_stack <= depth_at_cursor then
return start_idx, idx - 1
end
return nil
end
-- open 文字: 括弧の開始
if precedes(line, open, idx) then
-- util.dbg"open char detected!"
idx = idx + #open
if escaped then
escaped = false
return nil
end
escaped = false
if nested or #start_idx_stack == 0 then
table.insert(start_idx_stack, idx - #open)
end
return nil
end
escaped = false
idx = idx + 1
end)()
-- range が見つかった場合は速やかに本関数から return する
if from ~= nil then
return { from = from, to = to }
end
end
-- nest がすべて解決しなかったので nil を返す
return nil
end
---@param config { patterns?: string[][], escape_char?: string, cyclic?: boolean, nested?: boolean }
---@return Augend
function M.new(config)
if config.patterns == nil then
config.patterns = { { [[']], [[']] }, { [["]], [["]] } }
end
if config.nested == nil then
config.nested = true
end
if config.cyclic == nil then
config.cyclic = true
end
vim.validate {
cyclic = { config.cyclic, "boolean" },
}
util.validate_list("patterns", config.patterns, "table")
return setmetatable({ config = config }, { __index = AugendParen })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendParen:find(line, cursor)
---@type textrange?
local tmp_range = nil
for _, ptn in ipairs(self.config.patterns) do
local open = ptn[1]
local close = ptn[2]
local range = find_nested_paren(line, open, close, self.config.nested, cursor, self.config.escape_char)
if range ~= nil then
if tmp_range == nil then
tmp_range = range
else
local rel = range.from > cursor
local tmp_rel = tmp_range.from > cursor
if tmp_rel and rel then
tmp_range = util.if_expr(tmp_range.from < range.from, tmp_range, range)
elseif tmp_rel and not rel then
tmp_range = range
elseif not tmp_rel and rel then
else
tmp_range = util.if_expr(tmp_range.from > range.from, tmp_range, range)
end
end
end
end
return tmp_range
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendParen:add(text, addend, cursor)
local n_patterns = #self.config.patterns
local n = 1
for i, elem in ipairs(self.config.patterns) do
local open = elem[1]
if precedes(text, open, 1) then
n = i
break
end
end
local old_paren_pair = self.config.patterns[n]
-- util.dbg{old_paren_pair = old_paren_pair, text = text}
local text_inner = text:sub(#old_paren_pair[1] + 1, #text - #old_paren_pair[2])
if self.config.cyclic then
n = (n + addend - 1) % n_patterns + 1
else
n = n + addend
if n < 1 then
n = 1
end
if n > n_patterns then
n = n_patterns
end
end
local new_paren_pair = self.config.patterns[n]
local new_paren_open = new_paren_pair[1]
local new_paren_close = new_paren_pair[2]
text = new_paren_open .. text_inner .. new_paren_close
cursor = #text
return { text = text, cursor = cursor }
end
M.alias = {
quote = M.new {
patterns = { { "'", "'" }, { '"', '"' } },
nested = false,
escape_char = [[\]],
cyclic = true,
},
brackets = M.new {
patterns = { { "(", ")" }, { "[", "]" }, { "{", "}" } },
nested = true,
cyclic = true,
},
lua_str_literal = M.new {
patterns = {
{ "'", "'" },
{ '"', '"' },
{ "[[", "]]" },
{ "[=[", "]=]" },
{ "[==[", "]==]" },
{ "[===[", "]===]" },
},
nested = false,
cyclic = false,
},
rust_str_literal = M.new {
patterns = {
{ '"', '"' },
{ 'r#"', '"#' },
{ 'r##"', '"##' },
{ 'r###"', '"###' },
},
nested = false,
cyclic = false,
},
}
return M

107
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/semver.lua

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
local util = require "dial.util"
local common = require "dial.augend.common"
local function cast_u8(n)
if n <= 0 then
return 0
end
if n >= 255 then
return 255
end
return n
end
---@class AugendSemver
---@implement Augend
---@field kind '"major"' | '"minor"' | '"patch"'
local AugendSemver = {}
local M = {}
---@param config {}
---@return Augend
function M.new(config)
vim.validate {}
return setmetatable({ kind = "patch" }, { __index = AugendSemver })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendSemver:find(line, cursor)
return common.find_pattern "%d+%.%d+%.%d+"(line, cursor)
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendSemver:find_stateful(line, cursor)
local range = common.find_pattern "%d+%.%d+%.%d+"(line, cursor)
if range == nil then
return
end
if cursor == nil then
-- always increments patch version in VISUAL mode
self.kind = "patch"
return range
end
local relcurpos = cursor - range.from + 1
local text = line:sub(range.from, range.to)
local iterator = text:gmatch "%d+"
local major = iterator()
local minor = iterator()
if relcurpos <= 0 then
self.kind = "patch"
elseif relcurpos <= #major then
self.kind = "major"
elseif relcurpos <= #major + #minor + 1 then
self.kind = "minor"
else
self.kind = "patch"
end
return range
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendSemver:add(text, addend, cursor)
local iterator = text:gmatch "%d+"
local major = tonumber(iterator())
local minor = tonumber(iterator())
local patch = tonumber(iterator())
if cursor == nil then
cursor = 0
end -- default: all
if self.kind == "major" then
major = major + addend
if addend > 0 then
minor = 0
patch = 0
end
cursor = #tostring(major)
elseif self.kind == "minor" then
minor = minor + addend
if addend > 0 then
patch = 0
end
cursor = #tostring(major) + 1 + #tostring(minor)
else -- (if cursor == 6 or cursor == 7 then)
patch = patch + addend
cursor = #tostring(major) + 1 + #tostring(minor) + 1 + #tostring(patch)
end
text = ("%d.%d.%d"):format(major, minor, patch)
return { text = text, cursor = cursor }
end
M.alias = {
semver = M.new {},
}
return M

37
etc/soft/nvim/+plugins/dial.nvim/lua/dial/augend/user.lua

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
local util = require "dial.util"
local common = require "dial.augend.common"
---@alias AugendUserConfig { find: findf, add: addf }
---@class AugendUser
---@implement Augend
---@field config AugendUserConfig
local AugendUser = {}
---@param config AugendUserConfig
---@return Augend
function AugendUser.new(config)
vim.validate {
find = { config.find, "function" },
add = { config.add, "function" },
}
return setmetatable({ config = config }, { __index = AugendUser })
end
---@param line string
---@param cursor? integer
---@return textrange?
function AugendUser:find(line, cursor)
return self.config.find(line, cursor)
end
---@param text string
---@param addend integer
---@param cursor? integer
---@return { text?: string, cursor?: integer }
function AugendUser:add(text, addend, cursor)
return self.config.add(text, addend, cursor)
end
return AugendUser

224
etc/soft/nvim/+plugins/dial.nvim/lua/dial/command.lua

@ -0,0 +1,224 @@ @@ -0,0 +1,224 @@
-- Interface between Neovim and dial.nvim.
-- Functions in this module edits the buffer and read variables defined by Neovim.
local config = require "dial.config"
local handler = require("dial.handle").new()
local util = require "dial.util"
local M = {}
VISUAL_BLOCK = string.char(22)
---Select the most appropriate augend from given augend group (in NORMAL mode).
---@param group_name? string
function M.select_augend_normal(group_name)
if group_name == nil and vim.v.register == "=" then
group_name = vim.fn.getreg("=", 1)
else
group_name = util.unwrap_or(group_name, "default")
end
local augends = config.augends.group[group_name]
if augends == nil then
error(("undefined augend group name: %s"):format(group_name))
end
local count = vim.v.count
if count ~= 0 then
handler:set_count(count)
else
handler:set_count(1)
end
local col = vim.fn.col "."
local line = vim.fn.getline "."
handler:select_augend(line, col, augends)
end
---Select the most appropriate augend from given augend group (in VISUAL mode).
---@param group_name? string
function M.select_augend_visual(group_name)
if group_name == nil and vim.v.register == "=" then
group_name = vim.fn.getreg("=", 1)
else
group_name = util.unwrap_or(group_name, "default")
end
local augends = config.augends.group[group_name]
if augends == nil then
error(("undefined augend group name: %s"):format(group_name))
end
local count = vim.v.count
if count ~= 0 then
handler:set_count(count)
else
handler:set_count(1)
end
local mode = vim.fn.mode(0)
---@type integer
local _, line1, col1, _ = unpack(vim.fn.getpos "v")
---@type integer
local _, line2, col2, _ = unpack(vim.fn.getpos ".")
if mode == "V" then
-- line-wise visual mode
local line_min = math.min(line1, line2)
local line_max = math.max(line1, line2)
local lines = {}
for line_num = line_min, line_max, 1 do
table.insert(lines, vim.fn.getline(line_num))
end
handler:select_augend_visual(lines, nil, augends)
elseif mode == VISUAL_BLOCK then
-- block-wise visual mode
local line_min = math.min(line1, line2)
local line_max = math.max(line1, line2)
local col_min = math.min(col1, col2)
local col_max = math.max(col1, col2)
local lines = {}
for line_num = line_min, line_max, 1 do
local line = vim.fn.getline(line_num)
table.insert(lines, line:sub(col_min))
end
handler:select_augend_visual(lines, nil, augends)
else
-- char-wise visual mode
local line_min = math.min(line1, line2)
local col_min = math.min(col1, col2)
---@type string
local text = vim.fn.getline(line_min)
if line1 == line2 then
local col_max = math.max(col1, col2)
text = text:sub(col_min, col_max)
else
text = text:sub(col_min)
end
handler:select_augend(text, nil, augends)
end
end
---Select the most appropriate augend from given augend group (in VISUAL mode with g<C-a>).
---@param group_name? string
function M.select_augend_gvisual(group_name)
M.select_augend_visual(group_name)
end
---The process that runs when operator is called (in NORMAL mode).
---@param direction direction
function M.operator_normal(direction)
local col = vim.fn.col "."
local line_num = vim.fn.line "."
local line = vim.fn.getline "."
local result = handler:operate(line, col, direction)
if result.line ~= nil then
vim.fn.setline(".", result.line)
end
if result.cursor ~= nil then
vim.fn.cursor { line_num, result.cursor }
end
end
---The process that runs when operator is called (in VISUAL mode).
---@param direction direction
---@param stairlike boolean
function M.operator_visual(direction, stairlike)
local mode = vim.fn.visualmode(0)
local _, line1, col1, _ = unpack(vim.fn.getpos "'[")
local _, line2, col2, _ = unpack(vim.fn.getpos "']")
local tier = 1
---@param lnum integer
---@param range {from: integer, to?: integer}
local function operate_line(lnum, range)
local line = vim.fn.getline(lnum)
local result = handler:operate_visual(line, range, direction, tier)
if result.line ~= nil then
vim.fn.setline(lnum, result.line)
if stairlike then
tier = tier + 1
end
end
end
local line_start = util.if_expr(line1 < line2, line1, line2)
local line_end = util.if_expr(line1 < line2, line2, line1)
if mode == "v" then
local col_start = util.if_expr(line1 < line2, col1, col2)
local col_end = util.if_expr(line1 < line2, col2, col1)
if line_start == line_end then
operate_line(line_start, { from = math.min(col1, col2), to = math.max(col1, col2) })
else
local lnum = line_start
operate_line(lnum, { from = col_start })
for idx = line_start + 1, line_end - 1, 1 do
operate_line(idx, { from = 1 })
end
operate_line(line_end, { from = 1, to = col_end })
end
elseif mode == "V" then
for lnum = line_start, line_end, 1 do
operate_line(lnum, { from = 1 })
end
else
local col_start = util.if_expr(col1 < col2, col1, col2)
local col_end = util.if_expr(col1 < col2, col2, col1)
for lnum = line_start, line_end, 1 do
operate_line(lnum, { from = col_start, to = col_end })
end
end
end
---The process that runs when text object is called.
---Call handler.findTextRange() to select a range based on the information in the current line.
---Also, for dot repeat, it receives the value of the specified counter and updates the addend.
function M.textobj()
local count = vim.v.count
if count ~= 0 then
handler:set_count(count)
end
local col = vim.fn.col "."
local line = vim.fn.getline "."
handler:find_text_range(line, col)
end
function M.command(direction, line_range, groups)
local group_name = groups[1]
if group_name == nil and vim.v.register == "=" then
group_name = vim.fn.getreg("=", 1)
else
group_name = util.unwrap_or(group_name, "default")
end
local augends = config.augends.group[group_name]
if augends == nil then
error(("undefined augend group name: %s"):format(group_name))
end
local line_min = line_range.from
local line_max = line_range.to
local lines = {}
for line_num = line_min, line_max, 1 do
table.insert(lines, vim.fn.getline(line_num))
end
handler:select_augend_visual(lines, nil, augends)
---@param lnum integer
---@param range {from: integer, to?: integer}
local function operate_line(lnum, range)
local line = vim.fn.getline(lnum)
local result = handler:operate_visual(line, range, direction, 1)
if result.line ~= nil then
vim.fn.setline(lnum, result.line)
end
end
for lnum = line_min, line_max, 1 do
operate_line(lnum, { from = 1 })
end
end
return M

58
etc/soft/nvim/+plugins/dial.nvim/lua/dial/config.lua

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
local augend = require "dial.augend"
local util = require "dial.util"
local M = {}
---@class augends
---@field group table<string, Augend[]>
M.augends = {
group = {
default = {
augend.integer.alias.decimal,
augend.integer.alias.hex,
augend.date.new {
pattern = "%Y/%m/%d",
default_kind = "day",
},
augend.date.new {
pattern = "%Y-%m-%d",
default_kind = "day",
},
augend.date.new {
pattern = "%m/%d",
default_kind = "day",
only_valid = true,
},
augend.date.new {
pattern = "%H:%M",
default_kind = "day",
only_valid = true,
},
augend.constant.alias.ja_weekday_full,
},
},
}
---新しいグループを登録する。
---@param tbl table<string, Augend[]>
function M.augends:register_group(tbl)
-- TODO: validate augends
for name, augends in pairs(tbl) do
local nil_keys = util.index_with_nil_value(augends)
if #nil_keys ~= 0 then
local str_nil_keys = table.concat(nil_keys, ", ")
error(("Failed to register augend group '%s'. it contains nil at index %s."):format(name, str_nil_keys))
end
self.group[name] = augends
end
end
---グループを取得する。
---@param group_name string
function M.augends:get(group_name)
return self.group[group_name]
end
return M

252
etc/soft/nvim/+plugins/dial.nvim/lua/dial/handle.lua

@ -0,0 +1,252 @@ @@ -0,0 +1,252 @@
-- The business logic of dial.nvim.
-- To achieve dot repeating, dial.nvim divides the increment/decrement process
-- into the following three parts:
--
-- 1. Select the rule:
-- determine the augend rule to increment/decrement from the current line and cursor position.
--
-- 2. Select the range:
-- determine the range of strings (text object) on the buffer
-- to be incremented/decremented based on the rule determined above.
--
-- 3. Edit the buffer:
-- actually increment/decrement the string (operator).
--
-- In NORMAL mode <C-a>/<C-x>, 1, 2, and 3 are called.
-- In NORMAL mode dot repeating, only 2 and 3 are called.
--
-- `Handler` class, defined in this module, saves information such as augend rule
-- and text range as a state, and performs the actual increment/decrement operation
-- by calling the augend function.
-- Text on buffers is not manipulated from the handler instance.
---Scores used to determine which rules to operate.
---@class Score
---@field cursor_loc integer
---@field start_pos integer
---@field neg_end_pos integer
local Score = {}
local util = require "dial.util"
---constructor
---@param cursor_loc integer
---@param start_pos integer
---@param neg_end_pos integer
---@return Score
function Score.new(cursor_loc, start_pos, neg_end_pos)
return setmetatable(
{ cursor_loc = cursor_loc, start_pos = start_pos, neg_end_pos = neg_end_pos },
{ __index = Score }
)
end
---Calculate the score.
---@param s integer
---@param e integer
---@param cursor? integer
---@return {cursor_loc: integer, start_pos: integer, neg_end_pos: integer}
local function calc_score(s, e, cursor)
local cursor_loc
if (cursor or 0) > e then
cursor_loc = 2
elseif (cursor or 0) < s then
cursor_loc = 1
else
cursor_loc = 0
end
return { cursor_loc = cursor_loc, start_pos = s, neg_end_pos = -e }
end
---Calculate the score from the cursor position and text range.
---@param s integer
---@param e integer
---@param cursor? integer
---@return Score
function Score.from_cursor(s, e, cursor)
local tbl = calc_score(s, e, cursor)
return setmetatable(tbl, { __index = Score })
end
---Compare the score.
---If and only if `self` has the higher priority than `rhs`, returns true.
---@param rhs Score
function Score.cmp(self, rhs)
if self.cursor_loc < rhs.cursor_loc then
return true
end
if self.cursor_loc > rhs.cursor_loc then
return false
end
if self.start_pos < rhs.start_pos then
return true
end
if self.start_pos > rhs.start_pos then
return false
end
if self.neg_end_pos < rhs.neg_end_pos then
return true
end
if self.neg_end_pos > rhs.neg_end_pos then
return false
end
return false
end
---@class Handler
---@field count integer
---@field range textrange?
---@field active_augend Augend?
local Handler = {}
function Handler.new()
return setmetatable({ count = 1, range = nil, active_augend = nil }, { __index = Handler })
end
---Get addend value.
---@param direction direction
---@return integer
function Handler:get_addend(direction)
if direction == "increment" then
return self.count
else
return -self.count
end
end
---Set count value.
---@param count integer
function Handler:set_count(count)
self.count = count
end
---Select the most appropriate augend (in NORMAL mode).
---@param line string
---@param cursor? integer
---@param augends Augend[]
function Handler:select_augend(line, cursor, augends)
local interim_augend = nil
local interim_score = Score.new(3, 0, 0) -- score with the lowest priority
for _, augend in ipairs(augends) do
(function()
---@type textrange?
local range = nil
if augend.find_stateful == nil then
range = augend:find(line, cursor)
else
range = augend:find_stateful(line, cursor)
end
if range == nil then
return
end
local score = Score.from_cursor(range.from, range.to, cursor)
if score:cmp(interim_score) then
interim_augend = augend
interim_score = score
end
end)()
end
self.active_augend = interim_augend
end
---Select the most appropriate augend (in VISUAL mode).
---@param lines string[]
---@param cursor? integer
---@param augends Augend[]
function Handler:select_augend_visual(lines, cursor, augends)
local interim_augend = nil
local interim_score = Score.new(3, 0, 0) -- 最も優先度の低いスコア
for _, line in ipairs(lines) do
for _, augend in ipairs(augends) do
(function()
---@type textrange?
local range = nil
if augend.find_stateful == nil then
range = augend:find(line, cursor)
else
range = augend:find_stateful(line, cursor)
end
if range == nil then
return -- equivalent to break (of nested for block)
end
local score = Score.from_cursor(range.from, range.to, cursor)
if score:cmp(interim_score) then
interim_augend = augend
interim_score = score
end
end)()
end
if interim_augend ~= nil then
self.active_augend = interim_augend
return
end
end
end
---The process that runs when operator is called (in NORMAL mode).
---@param line string
---@param cursor integer
---@param direction direction
---@return {line?: string, cursor?: integer}
function Handler:operate(line, cursor, direction)
if self.range == nil or self.active_augend == nil then
return {}
end
local text = line:sub(self.range.from, self.range.to)
local addend = self:get_addend(direction)
local add_result = self.active_augend:add(text, addend, cursor)
local new_line = nil
local new_cursor = nil
if add_result.text ~= nil then
new_line = line:sub(1, self.range.from - 1) .. add_result.text .. line:sub(self.range.to + 1)
end
if add_result.cursor ~= nil then
new_cursor = self.range.from - 1 + add_result.cursor
end
return { line = new_line, cursor = new_cursor }
end
---The process that runs when operator is called (in VISUAL mode).
---@param selected_range {from: integer, to?: integer}
---@param direction direction
---@param tier integer
---@return {result?: string}
function Handler:operate_visual(line, selected_range, direction, tier)
if self.active_augend == nil then
return {}
end
tier = util.unwrap_or(tier, 1)
local line_partial = line:sub(selected_range.from, selected_range.to)
local range = self.active_augend:find(line_partial, 0)
if range == nil then
return {}
end
local addend = self:get_addend(direction)
local from = selected_range.from + range.from - 1
local to = selected_range.from + range.to - 1
local text = line:sub(from, to)
local add_result = self.active_augend:add(text, addend * tier)
local newline = nil
if add_result.text ~= nil then
newline = line:sub(1, from - 1) .. add_result.text .. line:sub(to + 1)
end
return { line = newline }
end
---Set self.range to the target range of the currently active augend (without side effects).
---@param line any
---@param cursor any
function Handler:find_text_range(line, cursor)
if self.active_augend == nil then
self.range = nil
return
end
self.range = self.active_augend:find(line, cursor)
end
return Handler

67
etc/soft/nvim/+plugins/dial.nvim/lua/dial/map.lua

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
local M = {}
local command = require "dial.command"
local util = require "dial.util"
---Sandwich input string between <Cmd> and <CR>.
---@param body string
local function cmdcr(body)
local cmd_sequences = "<Cmd>"
local cr_sequences = "<CR>"
return cmd_sequences .. body .. cr_sequences
end
---Output command sequence which provides dial operation.
---@param direction direction
---@param mode mode
---@param group_name? string
local function _cmd_sequence(direction, mode, group_name)
local select
if group_name == nil then
select = cmdcr([[lua require"dial.command".select_augend_]] .. mode .. "()")
else
select = cmdcr([[lua require"dial.command".select_augend_]] .. mode .. [[("]] .. group_name .. [[")]])
end
-- command.select_augend_normal(vim.v.count, group_name)
local setopfunc = cmdcr([[let &opfunc="dial#operator#]] .. direction .. "_" .. mode .. [["]])
local textobj = util.if_expr(mode == "normal", cmdcr [[lua require("dial.command").textobj()]], "")
return select .. setopfunc .. "g@" .. textobj
end
---@param group_name? string
---@return string
function M.inc_normal(group_name)
return _cmd_sequence("increment", "normal", group_name)
end
---@param group_name? string
---@return string
function M.dec_normal(group_name)
return _cmd_sequence("decrement", "normal", group_name)
end
---@param group_name? string
---@return string
function M.inc_visual(group_name)
return _cmd_sequence("increment", "visual", group_name)
end
---@param group_name? string
---@return string
function M.dec_visual(group_name)
return _cmd_sequence("decrement", "visual", group_name)
end
---@param group_name? string
---@return string
function M.inc_gvisual(group_name)
return _cmd_sequence("increment", "gvisual", group_name)
end
---@param group_name? string
---@return string
function M.dec_gvisual(group_name)
return _cmd_sequence("decrement", "gvisual", group_name)
end
return M

18
etc/soft/nvim/+plugins/dial.nvim/lua/dial/types.lua

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
-- define type annotations
-- (See: https://github.com/sumneko/lua-language-server/wiki/Annotations)
---@alias direction '"increment"' | '"decrement"'
---@alias mode '"normal"' | '"visual"' | '"gvisual"'
---@alias textrange {from: integer, to: integer}
---@alias addresult {text?: string, cursor?: integer}
---@alias findf fun(line: string, cursor?: integer) -> textrange?
---@alias addf fun(text: string, addend: integer, cursor?: integer) -> addresult?
---@alias findmethod fun(self: Augend, line: string, cursor?: integer) -> textrange?
---@alias addmethod fun(self: Augend, text: string, addend: integer, cursor?: integer) -> addresult?
---@class Augend
---@field find findmethod
---@field find_stateful? findmethod
---@field add addmethod

189
etc/soft/nvim/+plugins/dial.nvim/lua/dial/util.lua

@ -0,0 +1,189 @@ @@ -0,0 +1,189 @@
-- utils
local M = {}
---@generic T
---@param cond boolean
---@param branch_true T
---@param branch_false T
---@return T
function M.if_expr(cond, branch_true, branch_false)
if cond then
return branch_true
end
return branch_false
end
---@generic T
---@param x T | nil
---@param default T
---@return T
function M.unwrap_or(x, default)
if x == nil then
return default
end
return x
end
function M.Set(list)
local set = {}
for _, l in ipairs(list) do
set[l] = true
end
return set
end
-- Check if the argument is a valid list (which does not contain nil).
---@param name string
---@param list any[]
---@param arg1 string | function
---@param arg2? string
function M.validate_list(name, list, arg1, arg2)
if not vim.tbl_islist(list) then
error(("%s is not list."):format(name))
end
if type(arg1) == "string" then
local typename, _ = arg1, arg2
local count_idx = 0
for idx, value in ipairs(list) do
count_idx = idx
if type(value) ~= typename then
error(("Type error: %s[%d] should have type %s, got %s"):format(name, idx, typename, type(value)))
end
end
if count_idx ~= #list then
error(("The %s[%d] is nil. nil is not allowed in a list."):format(name, count_idx + 1))
end
else
local checkf, errormsg = arg1, arg2
local count_idx = 0
for idx, value in ipairs(list) do
count_idx = idx
local ok, err = checkf(value)
if not ok then
error(("List validation error: %s[%d] does not satisfy '%s' (%s)"):format(name, idx, errormsg, err))
end
end
if count_idx ~= #list then
error(("The %s[%d] is nil. nil is not allowed in valid list."):format(name, count_idx + 1))
end
end
end
---Returns the indices with the value nil.
---returns an index array
---@param tbl array
---@return integer[]
function M.index_with_nil_value(tbl)
-- local maxn, k = 0, nil
-- repeat
-- k = next( table, k )
-- if type( k ) == 'number' and k > maxn then
-- maxn = k
-- end
-- until not k
-- M.dbg(maxn)
local maxn = table.maxn(tbl)
local nil_keys = {}
for i = 1, maxn, 1 do
if tbl[i] == nil then
table.insert(nil_keys, i)
end
end
return nil_keys
end
function M.filter(fn, ary)
local a = {}
for i = 1, #ary do
if fn(ary[i]) then
table.insert(a, ary[i])
end
end
return a
end
function M.filter_map(fn, ary)
local a = {}
for i = 1, #ary do
if fn(ary[i]) ~= nil then
table.insert(a, fn(ary[i]))
end
end
return a
end
function M.filter_map_zip(fn, ary)
local a = {}
for i = 1, #ary do
if fn(ary[i]) ~= nil then
table.insert(a, { ary[i], fn(ary[i]) })
end
end
return a
end
function M.tostring_with_base(n, b, wid, pad)
n = math.floor(n)
if not b or b == 10 then
return tostring(n)
end
local digits = "0123456789abcdefghijklmnopqrstuvwxyz"
local t = {}
if n < 0 then
-- be positive
n = -n
end
repeat
local d = (n % b) + 1
n = math.floor(n / b)
table.insert(t, 1, digits:sub(d, d))
until n == 0
local text = table.concat(t, "")
if wid then
if #text < wid then
if pad == nil then
pad = " "
end
local padding = pad:rep(wid - #text)
return padding .. text
end
end
return text
end
-- util.try_get_keys({foo = "bar", hoge = "fuga", teka = "pika"}, ["teka", "foo"])
-- -> ["pika", "bar"]
function M.try_get_keys(tbl, keylst)
if not vim.tbl_islist(keylst) then
return nil, "the 2nd argument is not list."
end
local values = {}
for _, key in ipairs(keylst) do
local val = tbl[key]
if val ~= nil then
table.insert(values, val)
else
local errmsg = ("The value corresponding to the key '%s' is not found in the table."):format(key)
return nil, errmsg
end
end
return values
end
---return the iterator returning UTF-8 based characters
---@param text string
---@return fun(): string | nil
function M.chars(text)
return text:gmatch "[%z\1-\127\194-\244][\128-\191]*"
end
return M

38
etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/case_spec.lua

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
local case = require("dial.augend").case
describe("Test of case between camelCase and snake_case (cyclic = true):", function()
local augend = case.new { types = { "camelCase", "snake_case" }, cyclic = true }
describe("find function", function()
it("can find camelCase or snake_case identifier", function()
assert.are.same(augend:find("fooBar", 1), { from = 1, to = 6 })
assert.are.same(augend:find("fooBarBaz", 1), { from = 1, to = 9 })
assert.are.same(augend:find("fooBarBazPiyo9", 1), { from = 1, to = 14 })
assert.are.same(augend:find("foo_bar", 1), { from = 1, to = 7 })
end)
it("skips non-camelCase and non-snake_case word", function()
assert.are.same(augend:find("foo_Bar_baz_foo", 1), nil)
assert.are.same(augend:find("foo_Bar", 1), nil)
assert.are.same(augend:find("FooBar", 1), nil)
assert.are.same(augend:find("1foo_bar", 1), nil)
end)
it("skips identifier constructed from one word", function()
assert.are.same(augend:find("foo", 1), nil)
assert.are.same(augend:find("foo bar_baz", 1), { from = 5, to = 11 })
end)
it("chooses the one in front", function()
assert.are.same(augend:find("foo_bar fooBar", 1), { from = 1, to = 7 })
assert.are.same(augend:find("foo_bar fooBar", 8), { from = 9, to = 14 })
assert.are.same(augend:find("fooBar foo_bar", 1), { from = 1, to = 6 })
end)
end)
describe("add function", function()
it("can convert cases", function()
assert.are.same(augend:add("fooBar", 1, 1), { text = "foo_bar", cursor = 7 })
assert.are.same(augend:add("fooBar", 2, 1), { cursor = 6 })
assert.are.same(augend:add("foo_bar", 1, 1), { text = "fooBar", cursor = 6 })
assert.are.same(augend:add("foo_bar_baz", 1, 1), { text = "fooBarBaz", cursor = 9 })
end)
end)
end)

5
etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/constant_spec.lua

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
local constant = require("dial.augend").constant
describe("Test of constant between two words", function()
local augend = constant.new { elements = { "true", "false" } }
end)

297
etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/date_spec.lua

@ -0,0 +1,297 @@ @@ -0,0 +1,297 @@
local date = require("dial.augend").date
describe([[Test of date with format "%Y-%m-%d":]], function()
local augend = date.alias["%Y-%m-%d"]
describe("find function", function()
it("can find dates in a given format", function()
assert.are.same(augend:find_stateful("date: 2022-10-16", 1), { from = 7, to = 16 })
assert.are.same(augend.kind, "day")
assert.are.same(augend:find_stateful("date: 2022-10-16", 6), { from = 7, to = 16 })
assert.are.same(augend.kind, "day")
assert.are.same(augend:find_stateful("date: 2022-10-16", 7), { from = 7, to = 16 })
assert.are.same(augend.kind, "year")
assert.are.same(augend:find_stateful("date: 2022-10-16", 10), { from = 7, to = 16 })
assert.are.same(augend.kind, "year")
assert.are.same(augend:find_stateful("date: 2022-10-16", 11), { from = 7, to = 16 })
assert.are.same(augend.kind, "month")
assert.are.same(augend:find_stateful("date: 2022-10-16", 13), { from = 7, to = 16 })
assert.are.same(augend.kind, "month")
assert.are.same(augend:find_stateful("date: 2022-10-16", 14), { from = 7, to = 16 })
assert.are.same(augend.kind, "day")
assert.are.same(augend:find_stateful("date: 2022-10-16", 16), { from = 7, to = 16 })
assert.are.same(augend.kind, "day")
end)
it("cannot find dates in unspecified format", function()
assert.are.same(augend:find("2022/10/16", 1), nil)
end)
it("can find dates in a given format but not exist", function()
assert.are.same(augend:find("2022-10-32", 1), { from = 1, to = 10 })
end)
end)
describe("add function", function()
it("can inc/dec days", function()
augend.kind = "day"
assert.are.same(augend:add("2022-10-16", 1), { text = "2022-10-17", cursor = 10 })
assert.are.same(augend:add("2022-10-16", 1, 1), { text = "2022-10-17", cursor = 10 })
assert.are.same(augend:add("2022-10-16", 1, 7), { text = "2022-10-17", cursor = 10 })
assert.are.same(augend:add("2022-10-16", 1, 10), { text = "2022-10-17", cursor = 10 })
assert.are.same(augend:add("2022-10-16", 5), { text = "2022-10-21", cursor = 10 })
assert.are.same(augend:add("2022-10-16", 21), { text = "2022-11-06", cursor = 10 })
assert.are.same(augend:add("2022-10-16", -21), { text = "2022-09-25", cursor = 10 })
assert.are.same(augend:add("2022-12-16", 21), { text = "2023-01-06", cursor = 10 })
end)
it("can inc/dec months", function()
augend.kind = "month"
assert.are.same(augend:add("2022-10-16", 1), { text = "2022-11-16", cursor = 7 })
assert.are.same(augend:add("2022-10-16", 1, 1), { text = "2022-11-16", cursor = 7 })
assert.are.same(augend:add("2022-10-16", 1, 7), { text = "2022-11-16", cursor = 7 })
assert.are.same(augend:add("2022-10-16", 1, 10), { text = "2022-11-16", cursor = 7 })
assert.are.same(augend:add("2022-10-16", 2), { text = "2022-12-16", cursor = 7 })
assert.are.same(augend:add("2022-10-16", 5), { text = "2023-03-16", cursor = 7 })
assert.are.same(augend:add("2022-10-16", -5), { text = "2022-05-16", cursor = 7 })
assert.are.same(augend:add("2022-01-31", 1), { text = "2022-03-03", cursor = 7 })
end)
it("can inc/dec years", function()
augend.kind = "year"
assert.are.same(augend:add("2022-10-16", 1), { text = "2023-10-16", cursor = 4 })
assert.are.same(augend:add("2022-10-16", 1, 1), { text = "2023-10-16", cursor = 4 })
assert.are.same(augend:add("2022-10-16", 1, 7), { text = "2023-10-16", cursor = 4 })
assert.are.same(augend:add("2022-10-16", 1, 10), { text = "2023-10-16", cursor = 4 })
assert.are.same(augend:add("2022-10-16", 2), { text = "2024-10-16", cursor = 4 })
assert.are.same(augend:add("2022-10-16", 5), { text = "2027-10-16", cursor = 4 })
assert.are.same(augend:add("2022-10-16", -5), { text = "2017-10-16", cursor = 4 })
assert.are.same(augend:add("2020-02-29", 1), { text = "2021-03-01", cursor = 4 })
end)
it("correct date and increment days", function()
augend.kind = "day"
assert.are.same(augend:add("2022-10-32", 1), { text = "2022-11-02", cursor = 10 })
end)
end)
end)
describe([[Test of date with format "%-m/%-d":]], function()
local augend = date.alias["%-m/%-d"]
describe("find function", function()
it("can find dates in a given format", function()
assert.are.same(augend:find("10/16", 1), { from = 1, to = 5 })
end)
it("cannot find dates in unspecified format", function()
assert.are.same(augend:find("10-16", 1), nil)
end)
it("can find dates in a given format but not exist", function()
assert.are.same(augend:find("10/32", 1), nil)
end)
end)
describe("add function", function()
it("can inc/dec days", function()
augend.kind = "day"
assert.are.same(augend:add("10/16", 1), { text = "10/17", cursor = 5 })
assert.are.same(augend:add("10/16", 1, 1), { text = "10/17", cursor = 5 })
assert.are.same(augend:add("10/16", 1, 5), { text = "10/17", cursor = 5 })
assert.are.same(augend:add("10/16", 5), { text = "10/21", cursor = 5 })
assert.are.same(augend:add("10/16", 21), { text = "11/6", cursor = 4 })
assert.are.same(augend:add("10/16", -21), { text = "9/25", cursor = 4 })
assert.are.same(augend:add("12/16", 21), { text = "1/6", cursor = 3 })
end)
it("can inc/dec months", function()
augend.kind = "month"
assert.are.same(augend:add("10/16", 1), { text = "11/16", cursor = 2 })
assert.are.same(augend:add("10/16", 1, 1), { text = "11/16", cursor = 2 })
assert.are.same(augend:add("10/16", 1, 5), { text = "11/16", cursor = 2 })
assert.are.same(augend:add("10/16", 2), { text = "12/16", cursor = 2 })
assert.are.same(augend:add("10/16", 5), { text = "3/16", cursor = 1 })
assert.are.same(augend:add("10/16", -5), { text = "5/16", cursor = 1 })
end)
end)
end)
describe([[Test of date with format "%Y年%-m月%-d日(%ja)":]], function()
local augend = date.alias["%Y年%-m月%-d日(%ja)"]
describe("find function", function()
it("can find dates in a given format", function()
assert.are.same(augend:find("2022年10月16日(日)", 1), { from = 1, to = 22 })
end)
it("cannot find dates in unspecified format", function()
assert.are.same(augend:find("2022/10/16", 1), nil)
end)
it("can find dates in a given format but not exist", function()
assert.are.same(augend:find("2022年10月16日(金)", 1), { from = 1, to = 22 })
assert.are.same(augend:find("2022年10月32日(火)", 1), { from = 1, to = 22 })
end)
end)
describe("add function", function()
it("can inc/dec days", function()
augend.kind = "day"
assert.are.same(augend:add("2022年10月16日(日)", 1), { text = "2022年10月17日(月)", cursor = 14 })
assert.are.same(
augend:add("2022年10月16日(日)", 1, 1),
{ text = "2022年10月17日(月)", cursor = 14 }
)
assert.are.same(
augend:add("2022年10月16日(日)", 1, 7),
{ text = "2022年10月17日(月)", cursor = 14 }
)
assert.are.same(
augend:add("2022年10月16日(日)", 1, 10),
{ text = "2022年10月17日(月)", cursor = 14 }
)
assert.are.same(augend:add("2022年10月16日(日)", 5), { text = "2022年10月21日(金)", cursor = 14 })
assert.are.same(augend:add("2022年10月16日(日)", 21), { text = "2022年11月6日(日)", cursor = 13 })
assert.are.same(augend:add("2022年10月16日(日)", -21), { text = "2022年9月25日(日)", cursor = 13 })
assert.are.same(augend:add("2022年12月16日(金)", 21), { text = "2023年1月6日(金)", cursor = 12 })
end)
it("correct date and increment days", function()
augend.kind = "day"
assert.are.same(augend:add("2022年10月32日(日)", 1), { text = "2022年11月2日(水)", cursor = 13 })
end)
end)
end)
describe([[Test of clamp & end_sensitive option:]], function()
describe("{clamp = false and end_sensitive = false}", function()
local augend = date.new {
pattern = "%Y/%m/%d",
default_kind = "day",
clamp = false,
end_sensitive = false,
}
it("does not clamp day or treat last days of month specially", function()
augend.kind = "month"
assert.are.same(augend:add("2022/01/28", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/29", 1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/01/30", 1), { text = "2022/03/02", cursor = 7 })
assert.are.same(augend:add("2022/01/31", 1), { text = "2022/03/03", cursor = 7 })
assert.are.same(augend:add("2022/02/01", 1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/02/27", 1), { text = "2022/03/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", 1), { text = "2022/03/28", cursor = 7 })
assert.are.same(augend:add("2022/03/30", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2022/03/31", 1), { text = "2022/05/01", cursor = 7 })
assert.are.same(augend:add("2021/12/31", 2), { text = "2022/03/03", cursor = 7 })
assert.are.same(augend:add("2022/03/28", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/29", -1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/03/30", -1), { text = "2022/03/02", cursor = 7 })
assert.are.same(augend:add("2022/03/31", -1), { text = "2022/03/03", cursor = 7 })
assert.are.same(augend:add("2022/02/27", -1), { text = "2022/01/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", -1), { text = "2022/01/28", cursor = 7 })
assert.are.same(augend:add("2022/12/31", -1), { text = "2022/12/01", cursor = 7 })
augend.kind = "year"
assert.are.same(augend:add("2024/02/29", 1), { text = "2025/03/01", cursor = 4 })
assert.are.same(augend:add("2025/02/28", -1), { text = "2024/02/28", cursor = 4 })
end)
end)
describe("{clamp = true and end_sensitive = false}", function()
local augend = date.new {
pattern = "%Y/%m/%d",
default_kind = "day",
clamp = true,
end_sensitive = false,
}
it("clamps day but does not treat last days of month specially", function()
augend.kind = "month"
assert.are.same(augend:add("2022/01/28", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/29", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/30", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/31", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/02/01", 1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/02/27", 1), { text = "2022/03/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", 1), { text = "2022/03/28", cursor = 7 })
assert.are.same(augend:add("2022/03/30", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2022/03/31", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2021/12/31", 2), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/28", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/29", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/30", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/31", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/02/27", -1), { text = "2022/01/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", -1), { text = "2022/01/28", cursor = 7 })
assert.are.same(augend:add("2022/12/31", -1), { text = "2022/11/30", cursor = 7 })
augend.kind = "year"
assert.are.same(augend:add("2024/02/29", 1), { text = "2025/02/28", cursor = 4 })
assert.are.same(augend:add("2025/02/28", -1), { text = "2024/02/28", cursor = 4 })
end)
end)
describe("{clamp = false and end_sensitive = true}", function()
local augend = date.new {
pattern = "%Y/%m/%d",
default_kind = "day",
clamp = false,
end_sensitive = true,
}
it("does not clamp day but treat last days of month specially", function()
augend.kind = "month"
assert.are.same(augend:add("2022/01/28", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/29", 1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/01/30", 1), { text = "2022/03/02", cursor = 7 })
assert.are.same(augend:add("2022/01/31", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/02/01", 1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/02/27", 1), { text = "2022/03/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", 1), { text = "2022/03/31", cursor = 7 })
assert.are.same(augend:add("2022/03/30", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2022/03/31", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2021/12/31", 2), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/28", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/29", -1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/03/30", -1), { text = "2022/03/02", cursor = 7 })
assert.are.same(augend:add("2022/03/31", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/02/27", -1), { text = "2022/01/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", -1), { text = "2022/01/31", cursor = 7 })
assert.are.same(augend:add("2022/12/31", -1), { text = "2022/11/30", cursor = 7 })
augend.kind = "year"
assert.are.same(augend:add("2024/02/29", 1), { text = "2025/02/28", cursor = 4 })
assert.are.same(augend:add("2025/02/28", -1), { text = "2024/02/29", cursor = 4 })
end)
end)
describe("{clamp = true and end_sensitive = true}", function()
local augend = date.new {
pattern = "%Y/%m/%d",
default_kind = "day",
clamp = true,
end_sensitive = true,
}
it("clamp day and treat last days of month specially", function()
augend.kind = "month"
assert.are.same(augend:add("2022/01/28", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/29", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/30", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/01/31", 1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/02/01", 1), { text = "2022/03/01", cursor = 7 })
assert.are.same(augend:add("2022/02/27", 1), { text = "2022/03/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", 1), { text = "2022/03/31", cursor = 7 })
assert.are.same(augend:add("2022/03/30", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2022/03/31", 1), { text = "2022/04/30", cursor = 7 })
assert.are.same(augend:add("2021/12/31", 2), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/28", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/29", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/30", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/03/31", -1), { text = "2022/02/28", cursor = 7 })
assert.are.same(augend:add("2022/02/27", -1), { text = "2022/01/27", cursor = 7 })
assert.are.same(augend:add("2022/02/28", -1), { text = "2022/01/31", cursor = 7 })
assert.are.same(augend:add("2022/12/31", -1), { text = "2022/11/30", cursor = 7 })
augend.kind = "year"
assert.are.same(augend:add("2024/02/29", 1), { text = "2025/02/28", cursor = 4 })
assert.are.same(augend:add("2025/02/28", -1), { text = "2024/02/29", cursor = 4 })
end)
end)
end)

201
etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/integer_spec.lua

@ -0,0 +1,201 @@ @@ -0,0 +1,201 @@
local integer = require("dial.augend").integer
describe("Test of integer.alias.decimal:", function()
local augend = integer.alias.decimal
describe("find function", function()
it("can find a decimal number", function()
assert.are.same(augend:find("123", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123 4567 89", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123 4567 89", 3), { from = 1, to = 3 })
assert.are.same(augend:find("123 4567 89", 4), { from = 5, to = 8 })
assert.are.same(augend:find("123 4567 89", 5), { from = 5, to = 8 })
end)
it("returns nil when no number appears", function()
assert.are.same(augend:find("", 1), nil)
assert.are.same(augend:find("foo bar", 1), nil)
end)
it("doesn't pick up negative number", function()
assert.are.same(augend:find("-123", 1), { from = 2, to = 4 })
assert.are.same(augend:find("fname-1", 1), { from = 7, to = 7 })
end)
it("doesn't pick up hex number", function()
assert.are.same(augend:find("0xaf", 1), { from = 1, to = 1 })
end)
end)
describe("add function", function()
it("can increment numbers", function()
assert.are.same(augend:add("12", 1, 1), { text = "13", cursor = 2 })
assert.are.same(augend:add("12", 1, 2), { text = "13", cursor = 2 })
assert.are.same(augend:add("12", 3, 1), { text = "15", cursor = 2 })
assert.are.same(augend:add("12", 123, 1), { text = "135", cursor = 3 })
assert.are.same(augend:add("12", -1, 1), { text = "11", cursor = 2 })
assert.are.same(augend:add("12", -3, 1), { text = "9", cursor = 1 })
assert.are.same(augend:add("12", -15, 1), { text = "0", cursor = 1 })
end)
end)
end)
describe("Test of integer.alias.decimal_int:", function()
local augend = integer.alias.decimal_int
describe("find function", function()
it("can find a decimal number", function()
assert.are.same(augend:find("123", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123 4567 89", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123 4567 89", 3), { from = 1, to = 3 })
assert.are.same(augend:find("123 4567 89", 4), { from = 5, to = 8 })
assert.are.same(augend:find("123 4567 89", 5), { from = 5, to = 8 })
end)
it("returns nil when no number appears", function()
assert.are.same(augend:find("", 1), nil)
assert.are.same(augend:find("foo bar", 1), nil)
end)
it("picks up negative number", function()
assert.are.same(augend:find("-123", 1), { from = 1, to = 4 })
assert.are.same(augend:find("fname-1", 1), { from = 6, to = 7 })
end)
end)
describe("add function", function()
it("can increment numbers", function()
assert.are.same(augend:add("12", 1, 1), { text = "13", cursor = 2 })
assert.are.same(augend:add("12", 1, 2), { text = "13", cursor = 2 })
assert.are.same(augend:add("12", 3, 1), { text = "15", cursor = 2 })
assert.are.same(augend:add("12", 123, 1), { text = "135", cursor = 3 })
assert.are.same(augend:add("12", -1, 1), { text = "11", cursor = 2 })
assert.are.same(augend:add("12", -3, 1), { text = "9", cursor = 1 })
assert.are.same(augend:add("12", -15, 1), { text = "-3", cursor = 2 })
assert.are.same(augend:add("-3", 1, 1), { text = "-2", cursor = 2 })
assert.are.same(augend:add("-3", 4, 1), { text = "1", cursor = 1 })
assert.are.same(augend:add("-3", -24, 1), { text = "-27", cursor = 3 })
end)
end)
end)
describe("Test of integer.alias.hex:", function()
local augend = integer.alias.hex
describe("find function", function()
it("can find a hex number", function()
assert.are.same(augend:find("0x0", 1), { from = 1, to = 3 })
assert.are.same(augend:find("0xa", 1), { from = 1, to = 3 })
assert.are.same(augend:find("0x1213", 3), { from = 1, to = 6 })
assert.are.same(augend:find("0x1a2b", 4), { from = 1, to = 6 })
assert.are.same(augend:find("0xabcd", 5), { from = 1, to = 6 })
assert.are.same(augend:find("0xabcdefg", 5), { from = 1, to = 8 })
assert.are.same(augend:find("0xAB12", 5), { from = 1, to = 6 })
assert.are.same(augend:find("0xAB12", 5), { from = 1, to = 6 })
end)
it("does not detect hex number without `0x`", function()
assert.are.same(augend:find("0", 1), nil)
assert.are.same(augend:find("1a1a", 1), nil)
assert.are.same(augend:find("1A1A", 1), nil)
end)
it("does not detect hex number starting with `0X`", function()
assert.are.same(augend:find("0X1", 1), nil)
end)
end)
describe("add function", function()
it("can increment numbers", function()
assert.are.same(augend:add("0x1", 1, 1), { text = "0x2", cursor = 3 })
assert.are.same(augend:add("0xf", 1, 1), { text = "0x10", cursor = 4 })
assert.are.same(augend:add("0x12", 8, 1), { text = "0x1a", cursor = 4 })
assert.are.same(augend:add("0x2020", -1, 1), { text = "0x201f", cursor = 6 })
assert.are.same(augend:add("0x1F1F", 2, 1), { text = "0x1f21", cursor = 6 })
end)
end)
end)
describe([[Test of integer.new {delimiter = ","}:]], function()
local augend = integer.new { delimiter = "," }
describe("find function", function()
it("can find comma-separated integer", function()
assert.are.same(augend:find("123", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123,456", 1), { from = 1, to = 7 })
assert.are.same(augend:find("123,456,789", 1), { from = 1, to = 11 })
assert.are.same(augend:find("123,456,789", 4), { from = 1, to = 11 })
assert.are.same(augend:find("123,456,789", 5), { from = 1, to = 11 })
assert.are.same(augend:find("123,456,789", 7), { from = 1, to = 11 })
end)
it("does not consider number of digits", function()
assert.are.same(augend:find("1,2,3", 1), { from = 1, to = 5 })
assert.are.same(augend:find("123,4,56", 1), { from = 1, to = 8 })
assert.are.same(augend:find("123,456,789", 1), { from = 1, to = 11 })
end)
it("does not match illegal format", function()
assert.are.same(augend:find("123, 456", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123, 456", 4), { from = 6, to = 8 })
assert.are.same(augend:find("123 ,456", 1), { from = 1, to = 3 })
assert.are.same(augend:find("foo,1", 1), { from = 5, to = 5 })
end)
end)
describe("add function", function()
it("can increment numbers", function()
assert.are.same(augend:add("1,999", 1, 1), { text = "2,000", cursor = 5 })
assert.are.same(augend:add("9,999,999", 1, 1), { text = "10,000,000", cursor = 10 })
assert.are.same(augend:add("999,999", 1, 1), { text = "1,000,000", cursor = 9 })
assert.are.same(augend:add("100,000", -1, 1), { text = "99,999", cursor = 6 })
assert.are.same(augend:add("1,000", -1, 1), { text = "999", cursor = 3 })
end)
it("fixes the place of separators", function()
assert.are.same(augend:add("19,99", 1, 1), { text = "2,000", cursor = 5 })
assert.are.same(augend:add("1,0,0,0,0,0", -1, 1), { text = "99,999", cursor = 6 })
end)
end)
end)
describe([[Test of integer.new {delimiter = "."}:]], function()
local augend = integer.new { delimiter = "." }
describe("find function", function()
it("can find comma-separated integer", function()
assert.are.same(augend:find("123", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123.456", 1), { from = 1, to = 7 })
end)
it("does not consider number of digits", function()
assert.are.same(augend:find("1.2.3", 1), { from = 1, to = 5 })
end)
it("does not match illegal format", function()
assert.are.same(augend:find("123. 456", 1), { from = 1, to = 3 })
assert.are.same(augend:find("123. 456", 4), { from = 6, to = 8 })
assert.are.same(augend:find("123 .456", 1), { from = 1, to = 3 })
assert.are.same(augend:find("foo.1", 1), { from = 5, to = 5 })
end)
end)
describe("add function", function()
it("can increment numbers", function()
assert.are.same(augend:add("1.999", 1, 1), { text = "2.000", cursor = 5 })
assert.are.same(augend:add("9.999.999", 1, 1), { text = "10.000.000", cursor = 10 })
assert.are.same(augend:add("999.999", 1, 1), { text = "1.000.000", cursor = 9 })
assert.are.same(augend:add("100.000", -1, 1), { text = "99.999", cursor = 6 })
assert.are.same(augend:add("1.000", -1, 1), { text = "999", cursor = 3 })
end)
it("fixes the place of separators", function()
assert.are.same(augend:add("19.99", 1, 1), { text = "2.000", cursor = 5 })
assert.are.same(augend:add("1.0.0.0.0.0", -1, 1), { text = "99.999", cursor = 6 })
end)
end)
end)
describe([[Test of integer.new {delimiter = ",", delimiter_digits = 4}:]], function()
local augend = integer.new { delimiter = ",", delimiter_digits = 4 }
describe("find function", function()
it("can find comma-separated integer", function()
assert.are.same(augend:find("123,456,789", 1), { from = 1, to = 11 })
end)
end)
describe("add function", function()
it("separates numbers by four digits", function()
assert.are.same(augend:add("1,999", 1, 1), { text = "2000", cursor = 4 })
assert.are.same(augend:add("9,999,999", 1, 1), { text = "1000,0000", cursor = 9 })
end)
end)
end)

29
etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/misc_spec.lua

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
local misc = require("dial.augend").misc
describe("Test of misc.alias.markdown_header:", function()
local augend = misc.alias.markdown_header
describe("find function", function()
it("can find a markdown header", function()
assert.are.same(augend:find("# Header 1", 1), { from = 1, to = 1 })
assert.are.same(augend:find("## Header 2", 1), { from = 1, to = 2 })
assert.are.same(augend:find("###### Header 6", 1), { from = 1, to = 6 })
assert.are.same(augend:find("#Header 1", 1), { from = 1, to = 1 })
assert.are.same(augend:find("# Header 1", 3), { from = 1, to = 1 })
end)
it("ignores non-header elements", function()
assert.are.same(augend:find("foo # bar", 1), nil)
assert.are.same(augend:find("####### Header 7?", 1), nil)
end)
end)
describe("add function", function()
it("can increment header level", function()
assert.are.same(augend:add("#", 1, 1), { text = "##", cursor = 1 })
assert.are.same(augend:add("##", 3, 1), { text = "#####", cursor = 1 })
assert.are.same(augend:add("#", 7, 1), { text = "######", cursor = 1 })
assert.are.same(augend:add("##", -4, 1), { text = "#", cursor = 1 })
assert.are.same(augend:add("####", -1, 6), { text = "###", cursor = 1 })
end)
end)
end)

181
etc/soft/nvim/+plugins/dial.nvim/lua/spec/dial/augend/paren_spec.lua

@ -0,0 +1,181 @@ @@ -0,0 +1,181 @@
local paren = require("dial.augend").paren
describe([[Test of paren between '...' and "...":]], function()
local augend = paren.new {
patterns = { { "'", "'" }, { '"', '"' } },
nested = false,
cyclic = true,
escape_char = [[\]],
}
describe("find function", function()
it("can find single- or double- quoted string", function()
assert.are.same(augend:find([["foo"]], 1), { from = 1, to = 5 })
assert.are.same(augend:find([['foo']], 1), { from = 1, to = 5 })
assert.are.same(augend:find([[foo"bar"baz]], 1), { from = 4, to = 8 })
assert.are.same(augend:find([[foo"bar"baz]], 4), { from = 4, to = 8 })
assert.are.same(augend:find([[foo"bar"baz]], 5), { from = 4, to = 8 })
assert.are.same(augend:find([[foo"bar"baz]], 8), { from = 4, to = 8 })
assert.are.same(augend:find([[foo"bar"baz]], 9), nil)
assert.are.same(augend:find([[foo""baz]], 1), { from = 4, to = 5 })
end)
it("returns nil when no string literal appears", function()
assert.are.same(augend:find([[foo bar]], 1), nil)
assert.are.same(augend:find([[foo "bar]], 1), nil)
assert.are.same(augend:find([[foo 'bar"]], 1), nil)
end)
it("considers escape character", function()
assert.are.same(augend:find([[foo"bar\"baz"]], 1), { from = 4, to = 13 })
assert.are.same(augend:find([[foo'bar\'baz']], 1), { from = 4, to = 13 })
assert.are.same(augend:find([[foo'bar\"baz']], 1), { from = 4, to = 13 })
assert.are.same(augend:find([[foo'bar\nbaz']], 1), { from = 4, to = 13 })
assert.are.same(augend:find([[foo'bar\'baz"]], 1), nil)
assert.are.same(augend:find([[foo"bar\\"baz"]], 1), { from = 4, to = 10 })
end)
it("handle multiple quote areas", function()
assert.are.same(augend:find([[a"b"c"b"a]], 1), { from = 2, to = 4 })
assert.are.same(augend:find([[a"b"c"b"a]], 4), { from = 2, to = 4 })
assert.are.same(augend:find([[a"b"c"b"a]], 5), { from = 6, to = 8 })
assert.are.same(augend:find([[a"b"c"b"a]], 8), { from = 6, to = 8 })
assert.are.same(augend:find([[a"b'c'b"a]], 1), { from = 2, to = 8 })
assert.are.same(augend:find([[a"b'c'b"a]], 4), { from = 4, to = 6 })
end)
end)
describe("add function", function()
it("can convert single-quote to double and vice versa", function()
assert.are.same(augend:add([['foo']], 1, 1), { text = [["foo"]], cursor = 5 })
assert.are.same(augend:add([["foo"]], -1, 1), { text = [['foo']], cursor = 5 })
assert.are.same(augend:add([['fo\'o']], 1, 1), { text = [["fo\'o"]], cursor = 7 })
end)
it("has cyclic behavior", function()
assert.are.same(augend:add([['foo']], -1, 1), { text = [["foo"]], cursor = 5 })
assert.are.same(augend:add([["foo"]], 1, 1), { text = [['foo']], cursor = 5 })
assert.are.same(augend:add([["foo"]], 2, 1), { text = [["foo"]], cursor = 5 })
assert.are.same(augend:add([["foo"]], 3, 1), { text = [['foo']], cursor = 5 })
end)
end)
end)
describe("Test of brackets `()`, `[]`, and `{}`:", function()
local augend = paren.new {
patterns = {
{ "(", ")" },
{ "[", "]" },
{ "{", "}" },
},
nested = true,
cyclic = true,
}
describe("find function", function()
it("can find brackets", function()
assert.are.same(augend:find("foo(bar)", 1), { from = 4, to = 8 })
assert.are.same(augend:find("foo[bar]", 1), { from = 4, to = 8 })
assert.are.same(augend:find("foo{bar}", 1), { from = 4, to = 8 })
assert.are.same(augend:find("foo(bar), bar[baz].", 1), { from = 4, to = 8 })
assert.are.same(augend:find("foo(bar), bar[baz].", 4), { from = 4, to = 8 })
assert.are.same(augend:find("foo(bar), bar[baz].", 8), { from = 4, to = 8 })
assert.are.same(augend:find("foo(bar), bar[baz].", 9), { from = 14, to = 18 })
assert.are.same(augend:find("foo(bar), bar[baz].", 14), { from = 14, to = 18 })
assert.are.same(augend:find("foo(bar), bar[baz].", 18), { from = 14, to = 18 })
assert.are.same(augend:find("foo(bar), bar[baz].", 19), nil)
assert.are.same(augend:find("foo(bar]", 1), nil)
assert.are.same(augend:find("foo(bar])", 1), { from = 4, to = 9 })
end)
it("considers nested brackets", function()
assert.are.same(augend:find("foo({true}, 1) + 1", 1), { from = 4, to = 14 })
assert.are.same(augend:find("foo({true}, 1) + 1", 4), { from = 4, to = 14 })
assert.are.same(augend:find("foo({true}, 1) + 1", 5), { from = 5, to = 10 })
assert.are.same(augend:find("foo({true}, 1) + 1", 10), { from = 5, to = 10 })
assert.are.same(augend:find("foo({true}, 1) + 1", 11), { from = 4, to = 14 })
assert.are.same(augend:find("foo({true}, 1) + 1", 14), { from = 4, to = 14 })
assert.are.same(augend:find("foo({true}, 1) + 1", 15), nil)
assert.are.same(augend:find("foo{{true}, 1} + 1", 1), { from = 4, to = 14 })
assert.are.same(augend:find("foo{{true}, 1} + 1", 4), { from = 4, to = 14 })
assert.are.same(augend:find("foo{{true}, 1} + 1", 5), { from = 5, to = 10 })
assert.are.same(augend:find("foo{{true}, 1} + 1", 10), { from = 5, to = 10 })
assert.are.same(augend:find("foo{{true}, 1} + 1", 11), { from = 4, to = 14 })
assert.are.same(augend:find("foo{{true}, 1} + 1", 14), { from = 4, to = 14 })
assert.are.same(augend:find("foo{{true}, 1} + 1", 15), nil)
assert.are.same(augend:find("foo(bar[)baz]", 1), { from = 4, to = 9 })
assert.are.same(augend:find("foo(bar[)baz]", 4), { from = 4, to = 9 })
assert.are.same(augend:find("foo(bar[)baz]", 5), { from = 4, to = 9 })
assert.are.same(augend:find("foo(bar[)baz]", 8), { from = 8, to = 13 })
assert.are.same(augend:find("foo(bar[)baz]", 9), { from = 8, to = 13 })
assert.are.same(augend:find("foo(bar[)baz]", 10), { from = 8, to = 13 })
end)
end)
describe("add function", function()
it("can convert brackets", function()
assert.are.same(augend:add("(foo)", 1, 1), { text = "[foo]", cursor = 5 })
assert.are.same(augend:add("[foo]", 1, 1), { text = "{foo}", cursor = 5 })
assert.are.same(augend:add("(foo)", 2, 1), { text = "{foo}", cursor = 5 })
assert.are.same(augend:add("[foo]", -1, 1), { text = "(foo)", cursor = 5 })
assert.are.same(augend:add("{foo}", -2, 1), { text = "(foo)", cursor = 5 })
end)
it("has cyclic behavior", function()
assert.are.same(augend:add("{foo}", 1, 1), { text = "(foo)", cursor = 5 })
assert.are.same(augend:add("(foo)", 3, 1), { text = "(foo)", cursor = 5 })
assert.are.same(augend:add("(foo)", -1, 1), { text = "{foo}", cursor = 5 })
assert.are.same(augend:add("(foo)", -3, 1), { text = "(foo)", cursor = 5 })
end)
end)
end)
describe([[Test of paren between Rust-style str literal:]], function()
local augend = paren.new {
patterns = {
{ '"', '"' },
{ 'r#"', '"#' },
{ 'r##"', '"##' },
{ 'r###"', '"###' },
},
nested = false,
cyclic = false,
}
describe("find function", function()
it("can find string literals", function()
assert.are.same(augend:find([["foo"]], 1), { from = 1, to = 5 })
assert.are.same(augend:find([[r#"foo"#]], 1), { from = 1, to = 8 })
assert.are.same(augend:find([[r##"foo"##]], 1), { from = 1, to = 10 })
assert.are.same(augend:find([[four##"foo"##]], 1), { from = 4, to = 13 })
assert.are.same(augend:find([[r##"foo"#]], 1), { from = 4, to = 8 })
assert.are.same(augend:find([[r##"foo"#bar"##]], 1), { from = 1, to = 15 })
end)
it("behaves naturally with respect to cursor", function()
assert.are.same(augend:find([[println!(r##"foo"##);]], 1), { from = 10, to = 19 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 9), { from = 10, to = 19 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 10), { from = 10, to = 19 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 11), { from = 10, to = 19 })
-- TODO: is this natural?
assert.are.same(augend:find([[println!(r##"foo"##);]], 13), { from = 13, to = 17 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 14), { from = 13, to = 17 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 17), { from = 13, to = 17 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 18), { from = 10, to = 19 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 19), { from = 10, to = 19 })
assert.are.same(augend:find([[println!(r##"foo"##);]], 21), nil)
end)
it("returns nil when no string literal appears", function()
assert.are.same(augend:find([[r#"foo#]], 1), nil)
end)
end)
describe("add function", function()
it("can convert quotes", function()
assert.are.same(augend:add([["foo"]], 1, 1), { text = [[r#"foo"#]], cursor = 8 })
assert.are.same(augend:add([["foo"]], 2, 1), { text = [[r##"foo"##]], cursor = 10 })
assert.are.same(augend:add([["foo"]], 3, 1), { text = [[r###"foo"###]], cursor = 12 })
assert.are.same(augend:add([[r#"foo"#]], 1, 1), { text = [[r##"foo"##]], cursor = 10 })
assert.are.same(augend:add([[r#"foo"#]], -1, 1), { text = [["foo"]], cursor = 5 })
assert.are.same(augend:add([[r###"foo"###]], -2, 1), { text = [[r#"foo"#]], cursor = 8 })
end)
it("does not have cyclic behavior", function()
assert.are.same(augend:add([["foo"]], -1, 1), { text = [["foo"]], cursor = 5 })
assert.are.same(augend:add([[r#"foo"#]], -2, 1), { text = [["foo"]], cursor = 5 })
assert.are.same(augend:add([["foo"]], 4, 1), { text = [[r###"foo"###]], cursor = 12 })
end)
end)
end)

21
etc/soft/nvim/+plugins/dial.nvim/plugin/dial.vim

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
if exists('g:loaded_dial') | finish | endif " prevent loading file twice
let s:save_cpo = &cpo " save user coptions
set cpo&vim " reset them to defaults
lua << EOF
vim.api.nvim_set_keymap("n", "<Plug>(dial-increment)", require("dial.map").inc_normal(), {noremap = true})
vim.api.nvim_set_keymap("n", "<Plug>(dial-decrement)", require("dial.map").dec_normal(), {noremap = true})
vim.api.nvim_set_keymap("v", "<Plug>(dial-increment)", require("dial.map").inc_visual() .. "gv", {noremap = true})
vim.api.nvim_set_keymap("v", "<Plug>(dial-decrement)", require("dial.map").dec_visual() .. "gv", {noremap = true})
vim.api.nvim_set_keymap("v", "g<Plug>(dial-increment)", require("dial.map").inc_gvisual() .. "gv", {noremap = true})
vim.api.nvim_set_keymap("v", "g<Plug>(dial-decrement)", require("dial.map").dec_gvisual() .. "gv", {noremap = true})
EOF
command! -range -nargs=? DialIncrement lua require"dial.command".command("increment", {from = <line1>, to = <line2>}, {<f-args>})
command! -range -nargs=? DialDecrement lua require"dial.command".command("decrement", {from = <line1>, to = <line2>}, {<f-args>})
let &cpo = s:save_cpo " and restore after
unlet s:save_cpo
let g:loaded_dial = 1

21
etc/soft/nvim/+plugins/dressing.nvim/LICENSE

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Steven Arcangeli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

356
etc/soft/nvim/+plugins/dressing.nvim/README.md

@ -0,0 +1,356 @@ @@ -0,0 +1,356 @@
# Dressing.nvim
With the release of Neovim 0.6 we were given the start of extensible core UI
hooks ([vim.ui.select](https://github.com/neovim/neovim/pull/15771) and
[vim.ui.input](https://github.com/neovim/neovim/pull/15959)). They exist to
allow plugin authors to override them with improvements upon the default
behavior, so that's exactly what we're going to do.
It is a goal to match and not extend the core Neovim API. All options that core
respects will be respected, and we will not accept any custom parameters or
options in the functions. Customization will be done entirely using a separate
[configuration](#configuration) method.
- [Requirements](#requirements)
- [Screenshots](#screenshots)
- [Installation](#installation)
- [Configuration](#configuration)
- [Highlights](#highlights)
- [Advanced configuration](#advanced-configuration)
- [Notes for plugin authors](#notes-for-plugin-authors)
- [Alternative and related projects](#alternative-and-related-projects)
## Requirements
Neovim 0.7.0+ (for earlier versions, use the [nvim-0.5 branch](https://github.com/stevearc/dressing.nvim/tree/nvim-0.5))
## Screenshots
`vim.input` replacement (handling a LSP rename)
![Screenshot from 2021-12-09 17-36-16](https://user-images.githubusercontent.com/506791/145502533-3dc2f87d-95ea-422d-a318-12c0092f1bdf.png)
`vim.select` (telescope)
![Screenshot from 2021-12-02 19-46-01](https://user-images.githubusercontent.com/506791/144541916-4fa60c50-cadc-4f0f-b3c1-6307310e6e99.png)
`vim.select` (fzf)
![Screenshot from 2021-12-02 19-46-54](https://user-images.githubusercontent.com/506791/144541986-6081b4f8-b3b2-418d-9265-b9dabec2c4c4.png)
`vim.select` (nui)
![Screenshot from 2021-12-02 19-47-56](https://user-images.githubusercontent.com/506791/144542071-1aa66f81-b07c-492e-9884-fdafed1006df.png)
`vim.select` (built-in)
![Screenshot from 2021-12-04 17-14-32](https://user-images.githubusercontent.com/506791/144729527-ede0d7ba-a6e6-41e0-be5a-1a5f16d35b05.png)
## Installation
dressing.nvim supports all the usual plugin managers
<details>
<summary>Packer</summary>
```lua
require('packer').startup(function()
use {'stevearc/dressing.nvim'}
end)
```
</details>
<details>
<summary>Paq</summary>
```lua
require "paq" {
{'stevearc/dressing.nvim'};
}
```
</details>
<details>
<summary>vim-plug</summary>
```vim
Plug 'stevearc/dressing.nvim'
```
</details>
<details>
<summary>dein</summary>
```vim
call dein#add('stevearc/dressing.nvim')
```
</details>
<details>
<summary>Pathogen</summary>
```sh
git clone --depth=1 https://github.com/stevearc/dressing.nvim.git ~/.vim/bundle/
```
</details>
<details>
<summary>Neovim native package</summary>
```sh
git clone --depth=1 https://github.com/stevearc/dressing.nvim.git \
"${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/dressing.nvim/start/dressing.nvim
```
</details>
## Configuration
If you're fine with the defaults, you're good to go after installation. If you
want to tweak, call this function:
```lua
require('dressing').setup({
input = {
-- Set to false to disable the vim.ui.input implementation
enabled = true,
-- Default prompt string
default_prompt = "Input:",
-- Can be 'left', 'right', or 'center'
prompt_align = "left",
-- When true, <Esc> will close the modal
insert_only = true,
-- When true, input will start in insert mode.
start_in_insert = true,
-- These are passed to nvim_open_win
anchor = "SW",
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "cursor",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
prefer_width = 40,
width = nil,
-- min_width and max_width can be a list of mixed types.
-- min_width = {20, 0.2} means "the greater of 20 columns or 20% of total"
max_width = { 140, 0.9 },
min_width = { 20, 0.2 },
-- Window transparency (0-100)
winblend = 10,
-- Change default highlight groups (see :help winhl)
winhighlight = "",
-- Set to `false` to disable
mappings = {
n = {
["<Esc>"] = "Close",
["<CR>"] = "Confirm",
},
i = {
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
["<Up>"] = "HistoryPrev",
["<Down>"] = "HistoryNext",
},
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
-- see :help dressing_get_config
get_config = nil,
},
select = {
-- Set to false to disable the vim.ui.select implementation
enabled = true,
-- Priority list of preferred vim.select implementations
backend = { "telescope", "fzf_lua", "fzf", "builtin", "nui" },
-- Trim trailing `:` from prompt
trim_prompt = true,
-- Options for telescope selector
-- These are passed into the telescope picker directly. Can be used like:
-- telescope = require('telescope.themes').get_ivy({...})
telescope = nil,
-- Options for fzf selector
fzf = {
window = {
width = 0.5,
height = 0.4,
},
},
-- Options for fzf_lua selector
fzf_lua = {
winopts = {
width = 0.5,
height = 0.4,
},
},
-- Options for nui Menu
nui = {
position = "50%",
size = nil,
relative = "editor",
border = {
style = "rounded",
},
buf_options = {
swapfile = false,
filetype = "DressingSelect",
},
win_options = {
winblend = 10,
},
max_width = 80,
max_height = 40,
min_width = 40,
min_height = 10,
},
-- Options for built-in selector
builtin = {
-- These are passed to nvim_open_win
anchor = "NW",
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "editor",
-- Window transparency (0-100)
winblend = 10,
-- Change default highlight groups (see :help winhl)
winhighlight = "",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
-- the min_ and max_ options can be a list of mixed types.
-- max_width = {140, 0.8} means "the lesser of 140 columns or 80% of total"
width = nil,
max_width = { 140, 0.8 },
min_width = { 40, 0.2 },
height = nil,
max_height = 0.9,
min_height = { 10, 0.2 },
-- Set to `false` to disable
mappings = {
["<Esc>"] = "Close",
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
},
-- Used to override format_item. See :help dressing-format
format_item_override = {},
-- see :help dressing_get_config
get_config = nil,
},
})
```
## Highlights
The built-in `vim.ui.input` and `vim.ui.select` components mostly use the
standard highlight groups for neovim floating windows (e.g. `NormalFloat` for
the text, `FloatBorder` for the border). In addition, the window title uses a
non-standard `FloatTitle` group that is linked to `FloatBorder` by default.
A common way to adjust the highlighting of just the dressing windows is by
providing a `winhighlight` option in the config. For example, `winhighlight = 'NormalFloat:DiagnosticError'`
would change the default text color in the dressing windows. See `:help winhighlight`
for more details.
Note that you can't change `FloatTitle` via `winhighlight` since it is not a
built-in group.
## Advanced configuration
For each of the `input` and `select` configs, there is an option
`get_config`. This can be a function that accepts the `opts` parameter that
is passed in to `vim.select` or `vim.input`. It must return either `nil` (to
no-op) or config values to use in place of the global config values for that
module.
For example, if you want to use a specific configuration for code actions:
```lua
require('dressing').setup({
select = {
get_config = function(opts)
if opts.kind == 'codeaction' then
return {
backend = 'nui',
nui = {
relative = 'cursor',
max_width = 40,
}
}
end
end
}
})
```
## Notes for plugin authors
TL;DR: you can customize the telescope `vim.ui.select` implementation by passing `telescope` into `opts`.
The `vim.ui` hooks are a great boon for us because we can now assume that users
will have a reasonable UI available for simple input operations. We no longer
have to build separate implementations for each of fzf, telescope, ctrlp, etc.
The tradeoff is that `vim.ui.select` is less customizable than any of these
options, so if you wanted to have a preview window (like telescope supports), it
is no longer an option.
My solution to this is extending the `opts` that are passed to `vim.ui.select`.
You can add a `telescope` field that will be passed directly into the picker,
allowing you to customize any part of the UI. If a user has both dressing and
telescope installed, they will get your custom picker UI. If either of those
are not true, the selection UI will gracefully degrade to whatever the user has
configured for `vim.ui.select`.
An example of usage:
```lua
vim.ui.select({'apple', 'banana', 'mango'}, {
prompt = "Title",
telescope = require("telescope.themes").get_cursor(),
}, function(selected) end)
```
For now this is available only for the telescope backend, but feel free to request additions.
## Alternative and related projects
- [telescope-ui-select](https://github.com/nvim-telescope/telescope-ui-select.nvim) - provides a `vim.ui.select` implementation for telescope
- [nvim-fzy](https://github.com/mfussenegger/nvim-fzy) - fzf alternative that also provides a `vim.ui.select` implementation ([#13](https://github.com/mfussenegger/nvim-fzy/pull/13))
- [guihua.lua](https://github.com/ray-x/guihua.lua) - multipurpose GUI library that provides `vim.ui.select` and `vim.ui.input` implementations
- [nvim-notify](https://github.com/rcarriga/nvim-notify) - doing pretty much the
same thing but for `vim.notify`
- [nui.nvim](https://github.com/MunifTanjim/nui.nvim) - provides common UI
components for plugin authors

12
etc/soft/nvim/+plugins/dressing.nvim/autoload/dressing.vim

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
function! dressing#fzf_run(labels, options, window) abort
call fzf#run(fzf#wrap({
\ 'source': a:labels,
\ 'sink': funcref('dressing#fzf_choice'),
\ 'options': a:options,
\ 'window': a:window,
\}))
endfunction
function! dressing#fzf_choice(label) abort
call v:lua.dressing_fzf_choice(a:label)
endfunction

208
etc/soft/nvim/+plugins/dressing.nvim/doc/dressing.txt

@ -0,0 +1,208 @@ @@ -0,0 +1,208 @@
*dressing.txt*
*Dressing* *dressing* *dressing.nvim*
===============================================================================
CONFIGURATION *dressing-configuration*
Configure dressing.nvim by calling the setup() function.
>
require('dressing').setup({
input = {
-- Set to false to disable the vim.ui.input implementation
enabled = true,
-- Default prompt string
default_prompt = "Input:",
-- Can be 'left', 'right', or 'center'
prompt_align = "left",
-- When true, <Esc> will close the modal
insert_only = true,
-- When true, input will start in insert mode.
start_in_insert = true,
-- These are passed to nvim_open_win
anchor = "SW",
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "cursor",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
prefer_width = 40,
width = nil,
-- min_width and max_width can be a list of mixed types.
-- min_width = {20, 0.2} means "the greater of 20 columns or 20% of total"
max_width = { 140, 0.9 },
min_width = { 20, 0.2 },
-- Window transparency (0-100)
winblend = 10,
-- Change default highlight groups (see :help winhl)
winhighlight = "",
-- Set to `false` to disable
mappings = {
n = {
["<Esc>"] = "Close",
["<CR>"] = "Confirm",
},
i = {
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
["<Up>"] = "HistoryPrev",
["<Down>"] = "HistoryNext",
},
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
-- see :help dressing_get_config
get_config = nil,
},
select = {
-- Set to false to disable the vim.ui.select implementation
enabled = true,
-- Priority list of preferred vim.select implementations
backend = { "telescope", "fzf_lua", "fzf", "builtin", "nui" },
-- Trim trailing `:` from prompt
trim_prompt = true,
-- Options for telescope selector
-- These are passed into the telescope picker directly. Can be used like:
-- telescope = require('telescope.themes').get_ivy({...})
telescope = nil,
-- Options for fzf selector
fzf = {
window = {
width = 0.5,
height = 0.4,
},
},
-- Options for fzf_lua selector
fzf_lua = {
winopts = {
width = 0.5,
height = 0.4,
},
},
-- Options for nui Menu
nui = {
position = "50%",
size = nil,
relative = "editor",
border = {
style = "rounded",
},
buf_options = {
swapfile = false,
filetype = "DressingSelect",
},
win_options = {
winblend = 10,
},
max_width = 80,
max_height = 40,
min_width = 40,
min_height = 10,
},
-- Options for built-in selector
builtin = {
-- These are passed to nvim_open_win
anchor = "NW",
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "editor",
-- Window transparency (0-100)
winblend = 10,
-- Change default highlight groups (see :help winhl)
winhighlight = "",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
-- the min_ and max_ options can be a list of mixed types.
-- max_width = {140, 0.8} means "the lesser of 140 columns or 80% of total"
width = nil,
max_width = { 140, 0.8 },
min_width = { 40, 0.2 },
height = nil,
max_height = 0.9,
min_height = { 10, 0.2 },
-- Set to `false` to disable
mappings = {
["<Esc>"] = "Close",
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
},
-- Used to override format_item. See :help dressing-format
format_item_override = {},
-- see :help dressing_get_config
get_config = nil,
},
})
dressing.get_config() *dressing_get_config()*
For each of the `input` and `select` configs, there is an option
`get_config`. This can be a function that accepts the `opts` parameter that
is passed in to `vim.select` or `vim.input`. It must return either `nil` (to
no-op) or config values to use in place of the global config values for that
module.
For example, if you want to use a specific configuration for code actions:
>
require('dressing').setup({
select = {
get_config = function(opts)
if opts.kind == 'codeaction' then
return {
backend = 'nui',
nui = {
relative = 'cursor',
max_width = 40,
}
}
end
end
}
})
===============================================================================
*dressing-format*
Sometimes you may wish to change how choices are displayed for
`vim.ui.select`. The calling function can pass a specific "kind" to the select
function (for example, code actions from |vim.lsp.buf.code_action|
use kind="codeaction"). You can, in turn, specify an override for the
"format_item" function when selecting for that kind. For example, this
configuration will display the name of the language server next to code
actions:
>
format_item_override = {
codeaction = function(action_tuple)
local title = action_tuple[2].title:gsub("\r\n", "\\r\\n")
local client = vim.lsp.get_client_by_id(action_tuple[1])
return string.format("%s\t[%s]", title:gsub("\n", "\\n"), client.name)
end,
}
===============================================================================
vim:ft=help:et:ts=2:sw=2:sts=2:norl

213
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/config.lua

@ -0,0 +1,213 @@ @@ -0,0 +1,213 @@
local default_config = {
input = {
-- Set to false to disable the vim.ui.input implementation
enabled = true,
-- Default prompt string
default_prompt = "Input:",
-- Can be 'left', 'right', or 'center'
prompt_align = "left",
-- When true, <Esc> will close the modal
insert_only = true,
-- When true, input will start in insert mode.
start_in_insert = true,
-- These are passed to nvim_open_win
anchor = "SW",
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "cursor",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
prefer_width = 40,
width = nil,
-- min_width and max_width can be a list of mixed types.
-- min_width = {20, 0.2} means "the greater of 20 columns or 20% of total"
max_width = { 140, 0.9 },
min_width = { 20, 0.2 },
-- Window transparency (0-100)
winblend = 10,
-- Change default highlight groups (see :help winhl)
winhighlight = "",
-- Set to `false` to disable
mappings = {
n = {
["<Esc>"] = "Close",
["<CR>"] = "Confirm",
},
i = {
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
["<Up>"] = "HistoryPrev",
["<Down>"] = "HistoryNext",
},
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
-- see :help dressing_get_config
get_config = nil,
},
select = {
-- Set to false to disable the vim.ui.select implementation
enabled = true,
-- Priority list of preferred vim.select implementations
backend = { "telescope", "fzf_lua", "fzf", "builtin", "nui" },
-- Trim trailing `:` from prompt
trim_prompt = true,
-- Options for telescope selector
-- These are passed into the telescope picker directly. Can be used like:
-- telescope = require('telescope.themes').get_ivy({...})
telescope = nil,
-- Options for fzf selector
fzf = {
window = {
width = 0.5,
height = 0.4,
},
},
-- Options for fzf_lua selector
fzf_lua = {
winopts = {
width = 0.5,
height = 0.4,
},
},
-- Options for nui Menu
nui = {
position = "50%",
size = nil,
relative = "editor",
border = {
style = "rounded",
},
buf_options = {
swapfile = false,
filetype = "DressingSelect",
},
win_options = {
winblend = 10,
},
max_width = 80,
max_height = 40,
min_width = 40,
min_height = 10,
},
-- Options for built-in selector
builtin = {
-- These are passed to nvim_open_win
anchor = "NW",
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "editor",
-- Window transparency (0-100)
winblend = 10,
-- Change default highlight groups (see :help winhl)
winhighlight = "",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
-- the min_ and max_ options can be a list of mixed types.
-- max_width = {140, 0.8} means "the lesser of 140 columns or 80% of total"
width = nil,
max_width = { 140, 0.8 },
min_width = { 40, 0.2 },
height = nil,
max_height = 0.9,
min_height = { 10, 0.2 },
-- Set to `false` to disable
mappings = {
["<Esc>"] = "Close",
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
},
-- Used to override format_item. See :help dressing-format
format_item_override = {},
-- see :help dressing_get_config
get_config = nil,
},
}
local M = vim.deepcopy(default_config)
M.update = function(opts)
local newconf = vim.tbl_deep_extend("force", default_config, opts or {})
if
newconf.input.row
or newconf.input.col
or newconf.select.builtin.row
or newconf.select.builtin.col
then
vim.notify(
"Deprecated: Dressing row and col are no longer used. Use the override to customize layout (:help dressing)",
vim.log.levels.WARN
)
end
if
newconf.select.telescope
and newconf.select.telescope.theme
and vim.tbl_count(newconf.select.telescope) == 1
then
vim.notify(
"Deprecated: dressing.select.telescope.theme is deprecated. Pass in telescope options directly (:help dressing)",
vim.log.levels.WARN
)
local theme = newconf.select.telescope.theme
local ttype = type(theme)
if ttype == "string" then
newconf.select.telescope = require("telescope.themes")[string.format("get_%s", theme)]()
elseif ttype == "function" then
newconf.select.telescope = theme({})
else
newconf.select.telescope = theme
end
end
for k, v in pairs(newconf) do
M[k] = v
end
end
-- Used to get the effective config value for a module.
-- Use like: config.get_mod_config('input')
M.get_mod_config = function(key, ...)
if not M[key].get_config then
return M[key]
end
local conf = M[key].get_config(...)
if conf then
return vim.tbl_deep_extend("force", M[key], conf)
else
return M[key]
end
end
return M

28
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/init.lua

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
local patch = require("dressing.patch")
local M = {}
M.setup = function(opts)
require("dressing.config").update(opts)
patch.all()
end
---Patch all the vim.ui methods
M.patch = function()
patch.all()
end
---Unpatch all the vim.ui methods
---@param names? string[] Names of vim.ui modules to unpatch
M.unpatch = function(names)
if not names then
return patch.all(false)
elseif type(names) ~= "table" then
names = { names }
end
for _, name in ipairs(names) do
patch.mod(name, false)
end
end
return M

364
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/input.lua

@ -0,0 +1,364 @@ @@ -0,0 +1,364 @@
local map_util = require("dressing.map_util")
local global_config = require("dressing.config")
local patch = require("dressing.patch")
local util = require("dressing.util")
local M = {}
local context = {
opts = nil,
on_confirm = nil,
winid = nil,
history_idx = nil,
history_tip = nil,
start_in_insert = nil,
}
local keymaps = {
{
desc = "Close vim.ui.input without a result",
plug = "<Plug>DressingInput:Close",
rhs = function()
M.close()
end,
},
{
desc = "Close vim.ui.input with the current buffer contents",
plug = "<Plug>DressingInput:Confirm",
rhs = function()
M.confirm()
end,
},
{
desc = "Show previous vim.ui.input history entry",
plug = "<Plug>DressingInput:HistoryPrev",
rhs = function()
M.history_prev()
end,
},
{
desc = "Show next vim.ui.input history entry",
plug = "<Plug>DressingInput:HistoryNext",
rhs = function()
M.history_next()
end,
},
}
local function set_input(text)
vim.api.nvim_buf_set_lines(0, 0, -1, true, { text })
vim.api.nvim_win_set_cursor(0, { 1, vim.api.nvim_strwidth(text) })
end
local history = {}
M.history_prev = function()
if context.history_idx == nil then
if #history == 0 then
return
end
context.history_tip = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
context.history_idx = #history
elseif context.history_idx == 1 then
return
else
context.history_idx = context.history_idx - 1
end
set_input(history[context.history_idx])
end
M.history_next = function()
if not context.history_idx then
return
elseif context.history_idx == #history then
context.history_idx = nil
set_input(context.history_tip)
else
context.history_idx = context.history_idx + 1
set_input(history[context.history_idx])
end
end
local function close_completion_window()
if vim.fn.pumvisible() == 1 then
local escape_key = vim.api.nvim_replace_termcodes("<C-e>", true, false, true)
vim.api.nvim_feedkeys(escape_key, "n", true)
end
end
local function confirm(text)
if not context.on_confirm then
return
end
close_completion_window()
local ctx = context
context = {}
if not ctx.start_in_insert then
vim.cmd("stopinsert")
end
-- We have to wait briefly for the popup window to close (if present),
-- otherwise vim gets into a very weird and bad state. I was seeing text get
-- deleted from the buffer after the input window closes.
vim.defer_fn(function()
pcall(vim.api.nvim_win_close, ctx.winid, true)
if text and history[#history] ~= text then
table.insert(history, text)
end
-- Defer the callback because we just closed windows and left insert mode.
-- In practice from my testing, if the user does something right now (like,
-- say, opening another input modal) it could happen improperly. I was
-- seeing my successive modals fail to enter insert mode.
vim.defer_fn(function()
ctx.on_confirm(text)
end, 5)
end, 5)
end
M.confirm = function()
local text = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
confirm(text)
end
M.close = function()
confirm(context.opts and context.opts.cancelreturn)
end
M.highlight = function()
if not context.opts then
return
end
local bufnr = vim.api.nvim_win_get_buf(context.winid)
local opts = context.opts
local text = vim.api.nvim_buf_get_lines(bufnr, 0, 1, true)[1]
local ns = vim.api.nvim_create_namespace("DressingHighlight")
local highlights = {}
if type(opts.highlight) == "function" then
highlights = opts.highlight(text)
elseif opts.highlight then
highlights = vim.fn[opts.highlight](text)
end
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
for _, highlight in ipairs(highlights) do
local start = highlight[1]
local stop = highlight[2]
local group = highlight[3]
vim.api.nvim_buf_add_highlight(bufnr, ns, group, 0, start, stop)
end
end
local function split(string, pattern)
local ret = {}
for token in string.gmatch(string, "[^" .. pattern .. "]+") do
table.insert(ret, token)
end
return ret
end
M.completefunc = function(findstart, base)
if not context.opts or not context.opts.completion then
return findstart == 1 and 0 or {}
end
if findstart == 1 then
return 0
else
local completion = context.opts.completion
local pieces = split(completion, ",")
if pieces[1] == "custom" or pieces[1] == "customlist" then
local vimfunc = pieces[2]
local ret
if vim.startswith(vimfunc, "v:lua.") then
local load_func = string.format("return %s(...)", vimfunc:sub(7))
local luafunc, err = loadstring(load_func)
if not luafunc then
vim.api.nvim_err_writeln(
string.format("Could not find completion function %s: %s", vimfunc, err)
)
return {}
end
ret = luafunc(base, base, vim.fn.strlen(base))
else
ret = vim.fn[vimfunc](base, base, vim.fn.strlen(base))
end
if pieces[1] == "custom" then
ret = split(ret, "\n")
end
return ret
else
local ok, result = pcall(vim.fn.getcompletion, base, context.opts.completion)
if ok then
return result
else
vim.api.nvim_err_writeln(
string.format("dressing.nvim: unsupported completion method '%s'", completion)
)
return {}
end
end
end
end
_G.dressing_input_complete = M.completefunc
M.trigger_completion = function()
if vim.fn.pumvisible() == 1 then
return "<C-n>"
else
return "<C-x><C-u>"
end
end
local function create_or_update_win(config, prompt, opts)
local parent_win = 0
local winopt
local win_conf
-- If the previous window is still open and valid, we're going to update it
if context.winid and vim.api.nvim_win_is_valid(context.winid) then
win_conf = vim.api.nvim_win_get_config(context.winid)
parent_win = win_conf.win
winopt = {
relative = win_conf.relative,
win = win_conf.win,
}
else
winopt = {
relative = config.relative,
anchor = config.anchor,
border = config.border,
height = 1,
style = "minimal",
noautocmd = true,
}
end
-- First calculate the desired base width of the modal
local prefer_width =
util.calculate_width(config.relative, config.prefer_width, config, parent_win)
-- Then expand the width to fit the prompt and default value
prefer_width = math.max(prefer_width, 4 + vim.api.nvim_strwidth(prompt))
if opts.default then
prefer_width = math.max(prefer_width, 2 + vim.api.nvim_strwidth(opts.default))
end
-- Then recalculate to clamp final value to min/max
local width = util.calculate_width(config.relative, prefer_width, config, parent_win)
winopt.row = util.calculate_row(config.relative, 1, parent_win)
winopt.col = util.calculate_col(config.relative, width, parent_win)
winopt.width = width
if win_conf and config.relative == "cursor" then
-- If we're cursor-relative we should actually not adjust the row/col to
-- prevent jumping. Also remove related args.
if config.relative == "cursor" then
winopt.row = nil
winopt.col = nil
winopt.relative = nil
winopt.win = nil
end
end
winopt = config.override(winopt) or winopt
-- If the floating win was already open
if win_conf then
-- Make sure the previous on_confirm callback is called with nil
vim.schedule(context.on_confirm)
vim.api.nvim_win_set_config(context.winid, winopt)
local start_in_insert = context.start_in_insert
return context.winid, start_in_insert
else
local start_in_insert = string.sub(vim.api.nvim_get_mode().mode, 1, 1) == "i"
local bufnr = vim.api.nvim_create_buf(false, true)
local winid = vim.api.nvim_open_win(bufnr, true, winopt)
return winid, start_in_insert
end
end
setmetatable(M, {
-- use schedule_wrap to avoid a bug when vim opens
-- (see https://github.com/stevearc/dressing.nvim/issues/15)
__call = util.schedule_wrap_before_vimenter(function(_, opts, on_confirm)
vim.validate({
on_confirm = { on_confirm, "function", false },
})
opts = opts or {}
if type(opts) ~= "table" then
opts = { prompt = tostring(opts) }
end
local config = global_config.get_mod_config("input", opts)
if not config.enabled then
return patch.original_mods.input(opts, on_confirm)
end
if vim.fn.hlID("DressingInputText") ~= 0 then
vim.notify(
'DressingInputText highlight group is deprecated. Set winhighlight="NormalFloat:MyHighlightGroup" instead',
vim.log.levels.WARN
)
end
-- Create or update the window
local prompt = opts.prompt or config.default_prompt
local winid, start_in_insert = create_or_update_win(config, prompt, opts)
context = {
winid = winid,
on_confirm = on_confirm,
opts = opts,
history_idx = nil,
start_in_insert = start_in_insert,
}
vim.api.nvim_win_set_option(winid, "winblend", config.winblend)
vim.api.nvim_win_set_option(winid, "winhighlight", config.winhighlight)
vim.api.nvim_win_set_option(winid, "wrap", false)
local bufnr = vim.api.nvim_win_get_buf(winid)
-- Finish setting up the buffer
vim.api.nvim_buf_set_option(bufnr, "swapfile", false)
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
map_util.create_plug_maps(bufnr, keymaps)
for mode, user_maps in pairs(config.mappings) do
map_util.create_maps_to_plug(bufnr, mode, user_maps, "DressingInput:")
end
if config.insert_only then
vim.keymap.set("i", "<Esc>", M.close, { buffer = bufnr })
end
vim.api.nvim_buf_set_option(bufnr, "filetype", "DressingInput")
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { opts.default or "" })
-- Disable nvim-cmp if installed
local ok, cmp = pcall(require, "cmp")
if ok then
cmp.setup.buffer({ enabled = false })
end
-- Disable mini.nvim completion if installed
vim.api.nvim_buf_set_var(bufnr, "minicompletion_disable", true)
util.add_title_to_win(
winid,
string.gsub(prompt, "^%s*(.-)%s*$", "%1"),
{ align = config.prompt_align }
)
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
desc = "Update highlights",
buffer = bufnr,
callback = M.highlight,
})
if opts.completion then
vim.api.nvim_buf_set_option(bufnr, "completefunc", "v:lua.dressing_input_complete")
vim.api.nvim_buf_set_option(bufnr, "omnifunc", "")
vim.keymap.set("i", "<Tab>", M.trigger_completion, { buffer = bufnr, expr = true })
end
vim.api.nvim_create_autocmd("BufLeave", {
desc = "Cancel vim.ui.input",
buffer = bufnr,
nested = true,
once = true,
callback = M.close,
})
if config.start_in_insert then
vim.cmd("startinsert!")
end
close_completion_window()
M.highlight()
end),
})
return M

38
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/map_util.lua

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
local M = {}
M.create_plug_maps = function(bufnr, plug_bindings)
for _, binding in ipairs(plug_bindings) do
vim.keymap.set("", binding.plug, binding.rhs, { buffer = bufnr, desc = binding.desc })
end
end
---@param bufnr number
---@param mode string
---@param bindings table<string, string>
---@param prefix string
M.create_maps_to_plug = function(bufnr, mode, bindings, prefix)
local maps
if mode == "i" then
maps = vim.api.nvim_buf_get_keymap(bufnr, "")
end
for lhs, rhs in pairs(bindings) do
if rhs then
-- Prefix with <Plug> unless this is a <Cmd> or :Cmd mapping
if type(rhs) == "string" and not rhs:match("[<:]") then
rhs = "<Plug>" .. prefix .. rhs
end
if mode == "i" then
-- HACK for some reason I can't get plug mappings to work in insert mode
for _, map in ipairs(maps) do
if map.lhs == rhs then
rhs = map.callback or map.rhs
break
end
end
end
vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, remap = true })
end
end
end
return M

42
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/patch.lua

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
local all_modules = { "input", "select" }
local M = {}
-- For Neovim before 0.6
if not vim.ui then
vim.ui = {}
end
local enabled_mods = {}
M.original_mods = {}
for _, key in ipairs(all_modules) do
M.original_mods[key] = vim.ui[key]
vim.ui[key] = function(...)
local enabled = enabled_mods[key]
if enabled == nil then
enabled = require("dressing.config")[key].enabled
end
if enabled then
require(string.format("dressing.%s", key))(...)
else
return M.original_mods[key](...)
end
end
end
---Patch or unpatch all vim.ui methods
---@param enabled? boolean When nil, use the default from config
M.all = function(enabled)
for _, name in ipairs(all_modules) do
M.mod(name, enabled)
end
end
---@param name string
---@param enabled? boolean When nil, use the default from config
M.mod = function(name, enabled)
enabled_mods[name] = enabled
end
return M

110
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/builtin.lua

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
local map_util = require("dressing.map_util")
local util = require("dressing.util")
local M = {}
local keymaps = {
{
desc = "Close vim.ui.select without a result",
plug = "<Plug>DressingSelect:Close",
rhs = function()
M.cancel()
end,
},
{
desc = "Select the current vim.ui.select item under the cursor",
plug = "<Plug>DressingSelect:Confirm",
rhs = function()
M.choose()
end,
},
}
M.is_supported = function()
return true
end
local _callback = function(item, idx) end
local _items = {}
local function clear_callback()
_callback = function() end
_items = {}
end
M.select = function(config, items, opts, on_choice)
if vim.fn.hlID("DressingSelectText") ~= 0 then
vim.notify(
'DressingSelectText highlight group is deprecated. Set winhighlight="NormalFloat:MyHighlightGroup" instead',
vim.log.levels.WARN
)
end
_callback = on_choice
_items = items
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_option(bufnr, "swapfile", false)
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
local lines = {}
local max_width = 1
for _, item in ipairs(items) do
local line = opts.format_item(item)
max_width = math.max(max_width, vim.api.nvim_strwidth(line))
table.insert(lines, line)
end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
local width = util.calculate_width(config.relative, max_width, config, 0)
local height = util.calculate_height(config.relative, #lines, config, 0)
local row = util.calculate_row(config.relative, height, 0)
local col = util.calculate_col(config.relative, width, 0)
local winopt = {
relative = config.relative,
anchor = config.anchor,
row = row,
col = col,
border = config.border,
width = width,
height = height,
zindex = 150,
style = "minimal",
}
winopt = config.override(winopt) or winopt
local winnr = vim.api.nvim_open_win(bufnr, true, winopt)
vim.api.nvim_win_set_option(winnr, "winblend", config.winblend)
vim.api.nvim_win_set_option(winnr, "winhighlight", config.winhighlight)
vim.api.nvim_win_set_option(winnr, "cursorline", true)
pcall(vim.api.nvim_win_set_option, winnr, "cursorlineopt", "both")
vim.api.nvim_buf_set_option(bufnr, "filetype", "DressingSelect")
util.add_title_to_win(winnr, opts.prompt)
map_util.create_plug_maps(bufnr, keymaps)
map_util.create_maps_to_plug(bufnr, "n", config.mappings, "DressingSelect:")
vim.api.nvim_create_autocmd("BufLeave", {
desc = "Cancel vim.ui.select",
buffer = bufnr,
nested = true,
once = true,
callback = M.cancel,
})
end
local function close_window()
local callback = _callback
local items = _items
clear_callback()
vim.api.nvim_win_close(0, true)
return callback, items
end
M.choose = function()
local cursor = vim.api.nvim_win_get_cursor(0)
local idx = cursor[1]
local callback, items = close_window()
local item = items[idx]
callback(item, idx)
end
M.cancel = function()
local callback = close_window()
callback(nil, nil)
end
return M

41
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/fzf.lua

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
local M = {}
M.is_supported = function()
return vim.fn.exists("*fzf#run") ~= 0
end
local function clear_callback()
_G.dressing_fzf_choice = function() end
_G.dressing_fzf_cancel = function() end
end
clear_callback()
M._on_term_close = function()
if vim.v.event.status ~= 0 then
_G.dressing_fzf_cancel()
end
end
M.select = function(config, items, opts, on_choice)
local labels = {}
for i, item in ipairs(items) do
table.insert(labels, string.format("%d: %s", i, opts.format_item(item)))
end
_G.dressing_fzf_cancel = function()
clear_callback()
on_choice(nil, nil)
end
_G.dressing_fzf_choice = function(label)
clear_callback()
local colon = string.find(label, ":")
local lnum = tonumber(string.sub(label, 1, colon - 1))
local item = items[lnum]
on_choice(item, lnum)
end
vim.fn["dressing#fzf_run"](labels, string.format('--prompt="%s"', opts.prompt), config.window)
-- fzf doesn't have a cancel callback, so we have to make one.
vim.cmd([[autocmd TermClose <buffer> ++once lua require('dressing.select.fzf')._on_term_close()]])
end
return M

40
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/fzf_lua.lua

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
local M = {}
M.is_supported = function()
return pcall(require, "fzf-lua")
end
M.select = function(config, items, opts, on_choice)
local fzf = require("fzf-lua")
local labels = {}
for i, item in ipairs(items) do
table.insert(labels, string.format("%d: %s", i, opts.format_item(item)))
end
local prompt = (opts.prompt or "Select one of") .. "> "
local fzf_opts = vim.tbl_deep_extend("keep", config, {
prompt = prompt,
fzf_opts = {
["--no-multi"] = "",
["--preview-window"] = "hidden:right:0",
},
actions = {
-- "default" gets called when pressing "enter"
-- all fzf style binds (i.e. "ctrl-y") are valid
["default"] = function(selected, _)
if not selected then
on_choice(nil, nil)
else
local label = selected[1]
local lnum = tonumber(label:match("^(%d+):"))
local item = items[lnum]
on_choice(item, lnum)
end
end,
},
})
fzf.fzf_exec(labels, fzf_opts)
end
return M

74
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/init.lua

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
local global_config = require("dressing.config")
local patch = require("dressing.patch")
local function get_backend(config)
local backends = config.backend
if type(backends) ~= "table" then
backends = { backends }
end
for _, backend in ipairs(backends) do
local ok, mod = pcall(require, string.format("dressing.select.%s", backend))
if ok and mod.is_supported() then
return mod, backend
end
end
return require("dressing.select.builtin"), "builtin"
end
-- use schedule_wrap to avoid a bug when vim opens
-- (see https://github.com/stevearc/dressing.nvim/issues/15)
-- also to prevent focus problems for providers
-- (see https://github.com/stevearc/dressing.nvim/issues/59)
return vim.schedule_wrap(function(items, opts, on_choice)
vim.validate({
items = {
items,
function(a)
return type(a) == "table" and vim.tbl_islist(a)
end,
"list-like table",
},
on_choice = { on_choice, "function", false },
})
opts = opts or {}
local config = global_config.get_mod_config("select", opts, items)
if not config.enabled then
return patch.original_mods.select(items, opts, on_choice)
end
opts.prompt = opts.prompt or "Select one of:"
if config.trim_prompt and opts.prompt:sub(-1, -1) == ":" then
opts.prompt = opts.prompt:sub(1, -2)
end
local format_override = config.format_item_override[opts.kind]
if format_override then
opts.format_item = format_override
elseif opts.format_item then
-- format_item doesn't *technically* have to return a string for the
-- core implementation. We should maintain compatibility by wrapping the
-- return value with tostring
local format_item = opts.format_item
opts.format_item = function(item)
return tostring(format_item(item))
end
else
opts.format_item = tostring
end
local backend, name = get_backend(config)
local winid = vim.api.nvim_get_current_win()
local cursor = vim.api.nvim_win_get_cursor(winid)
backend.select(
config[name],
items,
opts,
vim.schedule_wrap(function(...)
if vim.api.nvim_win_is_valid(winid) then
vim.api.nvim_win_set_cursor(winid, cursor)
end
on_choice(...)
end)
)
end)

63
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/nui.lua

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
local M = {}
M.is_supported = function()
return pcall(require, "nui.menu")
end
M.select = function(config, items, opts, on_choice)
local Menu = require("nui.menu")
local event = require("nui.utils.autocmd").event
local lines = {}
local line_width = 1
for i, item in ipairs(items) do
local line = opts.format_item(item)
line_width = math.max(line_width, vim.api.nvim_strwidth(line))
table.insert(lines, Menu.item(line, { value = item, idx = i }))
end
if not config.size then
line_width = math.max(line_width, config.min_width)
local height = math.max(#lines, config.min_height)
config.size = {
width = line_width,
height = height,
}
end
local border = vim.deepcopy(config.border)
border.text = {
top = opts.prompt,
top_align = "center",
}
local menu = Menu({
position = config.position,
size = config.size,
relative = config.relative,
border = border,
buf_options = config.buf_options,
win_options = config.win_options,
enter = true,
}, {
lines = lines,
max_width = config.max_width,
max_height = config.max_height,
keymap = {
focus_next = { "j", "<Down>", "<Tab>" },
focus_prev = { "k", "<Up>", "<S-Tab>" },
close = { "<Esc>", "<C-c>" },
submit = { "<CR>" },
},
on_close = function()
on_choice(nil, nil)
end,
on_submit = function(item)
on_choice(item.value, item.idx)
end,
})
menu:mount()
menu:on(event.BufLeave, menu.menu_props.on_close, { once = true })
end
return M

138
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/select/telescope.lua

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
local M = {}
M.is_supported = function()
return pcall(require, "telescope")
end
M.custom_kind = {
codeaction = function(opts, defaults, items)
local entry_display = require("telescope.pickers.entry_display")
local finders = require("telescope.finders")
local displayer
local function make_display(entry)
local columns = {
{ entry.idx .. ":", "TelescopePromptPrefix" },
entry.text,
{ entry.client_name, "Comment" },
}
return displayer(columns)
end
local entries = {}
local client_width = 1
local text_width = 1
local idx_width = 1
for idx, item in ipairs(items) do
local client_id = item[1]
local client_name = vim.lsp.get_client_by_id(client_id).name
local text = opts.format_item(item)
client_width = math.max(client_width, vim.api.nvim_strwidth(client_name))
text_width = math.max(text_width, vim.api.nvim_strwidth(text))
idx_width = math.max(idx_width, vim.api.nvim_strwidth(tostring(idx)))
table.insert(entries, {
idx = idx,
display = make_display,
text = text,
client_name = client_name,
ordinal = idx .. " " .. text .. " " .. client_name,
value = item,
})
end
displayer = entry_display.create({
separator = " ",
items = {
{ width = idx_width + 1 },
{ width = text_width },
{ width = client_width },
},
})
defaults.finder = finders.new_table({
results = entries,
entry_maker = function(item)
return item
end,
})
end,
}
M.select = function(config, items, opts, on_choice)
local themes = require("telescope.themes")
local actions = require("telescope.actions")
local state = require("telescope.actions.state")
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local entry_maker = function(item)
local formatted = opts.format_item(item)
return {
display = formatted,
ordinal = formatted,
value = item,
}
end
local picker_opts = config
-- Default to the dropdown theme if no options supplied
if picker_opts == nil then
picker_opts = themes.get_dropdown()
end
local defaults = {
prompt_title = opts.prompt,
previewer = false,
finder = finders.new_table({
results = items,
entry_maker = entry_maker,
}),
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = state.get_selected_entry()
local callback = on_choice
-- Replace on_choice with a no-op so closing doesn't trigger it
on_choice = function(_, _) end
actions.close(prompt_bufnr)
if not selection then
-- User did not select anything.
callback(nil, nil)
return
end
local idx = nil
for i, item in ipairs(items) do
if item == selection.value then
idx = i
break
end
end
callback(selection.value, idx)
end)
actions.close:enhance({
post = function()
on_choice(nil, nil)
end,
})
return true
end,
}
if M.custom_kind[opts.kind] then
M.custom_kind[opts.kind](opts, defaults, items)
end
-- Hook to allow the caller of vim.ui.select to customize the telescope opts
if opts.telescope then
pickers.new(opts.telescope, defaults):find()
else
pickers.new(picker_opts, defaults):find()
end
end
return M

177
etc/soft/nvim/+plugins/dressing.nvim/lua/dressing/util.lua

@ -0,0 +1,177 @@ @@ -0,0 +1,177 @@
local M = {}
local function is_float(value)
local _, p = math.modf(value)
return p ~= 0
end
local function calc_float(value, max_value)
if value and is_float(value) then
return math.min(max_value, value * max_value)
else
return value
end
end
local function calc_list(values, max_value, aggregator, limit)
local ret = limit
if type(values) == "table" then
for _, v in ipairs(values) do
ret = aggregator(ret, calc_float(v, max_value))
end
return ret
else
ret = aggregator(ret, calc_float(values, max_value))
end
return ret
end
local function calculate_dim(desired_size, size, min_size, max_size, total_size)
local ret = calc_float(size, total_size)
local min_val = calc_list(min_size, total_size, math.max, 1)
local max_val = calc_list(max_size, total_size, math.min, total_size)
if not ret then
if not desired_size then
ret = (min_val + max_val) / 2
else
ret = calc_float(desired_size, total_size)
end
end
ret = math.min(ret, max_val)
ret = math.max(ret, min_val)
return math.floor(ret)
end
local function get_max_width(relative, winid)
if relative == "editor" then
return vim.o.columns
else
return vim.api.nvim_win_get_width(winid or 0)
end
end
local function get_max_height(relative, winid)
if relative == "editor" then
return vim.o.lines - vim.o.cmdheight
else
return vim.api.nvim_win_get_height(winid or 0)
end
end
M.calculate_col = function(relative, width, winid)
if relative == "cursor" then
return 0
else
return math.floor((get_max_width(relative, winid) - width) / 2)
end
end
M.calculate_row = function(relative, height, winid)
if relative == "cursor" then
return 0
else
return math.floor((get_max_height(relative, winid) - height) / 2)
end
end
M.calculate_width = function(relative, desired_width, config, winid)
return calculate_dim(
desired_width,
config.width,
config.min_width,
config.max_width,
get_max_width(relative, winid)
)
end
M.calculate_height = function(relative, desired_height, config, winid)
return calculate_dim(
desired_height,
config.height,
config.min_height,
config.max_height,
get_max_height(relative, winid)
)
end
local winid_map = {}
M.add_title_to_win = function(winid, title, opts)
opts = opts or {}
opts.align = opts.align or "center"
if not vim.api.nvim_win_is_valid(winid) then
return
end
-- HACK to force the parent window to position itself
-- See https://github.com/neovim/neovim/issues/13403
vim.cmd("redraw")
local width = math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(title))
local title_winid = winid_map[winid]
local bufnr
if title_winid and vim.api.nvim_win_is_valid(title_winid) then
vim.api.nvim_win_set_width(title_winid, width)
bufnr = vim.api.nvim_win_get_buf(title_winid)
else
bufnr = vim.api.nvim_create_buf(false, true)
local col = 1
if opts.align == "center" then
col = math.floor((vim.api.nvim_win_get_width(winid) - width) / 2)
elseif opts.align == "right" then
col = vim.api.nvim_win_get_width(winid) - 1 - width
elseif opts.align ~= "left" then
vim.notify(
string.format("Unknown dressing window title alignment: '%s'", opts.align),
vim.log.levels.ERROR
)
end
title_winid = vim.api.nvim_open_win(bufnr, false, {
relative = "win",
win = winid,
width = width,
height = 1,
row = -1,
col = col,
focusable = false,
zindex = 151,
style = "minimal",
noautocmd = true,
})
winid_map[winid] = title_winid
vim.api.nvim_win_set_option(
title_winid,
"winblend",
vim.api.nvim_win_get_option(winid, "winblend")
)
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
vim.cmd(string.format(
[[
autocmd WinClosed %d ++once lua require('dressing.util')._on_win_closed(%d)
]],
winid,
winid
))
end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { " " .. title .. " " })
local ns = vim.api.nvim_create_namespace("DressingWindow")
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
vim.api.nvim_buf_add_highlight(bufnr, ns, "FloatTitle", 0, 0, -1)
end
M._on_win_closed = function(winid)
local title_winid = winid_map[winid]
if title_winid and vim.api.nvim_win_is_valid(title_winid) then
vim.api.nvim_win_close(title_winid, true)
end
winid_map[winid] = nil
end
M.schedule_wrap_before_vimenter = function(func)
return function(...)
if vim.v.vim_did_enter == 0 then
return vim.schedule_wrap(func)(...)
else
return func(...)
end
end
end
return M

2
etc/soft/nvim/+plugins/dressing.nvim/plugin/dressing.lua

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
require("dressing").patch()
vim.cmd([[highlight default link FloatTitle FloatBorder]])

22
etc/soft/nvim/+plugins/dressing.nvim/run_tests.sh

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
#!/bin/bash
set -e
mkdir -p ".testenv/config/nvim"
mkdir -p ".testenv/data/nvim"
mkdir -p ".testenv/state/nvim"
mkdir -p ".testenv/run/nvim"
mkdir -p ".testenv/cache/nvim"
PLUGINS=".testenv/data/nvim/site/pack/plugins/start"
if [ ! -e "$PLUGINS/plenary.nvim" ]; then
git clone --depth=1 https://github.com/nvim-lua/plenary.nvim.git "$PLUGINS/plenary.nvim"
fi
XDG_CONFIG_HOME=".testenv/config" \
XDG_DATA_HOME=".testenv/data" \
XDG_STATE_HOME=".testenv/state" \
XDG_RUNTIME_DIR=".testenv/run" \
XDG_CACHE_HOME=".testenv/cache" \
nvim --headless -u tests/minimal_init.lua \
-c "PlenaryBustedDirectory ${1-tests} { minimal_init = './tests/minimal_init.lua' }"
echo "Success"

181
etc/soft/nvim/+plugins/dressing.nvim/tests/input_spec.lua

@ -0,0 +1,181 @@ @@ -0,0 +1,181 @@
require("plenary.async").tests.add_to_env()
local dressing = require("dressing")
local util = require("tests.util")
local channel = a.control.channel
local function run_input(keys, opts)
opts = opts or {}
local tx, rx = channel.oneshot()
vim.ui.input(opts, tx)
util.feedkeys(vim.list_extend(
{ "i" }, -- HACK have to do this because :startinsert doesn't work in tests,
keys
))
if opts.after_fn then
opts.after_fn()
end
return rx()
end
a.describe("input modal", function()
before_each(function()
dressing.patch()
dressing.setup()
end)
after_each(function()
-- Clean up all floating windows so one test failure doesn't cascade
for _, winid in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_config(winid).relative ~= "" then
vim.api.nvim_win_close(winid, true)
end
end
end)
a.it("accepts input", function()
local ret = run_input({
"my text",
"<CR>",
})
assert(ret == "my text", string.format("Got '%s' expected 'my text'", ret))
end)
a.it("Cancels input on <C-c>", function()
local ret = run_input({
"my text",
"<C-c>",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("cancels input when leaving the window", function()
local ret = run_input({
"my text",
}, {
after_fn = function()
vim.cmd([[wincmd p]])
end,
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("cancels on <Esc> when insert_only = true", function()
require("dressing.config").input.insert_only = true
local ret = run_input({
"my text",
"<Esc>",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("does not cancel on <Esc> when insert_only = false", function()
require("dressing.config").input.insert_only = false
local ret = run_input({
"my text",
"<Esc>",
"<CR>",
})
assert(ret == "my text", string.format("Got '%s' expected 'my text'", ret))
end)
a.it("returns cancelreturn when input is canceled <C-c>", function()
local ret = run_input({
"my text",
"<C-c>",
}, { cancelreturn = "CANCELED" })
assert(ret == "CANCELED", string.format("Got '%s' expected nil", ret))
end)
a.it("returns empty string when input is empty", function()
local ret = run_input({
"<CR>",
})
assert(ret == "", string.format("Got '%s' expected nil", ret))
end)
a.it("returns empty string when input is empty, even if cancelreturn set", function()
local ret = run_input({
"<CR>",
}, { cancelreturn = "CANCELED" })
assert(ret == "", string.format("Got '%s' expected nil", ret))
end)
a.it("starts in normal mode when start_in_insert = false", function()
local orig_cmd = vim.cmd
local startinsert_called = false
vim.cmd = function(cmd)
if cmd == "startinsert!" then
startinsert_called = true
end
orig_cmd(cmd)
end
require("dressing.config").input.start_in_insert = false
run_input({
"my text",
"<CR>",
}, {
after_fn = function()
vim.cmd = orig_cmd
end,
})
assert(not startinsert_called, "Got 'true' expected 'false'")
end)
a.it("cancels first callback if second input is opened", function()
local tx, rx = channel.oneshot()
vim.ui.input({}, tx)
util.feedkeys({
"i", -- HACK have to do this because :startinsert doesn't work in tests,
"my text",
})
vim.ui.input({}, function() end)
local ret = rx()
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("supports completion", function()
vim.opt.completeopt = { "menu", "menuone", "noselect" }
vim.cmd([[
function! CustomComplete(arglead, cmdline, cursorpos)
return "first\nsecond\nthird"
endfunction
]])
local ret = run_input({
"<Tab><Tab><C-n><CR>", -- Using tab twice to test both versions of the mapping
}, {
completion = "custom,CustomComplete",
})
assert(ret == "second", string.format("Got '%s' expected 'second'", ret))
assert(vim.fn.pumvisible() == 0, "Popup menu should not be visible after leaving modal")
end)
a.it("can cancel out when popup menu is open", function()
vim.opt.completeopt = { "menu", "menuone", "noselect" }
local ret = run_input({
"<Tab>",
"<C-c>",
}, {
completion = "command",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
assert(vim.fn.pumvisible() == 0, "Popup menu should not be visible after leaving modal")
end)
a.it("doesn't delete text in original buffer", function()
-- This is a regression test for weird behavior I was seeing with the
-- completion popup menu
vim.api.nvim_buf_set_lines(0, 0, 1, true, { "some text" })
vim.api.nvim_win_set_cursor(0, { 1, 4 })
vim.opt.completeopt = { "menu", "menuone", "noselect" }
local ret = run_input({
"<Tab>",
"<C-c>",
}, {
completion = "command",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
local line = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
assert(line == "some text", "Doing <C-c> with popup menu open deleted buffer text o.0")
end)
end)

67
etc/soft/nvim/+plugins/dressing.nvim/tests/manual/completion.lua

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
-- Run this test with :source %
local idx = 1
local cases = {
{
prompt = "Complete file: ",
completion = "file",
},
{
prompt = "Complete cmd: ",
completion = "command",
},
{
prompt = "Complete custom: ",
completion = "custom,CustomComplete",
},
{
prompt = "Complete customlist: ",
completion = "customlist,CustomCompleteList",
},
{
prompt = "Complete custom lua: ",
completion = "custom,v:lua.custom_complete_func",
},
{
prompt = "Complete customlist: ",
completion = "customlist,v:lua.custom_complete_list",
},
}
vim.cmd([[
function! CustomComplete(arglead, cmdline, cursorpos)
return "first\nsecond\nthird"
endfunction
function! CustomCompleteList(arglead, cmdline, cursorpos)
return ['first', 'second', 'third']
endfunction
]])
function _G.custom_complete_func(arglead, cmdline, cursorpos)
return "first\nsecond\nthird"
end
function _G.custom_complete_list(arglead, cmdline, cursorpos)
return { "first", "second", "third" }
end
local function next()
local opts = cases[idx]
if opts then
idx = idx + 1
vim.ui.input(opts, next)
end
end
next()
-- Uncomment this to test opening a modal while the previous one is open
-- vim.ui.input(cases[1], function(text)
-- print(text)
-- end)
-- vim.defer_fn(function()
-- vim.ui.input(cases[2], function(text)
-- print(text)
-- end)
-- end, 2000)

30
etc/soft/nvim/+plugins/dressing.nvim/tests/manual/highlight.lua

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
-- Run this test with :source %
vim.cmd([[
highlight RBP1 guibg=Red ctermbg=red
highlight RBP2 guibg=Yellow ctermbg=yellow
highlight RBP3 guibg=Green ctermbg=green
highlight RBP4 guibg=Blue ctermbg=blue
]])
local rainbow_levels = 4
local function rainbow_hl(cmdline)
local ret = {}
local lvl = 0
for i = 1, string.len(cmdline) do
local char = string.sub(cmdline, i, i)
if char == "(" then
table.insert(ret, { i - 1, i, string.format("RBP%d", (lvl % rainbow_levels) + 1) })
lvl = lvl + 1
elseif char == ")" then
lvl = lvl - 1
table.insert(ret, { i - 1, i, string.format("RBP%d", (lvl % rainbow_levels) + 1) })
end
end
return ret
end
vim.ui.input({
prompt = "Rainbow: ",
default = "((()(())))",
highlight = rainbow_hl,
}, function() end)

21
etc/soft/nvim/+plugins/dressing.nvim/tests/manual/select.lua

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
-- Run this test with :source %
local function run_test(backend)
local config = require("dressing.config")
local prev_backend = config.select.backend
config.select.backend = backend
vim.ui.select({ "first", "second", "third" }, {
prompt = "Make selection",
kind = "test",
}, function(item, lnum)
if item and lnum then
vim.notify(string.format("selected '%s' (idx %d)", item, lnum), vim.log.levels.INFO)
else
vim.notify("Selection canceled", vim.log.levels.INFO)
end
config.select.backend = prev_backend
end)
end
-- Replace this with the desired backend to test
run_test("fzf_lua")

2
etc/soft/nvim/+plugins/dressing.nvim/tests/minimal_init.lua

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
vim.cmd([[set runtimepath+=.]])
vim.cmd([[runtime! plugin/plenary.vim]])

19
etc/soft/nvim/+plugins/dressing.nvim/tests/util.lua

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
require("plenary.async").tests.add_to_env()
local M = {}
M.feedkeys = function(actions, timestep)
timestep = timestep or 10
a.util.sleep(timestep)
for _, action in ipairs(actions) do
a.util.sleep(timestep)
local escaped = vim.api.nvim_replace_termcodes(action, true, false, true)
vim.api.nvim_feedkeys(escaped, "m", true)
end
a.util.sleep(timestep)
-- process pending keys until the queue is empty.
-- Note that this will exit insert mode.
vim.api.nvim_feedkeys("", "x", true)
a.util.sleep(timestep)
end
return M

114
etc/soft/nvim/+plugins/git-conflict.nvim/README.md

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
# git-conflict.nvim
https://user-images.githubusercontent.com/22454918/159362564-a66d8c23-f7dc-4d1d-8e88-c5c73a49047e.mov
A plugin to visualise and resolve conflicts in neovim.
This plugin was inspired by [conflict-marker.vim](https://github.com/rhysd/conflict-marker.vim)
## Status
This plugin is under active development, it should generally work, but you're likely to
encounter some bugs during usage.
## Requirements
- `git`
- `nvim 0.7+`
## Installation
```lua
use {'akinsho/git-conflict.nvim', tag = "*", config = function()
require('git-conflict').setup()
end}
```
I recommend using the tag field of you package manager, so your version of this plugin is only updated when a new tag is pushed as `main` itself might be **unstable**.
## Configuration
```lua
{
default_mappings = true, -- disable buffer local mapping created by this plugin
default_commands = true, -- disable commands created by this plugin
disable_diagnostics = false, -- This will disable the diagnostics in a buffer whilst it is conflicted
highlights = { -- They must have background color, otherwise the default color will be used
incoming = 'DiffText',
current = 'DiffAdd',
}
}
```
## Commands
- `GitConflictChooseOurs` — Select the current changes.
- `GitConflictChooseTheirs` — Select the incoming changes.
- `GitConflictChooseBoth` — Select both changes.
- `GitConflictChooseNone` — Select none of the changes.
- `GitConflictNextConflict` — Move to the next conflict.
- `GitConflictPrevConflict` — Move to the previous conflict.
- `GitConflictListQf` — Get all conflict to quickfix
### Listing conflicts
You can list conflicts in the quick fix list using the `GitConflictListQf` command
<img width="475" alt="Screen Shot 2022-03-27 at 12 03 43" src="https://user-images.githubusercontent.com/22454918/160278511-705a0361-a387-4fc1-8b20-bd799bf85b82.png">
quickfix displayed using [nvim-pqf](https://gitlab.com/yorickpeterse/nvim-pqf)
## Autocommands
When a conflict is detected by this plugin a `User` autocommand is fired
called `GitConflictDetected`. When this is resolved another command is
fired called `GitConflictResolved`.
Either of these can be used to run logic whilst dealing with conflicts
e.g.
```lua
vim.api.nvim_create_autocommand('User', {
pattern = 'GitConflictDetected',
callback = function()
vim.notify('Conflict detected in '..vim.fn.expand('<afile>'))
vim.keymap.set('n', 'cww', function()
engage.conflict_buster()
create_buffer_local_mappings()
end)
end
})
```
## Mappings
This plugin offers default buffer local mappings inside conflicted files. This is primarily because applying these mappings only to relevant buffers
is impossible through global mappings. A user can however disable these by setting `default_mappings = false` anyway and create global mappings as shown below.
The default mappings are:
- <kbd>c</kbd><kbd>o</kbd> — choose ours
- <kbd>c</kbd><kbd>t</kbd> — choose theirs
- <kbd>c</kbd><kbd>b</kbd> — choose both
- <kbd>c</kbd><kbd>0</kbd> — choose none
- <kbd>]</kbd><kbd>x</kbd> — move to previous conflict
- <kbd>[</kbd><kbd>x</kbd> — move to next conflict
If you would rather not use these then disable default mappings an can then map these yourself.
```lua
vim.keymap.set('n', 'co', '<Plug>(git-conflict-ours)')
vim.keymap.set('n', 'ct', '<Plug>(git-conflict-theirs)')
vim.keymap.set('n', 'cb', '<Plug>(git-conflict-both)')
vim.keymap.set('n', 'c0', '<Plug>(git-conflict-none)')
vim.keymap.set('n', ']x', '<Plug>(git-conflict-prev-conflict)')
vim.keymap.set('n', '[x', '<Plug>(git-conflict-next-conflict)')
```
## Issues
**Please read this** — This plugin is not intended to do anything other than provide fancy visuals, and some mappings to handle conflict resolution
It will not be expanded to become a full git management plugin, there are a zillion plugins that do that already, this won't be one of those.
### Feature requests
Open source should be collaborative, if you have an idea for a feature you'd like to see added. Submit a PR rather than a feature request.

20
etc/soft/nvim/+plugins/git-conflict.nvim/create_conflict.sh

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
#!/bin/bash
[ -d ./../conflict-test/ ] && rm -rf ./conflict-test/
mkdir conflict-test
cd conflict-test || exit
git init
touch conflicted.lua
git add conflicted.lua
echo "local value = 1 + 1" > conflicted.lua
git commit -am 'initial'
git checkout -b new_branch
echo "local value = 1 - 1" > conflicted.lua
git commit -am 'first commit on new_branch'
git checkout main
cat > conflicted.lua<< EOF
local value = 5 + 7
print(value)
print(string.format("value is %d", value))
EOF
git commit -am 'second commit on main'
git merge new_branch

734
etc/soft/nvim/+plugins/git-conflict.nvim/lua/git-conflict.lua

@ -0,0 +1,734 @@ @@ -0,0 +1,734 @@
local M = {}
local color = require('git-conflict.colors')
local utils = require('git-conflict.utils')
local fn = vim.fn
local api = vim.api
local fmt = string.format
local map = vim.keymap.set
local job = utils.job
-----------------------------------------------------------------------------//
-- REFERENCES:
-----------------------------------------------------------------------------//
-- Detecting the state of a git repository based on files in the .git directory.
-- https://stackoverflow.com/questions/49774200/how-to-tell-if-my-git-repo-is-in-a-conflict
-- git diff commands to git a list of conflicted files
-- https://stackoverflow.com/questions/3065650/whats-the-simplest-way-to-list-conflicted-files-in-git
-- how to show a full path for files in a git diff command
-- https://stackoverflow.com/questions/10459374/making-git-diff-stat-show-full-file-path
-- Advanced merging
-- https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging
-----------------------------------------------------------------------------//
-- Types
-----------------------------------------------------------------------------//
---@alias ConflictSide "'ours'"|"'theirs'"|"'both'"|"'none"
--- @class ConflictHighlights
--- @field current string
--- @field incoming string
--- @field ancestor string?
---@class RangeMark
---@field label integer
---@field content string
--- @class PositionMarks
--- @field current RangeMark
--- @field incoming RangeMark
--- @field ancestor RangeMark
--- @class Range
--- @field range_start integer
--- @field range_end integer
--- @field content_start integer
--- @field content_end integer
--- @class ConflictPosition
--- @field incoming Range
--- @field middle Range
--- @field current Range
--- @field marks PositionMarks
--- @class ConflictBufferCache
--- @field lines table<integer, boolean> map of conflicted line numbers
--- @field positions ConflictPosition[]
--- @field tick integer
--- @field bufnr integer
--- @class GitConflictConfig
--- @field default_mappings boolean
--- @field disable_diagnostics boolean
--- @field highlights ConflictHighlights
--- @field debug boolean
-----------------------------------------------------------------------------//
-- Constants
-----------------------------------------------------------------------------//
local SIDES = {
OURS = 'ours',
THEIRS = 'theirs',
BOTH = 'both',
BASE = 'base',
NONE = 'none',
}
-- A mapping between the internal names and the display names
local name_map = {
ours = 'current',
theirs = 'incoming',
base = 'ancestor',
both = 'both',
none = 'none',
}
local CURRENT_HL = 'GitConflictCurrent'
local INCOMING_HL = 'GitConflictIncoming'
local ANCESTOR_HL = 'GitConflictAncestor'
local CURRENT_LABEL_HL = 'GitConflictCurrentLabel'
local INCOMING_LABEL_HL = 'GitConflictIncomingLabel'
local ANCESTOR_LABEL_HL = 'GitConflictAncestorLabel'
local PRIORITY = vim.highlight.priorities.user
local NAMESPACE = api.nvim_create_namespace('git-conflict')
local AUGROUP_NAME = 'GitConflictCommands'
local sep = package.config:sub(1, 1)
local conflict_start = '^<<<<<<<'
local conflict_middle = '^======='
local conflict_end = '^>>>>>>>'
local conflict_ancestor = '^|||||||'
local DEFAULT_CURRENT_BG_COLOR = 4218238 -- #405d7e
local DEFAULT_INCOMING_BG_COLOR = 3229523 -- #314753
local DEFAULT_ANCESTOR_BG_COLOR = 6824314 -- #68217A
-----------------------------------------------------------------------------//
--- @type GitConflictConfig
local config = {
debug = false,
default_mappings = true,
default_commands = true,
disable_diagnostics = false,
highlights = {
current = 'DiffText',
incoming = 'DiffAdd',
ancestor = nil,
},
}
--- @return table<string, ConflictBufferCache>
local function create_visited_buffers()
return setmetatable({}, {
__index = function(t, k)
if type(k) == 'number' then return t[api.nvim_buf_get_name(k)] end
end,
})
end
--- A list of buffers that have conflicts in them. This is derived from
--- git using the diff command, and updated at intervals
local visited_buffers = create_visited_buffers()
local state = {
---@type string?
current_watcher_dir = nil,
}
-----------------------------------------------------------------------------//
---Get full path to the repository of the directory passed in
---@param dir any
---@param callback fun(data: string)
local function get_git_root(dir, callback)
job(fmt('git -C "%s" rev-parse --show-toplevel', dir), function(data) callback(data[1]) end)
end
--- Get a list of the conflicted files within the specified directory
--- NOTE: only conflicted files within the git repository of the directory passed in are returned
--- also we add a line prefix to the git command so that the full path is returned
--- e.g. --line-prefix=`git rev-parse --show-toplevel`
---@reference: https://stackoverflow.com/a/10874862
---@param dir string?
---@param callback fun(files: table<string, integer[]>, string)
local function get_conflicted_files(dir, callback)
local cmd = fmt('git -C "%s" diff --line-prefix=%s%s --name-only --diff-filter=U', dir, dir, sep)
job(cmd, function(data)
local files = {}
for _, filename in ipairs(data) do
if #filename > 0 then files[filename] = files[filename] or {} end
end
callback(files, dir)
end)
end
---Add the positions to the buffer in our in memory buffer list
---positions are keyed by a list of range start and end for each mark
---@param buf integer
---@param positions ConflictPosition[]
local function update_visited_buffers(buf, positions)
if not buf or not api.nvim_buf_is_valid(buf) then return end
local name = api.nvim_buf_get_name(buf)
-- If this buffer is not in the list
if not visited_buffers[name] then return end
visited_buffers[name].bufnr = buf
visited_buffers[name].tick = vim.b[buf].changedtick
visited_buffers[name].positions = positions
end
---Set an extmark for each section of the git conflict
---@param bufnr integer
---@param hl string
---@param range_start integer
---@param range_end integer
---@return integer? extmark_id
local function hl_range(bufnr, hl, range_start, range_end)
if not range_start or not range_end then return end
return api.nvim_buf_set_extmark(bufnr, NAMESPACE, range_start, 0, {
hl_group = hl,
hl_eol = true,
hl_mode = 'combine',
end_row = range_end,
priority = PRIORITY,
})
end
---Add highlights and additional data to each section heading of the conflict marker
---These works by covering the underlying text with an extmark that contains the same information
---with some extra detail appended.
---TODO: ideally this could be done by using virtual text at the EOL and highlighting the
---background but this doesn't work and currently this is done by filling the rest of the line with
---empty space and overlaying the line content
---@param bufnr integer
---@param hl_group string
---@param label string
---@param lnum integer
---@return integer extmark id
local function draw_section_label(bufnr, hl_group, label, lnum)
local remaining_space = api.nvim_win_get_width(0) - api.nvim_strwidth(label)
return api.nvim_buf_set_extmark(bufnr, NAMESPACE, lnum, 0, {
hl_group = hl_group,
virt_text = { { label .. string.rep(' ', remaining_space), hl_group } },
virt_text_pos = 'overlay',
priority = PRIORITY,
})
end
---Highlight each part of a git conflict i.e. the incoming changes vs the current/HEAD changes
---TODO: should extmarks be ephemeral? or is it less expensive to save them and only re-apply
---them when a buffer changes since otherwise we have to reparse the whole buffer constantly
---@param positions table
---@param lines string[]
local function highlight_conflicts(positions, lines)
local bufnr = api.nvim_get_current_buf()
M.clear(bufnr)
for _, position in ipairs(positions) do
local current_start = position.current.range_start
local current_end = position.current.range_end
local incoming_start = position.incoming.range_start
local incoming_end = position.incoming.range_end
-- Add one since the index access in lines is 1 based
local current_label = lines[current_start + 1] .. ' (Current changes)'
local incoming_label = lines[incoming_end + 1] .. ' (Incoming changes)'
local curr_label_id = draw_section_label(bufnr, CURRENT_LABEL_HL, current_label, current_start)
local curr_id = hl_range(bufnr, CURRENT_HL, current_start, current_end + 1)
local inc_id = hl_range(bufnr, INCOMING_HL, incoming_start, incoming_end)
local inc_label_id = draw_section_label(bufnr, INCOMING_LABEL_HL, incoming_label, incoming_end)
position.marks = {
current = { label = curr_label_id, content = curr_id },
incoming = { label = inc_label_id, content = inc_id },
ancestor = {},
}
if not vim.tbl_isempty(position.ancestor) then
local ancestor_start = position.ancestor.range_start
local ancestor_end = position.ancestor.range_end
local ancestor_label = lines[ancestor_start + 1] .. ' (Base changes)'
local id = hl_range(bufnr, ANCESTOR_HL, ancestor_start + 1, ancestor_end + 1)
local label_id = draw_section_label(bufnr, ANCESTOR_LABEL_HL, ancestor_label, ancestor_start)
position.marks.ancestor = { label = label_id, content = id }
end
end
end
---Iterate through the buffer line by line checking there is a matching conflict marker
---when we find a starting mark we collect the position details and add it to a list of positions
---@param lines string[]
---@return boolean
---@return ConflictPosition[]
local function detect_conflicts(lines)
local positions = {}
local position, has_start, has_middle, has_ancestor = nil, false, false, false
for index, line in ipairs(lines) do
local lnum = index - 1
if line:match(conflict_start) then
has_start = true
position = {
current = { range_start = lnum, content_start = lnum + 1 },
middle = {},
incoming = {},
ancestor = {},
}
end
if has_start and line:match(conflict_ancestor) then
has_ancestor = true
position.ancestor.range_start = lnum
position.ancestor.content_start = lnum + 1
position.current.range_end = lnum - 1
position.current.content_end = lnum - 1
end
if has_start and line:match(conflict_middle) then
has_middle = true
if has_ancestor then
position.ancestor.content_end = lnum - 1
position.ancestor.range_end = lnum - 1
else
position.current.range_end = lnum - 1
position.current.content_end = lnum - 1
end
position.middle.range_start = lnum
position.middle.range_end = lnum + 1
position.incoming.range_start = lnum + 1
position.incoming.content_start = lnum + 1
end
if has_start and has_middle and line:match(conflict_end) then
position.incoming.range_end = lnum
position.incoming.content_end = lnum - 1
positions[#positions + 1] = position
position, has_start, has_middle, has_ancestor = nil, false, false, false
end
end
return #positions > 0, positions
end
---Helper function to find a conflict position based on a comparator function
---@param bufnr integer
---@param comparator fun(string, integer): boolean
---@param opts table?
---@return ConflictPosition?
local function find_position(bufnr, comparator, opts)
local match = visited_buffers[bufnr]
if not match then return end
local line = utils.get_cursor_pos()
if opts and opts.reverse then
for i = #match.positions, 1, -1 do
local position = match.positions[i]
if comparator(line, position) then return position end
end
return nil
end
for _, position in ipairs(match.positions) do
if comparator(line, position) then return position end
end
return nil
end
---Retrieves a conflict marker position by checking the visited buffers for a supported range
---@param bufnr integer
---@return ConflictPosition?
local function get_current_position(bufnr)
return find_position(
bufnr,
function(line, position)
return position.current.range_start <= line and position.incoming.range_end >= line
end
)
end
---@param position ConflictPosition?
---@param side ConflictSide
local function set_cursor(position, side)
if not position then return end
local target = side == SIDES.OURS and position.current or position.incoming
api.nvim_win_set_cursor(0, { target.range_start + 1, 0 })
end
---Get the conflict marker positions for a buffer if any and update the buffers state
---@param bufnr integer
---@param range_start integer
---@param range_end integer
local function parse_buffer(bufnr, range_start, range_end)
local lines = utils.get_buf_lines(range_start or 0, range_end or -1, bufnr)
local prev_conflicts = visited_buffers[bufnr].positions ~= nil
and #visited_buffers[bufnr].positions > 0
local has_conflict, positions = detect_conflicts(lines)
update_visited_buffers(bufnr, positions)
if has_conflict then
highlight_conflicts(positions, lines)
else
M.clear(bufnr)
end
if prev_conflicts ~= has_conflict then
local pattern = has_conflict and 'GitConflictDetected' or 'GitConflictResolved'
api.nvim_exec_autocmds('User', { pattern = pattern })
end
end
--- Fetch the conflicted files for the current buffer file's repo
--- this is throttled by tracking when last we checked for conflicts
--- and if it is over this interval we check again otherwise we return.
--- When clearing only clear buffers that are in the same repository as the conflicted files
--- as the result (files) might contain only files from a buffer in
--- a different repository in which case extmarks could be cleared for unrelated projects
local function fetch_conflicts(buf)
buf = buf or api.nvim_get_current_buf()
get_git_root(fn.fnamemodify(api.nvim_buf_get_name(buf), ':h'), function(git_root)
get_conflicted_files(git_root, function(files, repo)
for name, b in pairs(visited_buffers) do
-- FIXME: this will not work for nested repositories
if vim.startswith(name, repo) and not files[name] and b.bufnr then
visited_buffers[name] = nil
M.clear(b.bufnr)
end
end
for path, _ in pairs(files) do
visited_buffers[path] = visited_buffers[path] or {}
end
end)
end)
end
---@type table<string, userdata>
local watchers = {}
local on_throttled_change = utils.throttle(1000, function(dir, err, change)
if err then return utils.notify(fmt('Error watching %s(%s): %s', dir, err, change), 'error') end
if config.debug then utils.notify(fmt('Watching %s - change: %s ', dir, change), 'info') end
fetch_conflicts()
end)
--- Stop any watchers that aren't for the current project
---@param curr_dir string
local function stop_running_watchers(curr_dir)
for prev_dir, watcher in pairs(watchers) do
if watcher ~= watchers[curr_dir] then
watcher:stop()
watchers[prev_dir] = nil
end
end
end
--- Create a FS watcher for the current git directory or restart an existing one
---@param dir string
local function watch_gitdir(dir)
-- Stop if there is already a watcher running
if watchers[dir] then return end
---@type userdata
watchers[dir] = vim.loop.new_fs_event()
watchers[dir]:start(
dir,
{ recursive = true },
vim.schedule_wrap(function(...) on_throttled_change(dir, ...) end)
)
state.current_watcher_dir = dir
end
local throttled_watcher = utils.throttle(1000, watch_gitdir)
---Process a buffer if the changed tick has changed
---@param bufnr integer?
local function process(bufnr, range_start, range_end)
bufnr = bufnr or api.nvim_get_current_buf()
if visited_buffers[bufnr] and visited_buffers[bufnr].tick == vim.b[bufnr].changedtick then
return
end
parse_buffer(bufnr, range_start, range_end)
end
-----------------------------------------------------------------------------//
-- Commands
-----------------------------------------------------------------------------//
local function set_commands()
local command = api.nvim_create_user_command
command('GitConflictRefresh', function() fetch_conflicts() end, { nargs = 0 })
command('GitConflictListQf', function()
M.conflicts_to_qf_items(function(items)
if #items > 0 then
fn.setqflist(items, 'r')
vim.cmd([[copen]])
end
end)
end, { nargs = 0 })
command('GitConflictChooseOurs', function() M.choose('ours') end, { nargs = 0 })
command('GitConflictChooseTheirs', function() M.choose('theirs') end, { nargs = 0 })
command('GitConflictChooseBoth', function() M.choose('both') end, { nargs = 0 })
command('GitConflictChooseBase', function() M.choose('base') end, { nargs = 0 })
command('GitConflictChooseNone', function() M.choose('none') end, { nargs = 0 })
command('GitConflictNextConflict', function() M.find_next('ours') end, { nargs = 0 })
command('GitConflictPrevConflict', function() M.find_prev('ours') end, { nargs = 0 })
end
-----------------------------------------------------------------------------//
-- Mappings
-----------------------------------------------------------------------------//
local function set_plug_mappings()
local function opts(desc) return { silent = true, desc = 'Git Conflict: ' .. desc } end
map('n', '<Plug>(git-conflict-ours)', '<Cmd>GitConflictChooseOurs<CR>', opts('Choose Ours'))
map('n', '<Plug>(git-conflict-both)', '<Cmd>GitConflictChooseBoth<CR>', opts('Choose Both'))
map('n', '<Plug>(git-conflict-none)', '<Cmd>GitConflictChooseNone<CR>', opts('Choose None'))
map('n', '<Plug>(git-conflict-theirs)', '<Cmd>GitConflictChooseTheirs<CR>', opts('Choose Theirs'))
map(
'n',
'<Plug>(git-conflict-next-conflict)',
'<Cmd>GitConflictNextConflict<CR>',
opts('Next Conflict')
)
map(
'n',
'<Plug>(git-conflict-prev-conflict)',
'<Cmd>GitConflictPrevConflict<CR>',
opts('Previous Conflict')
)
end
local function setup_buffer_mappings(bufnr)
local function opts(desc)
return { silent = true, buffer = bufnr, desc = 'Git Conflict: ' .. desc }
end
map('n', 'co', '<Plug>(git-conflict-ours)', opts('Choose Ours'))
map('n', 'cb', '<Plug>(git-conflict-both)', opts('Choose Both'))
map('n', 'c0', '<Plug>(git-conflict-none)', opts('Choose None'))
map('n', 'ct', '<Plug>(git-conflict-theirs)', opts('Choose Theirs'))
map('n', '[x', '<Plug>(git-conflict-prev-conflict)', opts('Previous Conflict'))
map('n', ']x', '<Plug>(git-conflict-next-conflict)', opts('Next Conflict'))
vim.b[bufnr].conflict_mappings_set = true
end
---@param key string
---@param mode "'n'|'v'|'o'|'nv'|'nvo'"?
---@return boolean
local function is_mapped(key, mode) return fn.hasmapto(key, mode or 'n') > 0 end
local function clear_buffer_mappings(bufnr)
if not bufnr or not vim.b[bufnr].conflict_mappings_set then return end
if is_mapped('co') then api.nvim_buf_del_keymap(bufnr, 'n', 'co') end
if is_mapped('cb') then api.nvim_buf_del_keymap(bufnr, 'n', 'cb') end
if is_mapped('c0') then api.nvim_buf_del_keymap(bufnr, 'n', 'c0') end
if is_mapped('ct') then api.nvim_buf_del_keymap(bufnr, 'n', 'ct') end
if is_mapped('[x') then api.nvim_buf_del_keymap(bufnr, 'n', '[x') end
if is_mapped(']x') then api.nvim_buf_del_keymap(bufnr, 'n', ']x') end
vim.b[bufnr].conflict_mappings_set = false
end
-----------------------------------------------------------------------------//
-- Highlights
-----------------------------------------------------------------------------//
---Derive the colour of the section label highlights based on each sections highlights
---@param highlights ConflictHighlights
local function set_highlights(highlights)
local current_color = utils.get_hl(highlights.current)
local incoming_color = utils.get_hl(highlights.incoming)
local ancestor_color = utils.get_hl(highlights.ancestor)
local current_bg = current_color.background or DEFAULT_CURRENT_BG_COLOR
local incoming_bg = incoming_color.background or DEFAULT_INCOMING_BG_COLOR
local ancestor_bg = ancestor_color.background or DEFAULT_ANCESTOR_BG_COLOR
local current_label_bg = color.shade_color(current_bg, 60)
local incoming_label_bg = color.shade_color(incoming_bg, 60)
local ancestor_label_bg = color.shade_color(ancestor_bg, 60)
api.nvim_set_hl(0, CURRENT_HL, { background = current_bg, bold = true })
api.nvim_set_hl(0, INCOMING_HL, { background = incoming_bg, bold = true })
api.nvim_set_hl(0, ANCESTOR_HL, { background = ancestor_bg, bold = true })
api.nvim_set_hl(0, CURRENT_LABEL_HL, { background = current_label_bg })
api.nvim_set_hl(0, INCOMING_LABEL_HL, { background = incoming_label_bg })
api.nvim_set_hl(0, ANCESTOR_LABEL_HL, { background = ancestor_label_bg })
end
function M.setup(user_config)
if fn.executable('git') <= 0 then
return vim.schedule(
function() utils.notify('You need to have git installed in order to use this plugin', 'error', true) end
)
end
config = vim.tbl_deep_extend('force', config, user_config or {})
set_highlights(config.highlights)
if config.default_commands then
set_commands()
end
set_plug_mappings()
api.nvim_create_augroup(AUGROUP_NAME, { clear = true })
api.nvim_create_autocmd({ 'VimEnter', 'BufRead', 'SessionLoadPost', 'DirChanged' }, {
group = AUGROUP_NAME,
callback = function(args)
local gitdir = fn.getcwd() .. sep .. '.git'
if fn.isdirectory(gitdir) == 0 or state.current_watcher_dir == fn.getcwd() then return end
stop_running_watchers(gitdir)
fetch_conflicts(args.buf)
throttled_watcher(gitdir)
end,
})
api.nvim_create_autocmd('VimLeavePre', {
group = AUGROUP_NAME,
callback = function()
for key, watcher in pairs(watchers) do
watcher:stop()
watchers[key] = nil
end
end,
})
api.nvim_create_autocmd('User', {
group = AUGROUP_NAME,
pattern = 'GitConflictDetected',
callback = function()
local bufnr = api.nvim_get_current_buf()
if config.disable_diagnostics then vim.diagnostic.disable(bufnr) end
if config.default_mappings then setup_buffer_mappings(bufnr) end
end,
})
api.nvim_create_autocmd('User', {
group = AUGROUP_NAME,
pattern = 'GitConflictResolved',
callback = function()
local bufnr = api.nvim_get_current_buf()
if config.disable_diagnostics then vim.diagnostic.enable(bufnr) end
if config.default_mappings then clear_buffer_mappings(bufnr) end
end,
})
api.nvim_set_decoration_provider(NAMESPACE, {
on_buf = function(_, bufnr, _) return utils.is_valid_buf(bufnr) end,
on_win = function(_, _, bufnr, _, _)
if visited_buffers[bufnr] then process(bufnr) end
end,
})
end
--- Add additional metadata to a quickfix entry if we have already visited the buffer and have that
--- information
---@param item table<string, integer|string>
---@param items table<string, integer|string>[]
---@param visited_buf ConflictBufferCache
local function quickfix_items_from_positions(item, items, visited_buf)
if vim.tbl_isempty(visited_buf.positions) then return end
for _, pos in ipairs(visited_buf.positions) do
for key, value in pairs(pos) do
if
vim.tbl_contains({ name_map.ours, name_map.theirs, name_map.base }, key)
and not vim.tbl_isempty(value)
then
local lnum = value.range_start + 1
local next_item = vim.deepcopy(item)
next_item.text = fmt('%s change', key, lnum)
next_item.lnum = lnum
next_item.col = 0
table.insert(items, next_item)
end
end
end
end
--- Convert the conflicts detected via get conflicted files into a list of quickfix entries.
---@param callback fun(files: table<string, integer[]>)
function M.conflicts_to_qf_items(callback)
local items = {}
---@diagnostic disable-next-line: missing-parameter
get_conflicted_files(fn.expand('%:p:h'), function(files)
for filename, _ in pairs(files) do
local item = {
filename = filename,
pattern = conflict_start,
text = 'git conflict',
type = 'E',
valid = 1,
}
local visited_buf = nil
if next(visited_buffers[filename]) ~= nil then visited_buf = visited_buffers[filename] end
if visited_buf then
quickfix_items_from_positions(item, items, visited_buf)
else
table.insert(items, item)
end
end
callback(items)
end)
end
---@param bufnr integer?
function M.clear(bufnr)
if bufnr and not api.nvim_buf_is_valid(bufnr) then return end
bufnr = bufnr or 0
api.nvim_buf_clear_namespace(bufnr, NAMESPACE, 0, -1)
end
---@param side ConflictSide
function M.find_next(side)
local pos = find_position(
0,
function(line, position)
return position.current.range_start >= line and position.incoming.range_end >= line
end
)
set_cursor(pos, side)
end
---@param side ConflictSide
function M.find_prev(side)
local pos = find_position(
0,
function(line, position)
return position.current.range_start <= line and position.incoming.range_end <= line
end,
{ reverse = true }
)
set_cursor(pos, side)
end
---Select the changes to keep
---@param side ConflictSide
function M.choose(side)
local bufnr = api.nvim_get_current_buf()
local position = get_current_position(bufnr)
if not position then return end
local lines = {}
if vim.tbl_contains({ SIDES.OURS, SIDES.THEIRS, SIDES.BASE }, side) then
local data = position[name_map[side]]
lines = utils.get_buf_lines(data.content_start, data.content_end + 1)
elseif side == SIDES.BOTH then
local first =
utils.get_buf_lines(position.current.content_start, position.current.content_end + 1)
local second =
utils.get_buf_lines(position.incoming.content_start, position.incoming.content_end + 1)
lines = vim.list_extend(first, second)
elseif side == SIDES.NONE then
lines = {}
else
return
end
local pos_start = position.current.range_start < 0 and 0 or position.current.range_start
local pos_end = position.incoming.range_end + 1
api.nvim_buf_set_lines(0, pos_start, pos_end, false, lines)
api.nvim_buf_del_extmark(0, NAMESPACE, position.marks.incoming.label)
api.nvim_buf_del_extmark(0, NAMESPACE, position.marks.current.label)
if position.marks.ancestor.label then
api.nvim_buf_del_extmark(0, NAMESPACE, position.marks.ancestor.label)
end
parse_buffer(bufnr)
end
function M.debug_watchers() vim.pretty_print({ watchers = watchers }) end
return M

35
etc/soft/nvim/+plugins/git-conflict.nvim/lua/git-conflict/colors.lua

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
local M = {}
local bit = require('bit')
local rshift, band = bit.rshift, bit.band
--- Returns a table containing the RGB values encoded inside 24 least
--- significant bits of the number @rgb_24bit
---
--@param rgb_24bit (number) 24-bit RGB value
--@returns (table) with keys 'r', 'g', 'b' in [0,255]
local function decode_24bit_rgb(rgb_24bit)
vim.validate({ rgb_24bit = { rgb_24bit, 'n', true } })
local r = band(rshift(rgb_24bit, 16), 255)
local g = band(rshift(rgb_24bit, 8), 255)
local b = band(rgb_24bit, 255)
return { r = r, g = g, b = b }
end
local function alter(attr, percent) return math.floor(attr * (100 + percent) / 100) end
---@source https://stackoverflow.com/q/5560248
---@see: https://stackoverflow.com/a/37797380
---Darken a specified hex color
---@param color string
---@param percent number
---@return string
function M.shade_color(color, percent)
local rgb = decode_24bit_rgb(color)
if not rgb.r or not rgb.g or not rgb.b then return 'NONE' end
local r, g, b = alter(rgb.r, percent), alter(rgb.g, percent), alter(rgb.b, percent)
r, g, b = math.min(r, 255), math.min(g, 255), math.min(b, 255)
return string.format('#%02x%02x%02x', r, g, b)
end
return M

76
etc/soft/nvim/+plugins/git-conflict.nvim/lua/git-conflict/utils.lua

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
-----------------------------------------------------------------------------//
-- Utils
-----------------------------------------------------------------------------//
local M = {}
local api = vim.api
local fn = vim.fn
--- Wrapper for [vim.notify]
---@param msg string|string[]
---@param level "error" | "trace" | "debug" | "info" | "warn"
---@param once boolean?
function M.notify(msg, level, once)
if type(msg) == 'table' then msg = table.concat(msg, '\n') end
local lvl = vim.log.levels[level:upper()] or vim.log.levels.INFO
local opts = { title = 'Git conflict' }
if once then return vim.notify_once(msg, lvl, opts) end
vim.notify(msg, lvl, opts)
end
--- Start an async job
---@param cmd string
---@param callback fun(data: string[]): nil
function M.job(cmd, callback)
fn.jobstart(cmd, {
stdout_buffered = true,
on_stdout = function(_, data, _) callback(data) end,
})
end
---Only call the passed function once every timeout in ms
---@param timeout integer
---@param func function
---@return function
function M.throttle(timeout, func)
local timer = vim.loop.new_timer()
local running = false
return function(...)
if not running then
func(...)
running = true
timer:start(timeout, 0, function() running = false end)
end
end
end
---Wrapper around `api.nvim_buf_get_lines` which defaults to the current buffer
---@param start integer
---@param _end integer
---@param buf integer?
---@return string[]
function M.get_buf_lines(start, _end, buf)
return api.nvim_buf_get_lines(buf or 0, start, _end, false)
end
---Get cursor row and column as (1, 0) based
---@param win_id integer?
---@return integer, integer
function M.get_cursor_pos(win_id) return unpack(api.nvim_win_get_cursor(win_id or 0)) end
---Check if the buffer is likely to have actionable conflict markers
---@param bufnr integer?
---@return boolean
function M.is_valid_buf(bufnr)
bufnr = bufnr or 0
return #vim.bo[bufnr].buftype == 0 and vim.bo[bufnr].modifiable
end
---@param name string?
---@return table<string, string>
function M.get_hl(name)
if not name then return {} end
return api.nvim_get_hl_by_name(name, true)
end
return M

6
etc/soft/nvim/+plugins/git-conflict.nvim/stylua.toml

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
column_width = 100
indent_type = 'Spaces'
quote_style = 'AutoPreferSingle'
indent_width = 2
no_call_parentheses = false
collapse_simple_statement = "Always"

85
etc/soft/nvim/+plugins/gitsigns.nvim/CONTRIBUTING.md

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
## Requirements
- [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.
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:
```
make gen_help
```
Note: The default Make target is to run both `tl-build` and `gen_help` so it's often easier to just run `make` to update generated files (or even `:make` from within Neovim).
## Testsuite
The testsuite uses the same framework as Neovims funcitonaltest suite.
This is just busted with lots of helper code to create headless neovim instances which are controlled via RPC.
The first time you run the testsuite, Neovim will be compiled from source (this is the Neovim build that tests will use).
This is arguably a little bit overkill for such a plugin but it allows:
- Easily running tests with Neovim master
- Tests which check the screen state (essential for a UI plugin)
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 {}
```

21
etc/soft/nvim/+plugins/gitsigns.nvim/LICENSE

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Lewis Russell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

85
etc/soft/nvim/+plugins/gitsigns.nvim/Makefile

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
export PJ_ROOT=$(PWD)
FILTER ?= .*
LUA_VERSION := 5.1
TL_VERSION := 0.14.1
NEOVIM_BRANCH ?= master
DEPS_DIR := $(PWD)/deps/nvim-$(NEOVIM_BRANCH)
LUAROCKS := luarocks --lua-version=$(LUA_VERSION)
LUAROCKS_TREE := $(DEPS_DIR)/luarocks/usr
LUAROCKS_LPATH := $(LUAROCKS_TREE)/share/lua/$(LUA_VERSION)
LUAROCKS_INIT := eval $$($(LUAROCKS) --tree $(LUAROCKS_TREE) path) &&
.DEFAULT_GOAL := build
$(DEPS_DIR)/neovim:
@mkdir -p $(DEPS_DIR)
git clone --depth 1 https://github.com/neovim/neovim --branch $(NEOVIM_BRANCH) $@
@# disable LTO to reduce compile time
make -C $@ \
DEPS_BUILD_DIR=$(dir $(LUAROCKS_TREE)) \
CMAKE_BUILD_TYPE=RelWithDebInfo \
CMAKE_EXTRA_FLAGS=-DENABLE_LTO=OFF
TL := $(LUAROCKS_TREE)/bin/tl
$(TL):
@mkdir -p $@
$(LUAROCKS) --tree $(LUAROCKS_TREE) install tl $(TL_VERSION)
INSPECT := $(LUAROCKS_LPATH)/inspect.lua
$(INSPECT):
@mkdir -p $@
$(LUAROCKS) --tree $(LUAROCKS_TREE) install inspect
.PHONY: lua_deps
lua_deps: $(TL) $(INSPECT)
.PHONY: test_deps
test_deps: $(DEPS_DIR)/neovim
export VIMRUNTIME=$(DEPS_DIR)/neovim/runtime
export TEST_COLORS=1
.PHONY: test
test: $(DEPS_DIR)/neovim
$(LUAROCKS_INIT) busted \
-v \
--lazy \
--helper=$(PWD)/test/preload.lua \
--output test.busted.outputHandlers.nvim \
--lpath=$(DEPS_DIR)/neovim/?.lua \
--lpath=$(DEPS_DIR)/neovim/build/?.lua \
--lpath=$(DEPS_DIR)/neovim/runtime/lua/?.lua \
--lpath=$(DEPS_DIR)/?.lua \
--lpath=$(PWD)/lua/?.lua \
--filter="$(FILTER)" \
$(PWD)/test
-@stty sane
.PHONY: tl-check
tl-check: $(TL)
$(TL) check teal/*.tl teal/**/*.tl
.PHONY: tl-build
tl-build: tlconfig.lua $(TL)
@$(TL) build
@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

334
etc/soft/nvim/+plugins/gitsigns.nvim/README.md

@ -0,0 +1,334 @@ @@ -0,0 +1,334 @@
# gitsigns.nvim
[![CI](https://github.com/lewis6991/gitsigns.nvim/workflows/CI/badge.svg?branch=main)](https://github.com/lewis6991/gitsigns.nvim/actions?query=workflow%3ACI)
[![Version](https://img.shields.io/github/v/release/lewis6991/gitsigns.nvim)](https://github.com/lewis6991/gitsigns.nvim/releases)
[![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.
## Preview
| Hunk Actions | Line Blame |
| --- | ----------- |
| <img src="https://raw.githubusercontent.com/lewis6991/media/main/gitsigns_actions.gif" width="450em"/> | <img src="https://raw.githubusercontent.com/lewis6991/media/main/gitsigns_blame.gif" width="450em"/> |
## Features
- Signs for added, removed, and changed lines
- Asynchronous using [luv]
- Navigation between hunks
- Stage hunks (with undo)
- Preview diffs of hunks (with word diff)
- Customizable (signs, highlights, mappings, etc)
- Status bar integration
- Git blame a specific line using virtual text.
- Hunk text object
- Automatically follow files moved in the index.
- Live intra-line word diff
- Ability to display deleted/changed lines via virtual lines.
- Support for [yadm]
- Support for detached working trees.
## Requirements
- Neovim >= 0.7.0
**Note:** If your version of Neovim is too old, then you can use a past [release].
**Note:** If you are running a development version of Neovim (aka `master`), then breakage may occur if your build is behind latest.
- Newish version of git. Older versions may not work with some features.
## Installation
[packer.nvim]:
```lua
use {
'lewis6991/gitsigns.nvim',
-- tag = 'release' -- To use the latest release (do not use this if you run Neovim nightly or dev builds!)
}
```
[vim-plug]:
```vim
Plug 'lewis6991/gitsigns.nvim'
```
## Usage
For basic setup with all batteries included:
```lua
require('gitsigns').setup()
```
If using [packer.nvim] gitsigns can
be setup directly in the plugin spec:
```lua
use {
'lewis6991/gitsigns.nvim',
config = function()
require('gitsigns').setup()
end
}
```
Configuration can be passed to the setup function. Here is an example with most of
the default settings:
```lua
require('gitsigns').setup {
signs = {
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 = 'GitSignsDelete', text = '‾', numhl='GitSignsDeleteNr', linehl='GitSignsDeleteLn' },
changedelete = { hl = 'GitSignsChange', text = '~', numhl='GitSignsChangeNr', linehl='GitSignsChangeLn' },
untracked = { hl = 'GitSignsAdd' , text = '┆', numhl='GitSignsAddNr' , linehl='GitSignsAddLn' },
},
signcolumn = true, -- Toggle with `:Gitsigns toggle_signs`
numhl = false, -- Toggle with `:Gitsigns toggle_numhl`
linehl = false, -- Toggle with `:Gitsigns toggle_linehl`
word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff`
watch_gitdir = {
interval = 1000,
follow_files = true
},
attach_to_untracked = true,
current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame`
current_line_blame_opts = {
virt_text = true,
virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align'
delay = 1000,
ignore_whitespace = false,
},
current_line_blame_formatter = '<author>, <author_time:%Y-%m-%d> - <summary>',
sign_priority = 6,
update_debounce = 100,
status_formatter = nil, -- Use default
max_file_length = 40000, -- Disable if file is longer than this (in lines)
preview_config = {
-- Options passed to nvim_open_win
border = 'single',
style = 'minimal',
relative = 'cursor',
row = 0,
col = 1
},
yadm = {
enable = false
},
}
```
For information on configuring Neovim via lua please see [nvim-lua-guide].
### Keymaps
Gitsigns provides an `on_attach` callback which can be used to setup buffer mappings.
Here is a suggested example:
```lua
require('gitsigns').setup{
...
on_attach = function(bufnr)
local gs = package.loaded.gitsigns
local function map(mode, l, r, opts)
opts = opts or {}
opts.buffer = bufnr
vim.keymap.set(mode, l, r, opts)
end
-- Navigation
map('n', ']c', function()
if vim.wo.diff then return ']c' end
vim.schedule(function() gs.next_hunk() end)
return '<Ignore>'
end, {expr=true})
map('n', '[c', function()
if vim.wo.diff then return '[c' end
vim.schedule(function() gs.prev_hunk() end)
return '<Ignore>'
end, {expr=true})
-- Actions
map({'n', 'v'}, '<leader>hs', ':Gitsigns stage_hunk<CR>')
map({'n', 'v'}, '<leader>hr', ':Gitsigns reset_hunk<CR>')
map('n', '<leader>hS', gs.stage_buffer)
map('n', '<leader>hu', gs.undo_stage_hunk)
map('n', '<leader>hR', gs.reset_buffer)
map('n', '<leader>hp', gs.preview_hunk)
map('n', '<leader>hb', function() gs.blame_line{full=true} end)
map('n', '<leader>tb', gs.toggle_current_line_blame)
map('n', '<leader>hd', gs.diffthis)
map('n', '<leader>hD', function() gs.diffthis('~') end)
map('n', '<leader>td', gs.toggle_deleted)
-- Text object
map({'o', 'x'}, 'ih', ':<C-U>Gitsigns select_hunk<CR>')
end
}
```
Note this requires Neovim v0.7 which introduces `vim.keymap.set`. If you are using Neovim with version prior to v0.7 then use the following:
<details>
<summary>Click to expand</summary>
```lua
require('gitsigns').setup {
...
on_attach = function(bufnr)
local function map(mode, lhs, rhs, opts)
opts = vim.tbl_extend('force', {noremap = true, silent = true}, opts or {})
vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts)
end
-- Navigation
map('n', ']c', "&diff ? ']c' : '<cmd>Gitsigns next_hunk<CR>'", {expr=true})
map('n', '[c', "&diff ? '[c' : '<cmd>Gitsigns prev_hunk<CR>'", {expr=true})
-- Actions
map('n', '<leader>hs', ':Gitsigns stage_hunk<CR>')
map('v', '<leader>hs', ':Gitsigns stage_hunk<CR>')
map('n', '<leader>hr', ':Gitsigns reset_hunk<CR>')
map('v', '<leader>hr', ':Gitsigns reset_hunk<CR>')
map('n', '<leader>hS', '<cmd>Gitsigns stage_buffer<CR>')
map('n', '<leader>hu', '<cmd>Gitsigns undo_stage_hunk<CR>')
map('n', '<leader>hR', '<cmd>Gitsigns reset_buffer<CR>')
map('n', '<leader>hp', '<cmd>Gitsigns preview_hunk<CR>')
map('n', '<leader>hb', '<cmd>lua require"gitsigns".blame_line{full=true}<CR>')
map('n', '<leader>tb', '<cmd>Gitsigns toggle_current_line_blame<CR>')
map('n', '<leader>hd', '<cmd>Gitsigns diffthis<CR>')
map('n', '<leader>hD', '<cmd>lua require"gitsigns".diffthis("~")<CR>')
map('n', '<leader>td', '<cmd>Gitsigns toggle_deleted<CR>')
-- Text object
map('o', 'ih', ':<C-U>Gitsigns select_hunk<CR>')
map('x', 'ih', ':<C-U>Gitsigns select_hunk<CR>')
end
}
```
</details>
## Non-Goals
### Implement every feature in [vim-fugitive]
This plugin is actively developed and by one of the most well regarded vim plugin developers.
Gitsigns will only implement features of this plugin if: it is simple, or, the technologies leveraged by Gitsigns (LuaJIT, Libuv, Neovim's API, etc) can provide a better experience.
### Support for other VCS
There aren't any active developers of this plugin who use other kinds of VCS, so adding support for them isn't feasible.
However a well written PR with a commitment of future support could change this.
## Status Line
Use `b:gitsigns_status` or `b:gitsigns_status_dict`. `b:gitsigns_status` is
formatted using `config.status_formatter`. `b:gitsigns_status_dict` is a
dictionary with the keys `added`, `removed`, `changed` and `head`.
Example:
```viml
set statusline+=%{get(b:,'gitsigns_status','')}
```
For the current branch use the variable `b:gitsigns_head`.
## Comparison with [vim-gitgutter]
Feature | gitsigns.nvim | vim-gitgutter | Note
---------------------------------------------------------|----------------------|-----------------------------------------------|--------
Shows signs for added, modified, and removed lines | :white_check_mark: | :white_check_mark: |
Asynchronous | :white_check_mark: | :white_check_mark: |
Runs diffs in-process (no IO or pipes) | :white_check_mark: * | | * Via [lua](https://github.com/neovim/neovim/pull/14536) or FFI.
Supports Nvim's diff-linematch | :white_check_mark: * | | * Via [diff-linematch]
Only adds signs for drawn lines | :white_check_mark: * | | * Via Neovims decoration API
Updates immediately | :white_check_mark: | * | * Triggered on CursorHold
Ensures signs are always up to date | :white_check_mark: * | | * Watches the git dir to do so
Never saves the buffer | :white_check_mark: | :white_check_mark: :heavy_exclamation_mark: * | * Writes [buffer](https://github.com/airblade/vim-gitgutter/blob/0f98634b92da9a35580b618c11a6d2adc42d9f90/autoload/gitgutter/diff.vim#L106) (and index) to short lived temp files
Quick jumping between hunks | :white_check_mark: | :white_check_mark: |
Stage/reset/preview individual hunks | :white_check_mark: | :white_check_mark: |
Preview hunks directly in the buffer (inline) | :white_check_mark: * | | * Via `preview_hunk_inline`
Stage/reset hunks in range/selection | :white_check_mark: | :white_check_mark: :heavy_exclamation_mark: * | * Only stage
Stage/reset all hunks in buffer | :white_check_mark: | |
Undo staged hunks | :white_check_mark: | |
Word diff in buffer | :white_check_mark: | |
Word diff in hunk preview | :white_check_mark: | :white_check_mark: |
Show deleted/changes lines directly in buffer | :white_check_mark: * | | * Via [virtual lines]
Stage partial hunks | :white_check_mark: | |
Hunk text object | :white_check_mark: | :white_check_mark: |
Diff against index or any commit | :white_check_mark: | :white_check_mark: |
Folding of unchanged text | | :white_check_mark: |
Fold text showing whether folded lines have been changed | | :white_check_mark: |
Load hunk locations into the quickfix or location list | :white_check_mark: | :white_check_mark: |
Optional line highlighting | :white_check_mark: | :white_check_mark: |
Optional line number highlighting | :white_check_mark: | :white_check_mark: |
Optional counts on signs | :white_check_mark: | |
Customizable signs and mappings | :white_check_mark: | :white_check_mark: |
Customizable extra diff arguments | :white_check_mark: | :white_check_mark: |
Can be toggled globally or per buffer | :white_check_mark: * | :white_check_mark: | * Through the detach/attach functions
Statusline integration | :white_check_mark: | :white_check_mark: |
Works with [yadm] | :white_check_mark: | |
Live blame in buffer (using virtual text) | :white_check_mark: | |
Blame preview | :white_check_mark: | |
Automatically follows open files moved with `git mv` | :white_check_mark: | |
CLI with completion | :white_check_mark: | * | * Provides individual commands for some actions
Open diffview with any revision/commit | :white_check_mark: | |
As of 2022-09-01
## Integrations
### [vim-fugitive]
When viewing revisions of a file (via `:0Gclog` for example), Gitsigns will attach to the fugitive buffer with the base set to the commit immediately before the commit of that revision.
This means the signs placed in the buffer reflect the changes introduced by that revision of the file.
### [null-ls]
Null-ls can provide code actions from Gitsigns. To setup:
```lua
local null_ls = require("null-ls")
null_ls.setup {
sources = {
null_ls.builtins.code_actions.gitsigns,
...
}
}
```
Will enable `:lua vim.lsp.buf.code_action()` to retrieve code actions from Gitsigns.
### [trouble.nvim]
If installed and enabled (via `config.trouble`; defaults to true if installed), `:Gitsigns setqflist` or `:Gitsigns seqloclist` will open Trouble instead of Neovim's built-in quickfix or location list windows.
## Similar plugins
- [coc-git]
- [vim-gitgutter]
- [vim-signify]
<!-- links -->
[coc-git]: https://github.com/neoclide/coc-git
[diff-linematch]: https://github.com/neovim/neovim/pull/14537
[luv]: https://github.com/luvit/luv/blob/master/docs.md
[null-ls]: https://github.com/jose-elias-alvarez/null-ls.nvim
[nvim-lua-guide]: https://github.com/nanotee/nvim-lua-guide
[packer.nvim]: https://github.com/wbthomason/packer.nvim
[release]: https://github.com/lewis6991/gitsigns.nvim/releases
[trouble.nvim]: https://github.com/folke/trouble.nvim
[vim-fugitive]: https://github.com/tpope/vim-fugitive
[vim-gitgutter]: https://github.com/airblade/vim-gitgutter
[vim-plug]: https://github.com/junegunn/vim-plug
[vim-signify]: https://github.com/mhinz/vim-signify
[virtual lines]: https://github.com/neovim/neovim/pull/15351
[yadm]: https://yadm.io

1023
etc/soft/nvim/+plugins/gitsigns.nvim/doc/gitsigns.txt

File diff suppressed because it is too large Load Diff

165
etc/soft/nvim/+plugins/gitsigns.nvim/etc/doc_template.txt

@ -0,0 +1,165 @@ @@ -0,0 +1,165 @@
*gitsigns.txt* Gitsigns
*gitsigns.nvim*
Author: Lewis Russell <lewis6991@gmail.com>
Version: {{VERSION}}
Homepage: <https://github.com/lewis6991/gitsigns.nvim>
License: MIT license
==============================================================================
INTRODUCTION *gitsigns*
Gitsigns is a plugin for Neovim that provides integration with Git via a
feature set which includes (but not limited to):
• Provides signs in the |signcolumn| to show changed/added/removed lines.
• Mappings to operate on hunks to stage, undo or reset against Git's index.
Gitsigns is implemented entirely in Lua which is built into Neovim and
requires no external dependencies other than git. This is unlike other plugins
that require python, node, etc, which need to communicate with Neovim using
|RPC|. By default, Gitsigns also uses Neovim's built-in diff library
(`vim.diff`) unlike other similar plugins that need to run `git-diff` as an
external process which is less efficient, has tighter bottlenecks and requires
file IO.
==============================================================================
USAGE *gitsigns-usage*
For basic setup with all batteries included:
>
require('gitsigns').setup()
<
Configuration can be passed to the setup function. Here is an example with most
of the default settings:
>
{{SETUP}}
<
==============================================================================
MAPPINGS *gitsigns-mappings*
Custom mappings can be defined in the `on_attach` callback in the config table
passed to |gitsigns-setup()|. See |gitsigns-config-on_attach|.
Most actions can be repeated with `.` if you have |vim-repeat| installed.
==============================================================================
FUNCTIONS *gitsigns-functions*
Note functions with the {async} attribute are run asynchronously and are
non-blocking (return immediately).
{{FUNCTIONS}}
==============================================================================
CONFIGURATION *gitsigns-config*
This section describes the configuration fields which can be passed to
|gitsigns.setup()|. Note fields of type `table` may be marked with extended
meaning the field is merged with the default, with the user value given higher
precedence. This allows only specific sub-fields to be configured without
having to redefine the whole field.
{{CONFIG}}
==============================================================================
COMMAND *gitsigns-command*
*:Gitsigns*
:Gitsigns {subcmd} {args} Run a Gitsigns command. {subcmd} can be any
function documented in |gitsigns-functions|.
Each argument in {args} will be attempted to be
parsed as a Lua value using `loadstring`, however
if this fails the argument will remain as the
string given by |<f-args>|.
Note this command is equivalent to:
`:lua require('gitsigns').{subcmd}({args})`
Examples: >
:Gitsigns diffthis HEAD~1
:Gitsigns blame_line
:Gitsigns toggle_signs
:Gitsigns toggle_current_line_blame
:Gitsigns change_base ~
:Gitsigns reset_buffer
:Gitsigns change_base nil true
==============================================================================
SPECIFYING OBJECTS *gitsigns-object* *gitsigns-revision*
Gitsigns objects are Git revisions as defined in the "SPECIFYING REVISIONS"
section in the gitrevisions(7) man page. For commands that accept an optional
base, the default is the file in the index. Examples:
Object Meaning ~
@ Version of file in the commit referenced by @ aka HEAD
main Version of file in the commit referenced by main
main^ Version of file in the parent of the commit referenced by main
main~ "
main~1 "
main...other Version of file in the merge base of main and other
@^ Version of file in the parent of HEAD
@~2 Version of file in the grandparent of HEAD
92eb3dd Version of file in the commit 92eb3dd
:1 The file's common ancestor during a conflict
:2 The alternate file in the target branch during a conflict
==============================================================================
STATUSLINE *gitsigns-statusline*
*b:gitsigns_status* *b:gitsigns_status_dict*
The buffer variables `b:gitsigns_status` and `b:gitsigns_status_dict` are
provided. `b:gitsigns_status` is formatted using `config.status_formatter`
. `b:gitsigns_status_dict` is a dictionary with the keys:
• `added` - Number of added lines.
• `changed` - Number of changed lines.
• `removed` - Number of removed lines.
• `head` - Name of current HEAD (branch or short commit hash).
• `root` - Top level directory of the working tree.
• `gitdir` - .git directory.
Example:
>
set statusline+=%{get(b:,'gitsigns_status','')}
<
*b:gitsigns_head* *g:gitsigns_head*
Use `g:gitsigns_head` and `b:gitsigns_head` to return the name of the current
HEAD (usually branch name). If the current HEAD is detached then this will be
a short commit hash. `g:gitsigns_head` returns the current HEAD for the
current working directory, whereas `b:gitsigns_head` returns the current HEAD
for each buffer.
*b:gitsigns_blame_line* *b:gitsigns_blame_line_dict*
Provided if |gitsigns-config-current_line_blame| is enabled.
`b:gitsigns_blame_line` if formatted using
`config.current_line_blame_formatter`. `b:gitsigns_blame_line_dict` is a
dictionary containing of the blame object for the current line. For complete
list of keys, see the {blame_info} argument from
|gitsigns-config-current_line_blame_formatter|.
==============================================================================
TEXT OBJECTS *gitsigns-textobject*
Since text objects are defined via keymaps, these are exposed and configurable
via the config, see |gitsigns-config-keymaps|. The lua implementation is
exposed through |gitsigns.select_hunk()|.
==============================================================================
EVENT *gitsigns-event*
Every time Gitsigns updates its knowledge about hunks, it issues a custom
|User| event named `GitSignsUpdate`. You can use it via usual autocommands,
like so: >
vim.api.nvim_create_autocmd('User', {
pattern = 'GitSignsUpdate',
callback = function()
print(os.time() .. ' Gitsigns made an update')
end
})
------------------------------------------------------------------------------
vim:tw=78:ts=8:ft=help:norl:

327
etc/soft/nvim/+plugins/gitsigns.nvim/gen_help.lua

@ -0,0 +1,327 @@ @@ -0,0 +1,327 @@
#!/bin/sh
_=[[
exec lua "$0" "$@"
]]
-- Simple script to update the help doc by reading the config schema.
local inspect = require('inspect')
local config = require('lua.gitsigns.config')
function table.slice(tbl, first, last, step)
local sliced = {}
for i = first or 1, last or #tbl, step or 1 do
sliced[#sliced+1] = tbl[i]
end
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
local function read_file(path)
local f = assert(io.open(path, 'r'))
local t = f:read("*all")
f:close()
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.
local function get_ordered_schema_keys()
local c = read_file('lua/gitsigns/config.lua')
local ci = c:gmatch("[^\n\r]+")
for l in ci do
if startswith(l, 'M.schema = {') then
break
end
end
local keys = {}
for l in ci do
if startswith(l, '}') then
break
end
if l:find('^ (%w+).*') then
local lc = l:gsub('^%s*([%w_]+).*', '%1')
table.insert(keys, lc)
end
end
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')
else
out(' DEPRECATED')
end
if type(dep_info) == 'table' then
if dep_info.message then
out(' '..dep_info.message)
end
if dep_info.new_field then
out('')
local opts_key, field = dep_info.new_field:match('(.*)%.(.*)')
if opts_key and field then
out((' Please instead use the field `%s` in |gitsigns-config-%s|.'):format(field, opts_key))
else
out((' Please instead use |gitsigns-config-%s|.'):format(dep_info.new_field))
end
end
end
out('')
end
local function gen_config_doc_field(field, out)
local v = config.schema[field]
-- Field heading and tag
local t = ('*gitsigns-config-%s*'):format(field)
if #field + #t < 80 then
out(('%-29s %48s'):format(field, t))
else
out(('%-29s'):format(field))
out(('%78s'):format(t))
end
if v.deprecated then
gen_config_doc_deprecated(v.deprecated, out)
end
if v.description then
local d
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
end
local vtype = (function()
if v.type == 'table' and v.deep_extend then
return 'table[extended]'
end
if type(v.type) == 'table' then
v.type = table.concat(v.type, '|')
end
return v.type
end)()
if d:find('\n') then
out((' Type: `%s`'):format(vtype))
out(' Default: >')
out(' '..d:gsub('\n([^\n\r])', '\n %1'))
out('<')
else
out((' Type: `%s`, Default: %s'):format(vtype, d))
out()
end
out(v.description:gsub(' +$', ''))
end
end
local function gen_config_doc()
local res = {}
local function out(line)
res[#res+1] = line or ''
end
for _, k in ipairs(get_ordered_schema_keys()) do
gen_config_doc_field(k, out)
end
return table.concat(res, '\n')
end
local function parse_func_header(line)
local func = line:match('%.([^ ]+)')
if not func then
error('Unable to parse: '..line)
end
local args_raw = line:match('function%((.*)%)')
local args = {}
for k in string.gmatch(args_raw, "([%w_]+):") do
if k:sub(1, 1) ~= '_' then
args[#args+1] = string.format('{%s}', k)
end
end
return string.format(
'%-40s%38s',
string.format('%s(%s)', func, table.concat(args, ', ')),
'*gitsigns.'..func..'()*'
)
end
local function gen_functions_doc_from_file(path)
local i = read_file(path):gmatch("([^\n]*)\n?")
local res = {}
local blocks = {}
local block = {''}
local in_block = false
for l in i do
local l1 = l:match('^%-%-%- ?(.*)')
if l1 then
in_block = true
if l1 ~= '' and l1 ~= '<' then
l1 = ' '..l1
end
block[#block+1] = l1
else
if in_block then
-- First line after block
block[1] = parse_func_header(l)
blocks[#blocks+1] = block
block = {''}
end
in_block = false
end
end
for j = #blocks, 1, -1 do
local b = blocks[j]
for k = 1, #b do
res[#res+1] = b[k]
end
res[#res+1] = ''
end
return table.concat(res, '\n')
end
local function gen_functions_doc(files)
local res = ''
for _, path in ipairs(files) do
res = res..'\n'..gen_functions_doc_from_file(path)
end
return res
end
local function get_setup_from_readme()
local i = read_file('README.md'):gmatch("([^\n]*)\n?")
local res = {}
local function append(line)
res[#res+1] = line ~= '' and ' '..line or ''
end
for l in i do
if l:match("require%('gitsigns'%).setup {") then
append(l)
break
end
end
for l in i do
append(l)
if l == '}' then
break
end
end
return table.concat(res, '\n')
end
local function get_marker_text(marker)
return ({
VERSION = '0.6-dev',
CONFIG = gen_config_doc,
FUNCTIONS = gen_functions_doc{
'teal/gitsigns.tl',
'teal/gitsigns/actions.tl',
},
SETUP = get_setup_from_readme
})[marker]
end
local function main()
local i = read_file('etc/doc_template.txt'):gmatch("([^\n]*)\n?")
local out = io.open('doc/gitsigns.txt', 'w')
for l in i do
local marker = l:match('{{(.*)}}')
if marker then
local sub = get_marker_text(marker)
if sub then
if type(sub) == 'function' then
sub = sub()
end
sub = sub:gsub('%%', '%%%%')
l = l:gsub('{{'..marker..'}}', sub)
end
end
out:write(l or '', '\n')
end
end
main()

37
etc/soft/nvim/+plugins/gitsigns.nvim/gitsigns.nvim-scm-1.rockspec

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
local _MODREV, _SPECREV = 'scm', '-1'
rockspec_format = "3.0"
package = 'gitsigns.nvim'
version = _MODREV .. _SPECREV
description = {
summary = 'Git signs written in pure lua',
detailed = [[
Super fast git decorations implemented purely in lua/teal.
]],
homepage = 'http://github.com/lewis6991/gitsigns.nvim',
license = 'MIT/X11',
labels = { 'neovim' }
}
dependencies = {
'lua == 5.1',
}
source = {
url = 'http://github.com/lewis6991/gitsigns.nvim/archive/v' .. _MODREV .. '.zip',
dir = 'gitsigns.nvim-' .. _MODREV,
}
if _MODREV == 'scm' then
source = {
url = 'git://github.com/lewis6991/gitsigns.nvim',
}
end
build = {
type = 'builtin',
copy_directories = {
'doc'
}
}

3
etc/soft/nvim/+plugins/gitsigns.nvim/lua/README.md

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
**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.

546
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns.lua

@ -0,0 +1,546 @@ @@ -0,0 +1,546 @@
local async = require('gitsigns.async')
local void = require('gitsigns.async').void
local scheduler = require('gitsigns.async').scheduler
local Status = require("gitsigns.status")
local git = require('gitsigns.git')
local manager = require('gitsigns.manager')
local util = require('gitsigns.util')
local hl = require('gitsigns.highlight')
local gs_cache = require('gitsigns.cache')
local cache = gs_cache.cache
local CacheEntry = gs_cache.CacheEntry
local gs_config = require('gitsigns.config')
local Config = gs_config.Config
local config = gs_config.config
local gs_debug = require("gitsigns.debug")
local dprintf = gs_debug.dprintf
local dprint = gs_debug.dprint
local Debounce = require("gitsigns.debounce")
local debounce_trailing = Debounce.debounce_trailing
local throttle_by_id = Debounce.throttle_by_id
local api = vim.api
local uv = vim.loop
local current_buf = api.nvim_get_current_buf
local M = {}
M.detach_all = function()
for k, _ in pairs(cache) do
M.detach(k)
end
end
M.detach = function(bufnr, _keep_signs)
bufnr = bufnr or current_buf()
dprint('Detached')
local bcache = cache[bufnr]
if not bcache then
dprint('Cache was nil')
return
end
manager.detach(bufnr, _keep_signs)
Status:clear(bufnr)
cache:destroy(bufnr)
end
local function parse_fugitive_uri(name)
local _, _, root_path, sub_module_path, commit, real_path =
name:find([[^fugitive://(.*)/%.git(.*/)/(%x-)/(.*)]])
if commit == '0' then
commit = nil
end
if root_path then
sub_module_path = sub_module_path:gsub("^/modules", "")
name = root_path .. sub_module_path .. real_path
end
return name, commit
end
local function parse_gitsigns_uri(name)
local _, _, root_path, commit, rel_path =
name:find([[^gitsigns://(.*)/%.git/(.*):(.*)]])
if commit == ':0' then
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()
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 vimgrep_running = false
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
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)
end
local function on_detach(_, bufnr)
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
end
end
return gitdir, toplevel
end
local function try_worktrees(_bufnr, file, encoding)
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 attach_throttled = throttle_by_id(function(cbuf, aucmd)
local __FUNC__ = 'attach'
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
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
local file, commit = get_buf_path(cbuf)
local encoding = vim.bo[cbuf].fileencoding
local file_dir = util.dirname(file)
if not file_dir or not util.path_exists(file_dir) then
dprint('Not a path')
return
end
local gitdir_oap, toplevel_oap = on_attach_pre(cbuf)
local git_obj = git.Obj.new(file, encoding, gitdir_oap, toplevel_oap)
if not git_obj then
git_obj = try_worktrees(cbuf, file, encoding)
scheduler()
end
if not git_obj then
dprint('Empty git obj')
return
end
local repo = git_obj.repo
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 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
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 = 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
api.nvim_buf_attach(cbuf, false, {
on_lines = on_lines,
on_reload = on_reload,
on_detach = on_detach,
})
manager.update(cbuf, cache[cbuf])
if config.keymaps and not vim.tbl_isempty(config.keymaps) then
require('gitsigns.mappings')(config.keymaps, cbuf)
end
end)
M.attach = void(function(bufnr, _trigger)
attach_throttled(bufnr or current_buf(), _trigger)
end)
local M0 = M
local function complete(arglead, line)
local words = vim.split(line, '%s+')
local n = #words
local actions = require('gitsigns.actions')
local matches = {}
if n == 2 then
for _, m in ipairs({ actions, M0 }) do
for func, _ in pairs(m) do
if not func:match('^[a-z]') then
elseif vim.startswith(func, arglead) then
table.insert(matches, func)
end
end
end
elseif n > 2 then
local cmp_func = actions._get_cmp_func(words[2])
if cmp_func then
return cmp_func(arglead)
end
end
return matches
end
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
end
local run_cmd_func = void(function(params)
local pos_args_raw, named_args_raw = require('gitsigns.argparse').parse_args(params.args)
local func = pos_args_raw[1]
if not func then
func = async.wrap(vim.ui.select, 3)(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 actions = require('gitsigns.actions')
local actions0 = actions
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
cmd_func(args, params)
return
end
if type(actions0[func]) == 'function' then
actions0[func](unpack(pos_args), named_args)
return
end
if type(M0[func]) == 'function' then
M0[func](unpack(pos_args))
return
end
error(string.format('%s is not a valid function or action', func))
end)
local function setup_command()
api.nvim_create_user_command('Gitsigns', run_cmd_func,
{ force = true, nargs = '*', range = true, complete = complete })
end
local function wrap_func(fn, ...)
local args = { ... }
local nargs = select('#', ...)
return function()
fn(unpack(args, 1, nargs))
end
end
local function autocmd(event, opts)
local opts0 = {}
if type(opts) == "function" then
opts0.callback = wrap_func(opts)
else
opts0 = opts
end
opts0.group = 'gitsigns'
api.nvim_create_autocmd(event, opts0)
end
local function on_or_after_vimenter(fn)
if vim.v.vim_did_enter == 1 then
fn()
else
api.nvim_create_autocmd('VimEnter', {
callback = wrap_func(fn),
once = true,
})
end
end
M.setup = void(function(cfg)
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
gs_debug.debug_mode = config.debug_mode
gs_debug.verbose = config._verbose
if config.debug_mode then
for nm, f in pairs(gs_debug.add_debug_functions(cache)) do
M0[nm] = f
end
end
manager.setup()
Status.formatter = config.status_formatter
on_or_after_vimenter(hl.setup_highlights)
setup_command()
git.enable_yadm = config.yadm.enable
git.set_version(config._git_version)
scheduler()
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, 'setup')
scheduler()
end
end
api.nvim_create_augroup('gitsigns', {})
autocmd('VimLeavePre', M.detach_all)
autocmd('ColorScheme', hl.setup_highlights)
autocmd('BufRead', wrap_func(M.attach, nil, 'BufRead'))
autocmd('BufNewFile', wrap_func(M.attach, nil, 'BufNewFile'))
autocmd('BufWritePost', wrap_func(M.attach, nil, 'BufWritePost'))
autocmd('OptionSet', {
pattern = 'fileformat',
callback = function()
require('gitsigns.actions').refresh()
end, })
autocmd('QuickFixCmdPre', {
pattern = '*vimgrep*',
callback = function()
vimgrep_running = true
end,
})
autocmd('QuickFixCmdPost', {
pattern = '*vimgrep*',
callback = function()
vimgrep_running = false
end,
})
require('gitsigns.current_line_blame').setup()
scheduler()
manager.update_cwd_head()
autocmd('DirChanged', debounce_trailing(100, manager.update_cwd_head))
end)
if _TEST then
M.parse_fugitive_uri = parse_fugitive_uri
end
return setmetatable(M, {
__index = function(_, f)
return (require('gitsigns.actions'))[f]
end,
})

1269
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/actions.lua

File diff suppressed because it is too large Load Diff

91
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/argparse.lua

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
local M = {}
local function is_char(x)
return x:match('[^=\'"%s]') ~= nil
end
function M.parse_args(x)
local pos_args, named_args = {}, {}
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 i = 1
while i <= #x do
local ch = x:sub(i, i)
if state == 'in_arg' then
if is_char(ch) then
cur_arg = cur_arg .. ch
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_ws' then
if is_char(ch) then
cur_arg = ch
state = 'in_arg'
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 state == 'in_arg' and #cur_arg > 0 then
pos_args[#pos_args + 1] = cur_arg
elseif state == 'in_value' and #cur_arg > 0 then
named_args[cur_arg] = cur_val
end
return pos_args, named_args
end
return M

87
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/async.lua

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
local M = {}
local main_co_or_nil = coroutine.running()
function M.wrap(func, argc)
assert(argc)
return function(...)
if coroutine.running() == main_co_or_nil then
return func(...)
end
return coroutine.yield(func, argc, ...)
end
end
function M.void(func)
return function(...)
if coroutine.running() ~= main_co_or_nil then
return func(...)
end
local co = coroutine.create(func)
local function step(...)
local ret = { coroutine.resume(co, ...) }
local stat, err_or_fn, nargs = unpack(ret)
if not stat then
error(string.format("The coroutine failed with this message: %s\n%s",
err_or_fn, debug.traceback(co)))
end
if coroutine.status(co) == 'dead' then
return
end
assert(type(err_or_fn) == "function", "type error :: expected func")
local args = { select(4, unpack(ret)) }
args[nargs] = step
err_or_fn(unpack(args, 1, nargs))
end
step(...)
end
end
M.scheduler = M.wrap(vim.schedule, 1)
return M

85
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/cache.lua

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
local Hunk = require("gitsigns.hunks").Hunk
local GitObj = require('gitsigns.git').Obj
local M = {CacheEntry = {}, CacheObj = {}, }
local CacheEntry = M.CacheEntry
CacheEntry.get_compare_rev = function(self, base)
base = base or self.base
if base then
return base
end
if self.commit then
return string.format('%s^', self.commit)
end
local stage = self.git_obj.has_conflicts and 1 or 0
return string.format(':%d', stage)
end
CacheEntry.get_rev_bufname = function(self, 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)
end
CacheEntry.invalidate = function(self)
self.compare_text = nil
self.hunks = nil
end
CacheEntry.new = function(o)
o.staged_diffs = o.staged_diffs or {}
return setmetatable(o, { __index = CacheEntry })
end
CacheEntry.destroy = function(self)
local w = self.gitdir_watcher
if w and not w:is_closing() then
w:close()
end
end
M.CacheObj.destroy = function(self, bufnr)
self[bufnr]:destroy()
self[bufnr] = nil
end
M.cache = setmetatable({}, {
__index = M.CacheObj,
})
return M

834
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/config.lua

@ -0,0 +1,834 @@ @@ -0,0 +1,834 @@
local warn
do
local ok, ret = pcall(require, 'gitsigns.message')
if ok then
warn = ret.warn
end
end
local M = {Config = {DiffOpts = {}, SignConfig = {}, watch_gitdir = {}, current_line_blame_formatter_opts = {}, current_line_blame_opts = {}, yadm = {}, Worktree = {}, }, }
M.config = {}
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 = 'GitSignsDelete', text = '', numhl = 'GitSignsDeleteNr', linehl = 'GitSignsDeleteLn' },
changedelete = { hl = 'GitSignsChange', text = '~', numhl = 'GitSignsChangeNr', linehl = 'GitSignsChangeLn' },
untracked = { hl = 'GitSignsAdd', text = '', numhl = 'GitSignsAddNr', linehl = 'GitSignsAddLn' },
},
description = [[
Configuration for signs:
`hl` specifies the highlight group to use for the sign.
`text` specifies the character to use for the sign.
`numhl` specifies the highlight group to use for the number column
(see |gitsigns-config.numhl|).
`linehl` specifies the highlight group to use for the line
(see |gitsigns-config.linehl|).
`show_count` to enable showing count of hunk, e.g. number of deleted
lines.
Note if a highlight is not defined, it will be automatically derived by
searching for other defined highlights in the following order:
`GitGutter*`
`Signify*`
`Diff*Gutter`
`diff*`
`Diff*`
For example if `GitSignsAdd` is not defined but `GitGutterAdd` is defined,
then `GitSignsAdd` will be linked to `GitGutterAdd`.
]],
},
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()
local r = {
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
elseif jit and jit.os ~= "Windows" 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))
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.
Note Neovim v0.5 uses LuaJIT's FFI interface, whereas v0.5+ uses
`vim.diff`.
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)
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()
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.
]],
},
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
]],
},
_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, ...)
vim.notify(s:format(...), vim.log.levels.WARN, { title = 'gitsigns' })
end
local function validate_config(config)
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 },
})
end
end
end
end
local function resolve_default(v)
if type(v.default) == 'function' and v.type ~= 'function' then
return (v.default)()
else
return v.default
end
end
local function handle_deprecated(cfg)
for k, v in pairs(M.schema) do
local dep = v.deprecated
if dep and cfg[k] ~= nil then
if type(dep) == "table" then
if dep.new_field then
local opts_key, field = dep.new_field:match('(.*)%.(.*)')
if opts_key and field then
local opts = (cfg[opts_key] or {})
opts[field] = cfg[k]
cfg[opts_key] = opts
else
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
function M.build(user_config)
user_config = user_config or {}
handle_deprecated(user_config)
validate_config(user_config)
local config = M.config
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, user_config[k])
else
config[k] = user_config[k]
end
else
config[k] = resolve_default(v)
end
end
end
return M

213
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/current_line_blame.lua

@ -0,0 +1,213 @@ @@ -0,0 +1,213 @@
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 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)
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
end
local function reset(bufnr)
bufnr = bufnr or current_buf()
api.nvim_buf_del_extmark(bufnr, namespace, 1)
vim.b[bufnr].gitsigns_blame_line_dict = nil
end
local max_cache_size = 1000
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
end
function BlameCache:get(bufnr, lnum)
if not config._blame_cache then return end
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, 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)
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)
end
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
return
end
if api.nvim_get_mode().mode == 'i' then
reset(bufnr)
return
end
if get_extmark(bufnr) then
reset(bufnr)
set_extmark(bufnr, lnum)
end
if vim.fn.foldclosed(lnum) ~= -1 then
return
end
local opts = config.current_line_blame_opts
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
return
end
if not api.nvim_buf_is_loaded(bufnr) then
return
end
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
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()
api.nvim_create_augroup('gitsigns_blame', {})
for k, _ in pairs(cache) do
reset(k)
end
if config.current_line_blame then
api.nvim_create_autocmd(
{ 'FocusGained', 'BufEnter', 'CursorMoved', 'CursorMovedI' },
{ group = 'gitsigns_blame', callback = function() update() end })
api.nvim_create_autocmd(
{ 'InsertEnter', 'FocusLost', 'BufLeave' },
{ group = 'gitsigns_blame', callback = function() reset() end })
vim.schedule(update)
end
end
return M

84
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/debounce.lua

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
local uv = require('gitsigns.uv')
local M = {}
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
end
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
end
function M.throttle_by_id(fn, schedule)
local scheduled = {}
local running = {}
return function(id, ...)
if scheduled[id] then
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

150
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/debug.lua

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
local M = {
debug_mode = false,
verbose = false,
messages = {},
}
local function getvarvalue(name, lvl)
lvl = lvl + 1
local value
local found
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
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
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)
return ret
end
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)
end
function M.dprint(obj)
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)
end
function M.vprint(obj)
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)
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
end
function M.eprint(msg)
eprint(msg, 1)
end
function M.eprintf(fmt, ...)
eprint(fmt:format(...), 1)
end
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' 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
function M.add_debug_functions(cache)
local R = {}
R.dump_cache = function()
local text = vim.inspect(cache, { process = process })
vim.api.nvim_echo({ { text } }, false, {})
return cache
end
R.debug_messages = function(noecho)
if not noecho then
for _, m in ipairs(M.messages) do
vim.api.nvim_echo({ { m } }, false, {})
end
end
return M.messages
end
R.clear_debug = function()
M.messages = {}
end
return R
end
return M

18
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff.lua

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
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 linematch0
if linematch ~= false then
linematch0 = diff_opts.linematch
end
return f(a, b, diff_opts.algorithm, diff_opts.indent_heuristic, linematch0)
end

80
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff_ext.lua

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
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 M = {}
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()
end
M.run_diff = function(
text_cmp,
text_buf,
diff_algo,
indent_heuristic)
local results = {}
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)
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

153
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff_int.lua

@ -0,0 +1,153 @@ @@ -0,0 +1,153 @@
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'
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 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)
if not vim.diff then
run_diff_xdl = require('gitsigns.diff_int.xdl_diff_ffi')
end
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
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 gaps_between_regions = 5
local function denoise_hunks(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, added)
local adds = {}
local rems = {}
if #removed ~= #added then
return rems, adds
end
for i = 1, #removed do
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)
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

145
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diff_int/xdl_diff_ffi.lua

@ -0,0 +1,145 @@ @@ -0,0 +1,145 @@
local ffi = require("ffi")
ffi.cdef([[
typedef struct s_mmbuffer { const char *ptr; long size; } mmbuffer_t;
typedef struct s_xpparam {
unsigned long flags;
// See Documentation/diff-options.txt.
char **anchors;
size_t anchors_nr;
} xpparam_t;
typedef long (__stdcall *find_func_t)(
const char *line,
long line_len,
char *buffer,
long buffer_size,
void *priv
);
typedef int (__stdcall *xdl_emit_hunk_consume_func_t)(
long start_a, long count_a, long start_b, long count_b,
void *cb_data
);
typedef struct s_xdemitconf {
long ctxlen;
long interhunkctxlen;
unsigned long flags;
find_func_t find_func;
void *find_func_priv;
xdl_emit_hunk_consume_func_t hunk_func;
} xdemitconf_t;
typedef struct s_xdemitcb {
void *priv;
int (__stdcall *outf)(void *, mmbuffer_t *, int);
} xdemitcb_t;
int xdl_diff(
mmbuffer_t *mf1,
mmbuffer_t *mf2,
xpparam_t const *xpp,
xdemitconf_t const *xecfg,
xdemitcb_t *ecb
);
]])
local function setup_mmbuffer(lines)
local text = vim.tbl_isempty(lines) and '' or table.concat(lines, '\n') .. '\n'
return text, #text
end
local function get_xpparam_flag(diff_algo)
local daflag = 0
if diff_algo == 'minimal' then daflag = 1
elseif diff_algo == 'patience' then daflag = math.floor(2 ^ 14)
elseif diff_algo == 'histogram' then daflag = math.floor(2 ^ 15)
end
return daflag
end
local mmba = ffi.new('mmbuffer_t')
local mmbb = ffi.new('mmbuffer_t')
local xpparam = ffi.new('xpparam_t')
local emitcb = ffi.new('xdemitcb_t')
local function run_diff_xdl(fa, fb, diff_algo)
mmba.ptr, mmba.size = setup_mmbuffer(fa)
mmbb.ptr, mmbb.size = setup_mmbuffer(fb)
xpparam.flags = get_xpparam_flag(diff_algo)
local results = {}
local hunk_func = ffi.cast('xdl_emit_hunk_consume_func_t', function(
start_a, count_a, start_b, count_b)
local ca = tonumber(count_a)
local cb = tonumber(count_b)
local sa = tonumber(start_a)
local sb = tonumber(start_b)
if ca > 0 then sa = sa + 1 end
if cb > 0 then sb = sb + 1 end
results[#results + 1] = { sa, ca, sb, cb }
return 0
end)
local emitconf = ffi.new('xdemitconf_t')
emitconf.hunk_func = hunk_func
local ok = ffi.C.xdl_diff(mmba, mmbb, xpparam, emitconf, emitcb)
hunk_func:free()
return ok == 0 and results
end
jit.off(run_diff_xdl)
return run_diff_xdl

201
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/diffthis.lua

@ -0,0 +1,201 @@ @@ -0,0 +1,201 @@
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 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 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, dbufnr, base, bcache)
local buftext = util.buf_lines(dbufnr)
bcache.git_obj:stage_lines(buftext)
scheduler()
vim.bo[dbufnr].modified = false
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
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, 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'
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, opts)
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
if not base and bcache.git_obj.has_conflicts then
local cwin = api.nvim_get_current_win()
run(':2', true, opts)
api.nvim_set_current_win(cwin)
opts.split = 'belowright'
run(':3', true, opts)
api.nvim_set_current_win(cwin)
else
run(base, true, opts)
end
end)
M.show = void(function(base)
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'
end
M.update = throttle_by_id(void(function(bufnr)
if not vim.wo.diff then
return
end
local bcache = cache[bufnr]
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
end
end
end))
return M

620
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/git.lua

@ -0,0 +1,620 @@ @@ -0,0 +1,620 @@
local wrap = require('gitsigns.async').wrap
local scheduler = require('gitsigns.async').scheduler
local gsd = require("gitsigns.debug")
local util = require('gitsigns.util')
local subprocess = require('gitsigns.subprocess')
local gs_hunks = require("gitsigns.hunks")
local Hunk = gs_hunks.Hunk
local uv = vim.loop
local startswith = vim.startswith
local dprint = require("gitsigns.debug").dprint
local eprint = require("gitsigns.debug").eprint
local err = require('gitsigns.message').error
local M = {BlameInfo = {}, Version = {}, RepoInfo = {}, Repo = {}, FileProps = {}, Obj = {}, }
local in_git_dir = function(file)
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)
assert(version:match('%d+%.%d+%.%w+'), 'Invalid git version: ' .. version)
local ret = {}
local parts = vim.split(version, '%.')
ret.major = tonumber(parts[1])
ret.minor = tonumber(parts[2])
if parts[3] == 'GIT' then
ret.patch = 0
else
ret.patch = tonumber(parts[3])
end
return ret
end
local function check_version(version)
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
M.command = wrap(function(args, spec, callback)
spec = spec or {}
spec.command = spec.command or 'git'
spec.args = spec.command == 'git' and
{ '--no-pager', '--literal-pathspecs', unpack(args) } or args
subprocess.run_job(spec, function(_, _, stdout, stderr)
if not spec.suppress_stderr then
if stderr then
gsd.eprint(stderr)
end
end
local stdout_lines = vim.split(stdout or '', '\n', true)
if stdout_lines[#stdout_lines] == '' then
stdout_lines[#stdout_lines] = nil
end
if gsd.verbose then
gsd.vprintf('%d lines:', #stdout_lines)
for i = 1, math.min(10, #stdout_lines) do
gsd.vprintf('\t%s', stdout_lines[i])
end
end
callback(stdout_lines, stderr)
end)
end, 3)
M.diff = function(file_cmp, file_buf, indent_heuristic, diff_algo)
return M.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
local function process_abbrev_head(gitdir, head_str, path, cmd)
if not gitdir then
return head_str
end
if head_str == 'HEAD' then
local short_sha = M.command({ 'rev-parse', '--short', 'HEAD' }, {
command = cmd or 'git',
suppress_stderr = true,
cwd = path,
})[1] or ''
if gsd.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
local cygpath_convert
if has_cygpath then
cygpath_convert = function(path)
return M.command({ '-aw', path }, { command = 'cygpath' })[1]
end
end
local function normalize_path(path)
if path and has_cygpath and not uv.fs_stat(path) then
path = cygpath_convert(path)
end
return path
end
M.get_repo_info = function(path, cmd, gitdir, toplevel)
local has_abs_gd = check_version({ 2, 13 })
local git_dir_opt = has_abs_gd and '--absolute-git-dir' or '--git-dir'
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 = M.command(args, {
command = cmd or 'git',
suppress_stderr = true,
cwd = path,
})
local ret = {
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
M.set_version = function(version)
if version ~= 'auto' then
M.version = parse_version(version)
return
end
local results, stderr = M.command({ '--version' })
local line = results[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
Repo.command = function(self, args, spec)
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 M.command(args1, spec)
end
Repo.files_changed = function(self)
local results = self:command({ 'status', '--porcelain', '--ignore-submodules' })
local ret = {}
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
Repo.get_show_text = function(self, object, encoding)
local stdout, stderr = self:command({ 'show', object }, { suppress_stderr = true })
if encoding ~= 'utf-8' then
scheduler()
for i, l in ipairs(stdout) do
if vim.fn.type(l) == vim.v.t_string then
stdout[i] = vim.fn.iconv(l, encoding, 'utf-8')
end
end
end
return stdout, stderr
end
Repo.update_abbrev_head = function(self)
self.abbrev_head = M.get_repo_info(self.toplevel).abbrev_head
end
Repo.new = function(dir, gitdir, toplevel)
local self = setmetatable({}, { __index = Repo })
self.username = M.command({ 'config', 'user.name' })[1]
local info = M.get_repo_info(dir, nil, gitdir, toplevel)
for k, v in pairs(info) do
(self)[k] = v
end
if M.enable_yadm and not self.gitdir then
if vim.startswith(dir, os.getenv('HOME')) and
#M.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) do
(self)[k] = v
end
end
end
return self
end
Obj.command = function(self, args, spec)
return self.repo:command(args, spec)
end
Obj.update_file_info = function(self, update_relpath, silent)
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
Obj.file_info = function(self, file, silent)
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
if not stderr:match('^warning: could not open directory .*: No such file or directory') then
gsd.eprint(stderr)
end
end
local result = {}
for _, line in ipairs(results) do
local parts = vim.split(line, '\t')
if #parts > 2 then
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
result.relpath = parts[2]
end
end
return result
end
Obj.get_show_text = function(self, revision)
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
for i = 1, #stdout do
stdout[i] = stdout[i] .. '\r'
end
end
return stdout, stderr
end
Obj.unstage_file = function(self)
self:command({ 'reset', self.file })
end
Obj.run_blame = function(self, lines, lnum, ignore_whitespace)
if not self.object_name or self.repo.abbrev_head == '' then
return {
author = 'Not Committed Yet',
['author_mail'] = '<not.committed.yet>',
committer = 'Not Committed Yet',
['committer_mail'] = '<not.committed.yet>',
}
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 = {}
ret.sha = header[1]
ret.orig_lnum = tonumber(header[2])
ret.final_lnum = tonumber(header[3])
ret.abbrev_sha = string.sub(ret.sha, 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
return ret
end
Obj.ensure_file_in_index = function(self)
if not self.object_name or self.has_conflicts then
if not self.object_name then
self:command({ 'add', '--intent-to-add', self.file })
else
local info = string.format('%s,%s,%s', self.mode_bits, self.object_name, self.relpath)
self:command({ 'update-index', '--add', '--cacheinfo', info })
end
self:update_file_info()
end
end
Obj.stage_lines = function(self, lines)
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
Obj.stage_hunks = function(self, hunks, invert)
self:ensure_file_in_index()
self:command({
'apply', '--whitespace=nowarn', '--cached', '--unidiff-zero', '-',
}, {
writer = gs_hunks.create_patch(self.relpath, hunks, self.mode_bits, invert),
})
end
Obj.has_moved = function(self)
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
Obj.new = function(file, encoding, gitdir, toplevel)
if in_git_dir(file) then
dprint('In git dir')
return nil
end
local self = 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
local silent = gitdir ~= nil and toplevel ~= nil
self:update_file_info(true, silent)
return self
end
return M

17
etc/soft/nvim/+plugins/gitsigns.nvim/lua/gitsigns/health.lua

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
local M = {}
function M.check()
local fns = vim.fn
local report_ok = fns['health#report_ok']
local report_error = fns['health#report_error']
local ok, v = pcall(vim.fn.systemlist, { 'git', '--version' })
if not ok then
report_error(v)
else
report_ok(v[1])
end
end
return M

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save