├── .gitignore ├── README.md ├── autoload ├── gindent.vim └── gindent │ ├── misc.vim │ ├── preset.vim │ ├── preset │ ├── default.vim │ ├── lua.vim │ └── vim.vim │ └── syntax.vim ├── doc └── gindent.txt ├── lua └── gindent │ └── kit │ ├── App │ ├── Cache.lua │ ├── Character.lua │ ├── Command.lua │ ├── Config.lua │ └── Event.lua │ ├── Async │ ├── AsyncTask.lua │ ├── Worker.lua │ └── init.lua │ ├── IO │ └── init.lua │ ├── LSP │ ├── Client.lua │ ├── DocumentSelector.lua │ ├── LanguageId.lua │ ├── Position.lua │ ├── Range.lua │ └── init.lua │ ├── Spec │ └── init.lua │ ├── System │ └── init.lua │ ├── Vim │ ├── FloatingWindow.lua │ ├── Keymap.lua │ ├── RegExp.lua │ └── Syntax.lua │ └── init.lua └── plugin └── gindent.vim /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-gindent 2 | 3 | General indentexpr plugin for vim and nvim. 4 | 5 | # Usage 6 | 7 | I don't recommend enabling `vim-gindent` on all filetypes. 8 | 9 | You should specify only for filetypes that the indentation doesn't work properly. 10 | 11 | ``` 12 | let g:gindent = {} 13 | let g:gindent.enabled = { -> index(['vim', 'php', ...], &filetype) != -1 } 14 | ``` 15 | 16 | # Note 17 | 18 | - This plugin provides very basic indentexpr. 19 | - This plugin supports `vim` filetype and the general language that uses `{}` / `()` / `[]` / `<>` for indentation. 20 | 21 | -------------------------------------------------------------------------------- /autoload/gindent.vim: -------------------------------------------------------------------------------- 1 | let s:presets = {} 2 | 3 | " 4 | " gindent#register_preset 5 | " 6 | function! gindent#register_preset(filetype, preset) abort 7 | let s:presets[a:filetype] = a:preset 8 | endfunction 9 | 10 | " 11 | " gindent#apply 12 | " 13 | function! gindent#apply() abort 14 | if g:gindent.enabled() 15 | let b:gindent = get(b:, 'gindent', {}) 16 | if get(b:gindent, 'setup', v:false) 17 | return 18 | endif 19 | let b:gindent.setup = v:true 20 | 21 | setlocal indentexpr=gindent#indentexpr() 22 | let l:preset = get(s:presets, &filetype, gindent#preset#default#get()) 23 | if has_key(l:preset, 'indentkeys') 24 | execute printf('setlocal indentkeys+=%s', join(map(l:preset.indentkeys, '"=" .. escape(v:val, ", \t\\")'), ',')) 25 | endif 26 | endif 27 | endfunction 28 | 29 | " 30 | " gindent#indent_count 31 | " 32 | function! gindent#indent_count(lnum) abort 33 | let l:one_indent = gindent#get_one_indent() 34 | let l:one_indent_size = strlen(l:one_indent) 35 | let l:total_indent = substitute(matchstr(getline(a:lnum), '^\s*'), '\t', l:one_indent, 'g') 36 | let l:rest_indent = l:total_indent 37 | while strlen(l:rest_indent) >= l:one_indent_size 38 | let l:rest_indent = strpart(l:rest_indent, l:one_indent_size) 39 | endwhile 40 | return strlen(l:total_indent) - strlen(l:rest_indent) 41 | endfunction 42 | 43 | " 44 | " gindent#get_one_indent 45 | " 46 | function! gindent#get_one_indent() abort 47 | return repeat(' ', &shiftwidth ? &shiftwidth : &tabstop) 48 | endfunction 49 | 50 | " 51 | " gindent#indentexpr 52 | " 53 | function! gindent#indentexpr() abort 54 | if v:lnum == 1 55 | return 0 56 | endif 57 | 58 | let l:preset = get(s:presets, &filetype, get(s:presets, '*', v:null)) 59 | if l:preset is v:null 60 | return 0 61 | endif 62 | 63 | let l:curr_lnum = v:lnum 64 | let l:curr_cursor = [l:curr_lnum, strlen(getline(l:curr_lnum))] 65 | let l:prev_lnum = s:prev_lnum(l:curr_lnum, l:preset) 66 | let l:prev_cursor = [l:prev_lnum, strlen(getline(l:prev_lnum))] 67 | let l:curr_line = getline(l:curr_lnum) 68 | let l:prev_line = getline(l:prev_lnum) 69 | let l:curr_indent_count = gindent#indent_count(l:curr_lnum) 70 | let l:prev_indent_count = gindent#indent_count(l:prev_lnum) 71 | 72 | " manual_patterns. 73 | for l:pattern in get(l:preset, 'manual_patterns', []) 74 | if s:match(l:prev_cursor, l:curr_cursor, l:prev_line, l:curr_line, l:pattern) 75 | return l:pattern.func({ 76 | \ 'curr_cursor': l:curr_cursor, 77 | \ 'prev_cursor': l:prev_cursor, 78 | \ 'one_indent_count': strlen(gindent#get_one_indent()), 79 | \ 'prev_indent_count': l:prev_indent_count, 80 | \ 'curr_indent_count': l:curr_indent_count, 81 | \ }) 82 | endif 83 | endfor 84 | 85 | " indent_patterns. 86 | for l:pattern in get(l:preset, 'indent_patterns', []) 87 | if s:match(l:prev_cursor, l:curr_cursor, l:prev_line, l:curr_line, l:pattern) 88 | let l:prev_indent_count += shiftwidth() 89 | break 90 | endif 91 | endfor 92 | 93 | " dedent_patterns. 94 | for l:pattern in get(l:preset, 'dedent_patterns', []) 95 | if s:match(l:prev_cursor, l:curr_cursor, l:prev_line, l:curr_line, l:pattern) 96 | let l:prev_indent_count -= shiftwidth() 97 | break 98 | endif 99 | endfor 100 | 101 | return l:prev_indent_count 102 | endfunction 103 | 104 | " 105 | " s:prev_lnum 106 | " 107 | function s:prev_lnum(curr_lnum, preset) abort 108 | let l:curr_lnum = a:curr_lnum 109 | let l:prev_lnum = max([1, l:curr_lnum - 1]) 110 | while l:curr_lnum > 1 111 | let l:curr_cursor = [l:curr_lnum, strlen(getline(l:curr_lnum))] 112 | let l:prev_lnum = prevnonblank(l:curr_lnum - 1) 113 | let l:prev_cursor = [l:prev_lnum, strlen(getline(l:prev_lnum))] 114 | let l:curr_line = getline(l:curr_lnum) 115 | let l:prev_line = getline(l:prev_lnum) 116 | 117 | let l:found = v:false 118 | for l:pattern in get(a:preset, 'continue_patterns', []) 119 | let l:found = l:found || s:match(l:prev_cursor, l:curr_cursor, l:prev_line, l:curr_line, l:pattern) 120 | if l:found 121 | break 122 | endif 123 | endfor 124 | if !l:found 125 | break 126 | endif 127 | let l:curr_lnum = l:prev_lnum 128 | endwhile 129 | return l:prev_lnum 130 | endfunction 131 | 132 | " 133 | " s:match 134 | " 135 | function! s:match(prev_cursor, curr_cursor, prev_text, curr_text, pattern) abort 136 | let l:matched = v:true 137 | if l:matched && has_key(a:pattern, 'prev') 138 | if a:prev_text !~# s:pattern(a:pattern.prev) 139 | let l:matched = v:false 140 | endif 141 | endif 142 | if l:matched && has_key(a:pattern, 'prev!') 143 | if a:prev_text =~# s:pattern(a:pattern['prev!']) 144 | let l:matched = v:false 145 | endif 146 | endif 147 | if l:matched && has_key(a:pattern, 'curr') 148 | if a:curr_text !~# s:pattern(a:pattern.curr) 149 | let l:matched = v:false 150 | endif 151 | endif 152 | if l:matched && has_key(a:pattern, 'curr!') 153 | if a:curr_text =~# s:pattern(a:pattern['curr!']) 154 | let l:matched = v:false 155 | endif 156 | endif 157 | for l:dir in ['prev', 'curr'] 158 | if !l:matched | break | endif 159 | for l:negate in [v:false, v:true] 160 | if !l:matched | break | endif 161 | for l:head in [v:false, v:true] 162 | if !l:matched | break | endif 163 | let l:key = printf('%s_syntax%s%s', l:dir, l:negate ? '!' : '', l:head ? '^' : '') 164 | if has_key(a:pattern, l:key) 165 | if !s:match_syntax(a:prev_cursor, a:curr_cursor, a:pattern[l:key], l:dir, l:negate, l:head) 166 | let l:matched = v:false 167 | endif 168 | endif 169 | endfor 170 | endfor 171 | endfor 172 | return l:matched 173 | endfunction 174 | 175 | " 176 | " s:match_syntax 177 | " 178 | function s:match_syntax(prev_cursor, curr_cursor, syntaxes, dir, negate, head) abort 179 | let l:cursor = a:dir ==# 'curr' ? a:curr_cursor : a:prev_cursor 180 | if a:head 181 | let l:cursor = [l:cursor[0], strlen(matchstr(getline(l:cursor[0]), '^%s*')) + 1] 182 | endif 183 | let l:contains = gindent#syntax#in(a:syntaxes, a:dir ==# 'curr' ? a:curr_cursor : a:prev_cursor) 184 | return a:negate ? !l:contains : l:contains 185 | endfunction 186 | 187 | " 188 | " s:pattern 189 | " 190 | function s:pattern(pattern) abort 191 | return type(a:pattern) == v:t_list ? join(a:pattern, '\s*') : a:pattern 192 | endfunction 193 | -------------------------------------------------------------------------------- /autoload/gindent/misc.vim: -------------------------------------------------------------------------------- 1 | " 2 | " gindent#misc#doc_comment 3 | " 4 | function gindent#misc#doc_comment(ctx) abort 5 | for l:i in range(a:ctx.prev_cursor[0], max([1, a:ctx.prev_cursor[0] - 100]), -1) 6 | if getline(l:i) =~# '^\s*/\*\*$' 7 | return gindent#indent_count(l:i) + 1 8 | elseif getline(l:i) !~# '^\s*\*' 9 | break 10 | endif 11 | endfor 12 | return a:ctx.prev_indent_count 13 | endfunction 14 | -------------------------------------------------------------------------------- /autoload/gindent/preset.vim: -------------------------------------------------------------------------------- 1 | " 2 | " gindent#preset#extend 3 | " @NOTE: The order is important. The `parent` preset must be added last. 4 | " 5 | function! gindent#preset#extend(parent, additional) abort 6 | let l:preset = deepcopy(a:additional) 7 | 8 | " indentkeys. 9 | let l:preset.indentkeys = get(l:preset, 'indentkeys', []) 10 | for l:indentkey in get(a:parent, 'indentkeys', []) 11 | call add(l:preset.indentkeys, l:indentkey) 12 | endfor 13 | 14 | " manual_patterns. 15 | let l:preset.manual_patterns = get(l:preset, 'manual_patterns', []) 16 | for l:pattern in get(a:parent, 'manual_patterns', []) 17 | call add(l:preset.manual_patterns, l:pattern) 18 | endfor 19 | 20 | " indent_patterns. 21 | let l:preset.indent_patterns = get(l:preset, 'indent_patterns', []) 22 | for l:pattern in get(a:parent, 'indent_patterns', []) 23 | call add(l:preset.indent_patterns, l:pattern) 24 | endfor 25 | 26 | " dedent_patterns. 27 | let l:preset.dedent_patterns = get(l:preset, 'dedent_patterns', []) 28 | for l:pattern in get(a:parent, 'dedent_patterns', []) 29 | call add(l:preset.dedent_patterns, l:pattern) 30 | endfor 31 | 32 | return l:preset 33 | endfunction 34 | 35 | -------------------------------------------------------------------------------- /autoload/gindent/preset/default.vim: -------------------------------------------------------------------------------- 1 | function! gindent#preset#default#get() abort 2 | return { 3 | \ 'indentkeys': [ 4 | \ '}', 5 | \ ']', 6 | \ ')', 7 | \ '>', 8 | \ ], 9 | \ 'manual_patterns': [ 10 | \ { 'curr': ['^', '\V*\m'], 'func': function('gindent#misc#doc_comment') }, 11 | \ ], 12 | \ 'indent_patterns': [ 13 | \ { 'prev': ['\V{\m', '$'] }, 14 | \ { 'prev': ['\V(\m', '$'] }, 15 | \ { 'prev': ['\V[\m', '$'] }, 16 | \ { 'prev': ['\V<\m', '[^>]\{-}', '$'] }, 17 | \ ], 18 | \ 'dedent_patterns': [ 19 | \ { 'curr': ['^', '\V}\m', ',\?'] }, 20 | \ { 'curr': ['^', '\V)\m', ',\?'] }, 21 | \ { 'curr': ['^', '\V]\m', ',\?'] }, 22 | \ { 'curr': ['^', '\V>\m', ',\?'] }, 23 | \ ] 24 | \ } 25 | endfunction 26 | 27 | -------------------------------------------------------------------------------- /autoload/gindent/preset/lua.vim: -------------------------------------------------------------------------------- 1 | function! gindent#preset#lua#get() abort 2 | return gindent#preset#extend(gindent#preset#default#get(), { 3 | \ 'indentkeys': [ 4 | \ 'elseif', 5 | \ 'else', 6 | \ 'end', 7 | \ ], 8 | \ 'indent_patterns': [ 9 | \ { 'prev': ['^', '\.\{-}\', '$'] }, 10 | \ { 'prev': ['^', '\.\{-}\', '$'] }, 11 | \ { 'prev': ['^', '\', '$'] }, 12 | \ { 'prev': ['^', '\.\{-}\', '$'] }, 13 | \ { 'prev': ['^', '\.\{-}\', '$'] }, 14 | \ { 'prev': ['^', '\', '$'] }, 15 | \ { 'prev': ['\', '(.\{-})', '$'] }, 16 | \ { 'prev': ['\', '\w\+', '\%([\.:]\w\+\)\?', '(.\{-})', '$'] }, 17 | \ ], 18 | \ 'dedent_patterns': [ 19 | \ { 'curr': ['^', '\'] }, 20 | \ { 'curr': ['^', '\.\{-}\', '$'] }, 21 | \ { 'curr': ['^', '\', '$'] }, 22 | \ { 'curr': ['^', '\', '$'] }, 23 | \ ] 24 | \ }) 25 | endfunction 26 | 27 | -------------------------------------------------------------------------------- /autoload/gindent/preset/vim.vim: -------------------------------------------------------------------------------- 1 | function! gindent#preset#vim#get() abort 2 | return gindent#preset#extend(gindent#preset#default#get(), { 3 | \ 'indentkeys': [ 4 | \ 'end', 5 | \ 'End', 6 | \ '\', 7 | \ ], 8 | \ 'manual_patterns': [ 9 | \ { 'prev': ['^', '\\'], 'func': { ctx -> ctx.prev_indent_count } }, 10 | \ { 'curr': ['^', '\\'], 'func': { ctx -> ctx.prev_indent_count + g:vim_indent_cont } }, 11 | \ { 'curr': ['^', '\w\+', '$'], 'curr_syntax': ['@endmarker'], 'func': { ctx -> 0 } }, 12 | \ ], 13 | \ 'indent_patterns': [ 14 | \ { 'prev': ['^', '\'] }, 15 | \ { 'prev': ['^', '\'] }, 16 | \ { 'prev': ['^', '\'] }, 17 | \ { 'prev': ['^', '\'] }, 18 | \ { 'prev': ['^', '\'] }, 19 | \ { 'prev': ['^', '\'] }, 20 | \ { 'prev': ['^', '\'], 'prev!': ['END', '$'] }, 21 | \ { 'prev': ['^', '\'] }, 22 | \ { 'prev': ['^', '\'] }, 23 | \ { 'prev': ['^', '\'] }, 24 | \ { 'prev': ['^', '\'] }, 25 | \ ], 26 | \ 'dedent_patterns': [ 27 | \ { 'curr': ['^', '\'] }, 28 | \ { 'curr': ['^', '\'] }, 29 | \ { 'curr': ['^', '\'] }, 30 | \ { 'curr': ['^', '\'] }, 31 | \ { 'curr': ['^', '\'] }, 32 | \ { 'curr': ['^', '\'] }, 33 | \ { 'curr': ['^', '\'] }, 34 | \ { 'curr': ['^', '\'] }, 35 | \ { 'curr': ['^', '\'] }, 36 | \ { 'curr': ['^', '\'] }, 37 | \ ] 38 | \ }) 39 | endfunction 40 | 41 | -------------------------------------------------------------------------------- /autoload/gindent/syntax.vim: -------------------------------------------------------------------------------- 1 | " 2 | " gindent#syntax#in 3 | " 4 | function! gindent#syntax#in(group_name, ...) abort 5 | let l:group_names = gindent#syntax#get(get(a:000, 0, getcurpos()[1 : 2])) 6 | for l:group_name in (type(a:group_name) == v:t_list ? a:group_name : [a:group_name]) 7 | if index(l:group_names, l:group_name) != -1 8 | return v:true 9 | endif 10 | endfor 11 | return v:false 12 | endfunction 13 | 14 | " 15 | " gindent#syntax#get 16 | " 17 | function! gindent#syntax#get(...) abort 18 | let l:curpos = get(a:000, 0, getcurpos()[1:2]) 19 | return luaeval('require("gindent.kit.Vim.Syntax").get_syntax_groups(_A)', [l:curpos[0] - 1, l:curpos[1] - 1]) 20 | endfunction 21 | 22 | -------------------------------------------------------------------------------- /doc/gindent.txt: -------------------------------------------------------------------------------- 1 | *vim-gindent* *gindent* 2 | 3 | A general purpose indentexpr plugin for vim and nvim. 4 | 5 | ============================================================================== 6 | CONTENTS *gindent-contents* 7 | 8 | Usage |gindent-usage| 9 | Variable |gindent-variable| 10 | Function |gindent-function| 11 | 12 | 13 | 14 | ============================================================================== 15 | Usage *gindent-usage* 16 | 17 | First of all, I don't recommend enabling `vim-gindent` for all filetypes. 18 | You should specify only for filetypes that the indentation doesn't work properly. 19 | > 20 | let g:gindent = {} 21 | let g:gindent.enabled = { -> index(['vim'], &filetype) != -1 } 22 | < 23 | If you want to enable `vim-gindent` for all filetypes, You can specify the `*` to filetypes. 24 | > 25 | let g:gindent.enabled = { -> v:true } 26 | < 27 | 28 | 29 | ============================================================================== 30 | Variable *gindent-variable* 31 | 32 | 33 | g:gindent.enabled~ 34 | Specify the function that returns v:true / v:false. 35 | 36 | 37 | 38 | ============================================================================== 39 | Function *gindent-function* 40 | 41 | 42 | gindent#register_preset(filetype, preset)~ 43 | Register new preset of indentation for specified filetype. 44 | 45 | The `preset` arguments can have the following properties. 46 | 47 | - `manual_patterns`: The `manual-pattern` for deciding indent count. 48 | - `indent_patterns`: The `gindent-pattern` for indent. 49 | - `dedent_patterns`: The `gindent-pattern` for dedent. 50 | - `indentkeys`: An additional indentkeys for specific filetype. 51 | 52 | NOTE: The `gindent-pattern` is currently can have the following properties. 53 | 54 | - `prev`: The pattern or array of patterns for prev line. 55 | - `prev!`: The pattern or array of patterns for prev line (negated). 56 | - `curr`: The pattern or array of patterns for curr line. 57 | - `curr!`: The pattern or array of patterns for curr line (negated). 58 | 59 | NOTE: The `manual-pattern` is currently can have the following properties. 60 | 61 | - `prev`: The pattern or array of patterns for prev line. 62 | - `prev!`: The pattern or array of patterns for prev line (negated). 63 | - `curr`: The pattern or array of patterns for curr line. 64 | - `curr!`: The pattern or array of patterns for curr line (negated). 65 | - `func`: The function that should return number of indent count. 66 | 67 | NOTE: The all patterns can have the `syntax-specifier-keys`. 68 | 1. The available key name are the following. 69 | - prev_syntax 70 | - prev_syntax! 71 | - prev_syntax^ 72 | - prev_syntax!^ 73 | - curr_syntax 74 | - curr_syntax! 75 | - curr_syntax^ 76 | - curr_syntax!^ 77 | 2. The `!` is meaning the negate. 78 | 3. The `^` is meaning should use head syntax group. 79 | 80 | 81 | See ../autoload/gindent/preset/vim.vim 82 | 83 | 84 | ============================================================================== 85 | vim:tw=78:ts=4:et:ft=help:norl: 86 | 87 | -------------------------------------------------------------------------------- /lua/gindent/kit/App/Cache.lua: -------------------------------------------------------------------------------- 1 | ---Create cache key. 2 | ---@private 3 | ---@param key string[]|string 4 | ---@return string 5 | local function _key(key) 6 | if type(key) == 'table' then 7 | return table.concat(key, ':') 8 | end 9 | return key 10 | end 11 | 12 | ---@class gindent.kit.App.Cache 13 | ---@field private keys table 14 | ---@field private entries table 15 | local Cache = {} 16 | Cache.__index = Cache 17 | 18 | ---Create new cache instance. 19 | function Cache.new() 20 | local self = setmetatable({}, Cache) 21 | self.keys = {} 22 | self.entries = {} 23 | return self 24 | end 25 | 26 | ---Get cache entry. 27 | ---@param key string[]|string 28 | ---@return any 29 | function Cache:get(key) 30 | return self.entries[_key(key)] 31 | end 32 | 33 | ---Set cache entry. 34 | ---@param key string[]|string 35 | ---@param val any 36 | function Cache:set(key, val) 37 | key = _key(key) 38 | self.keys[key] = true 39 | self.entries[key] = val 40 | end 41 | 42 | ---Delete cache entry. 43 | ---@param key string[]|string 44 | function Cache:del(key) 45 | key = _key(key) 46 | self.keys[key] = nil 47 | self.entries[key] = nil 48 | end 49 | 50 | ---Return this cache has the key entry or not. 51 | ---@param key string[]|string 52 | ---@return boolean 53 | function Cache:has(key) 54 | key = _key(key) 55 | return not not self.keys[key] 56 | end 57 | 58 | ---Ensure cache entry. 59 | ---@generic T 60 | ---@generic U 61 | ---@param key string[]|string 62 | ---@param callback function(...: U): T 63 | ---@param ... U 64 | ---@return T 65 | function Cache:ensure(key, callback, ...) 66 | if not self:has(key) then 67 | self:set(key, callback(...)) 68 | end 69 | return self:get(key) 70 | end 71 | 72 | return Cache 73 | -------------------------------------------------------------------------------- /lua/gindent/kit/App/Character.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: discard-returns 2 | 3 | local Character = {} 4 | 5 | ---@type table 6 | Character.alpha = {} 7 | string.gsub('abcdefghijklmnopqrstuvwxyz', '.', function(char) 8 | Character.alpha[string.byte(char)] = char 9 | end) 10 | 11 | ---@type table 12 | Character.digit = {} 13 | string.gsub('1234567890', '.', function(char) 14 | Character.digit[string.byte(char)] = char 15 | end) 16 | 17 | ---@type table 18 | Character.white = {} 19 | string.gsub(' \t\n', '.', function(char) 20 | Character.white[string.byte(char)] = char 21 | end) 22 | 23 | ---Return specified byte is alpha or not. 24 | ---@param byte integer 25 | ---@return boolean 26 | function Character.is_alpha(byte) 27 | return Character.alpha[byte] ~= nil or Character.alpha[byte + 32] ~= nil 28 | end 29 | 30 | ---Return specified byte is digit or not. 31 | ---@param byte integer 32 | ---@return boolean 33 | function Character.is_digit(byte) 34 | return Character.digit[byte] ~= nil 35 | end 36 | 37 | ---Return specified byte is alpha or not. 38 | ---@param byte integer 39 | ---@return boolean 40 | function Character.is_alnum(byte) 41 | return Character.is_alpha(byte) or Character.is_digit(byte) 42 | end 43 | 44 | ---Return specified byte is white or not. 45 | ---@param byte integer 46 | ---@return boolean 47 | function Character.is_white(byte) 48 | return Character.white[byte] ~= nil 49 | end 50 | 51 | ---Return specified byte is symbol or not. 52 | ---@param byte integer 53 | ---@return boolean 54 | function Character.is_symbol(byte) 55 | return not Character.is_alnum(byte) and not Character.is_white(byte) 56 | end 57 | 58 | return Character 59 | -------------------------------------------------------------------------------- /lua/gindent/kit/App/Command.lua: -------------------------------------------------------------------------------- 1 | ---@class gindent.kit.App.Command.SubCommand.Argument 2 | ---@field public complete? fun(prefix: string):string[] 3 | ---@field public required? boolean 4 | 5 | ---@class gindent.kit.App.Command.SubCommandSpecifier 6 | ---@field public desc? string 7 | ---@field public args? table 8 | ---@field public execute fun(params: gindent.kit.App.Command.ExecuteParams, arguments: table) 9 | 10 | ---@class gindent.kit.App.Command.SubCommand: gindent.kit.App.Command.SubCommandSpecifier 11 | ---@field public name string 12 | ---@field public args table 13 | 14 | ---@class gindent.kit.App.Command 15 | ---@field public name string 16 | ---@field public subcommands table 17 | local Command = {} 18 | Command.__index = Command 19 | 20 | ---Create a new command. 21 | ---@param name string 22 | ---@param subcommand_specifiers table 23 | function Command.new(name, subcommand_specifiers) 24 | -- normalize subcommand specifiers. 25 | local subcommands = {} 26 | for subcommand_name, subcommand_specifier in pairs(subcommand_specifiers) do 27 | subcommands[subcommand_name] = { 28 | name = subcommand_name, 29 | args = subcommand_specifier.args or {}, 30 | execute = subcommand_specifier.execute, 31 | } 32 | end 33 | 34 | -- create command. 35 | return setmetatable({ 36 | name = name, 37 | subcommands = subcommands, 38 | }, Command) 39 | end 40 | 41 | ---@class gindent.kit.App.Command.ExecuteParams 42 | ---@field public name string 43 | ---@field public args string 44 | ---@field public fargs string[] 45 | ---@field public nargs string 46 | ---@field public bang boolean 47 | ---@field public line1 integer 48 | ---@field public line2 integer 49 | ---@field public range 0|1|2 50 | ---@field public count integer 51 | ---@field public req string 52 | ---@field public mods string 53 | ---@field public smods string[] 54 | ---Execute command. 55 | ---@param params gindent.kit.App.Command.ExecuteParams 56 | function Command:execute(params) 57 | local parsed = self._parse(params.args) 58 | 59 | local subcommand = self.subcommands[parsed[1].text] 60 | if not subcommand then 61 | error(('Unknown subcommand: %s'):format(parsed[1].text)) 62 | end 63 | 64 | local arguments = {} 65 | 66 | local pos = 1 67 | for i, part in ipairs(parsed) do 68 | if i > 1 then 69 | local is_named_argument = vim.iter(pairs(subcommand.args)):any(function(name) 70 | return type(name) == 'string' and part.text:sub(1, #name + 1) == ('%s='):format(name) 71 | end) 72 | if is_named_argument then 73 | local s = part.text:find('=', 1, true) 74 | if s then 75 | local name = part.text:sub(1, s - 1) 76 | local value = part.text:sub(s + 1) 77 | arguments[name] = value 78 | end 79 | else 80 | arguments[pos] = part.text 81 | pos = pos + 1 82 | end 83 | end 84 | end 85 | 86 | -- check required arguments. 87 | for name, arg in pairs(subcommand.args or {}) do 88 | if arg.required and not arguments[name] then 89 | error(('Argument %s is required.'):format(name)) 90 | end 91 | end 92 | 93 | subcommand.execute(params, arguments) 94 | end 95 | 96 | ---Complete command. 97 | ---@param cmdline string 98 | ---@param cursor integer 99 | function Command:complete(cmdline, cursor) 100 | local parsed = self._parse(cmdline) 101 | 102 | -- check command. 103 | if parsed[1].text ~= self.name then 104 | return {} 105 | end 106 | 107 | -- complete subcommand names. 108 | if parsed[2] and parsed[2].s <= cursor and cursor <= parsed[2].e then 109 | return vim 110 | .iter(pairs(self.subcommands)) 111 | :map(function(_, subcommand) 112 | return subcommand.name 113 | end) 114 | :totable() 115 | end 116 | 117 | -- check subcommand is exists. 118 | local subcommand = self.subcommands[parsed[2].text] 119 | if not subcommand then 120 | return {} 121 | end 122 | 123 | -- check subcommand arguments. 124 | local pos = 1 125 | for i, part in ipairs(parsed) do 126 | if i > 2 then 127 | local is_named_argument_name = vim.regex([=[^--\?[^=]*$]=]):match_str(part.text) ~= nil 128 | local is_named_argument_value = vim.iter(pairs(subcommand.args)):any(function(name) 129 | name = tostring(name) 130 | return part.text:sub(1, #name + 1) == ('%s='):format(name) 131 | end) 132 | 133 | -- current cursor argument. 134 | if part.s <= cursor and cursor <= part.e then 135 | if is_named_argument_name then 136 | -- return named-argument completion. 137 | return vim 138 | .iter(pairs(subcommand.args)) 139 | :map(function(name) 140 | return name 141 | end) 142 | :filter(function(name) 143 | return type(name) == 'string' 144 | end) 145 | :totable() 146 | elseif is_named_argument_value then 147 | -- return specific named-argument value completion. 148 | for name, argument in pairs(subcommand.args) do 149 | if type(name) == 'string' then 150 | if part.text:sub(1, #name + 1) == ('%s='):format(name) then 151 | if argument.complete then 152 | return argument.complete(part.text:sub(#name + 2)) 153 | end 154 | return {} 155 | end 156 | end 157 | end 158 | elseif subcommand.args[pos] then 159 | local argument = subcommand.args[pos] 160 | if argument.complete then 161 | return argument.complete(part.text) 162 | end 163 | return {} 164 | end 165 | end 166 | 167 | -- increment positional argument. 168 | if not is_named_argument_name and not is_named_argument_value then 169 | pos = pos + 1 170 | end 171 | end 172 | end 173 | end 174 | 175 | ---Parse command line. 176 | ---@param cmdline string 177 | ---@return { text: string, s: integer, e: integer }[] 178 | function Command._parse(cmdline) 179 | ---@type { text: string, s: integer, e: integer }[] 180 | local parsed = {} 181 | 182 | local part = {} 183 | local s = 1 184 | local i = 1 185 | while i <= #cmdline do 186 | local c = cmdline:sub(i, i) 187 | if c == '\\' then 188 | table.insert(part, cmdline:sub(i + 1, i + 1)) 189 | i = i + 1 190 | elseif c == ' ' then 191 | if #part > 0 then 192 | table.insert(parsed, { 193 | text = table.concat(part), 194 | s = s - 1, 195 | e = i - 1, 196 | }) 197 | part = {} 198 | s = i + 1 199 | end 200 | else 201 | table.insert(part, c) 202 | end 203 | i = i + 1 204 | end 205 | 206 | if #part then 207 | table.insert(parsed, { 208 | text = table.concat(part), 209 | s = s - 1, 210 | e = i - 1, 211 | }) 212 | return parsed 213 | end 214 | 215 | table.insert(parsed, { 216 | text = '', 217 | s = #cmdline, 218 | e = #cmdline + 1, 219 | }) 220 | 221 | return parsed 222 | end 223 | 224 | return Command 225 | -------------------------------------------------------------------------------- /lua/gindent/kit/App/Config.lua: -------------------------------------------------------------------------------- 1 | local kit = require('gindent.kit') 2 | local Cache = require('gindent.kit.App.Cache') 3 | 4 | ---@class gindent.kit.App.Config.Schema 5 | 6 | ---@alias gindent.kit.App.Config.SchemaInternal gindent.kit.App.Config.Schema|{ revision: integer } 7 | 8 | ---@class gindent.kit.App.Config 9 | ---@field private _cache gindent.kit.App.Cache 10 | ---@field private _default gindent.kit.App.Config.SchemaInternal 11 | ---@field private _global gindent.kit.App.Config.SchemaInternal 12 | ---@field private _filetype table 13 | ---@field private _buffer table 14 | local Config = {} 15 | Config.__index = Config 16 | 17 | ---Create new config instance. 18 | ---@param default gindent.kit.App.Config.Schema 19 | function Config.new(default) 20 | local self = setmetatable({}, Config) 21 | self._cache = Cache.new() 22 | self._default = default 23 | self._global = {} 24 | self._filetype = {} 25 | self._buffer = {} 26 | return self 27 | end 28 | 29 | ---Update global config. 30 | ---@param config gindent.kit.App.Config.Schema 31 | function Config:global(config) 32 | local revision = (self._global.revision or 1) + 1 33 | self._global = config or {} 34 | self._global.revision = revision 35 | end 36 | 37 | ---Update filetype config. 38 | ---@param filetypes string|string[] 39 | ---@param config gindent.kit.App.Config.Schema 40 | function Config:filetype(filetypes, config) 41 | for _, filetype in ipairs(kit.to_array(filetypes)) do 42 | local revision = ((self._filetype[filetype] or {}).revision or 1) + 1 43 | self._filetype[filetype] = config or {} 44 | self._filetype[filetype].revision = revision 45 | end 46 | end 47 | 48 | ---Update filetype config. 49 | ---@param bufnr integer 50 | ---@param config gindent.kit.App.Config.Schema 51 | function Config:buffer(bufnr, config) 52 | bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr 53 | local revision = ((self._buffer[bufnr] or {}).revision or 1) + 1 54 | self._buffer[bufnr] = config or {} 55 | self._buffer[bufnr].revision = revision 56 | end 57 | 58 | ---Get current configuration. 59 | ---@return gindent.kit.App.Config.Schema 60 | function Config:get() 61 | local filetype = vim.api.nvim_get_option_value('filetype', { buf = 0 }) 62 | local bufnr = vim.api.nvim_get_current_buf() 63 | return self._cache:ensure({ 64 | tostring(self._global.revision or 0), 65 | tostring((self._buffer[bufnr] or {}).revision or 0), 66 | tostring((self._filetype[filetype] or {}).revision or 0), 67 | }, function() 68 | local config = self._default 69 | config = kit.merge(self._global, config) 70 | config = kit.merge(self._filetype[filetype] or {}, config) 71 | config = kit.merge(self._buffer[bufnr] or {}, config) 72 | config.revision = nil 73 | return config 74 | end) 75 | end 76 | 77 | ---Create setup interface. 78 | ---@return fun(config: gindent.kit.App.Config.Schema)|{ filetype: fun(filetypes: string|string[], config: gindent.kit.App.Config.Schema), buffer: fun(bufnr: integer, config: gindent.kit.App.Config.Schema) } 79 | function Config:create_setup_interface() 80 | return setmetatable({ 81 | ---@param filetypes string|string[] 82 | ---@param config gindent.kit.App.Config.Schema 83 | filetype = function(filetypes, config) 84 | self:filetype(filetypes, config) 85 | end, 86 | ---@param bufnr integer 87 | ---@param config gindent.kit.App.Config.Schema 88 | buffer = function(bufnr, config) 89 | self:buffer(bufnr, config) 90 | end, 91 | }, { 92 | ---@param config gindent.kit.App.Config.Schema 93 | __call = function(_, config) 94 | self:global(config) 95 | end, 96 | }) 97 | end 98 | 99 | return Config 100 | -------------------------------------------------------------------------------- /lua/gindent/kit/App/Event.lua: -------------------------------------------------------------------------------- 1 | ---@class gindent.kit.App.Event 2 | ---@field private _events table 3 | local Event = {} 4 | Event.__index = Event 5 | 6 | ---Create new Event. 7 | function Event.new() 8 | local self = setmetatable({}, Event) 9 | self._events = {} 10 | return self 11 | end 12 | 13 | ---Register listener. 14 | ---@param name string 15 | ---@param listener function 16 | ---@return function 17 | function Event:on(name, listener) 18 | self._events[name] = self._events[name] or {} 19 | table.insert(self._events[name], listener) 20 | return function() 21 | self:off(name, listener) 22 | end 23 | end 24 | 25 | ---Register once listener. 26 | ---@param name string 27 | ---@param listener function 28 | function Event:once(name, listener) 29 | local off 30 | off = self:on(name, function(...) 31 | listener(...) 32 | off() 33 | end) 34 | end 35 | 36 | ---Off specified listener from event. 37 | ---@param name string 38 | ---@param listener function 39 | function Event:off(name, listener) 40 | self._events[name] = self._events[name] or {} 41 | if not listener then 42 | self._events[name] = nil 43 | else 44 | for i = #self._events[name], 1, -1 do 45 | if self._events[name][i] == listener then 46 | table.remove(self._events[name], i) 47 | break 48 | end 49 | end 50 | end 51 | end 52 | 53 | ---Return if the listener is registered. 54 | ---@param name string 55 | ---@param listener? function 56 | ---@return boolean 57 | function Event:has(name, listener) 58 | self._events[name] = self._events[name] or {} 59 | for _, v in ipairs(self._events[name]) do 60 | if v == listener then 61 | return true 62 | end 63 | end 64 | return false 65 | end 66 | 67 | ---Emit event. 68 | ---@param name string 69 | ---@vararg any 70 | function Event:emit(name, ...) 71 | self._events[name] = self._events[name] or {} 72 | for _, v in ipairs(self._events[name]) do 73 | v(...) 74 | end 75 | end 76 | 77 | return Event 78 | -------------------------------------------------------------------------------- /lua/gindent/kit/Async/AsyncTask.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: invisible 2 | local uv = require('luv') 3 | local kit = require('gindent.kit') 4 | 5 | local is_thread = vim.is_thread() 6 | 7 | ---@class gindent.kit.Async.AsyncTask 8 | ---@field private value any 9 | ---@field private status gindent.kit.Async.AsyncTask.Status 10 | ---@field private synced boolean 11 | ---@field private chained boolean 12 | ---@field private children (fun(): any)[] 13 | local AsyncTask = {} 14 | AsyncTask.__index = AsyncTask 15 | 16 | ---Settle the specified task. 17 | ---@param task gindent.kit.Async.AsyncTask 18 | ---@param status gindent.kit.Async.AsyncTask.Status 19 | ---@param value any 20 | local function settle(task, status, value) 21 | task.status = status 22 | task.value = value 23 | for _, c in ipairs(task.children) do 24 | c() 25 | end 26 | 27 | if status == AsyncTask.Status.Rejected then 28 | if not task.chained and not task.synced then 29 | local timer = uv.new_timer() 30 | timer:start( 31 | 0, 32 | 0, 33 | kit.safe_schedule_wrap(function() 34 | timer:stop() 35 | timer:close() 36 | if not task.chained and not task.synced then 37 | AsyncTask.on_unhandled_rejection(value) 38 | end 39 | end) 40 | ) 41 | end 42 | end 43 | end 44 | 45 | ---@enum gindent.kit.Async.AsyncTask.Status 46 | AsyncTask.Status = { 47 | Pending = 0, 48 | Fulfilled = 1, 49 | Rejected = 2, 50 | } 51 | 52 | ---Handle unhandled rejection. 53 | ---@param err any 54 | function AsyncTask.on_unhandled_rejection(err) 55 | error('AsyncTask.on_unhandled_rejection: ' .. vim.inspect(err), 2) 56 | end 57 | 58 | ---Return the value is AsyncTask or not. 59 | ---@param value any 60 | ---@return boolean 61 | function AsyncTask.is(value) 62 | return getmetatable(value) == AsyncTask 63 | end 64 | 65 | ---Resolve all tasks. 66 | ---@param tasks any[] 67 | ---@return gindent.kit.Async.AsyncTask 68 | function AsyncTask.all(tasks) 69 | return AsyncTask.new(function(resolve, reject) 70 | if #tasks == 0 then 71 | resolve({}) 72 | return 73 | end 74 | 75 | local values = {} 76 | local count = 0 77 | for i, task in ipairs(tasks) do 78 | task:dispatch(function(value) 79 | values[i] = value 80 | count = count + 1 81 | if #tasks == count then 82 | resolve(values) 83 | end 84 | end, reject) 85 | end 86 | end) 87 | end 88 | 89 | ---Resolve first resolved task. 90 | ---@param tasks any[] 91 | ---@return gindent.kit.Async.AsyncTask 92 | function AsyncTask.race(tasks) 93 | return AsyncTask.new(function(resolve, reject) 94 | for _, task in ipairs(tasks) do 95 | task:dispatch(resolve, reject) 96 | end 97 | end) 98 | end 99 | 100 | ---Create resolved AsyncTask. 101 | ---@param v any 102 | ---@return gindent.kit.Async.AsyncTask 103 | function AsyncTask.resolve(v) 104 | if AsyncTask.is(v) then 105 | return v 106 | end 107 | return AsyncTask.new(function(resolve) 108 | resolve(v) 109 | end) 110 | end 111 | 112 | ---Create new AsyncTask. 113 | ---@NOET: The AsyncTask has similar interface to JavaScript Promise but the AsyncTask can be worked as synchronous. 114 | ---@param v any 115 | ---@return gindent.kit.Async.AsyncTask 116 | function AsyncTask.reject(v) 117 | if AsyncTask.is(v) then 118 | return v 119 | end 120 | return AsyncTask.new(function(_, reject) 121 | reject(v) 122 | end) 123 | end 124 | 125 | ---Create new async task object. 126 | ---@param runner fun(resolve?: fun(value: any?), reject?: fun(err: any?)) 127 | function AsyncTask.new(runner) 128 | local self = setmetatable({}, AsyncTask) 129 | 130 | self.value = nil 131 | self.status = AsyncTask.Status.Pending 132 | self.synced = false 133 | self.chained = false 134 | self.children = {} 135 | local ok, err = pcall(runner, function(res) 136 | if self.status == AsyncTask.Status.Pending then 137 | settle(self, AsyncTask.Status.Fulfilled, res) 138 | end 139 | end, function(err) 140 | if self.status == AsyncTask.Status.Pending then 141 | settle(self, AsyncTask.Status.Rejected, err) 142 | end 143 | end) 144 | if not ok then 145 | settle(self, AsyncTask.Status.Rejected, err) 146 | end 147 | return self 148 | end 149 | 150 | ---Sync async task. 151 | ---@NOTE: This method uses `vim.wait` so that this can't wait the typeahead to be empty. 152 | ---@param timeout integer 153 | ---@return any 154 | function AsyncTask:sync(timeout) 155 | self.synced = true 156 | 157 | local time = uv.now() 158 | while uv.now() - time <= timeout do 159 | if self.status ~= AsyncTask.Status.Pending then 160 | break 161 | end 162 | if is_thread then 163 | uv.run('once') 164 | else 165 | vim.wait(0) 166 | end 167 | end 168 | if self.status == AsyncTask.Status.Pending then 169 | error('AsyncTask:sync is timeout.', 2) 170 | end 171 | if self.status == AsyncTask.Status.Rejected then 172 | error(self.value, 2) 173 | end 174 | if self.status ~= AsyncTask.Status.Fulfilled then 175 | error('AsyncTask:sync is timeout.', 2) 176 | end 177 | return self.value 178 | end 179 | 180 | ---Await async task. 181 | ---@return any 182 | function AsyncTask:await() 183 | local Async = require('gindent.kit.Async') 184 | local in_fast_event = vim.in_fast_event() 185 | local ok, res = pcall(Async.await, self) 186 | if not ok then 187 | error(res, 2) 188 | end 189 | if not in_fast_event and vim.in_fast_event() then 190 | Async.schedule():await() 191 | end 192 | return res 193 | end 194 | 195 | ---Return current state of task. 196 | ---@return { status: gindent.kit.Async.AsyncTask.Status, value: any } 197 | function AsyncTask:state() 198 | return { 199 | status = self.status, 200 | value = self.value, 201 | } 202 | end 203 | 204 | ---Register next step. 205 | ---@param on_fulfilled fun(value: any): any 206 | function AsyncTask:next(on_fulfilled) 207 | return self:dispatch(on_fulfilled, function(err) 208 | error(err, 2) 209 | end) 210 | end 211 | 212 | ---Register catch step. 213 | ---@param on_rejected fun(value: any): any 214 | ---@return gindent.kit.Async.AsyncTask 215 | function AsyncTask:catch(on_rejected) 216 | return self:dispatch(function(value) 217 | return value 218 | end, on_rejected) 219 | end 220 | 221 | ---Dispatch task state. 222 | ---@param on_fulfilled fun(value: any): any 223 | ---@param on_rejected fun(err: any): any 224 | ---@return gindent.kit.Async.AsyncTask 225 | function AsyncTask:dispatch(on_fulfilled, on_rejected) 226 | self.chained = true 227 | 228 | local function dispatch(resolve, reject) 229 | local on_next = self.status == AsyncTask.Status.Fulfilled and on_fulfilled or on_rejected 230 | local ok, res = pcall(on_next, self.value) 231 | if AsyncTask.is(res) then 232 | res:dispatch(resolve, reject) 233 | else 234 | if ok then 235 | resolve(res) 236 | else 237 | reject(res) 238 | end 239 | end 240 | end 241 | 242 | if self.status == AsyncTask.Status.Pending then 243 | return AsyncTask.new(function(resolve, reject) 244 | table.insert(self.children, function() 245 | dispatch(resolve, reject) 246 | end) 247 | end) 248 | end 249 | return AsyncTask.new(dispatch) 250 | end 251 | 252 | return AsyncTask 253 | -------------------------------------------------------------------------------- /lua/gindent/kit/Async/Worker.lua: -------------------------------------------------------------------------------- 1 | local uv = require('luv') 2 | local AsyncTask = require('gindent.kit.Async.AsyncTask') 3 | 4 | ---@class gindent.kit.Async.WorkerOption 5 | ---@field public runtimepath string[] 6 | 7 | ---@class gindent.kit.Async.Worker 8 | ---@field private runner string 9 | local Worker = {} 10 | Worker.__index = Worker 11 | 12 | ---Create a new worker. 13 | ---@param runner function 14 | function Worker.new(runner) 15 | local self = setmetatable({}, Worker) 16 | self.runner = string.dump(runner) 17 | return self 18 | end 19 | 20 | ---Call worker function. 21 | ---@return gindent.kit.Async.AsyncTask 22 | function Worker:__call(...) 23 | local args_ = { ... } 24 | return AsyncTask.new(function(resolve, reject) 25 | uv.new_work(function(runner, args, option) 26 | args = vim.mpack.decode(args) 27 | option = vim.mpack.decode(option) 28 | 29 | --Initialize cwd. 30 | require('luv').chdir(option.cwd) 31 | 32 | --Initialize package.loaders. 33 | table.insert(package.loaders, 2, vim._load_package) 34 | 35 | --Run runner function. 36 | local ok, res = pcall(function() 37 | return require('gindent.kit.Async.AsyncTask').resolve(assert(loadstring(runner))(unpack(args))):sync(5000) 38 | end) 39 | 40 | res = vim.mpack.encode({ res }) 41 | 42 | --Return error or result. 43 | if not ok then 44 | return res, nil 45 | else 46 | return nil, res 47 | end 48 | end, function(err, res) 49 | if err then 50 | reject(vim.mpack.decode(err)[1]) 51 | else 52 | resolve(vim.mpack.decode(res)[1]) 53 | end 54 | end):queue( 55 | self.runner, 56 | vim.mpack.encode(args_), 57 | vim.mpack.encode({ 58 | cwd = uv.cwd(), 59 | }) 60 | ) 61 | end) 62 | end 63 | 64 | return Worker 65 | -------------------------------------------------------------------------------- /lua/gindent/kit/Async/init.lua: -------------------------------------------------------------------------------- 1 | local AsyncTask = require('gindent.kit.Async.AsyncTask') 2 | 3 | local Interrupt = {} 4 | 5 | local Async = {} 6 | 7 | _G.kit = _G.kit or {} 8 | _G.kit.Async = _G.kit.Async or {} 9 | _G.kit.Async.___threads___ = _G.kit.Async.___threads___ or {} 10 | 11 | ---Alias of AsyncTask.all. 12 | ---@param tasks gindent.kit.Async.AsyncTask[] 13 | ---@return gindent.kit.Async.AsyncTask 14 | function Async.all(tasks) 15 | return AsyncTask.all(tasks) 16 | end 17 | 18 | ---Alias of AsyncTask.race. 19 | ---@param tasks gindent.kit.Async.AsyncTask[] 20 | ---@return gindent.kit.Async.AsyncTask 21 | function Async.race(tasks) 22 | return AsyncTask.race(tasks) 23 | end 24 | 25 | ---Alias of AsyncTask.resolve(v). 26 | ---@param v any 27 | ---@return gindent.kit.Async.AsyncTask 28 | function Async.resolve(v) 29 | return AsyncTask.resolve(v) 30 | end 31 | 32 | ---Alias of AsyncTask.reject(v). 33 | ---@param v any 34 | ---@return gindent.kit.Async.AsyncTask 35 | function Async.reject(v) 36 | return AsyncTask.reject(v) 37 | end 38 | 39 | ---Alias of AsyncTask.new(...). 40 | ---@param runner fun(resolve: fun(value: any), reject: fun(err: any)) 41 | ---@return gindent.kit.Async.AsyncTask 42 | function Async.new(runner) 43 | return AsyncTask.new(runner) 44 | end 45 | 46 | ---Run async function immediately. 47 | ---@generic A: ... 48 | ---@param runner fun(...: A): any 49 | ---@param ...? A 50 | ---@return gindent.kit.Async.AsyncTask 51 | function Async.run(runner, ...) 52 | local args = { ... } 53 | 54 | local thread_parent = Async.in_context() and coroutine.running() or nil 55 | 56 | local thread = coroutine.create(runner) 57 | _G.kit.Async.___threads___[thread] = { 58 | thread = thread, 59 | thread_parent = thread_parent, 60 | now = vim.uv.hrtime() / 1000000, 61 | } 62 | return AsyncTask.new(function(resolve, reject) 63 | local function next_step(ok, v) 64 | if getmetatable(v) == Interrupt then 65 | vim.defer_fn(function() 66 | next_step(coroutine.resume(thread)) 67 | end, v.timeout) 68 | return 69 | end 70 | 71 | if coroutine.status(thread) == 'dead' then 72 | if AsyncTask.is(v) then 73 | v:dispatch(resolve, reject) 74 | else 75 | if ok then 76 | resolve(v) 77 | else 78 | reject(v) 79 | end 80 | end 81 | _G.kit.Async.___threads___[thread] = nil 82 | return 83 | end 84 | 85 | v:dispatch(function(...) 86 | next_step(coroutine.resume(thread, true, ...)) 87 | end, function(...) 88 | next_step(coroutine.resume(thread, false, ...)) 89 | end) 90 | end 91 | 92 | next_step(coroutine.resume(thread, unpack(args))) 93 | end) 94 | end 95 | 96 | ---Return current context is async coroutine or not. 97 | ---@return boolean 98 | function Async.in_context() 99 | return _G.kit.Async.___threads___[coroutine.running()] ~= nil 100 | end 101 | 102 | ---Await async task. 103 | ---@param task gindent.kit.Async.AsyncTask 104 | ---@return any 105 | function Async.await(task) 106 | if not _G.kit.Async.___threads___[coroutine.running()] then 107 | error('`Async.await` must be called in async context.') 108 | end 109 | if not AsyncTask.is(task) then 110 | error('`Async.await` must be called with AsyncTask.') 111 | end 112 | 113 | local ok, res = coroutine.yield(task) 114 | if not ok then 115 | error(res, 2) 116 | end 117 | return res 118 | end 119 | 120 | ---Interrupt sync process. 121 | ---@param interval integer 122 | ---@param timeout? integer 123 | function Async.interrupt(interval, timeout) 124 | local thread = coroutine.running() 125 | if not _G.kit.Async.___threads___[thread] then 126 | error('`Async.interrupt` must be called in async context.') 127 | end 128 | 129 | local thread_parent = thread 130 | while true do 131 | local next_thread_parent = _G.kit.Async.___threads___[thread_parent].thread_parent 132 | if not next_thread_parent then 133 | break 134 | end 135 | if not _G.kit.Async.___threads___[next_thread_parent] then 136 | break 137 | end 138 | thread_parent = next_thread_parent 139 | end 140 | 141 | local prev_now = _G.kit.Async.___threads___[thread_parent].now 142 | local curr_now = vim.uv.hrtime() / 1000000 143 | if (curr_now - prev_now) > interval then 144 | coroutine.yield(setmetatable({ timeout = timeout or 16 }, Interrupt)) 145 | if _G.kit.Async.___threads___[thread_parent] then 146 | _G.kit.Async.___threads___[thread_parent].now = vim.uv.hrtime() / 1000000 147 | end 148 | end 149 | end 150 | 151 | ---Create vim.schedule task. 152 | ---@return gindent.kit.Async.AsyncTask 153 | function Async.schedule() 154 | return AsyncTask.new(function(resolve) 155 | vim.schedule(resolve) 156 | end) 157 | end 158 | 159 | ---Create vim.defer_fn task. 160 | ---@param timeout integer 161 | ---@return gindent.kit.Async.AsyncTask 162 | function Async.timeout(timeout) 163 | return AsyncTask.new(function(resolve) 164 | vim.defer_fn(resolve, timeout) 165 | end) 166 | end 167 | 168 | ---Create async function from callback function. 169 | ---@generic T: ... 170 | ---@param runner fun(...: T) 171 | ---@param option? { schedule?: boolean, callback?: integer } 172 | ---@return fun(...: T): gindent.kit.Async.AsyncTask 173 | function Async.promisify(runner, option) 174 | option = option or {} 175 | option.schedule = not vim.is_thread() and (option.schedule or false) 176 | option.callback = option.callback or nil 177 | return function(...) 178 | local args = { ... } 179 | return AsyncTask.new(function(resolve, reject) 180 | local max = #args + 1 181 | local pos = math.min(option.callback or max, max) 182 | table.insert(args, pos, function(err, ...) 183 | if option.schedule and vim.in_fast_event() then 184 | resolve = vim.schedule_wrap(resolve) 185 | reject = vim.schedule_wrap(reject) 186 | end 187 | if err then 188 | reject(err) 189 | else 190 | resolve(...) 191 | end 192 | end) 193 | runner(unpack(args)) 194 | end) 195 | end 196 | end 197 | 198 | return Async 199 | -------------------------------------------------------------------------------- /lua/gindent/kit/IO/init.lua: -------------------------------------------------------------------------------- 1 | local uv = vim.uv 2 | local Async = require('gindent.kit.Async') 3 | 4 | local is_windows = uv.os_uname().sysname:lower() == 'windows' 5 | 6 | ---@see https://github.com/luvit/luvit/blob/master/deps/fs.lua 7 | local IO = {} 8 | 9 | ---@class gindent.kit.IO.UV.Stat 10 | ---@field public dev integer 11 | ---@field public mode integer 12 | ---@field public nlink integer 13 | ---@field public uid integer 14 | ---@field public gid integer 15 | ---@field public rdev integer 16 | ---@field public ino integer 17 | ---@field public size integer 18 | ---@field public blksize integer 19 | ---@field public blocks integer 20 | ---@field public flags integer 21 | ---@field public gen integer 22 | ---@field public atime { sec: integer, nsec: integer } 23 | ---@field public mtime { sec: integer, nsec: integer } 24 | ---@field public ctime { sec: integer, nsec: integer } 25 | ---@field public birthtime { sec: integer, nsec: integer } 26 | ---@field public type string 27 | 28 | ---@enum gindent.kit.IO.UV.AccessMode 29 | IO.AccessMode = { 30 | r = 'r', 31 | rs = 'rs', 32 | sr = 'sr', 33 | ['r+'] = 'r+', 34 | ['rs+'] = 'rs+', 35 | ['sr+'] = 'sr+', 36 | w = 'w', 37 | wx = 'wx', 38 | xw = 'xw', 39 | ['w+'] = 'w+', 40 | ['wx+'] = 'wx+', 41 | ['xw+'] = 'xw+', 42 | a = 'a', 43 | ax = 'ax', 44 | xa = 'xa', 45 | ['a+'] = 'a+', 46 | ['ax+'] = 'ax+', 47 | ['xa+'] = 'xa+', 48 | } 49 | 50 | ---@enum gindent.kit.IO.WalkStatus 51 | IO.WalkStatus = { 52 | SkipDir = 1, 53 | Break = 2, 54 | } 55 | 56 | ---@type fun(path: string): gindent.kit.Async.AsyncTask 57 | IO.fs_stat = Async.promisify(uv.fs_stat) 58 | 59 | ---@type fun(path: string): gindent.kit.Async.AsyncTask 60 | IO.fs_unlink = Async.promisify(uv.fs_unlink) 61 | 62 | ---@type fun(path: string): gindent.kit.Async.AsyncTask 63 | IO.fs_rmdir = Async.promisify(uv.fs_rmdir) 64 | 65 | ---@type fun(path: string, mode: integer): gindent.kit.Async.AsyncTask 66 | IO.fs_mkdir = Async.promisify(uv.fs_mkdir) 67 | 68 | ---@type fun(from: string, to: string, option?: { excl?: boolean, ficlone?: boolean, ficlone_force?: boolean }): gindent.kit.Async.AsyncTask 69 | IO.fs_copyfile = Async.promisify(uv.fs_copyfile) 70 | 71 | ---@type fun(path: string, flags: gindent.kit.IO.UV.AccessMode, mode: integer): gindent.kit.Async.AsyncTask 72 | IO.fs_open = Async.promisify(uv.fs_open) 73 | 74 | ---@type fun(fd: userdata): gindent.kit.Async.AsyncTask 75 | IO.fs_close = Async.promisify(uv.fs_close) 76 | 77 | ---@type fun(fd: userdata, chunk_size: integer, offset?: integer): gindent.kit.Async.AsyncTask 78 | IO.fs_read = Async.promisify(uv.fs_read) 79 | 80 | ---@type fun(fd: userdata, content: string, offset?: integer): gindent.kit.Async.AsyncTask 81 | IO.fs_write = Async.promisify(uv.fs_write) 82 | 83 | ---@type fun(fd: userdata, offset: integer): gindent.kit.Async.AsyncTask 84 | IO.fs_ftruncate = Async.promisify(uv.fs_ftruncate) 85 | 86 | ---@type fun(path: string, chunk_size?: integer): gindent.kit.Async.AsyncTask 87 | IO.fs_opendir = Async.promisify(uv.fs_opendir, { callback = 2 }) 88 | 89 | ---@type fun(fd: userdata): gindent.kit.Async.AsyncTask 90 | IO.fs_closedir = Async.promisify(uv.fs_closedir) 91 | 92 | ---@type fun(fd: userdata): gindent.kit.Async.AsyncTask 93 | IO.fs_readdir = Async.promisify(uv.fs_readdir) 94 | 95 | ---@type fun(path: string): gindent.kit.Async.AsyncTask 96 | IO.fs_scandir = Async.promisify(uv.fs_scandir) 97 | 98 | ---@type fun(path: string): gindent.kit.Async.AsyncTask 99 | IO.fs_realpath = Async.promisify(uv.fs_realpath) 100 | 101 | ---Return if the path is directory. 102 | ---@param path string 103 | ---@return gindent.kit.Async.AsyncTask 104 | function IO.is_directory(path) 105 | path = IO.normalize(path) 106 | return Async.run(function() 107 | return IO.fs_stat(path) 108 | :catch(function() 109 | return {} 110 | end) 111 | :await().type == 'directory' 112 | end) 113 | end 114 | 115 | ---Return if the path is exists. 116 | ---@param path string 117 | ---@return gindent.kit.Async.AsyncTask 118 | function IO.exists(path) 119 | path = IO.normalize(path) 120 | return Async.run(function() 121 | return IO.fs_stat(path) 122 | :next(function() 123 | return true 124 | end) 125 | :catch(function() 126 | return false 127 | end) 128 | :await() 129 | end) 130 | end 131 | 132 | ---Read file. 133 | ---@param path string 134 | ---@param chunk_size? integer 135 | ---@return gindent.kit.Async.AsyncTask 136 | function IO.read_file(path, chunk_size) 137 | chunk_size = chunk_size or 1024 138 | return Async.run(function() 139 | local stat = IO.fs_stat(path):await() 140 | local fd = IO.fs_open(path, IO.AccessMode.r, tonumber('755', 8)):await() 141 | local ok, res = pcall(function() 142 | local chunks = {} 143 | local offset = 0 144 | while offset < stat.size do 145 | local chunk = IO.fs_read(fd, math.min(chunk_size, stat.size - offset), offset):await() 146 | if not chunk then 147 | break 148 | end 149 | table.insert(chunks, chunk) 150 | offset = offset + #chunk 151 | end 152 | return table.concat(chunks, ''):sub(1, stat.size - 1) -- remove EOF. 153 | end) 154 | IO.fs_close(fd):await() 155 | if not ok then 156 | error(res) 157 | end 158 | return res 159 | end) 160 | end 161 | 162 | ---Write file. 163 | ---@param path string 164 | ---@param content string 165 | ---@param chunk_size? integer 166 | function IO.write_file(path, content, chunk_size) 167 | chunk_size = chunk_size or 1024 168 | content = content .. '\n' -- add EOF. 169 | return Async.run(function() 170 | local fd = IO.fs_open(path, IO.AccessMode.w, tonumber('755', 8)):await() 171 | local ok, err = pcall(function() 172 | local offset = 0 173 | while offset < #content do 174 | local chunk = content:sub(offset + 1, offset + chunk_size) 175 | offset = offset + IO.fs_write(fd, chunk, offset):await() 176 | end 177 | IO.fs_ftruncate(fd, offset):await() 178 | end) 179 | IO.fs_close(fd):await() 180 | if not ok then 181 | error(err) 182 | end 183 | end) 184 | end 185 | 186 | ---Create directory. 187 | ---@param path string 188 | ---@param mode integer 189 | ---@param option? { recursive?: boolean } 190 | function IO.mkdir(path, mode, option) 191 | path = IO.normalize(path) 192 | option = option or {} 193 | option.recursive = option.recursive or false 194 | return Async.run(function() 195 | if not option.recursive then 196 | IO.fs_mkdir(path, mode):await() 197 | else 198 | local not_exists = {} 199 | local current = path 200 | while current ~= '/' do 201 | local stat = IO.fs_stat(current):catch(function() end):await() 202 | if stat then 203 | break 204 | end 205 | table.insert(not_exists, 1, current) 206 | current = IO.dirname(current) 207 | end 208 | for _, dir in ipairs(not_exists) do 209 | IO.fs_mkdir(dir, mode):await() 210 | end 211 | end 212 | end) 213 | end 214 | 215 | ---Remove file or directory. 216 | ---@param start_path string 217 | ---@param option? { recursive?: boolean } 218 | function IO.rm(start_path, option) 219 | start_path = IO.normalize(start_path) 220 | option = option or {} 221 | option.recursive = option.recursive or false 222 | return Async.run(function() 223 | local stat = IO.fs_stat(start_path):await() 224 | if stat.type == 'directory' then 225 | local children = IO.scandir(start_path):await() 226 | if not option.recursive and #children > 0 then 227 | error(('IO.rm: `%s` is a directory and not empty.'):format(start_path)) 228 | end 229 | IO.walk(start_path, function(err, entry) 230 | if err then 231 | error('IO.rm: ' .. tostring(err)) 232 | end 233 | if entry.type == 'directory' then 234 | IO.fs_rmdir(entry.path):await() 235 | else 236 | IO.fs_unlink(entry.path):await() 237 | end 238 | end, { postorder = true }):await() 239 | else 240 | IO.fs_unlink(start_path):await() 241 | end 242 | end) 243 | end 244 | 245 | ---Copy file or directory. 246 | ---@param from any 247 | ---@param to any 248 | ---@param option? { recursive?: boolean } 249 | ---@return gindent.kit.Async.AsyncTask 250 | function IO.cp(from, to, option) 251 | from = IO.normalize(from) 252 | to = IO.normalize(to) 253 | option = option or {} 254 | option.recursive = option.recursive or false 255 | return Async.run(function() 256 | local stat = IO.fs_stat(from):await() 257 | if stat.type == 'directory' then 258 | if not option.recursive then 259 | error(('IO.cp: `%s` is a directory.'):format(from)) 260 | end 261 | IO.walk(from, function(err, entry) 262 | if err then 263 | error('IO.cp: ' .. tostring(err)) 264 | end 265 | local new_path = entry.path:gsub(vim.pesc(from), to) 266 | if entry.type == 'directory' then 267 | IO.mkdir(new_path, tonumber(stat.mode, 10), { recursive = true }):await() 268 | else 269 | IO.fs_copyfile(entry.path, new_path):await() 270 | end 271 | end):await() 272 | else 273 | IO.fs_copyfile(from, to):await() 274 | end 275 | end) 276 | end 277 | 278 | ---Walk directory entries recursively. 279 | ---@param start_path string 280 | ---@param callback fun(err: string|nil, entry: { path: string, type: string }): gindent.kit.IO.WalkStatus? 281 | ---@param option? { postorder?: boolean } 282 | function IO.walk(start_path, callback, option) 283 | start_path = IO.normalize(start_path) 284 | option = option or {} 285 | option.postorder = option.postorder or false 286 | return Async.run(function() 287 | local function walk_pre(dir) 288 | local ok, iter_entries = pcall(function() 289 | return IO.iter_scandir(dir.path):await() 290 | end) 291 | if not ok then 292 | return callback(iter_entries, dir) 293 | end 294 | local status = callback(nil, dir) 295 | if status == IO.WalkStatus.SkipDir then 296 | return 297 | elseif status == IO.WalkStatus.Break then 298 | return status 299 | end 300 | for entry in iter_entries do 301 | if entry.type == 'directory' then 302 | if walk_pre(entry) == IO.WalkStatus.Break then 303 | return IO.WalkStatus.Break 304 | end 305 | else 306 | if callback(nil, entry) == IO.WalkStatus.Break then 307 | return IO.WalkStatus.Break 308 | end 309 | end 310 | end 311 | end 312 | 313 | local function walk_post(dir) 314 | local ok, iter_entries = pcall(function() 315 | return IO.iter_scandir(dir.path):await() 316 | end) 317 | if not ok then 318 | return callback(iter_entries, dir) 319 | end 320 | for entry in iter_entries do 321 | if entry.type == 'directory' then 322 | if walk_post(entry) == IO.WalkStatus.Break then 323 | return IO.WalkStatus.Break 324 | end 325 | else 326 | if callback(nil, entry) == IO.WalkStatus.Break then 327 | return IO.WalkStatus.Break 328 | end 329 | end 330 | end 331 | return callback(nil, dir) 332 | end 333 | 334 | if not IO.is_directory(start_path) then 335 | error(('IO.walk: `%s` is not a directory.'):format(start_path)) 336 | end 337 | if option.postorder then 338 | walk_post({ path = start_path, type = 'directory' }) 339 | else 340 | walk_pre({ path = start_path, type = 'directory' }) 341 | end 342 | end) 343 | end 344 | 345 | ---Scan directory entries. 346 | ---@param path string 347 | ---@return gindent.kit.Async.AsyncTask 348 | function IO.scandir(path) 349 | path = IO.normalize(path) 350 | return Async.run(function() 351 | local fd = IO.fs_scandir(path):await() 352 | local entries = {} 353 | while true do 354 | local name, type = uv.fs_scandir_next(fd) 355 | if not name then 356 | break 357 | end 358 | table.insert(entries, { 359 | type = type, 360 | path = IO.join(path, name), 361 | }) 362 | end 363 | return entries 364 | end) 365 | end 366 | 367 | ---Scan directory entries. 368 | ---@param path any 369 | ---@return gindent.kit.Async.AsyncTask 370 | function IO.iter_scandir(path) 371 | path = IO.normalize(path) 372 | return Async.run(function() 373 | local fd = IO.fs_scandir(path):await() 374 | return function() 375 | local name, type = uv.fs_scandir_next(fd) 376 | if name then 377 | return { 378 | type = type, 379 | path = IO.join(path, name), 380 | } 381 | end 382 | end 383 | end) 384 | end 385 | 386 | ---Return normalized path. 387 | ---@param path string 388 | ---@return string 389 | function IO.normalize(path) 390 | if is_windows then 391 | path = path:gsub('\\', '/') 392 | end 393 | 394 | -- remove trailing slash. 395 | if path:sub(-1) == '/' then 396 | path = path:sub(1, -2) 397 | end 398 | 399 | -- skip if the path already absolute. 400 | if IO.is_absolute(path) then 401 | return path 402 | end 403 | 404 | -- homedir. 405 | if path:sub(1, 1) == '~' then 406 | path = IO.join(uv.os_homedir() or vim.fs.normalize('~'), path:sub(2)) 407 | end 408 | 409 | -- absolute. 410 | if IO.is_absolute(path) then 411 | return path:sub(-1) == '/' and path:sub(1, -2) or path 412 | end 413 | 414 | -- resolve relative path. 415 | local up = assert(uv.cwd()) 416 | up = up:sub(-1) == '/' and up:sub(1, -2) or up 417 | while true do 418 | if path:sub(1, 3) == '../' then 419 | path = path:sub(4) 420 | up = IO.dirname(up) 421 | elseif path:sub(1, 2) == './' then 422 | path = path:sub(3) 423 | else 424 | break 425 | end 426 | end 427 | return IO.join(up, path) 428 | end 429 | 430 | ---Join the paths. 431 | ---@param base string 432 | ---@param path string 433 | ---@return string 434 | function IO.join(base, path) 435 | if base:sub(-1) == '/' then 436 | base = base:sub(1, -2) 437 | end 438 | return base .. '/' .. path 439 | end 440 | 441 | ---Return the path of the current working directory. 442 | ---@param path string 443 | ---@return string 444 | function IO.dirname(path) 445 | if path:sub(-1) == '/' then 446 | path = path:sub(1, -2) 447 | end 448 | return (path:gsub('/[^/]+$', '')) 449 | end 450 | 451 | if is_windows then 452 | ---Return the path is absolute or not. 453 | ---@param path string 454 | ---@return boolean 455 | function IO.is_absolute(path) 456 | return path:sub(1, 1) == '/' or path:match('^%a://') 457 | end 458 | else 459 | ---Return the path is absolute or not. 460 | ---@param path string 461 | ---@return boolean 462 | function IO.is_absolute(path) 463 | return path:sub(1, 1) == '/' 464 | end 465 | end 466 | 467 | return IO 468 | -------------------------------------------------------------------------------- /lua/gindent/kit/LSP/Client.lua: -------------------------------------------------------------------------------- 1 | local LSP = require('gindent.kit.LSP') 2 | local AsyncTask = require('gindent.kit.Async.AsyncTask') 3 | 4 | ---@class gindent.kit.LSP.Client 5 | ---@field public client vim.lsp.Client 6 | local Client = {} 7 | Client.__index = Client 8 | 9 | ---Create LSP Client wrapper. 10 | ---@param client vim.lsp.Client 11 | ---@return gindent.kit.LSP.Client 12 | function Client.new(client) 13 | local self = setmetatable({}, Client) 14 | self.client = client 15 | return self 16 | end 17 | 18 | ---@param params gindent.kit.LSP.ImplementationParams 19 | function Client:textDocument_implementation(params) 20 | local that, _, request_id, reject_ = self, nil, nil, nil 21 | local task = AsyncTask.new(function(resolve, reject) 22 | reject_ = reject 23 | _, request_id = self.client:request('textDocument/implementation', params, function(err, res) 24 | if err then 25 | reject(err) 26 | else 27 | resolve(res) 28 | end 29 | end) 30 | end) 31 | function task.cancel() 32 | that.client:cancel_request(request_id) 33 | reject_(LSP.ErrorCodes.RequestCancelled) 34 | end 35 | return task 36 | end 37 | 38 | ---@param params gindent.kit.LSP.TypeDefinitionParams 39 | function Client:textDocument_typeDefinition(params) 40 | local that, _, request_id, reject_ = self, nil, nil, nil 41 | local task = AsyncTask.new(function(resolve, reject) 42 | reject_ = reject 43 | _, request_id = self.client:request('textDocument/typeDefinition', params, function(err, res) 44 | if err then 45 | reject(err) 46 | else 47 | resolve(res) 48 | end 49 | end) 50 | end) 51 | function task.cancel() 52 | that.client:cancel_request(request_id) 53 | reject_(LSP.ErrorCodes.RequestCancelled) 54 | end 55 | return task 56 | end 57 | 58 | ---@param params nil 59 | function Client:workspace_workspaceFolders(params) 60 | local that, _, request_id, reject_ = self, nil, nil, nil 61 | local task = AsyncTask.new(function(resolve, reject) 62 | reject_ = reject 63 | _, request_id = self.client:request('workspace/workspaceFolders', params, function(err, res) 64 | if err then 65 | reject(err) 66 | else 67 | resolve(res) 68 | end 69 | end) 70 | end) 71 | function task.cancel() 72 | that.client:cancel_request(request_id) 73 | reject_(LSP.ErrorCodes.RequestCancelled) 74 | end 75 | return task 76 | end 77 | 78 | ---@param params gindent.kit.LSP.ConfigurationParams 79 | function Client:workspace_configuration(params) 80 | local that, _, request_id, reject_ = self, nil, nil, nil 81 | local task = AsyncTask.new(function(resolve, reject) 82 | reject_ = reject 83 | _, request_id = self.client:request('workspace/configuration', params, function(err, res) 84 | if err then 85 | reject(err) 86 | else 87 | resolve(res) 88 | end 89 | end) 90 | end) 91 | function task.cancel() 92 | that.client:cancel_request(request_id) 93 | reject_(LSP.ErrorCodes.RequestCancelled) 94 | end 95 | return task 96 | end 97 | 98 | ---@param params gindent.kit.LSP.DocumentColorParams 99 | function Client:textDocument_documentColor(params) 100 | local that, _, request_id, reject_ = self, nil, nil, nil 101 | local task = AsyncTask.new(function(resolve, reject) 102 | reject_ = reject 103 | _, request_id = self.client:request('textDocument/documentColor', params, function(err, res) 104 | if err then 105 | reject(err) 106 | else 107 | resolve(res) 108 | end 109 | end) 110 | end) 111 | function task.cancel() 112 | that.client:cancel_request(request_id) 113 | reject_(LSP.ErrorCodes.RequestCancelled) 114 | end 115 | return task 116 | end 117 | 118 | ---@param params gindent.kit.LSP.ColorPresentationParams 119 | function Client:textDocument_colorPresentation(params) 120 | local that, _, request_id, reject_ = self, nil, nil, nil 121 | local task = AsyncTask.new(function(resolve, reject) 122 | reject_ = reject 123 | _, request_id = self.client:request('textDocument/colorPresentation', params, function(err, res) 124 | if err then 125 | reject(err) 126 | else 127 | resolve(res) 128 | end 129 | end) 130 | end) 131 | function task.cancel() 132 | that.client:cancel_request(request_id) 133 | reject_(LSP.ErrorCodes.RequestCancelled) 134 | end 135 | return task 136 | end 137 | 138 | ---@param params gindent.kit.LSP.FoldingRangeParams 139 | function Client:textDocument_foldingRange(params) 140 | local that, _, request_id, reject_ = self, nil, nil, nil 141 | local task = AsyncTask.new(function(resolve, reject) 142 | reject_ = reject 143 | _, request_id = self.client:request('textDocument/foldingRange', params, function(err, res) 144 | if err then 145 | reject(err) 146 | else 147 | resolve(res) 148 | end 149 | end) 150 | end) 151 | function task.cancel() 152 | that.client:cancel_request(request_id) 153 | reject_(LSP.ErrorCodes.RequestCancelled) 154 | end 155 | return task 156 | end 157 | 158 | ---@param params nil 159 | function Client:workspace_foldingRange_refresh(params) 160 | local that, _, request_id, reject_ = self, nil, nil, nil 161 | local task = AsyncTask.new(function(resolve, reject) 162 | reject_ = reject 163 | _, request_id = self.client:request('workspace/foldingRange/refresh', params, function(err, res) 164 | if err then 165 | reject(err) 166 | else 167 | resolve(res) 168 | end 169 | end) 170 | end) 171 | function task.cancel() 172 | that.client:cancel_request(request_id) 173 | reject_(LSP.ErrorCodes.RequestCancelled) 174 | end 175 | return task 176 | end 177 | 178 | ---@param params gindent.kit.LSP.DeclarationParams 179 | function Client:textDocument_declaration(params) 180 | local that, _, request_id, reject_ = self, nil, nil, nil 181 | local task = AsyncTask.new(function(resolve, reject) 182 | reject_ = reject 183 | _, request_id = self.client:request('textDocument/declaration', params, function(err, res) 184 | if err then 185 | reject(err) 186 | else 187 | resolve(res) 188 | end 189 | end) 190 | end) 191 | function task.cancel() 192 | that.client:cancel_request(request_id) 193 | reject_(LSP.ErrorCodes.RequestCancelled) 194 | end 195 | return task 196 | end 197 | 198 | ---@param params gindent.kit.LSP.SelectionRangeParams 199 | function Client:textDocument_selectionRange(params) 200 | local that, _, request_id, reject_ = self, nil, nil, nil 201 | local task = AsyncTask.new(function(resolve, reject) 202 | reject_ = reject 203 | _, request_id = self.client:request('textDocument/selectionRange', params, function(err, res) 204 | if err then 205 | reject(err) 206 | else 207 | resolve(res) 208 | end 209 | end) 210 | end) 211 | function task.cancel() 212 | that.client:cancel_request(request_id) 213 | reject_(LSP.ErrorCodes.RequestCancelled) 214 | end 215 | return task 216 | end 217 | 218 | ---@param params gindent.kit.LSP.WorkDoneProgressCreateParams 219 | function Client:window_workDoneProgress_create(params) 220 | local that, _, request_id, reject_ = self, nil, nil, nil 221 | local task = AsyncTask.new(function(resolve, reject) 222 | reject_ = reject 223 | _, request_id = self.client:request('window/workDoneProgress/create', params, function(err, res) 224 | if err then 225 | reject(err) 226 | else 227 | resolve(res) 228 | end 229 | end) 230 | end) 231 | function task.cancel() 232 | that.client:cancel_request(request_id) 233 | reject_(LSP.ErrorCodes.RequestCancelled) 234 | end 235 | return task 236 | end 237 | 238 | ---@param params gindent.kit.LSP.CallHierarchyPrepareParams 239 | function Client:textDocument_prepareCallHierarchy(params) 240 | local that, _, request_id, reject_ = self, nil, nil, nil 241 | local task = AsyncTask.new(function(resolve, reject) 242 | reject_ = reject 243 | _, request_id = self.client:request('textDocument/prepareCallHierarchy', params, function(err, res) 244 | if err then 245 | reject(err) 246 | else 247 | resolve(res) 248 | end 249 | end) 250 | end) 251 | function task.cancel() 252 | that.client:cancel_request(request_id) 253 | reject_(LSP.ErrorCodes.RequestCancelled) 254 | end 255 | return task 256 | end 257 | 258 | ---@param params gindent.kit.LSP.CallHierarchyIncomingCallsParams 259 | function Client:callHierarchy_incomingCalls(params) 260 | local that, _, request_id, reject_ = self, nil, nil, nil 261 | local task = AsyncTask.new(function(resolve, reject) 262 | reject_ = reject 263 | _, request_id = self.client:request('callHierarchy/incomingCalls', params, function(err, res) 264 | if err then 265 | reject(err) 266 | else 267 | resolve(res) 268 | end 269 | end) 270 | end) 271 | function task.cancel() 272 | that.client:cancel_request(request_id) 273 | reject_(LSP.ErrorCodes.RequestCancelled) 274 | end 275 | return task 276 | end 277 | 278 | ---@param params gindent.kit.LSP.CallHierarchyOutgoingCallsParams 279 | function Client:callHierarchy_outgoingCalls(params) 280 | local that, _, request_id, reject_ = self, nil, nil, nil 281 | local task = AsyncTask.new(function(resolve, reject) 282 | reject_ = reject 283 | _, request_id = self.client:request('callHierarchy/outgoingCalls', params, function(err, res) 284 | if err then 285 | reject(err) 286 | else 287 | resolve(res) 288 | end 289 | end) 290 | end) 291 | function task.cancel() 292 | that.client:cancel_request(request_id) 293 | reject_(LSP.ErrorCodes.RequestCancelled) 294 | end 295 | return task 296 | end 297 | 298 | ---@param params gindent.kit.LSP.SemanticTokensParams 299 | function Client:textDocument_semanticTokens_full(params) 300 | local that, _, request_id, reject_ = self, nil, nil, nil 301 | local task = AsyncTask.new(function(resolve, reject) 302 | reject_ = reject 303 | _, request_id = self.client:request('textDocument/semanticTokens/full', params, function(err, res) 304 | if err then 305 | reject(err) 306 | else 307 | resolve(res) 308 | end 309 | end) 310 | end) 311 | function task.cancel() 312 | that.client:cancel_request(request_id) 313 | reject_(LSP.ErrorCodes.RequestCancelled) 314 | end 315 | return task 316 | end 317 | 318 | ---@param params gindent.kit.LSP.SemanticTokensDeltaParams 319 | function Client:textDocument_semanticTokens_full_delta(params) 320 | local that, _, request_id, reject_ = self, nil, nil, nil 321 | local task = AsyncTask.new(function(resolve, reject) 322 | reject_ = reject 323 | _, request_id = self.client:request('textDocument/semanticTokens/full/delta', params, function(err, res) 324 | if err then 325 | reject(err) 326 | else 327 | resolve(res) 328 | end 329 | end) 330 | end) 331 | function task.cancel() 332 | that.client:cancel_request(request_id) 333 | reject_(LSP.ErrorCodes.RequestCancelled) 334 | end 335 | return task 336 | end 337 | 338 | ---@param params gindent.kit.LSP.SemanticTokensRangeParams 339 | function Client:textDocument_semanticTokens_range(params) 340 | local that, _, request_id, reject_ = self, nil, nil, nil 341 | local task = AsyncTask.new(function(resolve, reject) 342 | reject_ = reject 343 | _, request_id = self.client:request('textDocument/semanticTokens/range', params, function(err, res) 344 | if err then 345 | reject(err) 346 | else 347 | resolve(res) 348 | end 349 | end) 350 | end) 351 | function task.cancel() 352 | that.client:cancel_request(request_id) 353 | reject_(LSP.ErrorCodes.RequestCancelled) 354 | end 355 | return task 356 | end 357 | 358 | ---@param params nil 359 | function Client:workspace_semanticTokens_refresh(params) 360 | local that, _, request_id, reject_ = self, nil, nil, nil 361 | local task = AsyncTask.new(function(resolve, reject) 362 | reject_ = reject 363 | _, request_id = self.client:request('workspace/semanticTokens/refresh', params, function(err, res) 364 | if err then 365 | reject(err) 366 | else 367 | resolve(res) 368 | end 369 | end) 370 | end) 371 | function task.cancel() 372 | that.client:cancel_request(request_id) 373 | reject_(LSP.ErrorCodes.RequestCancelled) 374 | end 375 | return task 376 | end 377 | 378 | ---@param params gindent.kit.LSP.ShowDocumentParams 379 | function Client:window_showDocument(params) 380 | local that, _, request_id, reject_ = self, nil, nil, nil 381 | local task = AsyncTask.new(function(resolve, reject) 382 | reject_ = reject 383 | _, request_id = self.client:request('window/showDocument', params, function(err, res) 384 | if err then 385 | reject(err) 386 | else 387 | resolve(res) 388 | end 389 | end) 390 | end) 391 | function task.cancel() 392 | that.client:cancel_request(request_id) 393 | reject_(LSP.ErrorCodes.RequestCancelled) 394 | end 395 | return task 396 | end 397 | 398 | ---@param params gindent.kit.LSP.LinkedEditingRangeParams 399 | function Client:textDocument_linkedEditingRange(params) 400 | local that, _, request_id, reject_ = self, nil, nil, nil 401 | local task = AsyncTask.new(function(resolve, reject) 402 | reject_ = reject 403 | _, request_id = self.client:request('textDocument/linkedEditingRange', params, function(err, res) 404 | if err then 405 | reject(err) 406 | else 407 | resolve(res) 408 | end 409 | end) 410 | end) 411 | function task.cancel() 412 | that.client:cancel_request(request_id) 413 | reject_(LSP.ErrorCodes.RequestCancelled) 414 | end 415 | return task 416 | end 417 | 418 | ---@param params gindent.kit.LSP.CreateFilesParams 419 | function Client:workspace_willCreateFiles(params) 420 | local that, _, request_id, reject_ = self, nil, nil, nil 421 | local task = AsyncTask.new(function(resolve, reject) 422 | reject_ = reject 423 | _, request_id = self.client:request('workspace/willCreateFiles', params, function(err, res) 424 | if err then 425 | reject(err) 426 | else 427 | resolve(res) 428 | end 429 | end) 430 | end) 431 | function task.cancel() 432 | that.client:cancel_request(request_id) 433 | reject_(LSP.ErrorCodes.RequestCancelled) 434 | end 435 | return task 436 | end 437 | 438 | ---@param params gindent.kit.LSP.RenameFilesParams 439 | function Client:workspace_willRenameFiles(params) 440 | local that, _, request_id, reject_ = self, nil, nil, nil 441 | local task = AsyncTask.new(function(resolve, reject) 442 | reject_ = reject 443 | _, request_id = self.client:request('workspace/willRenameFiles', params, function(err, res) 444 | if err then 445 | reject(err) 446 | else 447 | resolve(res) 448 | end 449 | end) 450 | end) 451 | function task.cancel() 452 | that.client:cancel_request(request_id) 453 | reject_(LSP.ErrorCodes.RequestCancelled) 454 | end 455 | return task 456 | end 457 | 458 | ---@param params gindent.kit.LSP.DeleteFilesParams 459 | function Client:workspace_willDeleteFiles(params) 460 | local that, _, request_id, reject_ = self, nil, nil, nil 461 | local task = AsyncTask.new(function(resolve, reject) 462 | reject_ = reject 463 | _, request_id = self.client:request('workspace/willDeleteFiles', params, function(err, res) 464 | if err then 465 | reject(err) 466 | else 467 | resolve(res) 468 | end 469 | end) 470 | end) 471 | function task.cancel() 472 | that.client:cancel_request(request_id) 473 | reject_(LSP.ErrorCodes.RequestCancelled) 474 | end 475 | return task 476 | end 477 | 478 | ---@param params gindent.kit.LSP.MonikerParams 479 | function Client:textDocument_moniker(params) 480 | local that, _, request_id, reject_ = self, nil, nil, nil 481 | local task = AsyncTask.new(function(resolve, reject) 482 | reject_ = reject 483 | _, request_id = self.client:request('textDocument/moniker', params, function(err, res) 484 | if err then 485 | reject(err) 486 | else 487 | resolve(res) 488 | end 489 | end) 490 | end) 491 | function task.cancel() 492 | that.client:cancel_request(request_id) 493 | reject_(LSP.ErrorCodes.RequestCancelled) 494 | end 495 | return task 496 | end 497 | 498 | ---@param params gindent.kit.LSP.TypeHierarchyPrepareParams 499 | function Client:textDocument_prepareTypeHierarchy(params) 500 | local that, _, request_id, reject_ = self, nil, nil, nil 501 | local task = AsyncTask.new(function(resolve, reject) 502 | reject_ = reject 503 | _, request_id = self.client:request('textDocument/prepareTypeHierarchy', params, function(err, res) 504 | if err then 505 | reject(err) 506 | else 507 | resolve(res) 508 | end 509 | end) 510 | end) 511 | function task.cancel() 512 | that.client:cancel_request(request_id) 513 | reject_(LSP.ErrorCodes.RequestCancelled) 514 | end 515 | return task 516 | end 517 | 518 | ---@param params gindent.kit.LSP.TypeHierarchySupertypesParams 519 | function Client:typeHierarchy_supertypes(params) 520 | local that, _, request_id, reject_ = self, nil, nil, nil 521 | local task = AsyncTask.new(function(resolve, reject) 522 | reject_ = reject 523 | _, request_id = self.client:request('typeHierarchy/supertypes', params, function(err, res) 524 | if err then 525 | reject(err) 526 | else 527 | resolve(res) 528 | end 529 | end) 530 | end) 531 | function task.cancel() 532 | that.client:cancel_request(request_id) 533 | reject_(LSP.ErrorCodes.RequestCancelled) 534 | end 535 | return task 536 | end 537 | 538 | ---@param params gindent.kit.LSP.TypeHierarchySubtypesParams 539 | function Client:typeHierarchy_subtypes(params) 540 | local that, _, request_id, reject_ = self, nil, nil, nil 541 | local task = AsyncTask.new(function(resolve, reject) 542 | reject_ = reject 543 | _, request_id = self.client:request('typeHierarchy/subtypes', params, function(err, res) 544 | if err then 545 | reject(err) 546 | else 547 | resolve(res) 548 | end 549 | end) 550 | end) 551 | function task.cancel() 552 | that.client:cancel_request(request_id) 553 | reject_(LSP.ErrorCodes.RequestCancelled) 554 | end 555 | return task 556 | end 557 | 558 | ---@param params gindent.kit.LSP.InlineValueParams 559 | function Client:textDocument_inlineValue(params) 560 | local that, _, request_id, reject_ = self, nil, nil, nil 561 | local task = AsyncTask.new(function(resolve, reject) 562 | reject_ = reject 563 | _, request_id = self.client:request('textDocument/inlineValue', params, function(err, res) 564 | if err then 565 | reject(err) 566 | else 567 | resolve(res) 568 | end 569 | end) 570 | end) 571 | function task.cancel() 572 | that.client:cancel_request(request_id) 573 | reject_(LSP.ErrorCodes.RequestCancelled) 574 | end 575 | return task 576 | end 577 | 578 | ---@param params nil 579 | function Client:workspace_inlineValue_refresh(params) 580 | local that, _, request_id, reject_ = self, nil, nil, nil 581 | local task = AsyncTask.new(function(resolve, reject) 582 | reject_ = reject 583 | _, request_id = self.client:request('workspace/inlineValue/refresh', params, function(err, res) 584 | if err then 585 | reject(err) 586 | else 587 | resolve(res) 588 | end 589 | end) 590 | end) 591 | function task.cancel() 592 | that.client:cancel_request(request_id) 593 | reject_(LSP.ErrorCodes.RequestCancelled) 594 | end 595 | return task 596 | end 597 | 598 | ---@param params gindent.kit.LSP.InlayHintParams 599 | function Client:textDocument_inlayHint(params) 600 | local that, _, request_id, reject_ = self, nil, nil, nil 601 | local task = AsyncTask.new(function(resolve, reject) 602 | reject_ = reject 603 | _, request_id = self.client:request('textDocument/inlayHint', params, function(err, res) 604 | if err then 605 | reject(err) 606 | else 607 | resolve(res) 608 | end 609 | end) 610 | end) 611 | function task.cancel() 612 | that.client:cancel_request(request_id) 613 | reject_(LSP.ErrorCodes.RequestCancelled) 614 | end 615 | return task 616 | end 617 | 618 | ---@param params gindent.kit.LSP.InlayHint 619 | function Client:inlayHint_resolve(params) 620 | local that, _, request_id, reject_ = self, nil, nil, nil 621 | local task = AsyncTask.new(function(resolve, reject) 622 | reject_ = reject 623 | _, request_id = self.client:request('inlayHint/resolve', params, function(err, res) 624 | if err then 625 | reject(err) 626 | else 627 | resolve(res) 628 | end 629 | end) 630 | end) 631 | function task.cancel() 632 | that.client:cancel_request(request_id) 633 | reject_(LSP.ErrorCodes.RequestCancelled) 634 | end 635 | return task 636 | end 637 | 638 | ---@param params nil 639 | function Client:workspace_inlayHint_refresh(params) 640 | local that, _, request_id, reject_ = self, nil, nil, nil 641 | local task = AsyncTask.new(function(resolve, reject) 642 | reject_ = reject 643 | _, request_id = self.client:request('workspace/inlayHint/refresh', params, function(err, res) 644 | if err then 645 | reject(err) 646 | else 647 | resolve(res) 648 | end 649 | end) 650 | end) 651 | function task.cancel() 652 | that.client:cancel_request(request_id) 653 | reject_(LSP.ErrorCodes.RequestCancelled) 654 | end 655 | return task 656 | end 657 | 658 | ---@param params gindent.kit.LSP.DocumentDiagnosticParams 659 | function Client:textDocument_diagnostic(params) 660 | local that, _, request_id, reject_ = self, nil, nil, nil 661 | local task = AsyncTask.new(function(resolve, reject) 662 | reject_ = reject 663 | _, request_id = self.client:request('textDocument/diagnostic', params, function(err, res) 664 | if err then 665 | reject(err) 666 | else 667 | resolve(res) 668 | end 669 | end) 670 | end) 671 | function task.cancel() 672 | that.client:cancel_request(request_id) 673 | reject_(LSP.ErrorCodes.RequestCancelled) 674 | end 675 | return task 676 | end 677 | 678 | ---@param params gindent.kit.LSP.WorkspaceDiagnosticParams 679 | function Client:workspace_diagnostic(params) 680 | local that, _, request_id, reject_ = self, nil, nil, nil 681 | local task = AsyncTask.new(function(resolve, reject) 682 | reject_ = reject 683 | _, request_id = self.client:request('workspace/diagnostic', params, function(err, res) 684 | if err then 685 | reject(err) 686 | else 687 | resolve(res) 688 | end 689 | end) 690 | end) 691 | function task.cancel() 692 | that.client:cancel_request(request_id) 693 | reject_(LSP.ErrorCodes.RequestCancelled) 694 | end 695 | return task 696 | end 697 | 698 | ---@param params nil 699 | function Client:workspace_diagnostic_refresh(params) 700 | local that, _, request_id, reject_ = self, nil, nil, nil 701 | local task = AsyncTask.new(function(resolve, reject) 702 | reject_ = reject 703 | _, request_id = self.client:request('workspace/diagnostic/refresh', params, function(err, res) 704 | if err then 705 | reject(err) 706 | else 707 | resolve(res) 708 | end 709 | end) 710 | end) 711 | function task.cancel() 712 | that.client:cancel_request(request_id) 713 | reject_(LSP.ErrorCodes.RequestCancelled) 714 | end 715 | return task 716 | end 717 | 718 | ---@param params gindent.kit.LSP.InlineCompletionParams 719 | function Client:textDocument_inlineCompletion(params) 720 | local that, _, request_id, reject_ = self, nil, nil, nil 721 | local task = AsyncTask.new(function(resolve, reject) 722 | reject_ = reject 723 | _, request_id = self.client:request('textDocument/inlineCompletion', params, function(err, res) 724 | if err then 725 | reject(err) 726 | else 727 | resolve(res) 728 | end 729 | end) 730 | end) 731 | function task.cancel() 732 | that.client:cancel_request(request_id) 733 | reject_(LSP.ErrorCodes.RequestCancelled) 734 | end 735 | return task 736 | end 737 | 738 | ---@param params gindent.kit.LSP.RegistrationParams 739 | function Client:client_registerCapability(params) 740 | local that, _, request_id, reject_ = self, nil, nil, nil 741 | local task = AsyncTask.new(function(resolve, reject) 742 | reject_ = reject 743 | _, request_id = self.client:request('client/registerCapability', params, function(err, res) 744 | if err then 745 | reject(err) 746 | else 747 | resolve(res) 748 | end 749 | end) 750 | end) 751 | function task.cancel() 752 | that.client:cancel_request(request_id) 753 | reject_(LSP.ErrorCodes.RequestCancelled) 754 | end 755 | return task 756 | end 757 | 758 | ---@param params gindent.kit.LSP.UnregistrationParams 759 | function Client:client_unregisterCapability(params) 760 | local that, _, request_id, reject_ = self, nil, nil, nil 761 | local task = AsyncTask.new(function(resolve, reject) 762 | reject_ = reject 763 | _, request_id = self.client:request('client/unregisterCapability', params, function(err, res) 764 | if err then 765 | reject(err) 766 | else 767 | resolve(res) 768 | end 769 | end) 770 | end) 771 | function task.cancel() 772 | that.client:cancel_request(request_id) 773 | reject_(LSP.ErrorCodes.RequestCancelled) 774 | end 775 | return task 776 | end 777 | 778 | ---@param params gindent.kit.LSP.InitializeParams 779 | function Client:initialize(params) 780 | local that, _, request_id, reject_ = self, nil, nil, nil 781 | local task = AsyncTask.new(function(resolve, reject) 782 | reject_ = reject 783 | _, request_id = self.client:request('initialize', params, function(err, res) 784 | if err then 785 | reject(err) 786 | else 787 | resolve(res) 788 | end 789 | end) 790 | end) 791 | function task.cancel() 792 | that.client:cancel_request(request_id) 793 | reject_(LSP.ErrorCodes.RequestCancelled) 794 | end 795 | return task 796 | end 797 | 798 | ---@param params nil 799 | function Client:shutdown(params) 800 | local that, _, request_id, reject_ = self, nil, nil, nil 801 | local task = AsyncTask.new(function(resolve, reject) 802 | reject_ = reject 803 | _, request_id = self.client:request('shutdown', params, function(err, res) 804 | if err then 805 | reject(err) 806 | else 807 | resolve(res) 808 | end 809 | end) 810 | end) 811 | function task.cancel() 812 | that.client:cancel_request(request_id) 813 | reject_(LSP.ErrorCodes.RequestCancelled) 814 | end 815 | return task 816 | end 817 | 818 | ---@param params gindent.kit.LSP.ShowMessageRequestParams 819 | function Client:window_showMessageRequest(params) 820 | local that, _, request_id, reject_ = self, nil, nil, nil 821 | local task = AsyncTask.new(function(resolve, reject) 822 | reject_ = reject 823 | _, request_id = self.client:request('window/showMessageRequest', params, function(err, res) 824 | if err then 825 | reject(err) 826 | else 827 | resolve(res) 828 | end 829 | end) 830 | end) 831 | function task.cancel() 832 | that.client:cancel_request(request_id) 833 | reject_(LSP.ErrorCodes.RequestCancelled) 834 | end 835 | return task 836 | end 837 | 838 | ---@param params gindent.kit.LSP.WillSaveTextDocumentParams 839 | function Client:textDocument_willSaveWaitUntil(params) 840 | local that, _, request_id, reject_ = self, nil, nil, nil 841 | local task = AsyncTask.new(function(resolve, reject) 842 | reject_ = reject 843 | _, request_id = self.client:request('textDocument/willSaveWaitUntil', params, function(err, res) 844 | if err then 845 | reject(err) 846 | else 847 | resolve(res) 848 | end 849 | end) 850 | end) 851 | function task.cancel() 852 | that.client:cancel_request(request_id) 853 | reject_(LSP.ErrorCodes.RequestCancelled) 854 | end 855 | return task 856 | end 857 | 858 | ---@param params gindent.kit.LSP.CompletionParams 859 | function Client:textDocument_completion(params) 860 | local that, _, request_id, reject_ = self, nil, nil, nil 861 | local task = AsyncTask.new(function(resolve, reject) 862 | reject_ = reject 863 | _, request_id = self.client:request('textDocument/completion', params, function(err, res) 864 | if err then 865 | reject(err) 866 | else 867 | resolve(res) 868 | end 869 | end) 870 | end) 871 | function task.cancel() 872 | that.client:cancel_request(request_id) 873 | reject_(LSP.ErrorCodes.RequestCancelled) 874 | end 875 | return task 876 | end 877 | 878 | ---@param params gindent.kit.LSP.CompletionItem 879 | function Client:completionItem_resolve(params) 880 | local that, _, request_id, reject_ = self, nil, nil, nil 881 | local task = AsyncTask.new(function(resolve, reject) 882 | reject_ = reject 883 | _, request_id = self.client:request('completionItem/resolve', params, function(err, res) 884 | if err then 885 | reject(err) 886 | else 887 | resolve(res) 888 | end 889 | end) 890 | end) 891 | function task.cancel() 892 | that.client:cancel_request(request_id) 893 | reject_(LSP.ErrorCodes.RequestCancelled) 894 | end 895 | return task 896 | end 897 | 898 | ---@param params gindent.kit.LSP.HoverParams 899 | function Client:textDocument_hover(params) 900 | local that, _, request_id, reject_ = self, nil, nil, nil 901 | local task = AsyncTask.new(function(resolve, reject) 902 | reject_ = reject 903 | _, request_id = self.client:request('textDocument/hover', params, function(err, res) 904 | if err then 905 | reject(err) 906 | else 907 | resolve(res) 908 | end 909 | end) 910 | end) 911 | function task.cancel() 912 | that.client:cancel_request(request_id) 913 | reject_(LSP.ErrorCodes.RequestCancelled) 914 | end 915 | return task 916 | end 917 | 918 | ---@param params gindent.kit.LSP.SignatureHelpParams 919 | function Client:textDocument_signatureHelp(params) 920 | local that, _, request_id, reject_ = self, nil, nil, nil 921 | local task = AsyncTask.new(function(resolve, reject) 922 | reject_ = reject 923 | _, request_id = self.client:request('textDocument/signatureHelp', params, function(err, res) 924 | if err then 925 | reject(err) 926 | else 927 | resolve(res) 928 | end 929 | end) 930 | end) 931 | function task.cancel() 932 | that.client:cancel_request(request_id) 933 | reject_(LSP.ErrorCodes.RequestCancelled) 934 | end 935 | return task 936 | end 937 | 938 | ---@param params gindent.kit.LSP.DefinitionParams 939 | function Client:textDocument_definition(params) 940 | local that, _, request_id, reject_ = self, nil, nil, nil 941 | local task = AsyncTask.new(function(resolve, reject) 942 | reject_ = reject 943 | _, request_id = self.client:request('textDocument/definition', params, function(err, res) 944 | if err then 945 | reject(err) 946 | else 947 | resolve(res) 948 | end 949 | end) 950 | end) 951 | function task.cancel() 952 | that.client:cancel_request(request_id) 953 | reject_(LSP.ErrorCodes.RequestCancelled) 954 | end 955 | return task 956 | end 957 | 958 | ---@param params gindent.kit.LSP.ReferenceParams 959 | function Client:textDocument_references(params) 960 | local that, _, request_id, reject_ = self, nil, nil, nil 961 | local task = AsyncTask.new(function(resolve, reject) 962 | reject_ = reject 963 | _, request_id = self.client:request('textDocument/references', params, function(err, res) 964 | if err then 965 | reject(err) 966 | else 967 | resolve(res) 968 | end 969 | end) 970 | end) 971 | function task.cancel() 972 | that.client:cancel_request(request_id) 973 | reject_(LSP.ErrorCodes.RequestCancelled) 974 | end 975 | return task 976 | end 977 | 978 | ---@param params gindent.kit.LSP.DocumentHighlightParams 979 | function Client:textDocument_documentHighlight(params) 980 | local that, _, request_id, reject_ = self, nil, nil, nil 981 | local task = AsyncTask.new(function(resolve, reject) 982 | reject_ = reject 983 | _, request_id = self.client:request('textDocument/documentHighlight', params, function(err, res) 984 | if err then 985 | reject(err) 986 | else 987 | resolve(res) 988 | end 989 | end) 990 | end) 991 | function task.cancel() 992 | that.client:cancel_request(request_id) 993 | reject_(LSP.ErrorCodes.RequestCancelled) 994 | end 995 | return task 996 | end 997 | 998 | ---@param params gindent.kit.LSP.DocumentSymbolParams 999 | function Client:textDocument_documentSymbol(params) 1000 | local that, _, request_id, reject_ = self, nil, nil, nil 1001 | local task = AsyncTask.new(function(resolve, reject) 1002 | reject_ = reject 1003 | _, request_id = self.client:request('textDocument/documentSymbol', params, function(err, res) 1004 | if err then 1005 | reject(err) 1006 | else 1007 | resolve(res) 1008 | end 1009 | end) 1010 | end) 1011 | function task.cancel() 1012 | that.client:cancel_request(request_id) 1013 | reject_(LSP.ErrorCodes.RequestCancelled) 1014 | end 1015 | return task 1016 | end 1017 | 1018 | ---@param params gindent.kit.LSP.CodeActionParams 1019 | function Client:textDocument_codeAction(params) 1020 | local that, _, request_id, reject_ = self, nil, nil, nil 1021 | local task = AsyncTask.new(function(resolve, reject) 1022 | reject_ = reject 1023 | _, request_id = self.client:request('textDocument/codeAction', params, function(err, res) 1024 | if err then 1025 | reject(err) 1026 | else 1027 | resolve(res) 1028 | end 1029 | end) 1030 | end) 1031 | function task.cancel() 1032 | that.client:cancel_request(request_id) 1033 | reject_(LSP.ErrorCodes.RequestCancelled) 1034 | end 1035 | return task 1036 | end 1037 | 1038 | ---@param params gindent.kit.LSP.CodeAction 1039 | function Client:codeAction_resolve(params) 1040 | local that, _, request_id, reject_ = self, nil, nil, nil 1041 | local task = AsyncTask.new(function(resolve, reject) 1042 | reject_ = reject 1043 | _, request_id = self.client:request('codeAction/resolve', params, function(err, res) 1044 | if err then 1045 | reject(err) 1046 | else 1047 | resolve(res) 1048 | end 1049 | end) 1050 | end) 1051 | function task.cancel() 1052 | that.client:cancel_request(request_id) 1053 | reject_(LSP.ErrorCodes.RequestCancelled) 1054 | end 1055 | return task 1056 | end 1057 | 1058 | ---@param params gindent.kit.LSP.WorkspaceSymbolParams 1059 | function Client:workspace_symbol(params) 1060 | local that, _, request_id, reject_ = self, nil, nil, nil 1061 | local task = AsyncTask.new(function(resolve, reject) 1062 | reject_ = reject 1063 | _, request_id = self.client:request('workspace/symbol', params, function(err, res) 1064 | if err then 1065 | reject(err) 1066 | else 1067 | resolve(res) 1068 | end 1069 | end) 1070 | end) 1071 | function task.cancel() 1072 | that.client:cancel_request(request_id) 1073 | reject_(LSP.ErrorCodes.RequestCancelled) 1074 | end 1075 | return task 1076 | end 1077 | 1078 | ---@param params gindent.kit.LSP.WorkspaceSymbol 1079 | function Client:workspaceSymbol_resolve(params) 1080 | local that, _, request_id, reject_ = self, nil, nil, nil 1081 | local task = AsyncTask.new(function(resolve, reject) 1082 | reject_ = reject 1083 | _, request_id = self.client:request('workspaceSymbol/resolve', params, function(err, res) 1084 | if err then 1085 | reject(err) 1086 | else 1087 | resolve(res) 1088 | end 1089 | end) 1090 | end) 1091 | function task.cancel() 1092 | that.client:cancel_request(request_id) 1093 | reject_(LSP.ErrorCodes.RequestCancelled) 1094 | end 1095 | return task 1096 | end 1097 | 1098 | ---@param params gindent.kit.LSP.CodeLensParams 1099 | function Client:textDocument_codeLens(params) 1100 | local that, _, request_id, reject_ = self, nil, nil, nil 1101 | local task = AsyncTask.new(function(resolve, reject) 1102 | reject_ = reject 1103 | _, request_id = self.client:request('textDocument/codeLens', params, function(err, res) 1104 | if err then 1105 | reject(err) 1106 | else 1107 | resolve(res) 1108 | end 1109 | end) 1110 | end) 1111 | function task.cancel() 1112 | that.client:cancel_request(request_id) 1113 | reject_(LSP.ErrorCodes.RequestCancelled) 1114 | end 1115 | return task 1116 | end 1117 | 1118 | ---@param params gindent.kit.LSP.CodeLens 1119 | function Client:codeLens_resolve(params) 1120 | local that, _, request_id, reject_ = self, nil, nil, nil 1121 | local task = AsyncTask.new(function(resolve, reject) 1122 | reject_ = reject 1123 | _, request_id = self.client:request('codeLens/resolve', params, function(err, res) 1124 | if err then 1125 | reject(err) 1126 | else 1127 | resolve(res) 1128 | end 1129 | end) 1130 | end) 1131 | function task.cancel() 1132 | that.client:cancel_request(request_id) 1133 | reject_(LSP.ErrorCodes.RequestCancelled) 1134 | end 1135 | return task 1136 | end 1137 | 1138 | ---@param params nil 1139 | function Client:workspace_codeLens_refresh(params) 1140 | local that, _, request_id, reject_ = self, nil, nil, nil 1141 | local task = AsyncTask.new(function(resolve, reject) 1142 | reject_ = reject 1143 | _, request_id = self.client:request('workspace/codeLens/refresh', params, function(err, res) 1144 | if err then 1145 | reject(err) 1146 | else 1147 | resolve(res) 1148 | end 1149 | end) 1150 | end) 1151 | function task.cancel() 1152 | that.client:cancel_request(request_id) 1153 | reject_(LSP.ErrorCodes.RequestCancelled) 1154 | end 1155 | return task 1156 | end 1157 | 1158 | ---@param params gindent.kit.LSP.DocumentLinkParams 1159 | function Client:textDocument_documentLink(params) 1160 | local that, _, request_id, reject_ = self, nil, nil, nil 1161 | local task = AsyncTask.new(function(resolve, reject) 1162 | reject_ = reject 1163 | _, request_id = self.client:request('textDocument/documentLink', params, function(err, res) 1164 | if err then 1165 | reject(err) 1166 | else 1167 | resolve(res) 1168 | end 1169 | end) 1170 | end) 1171 | function task.cancel() 1172 | that.client:cancel_request(request_id) 1173 | reject_(LSP.ErrorCodes.RequestCancelled) 1174 | end 1175 | return task 1176 | end 1177 | 1178 | ---@param params gindent.kit.LSP.DocumentLink 1179 | function Client:documentLink_resolve(params) 1180 | local that, _, request_id, reject_ = self, nil, nil, nil 1181 | local task = AsyncTask.new(function(resolve, reject) 1182 | reject_ = reject 1183 | _, request_id = self.client:request('documentLink/resolve', params, function(err, res) 1184 | if err then 1185 | reject(err) 1186 | else 1187 | resolve(res) 1188 | end 1189 | end) 1190 | end) 1191 | function task.cancel() 1192 | that.client:cancel_request(request_id) 1193 | reject_(LSP.ErrorCodes.RequestCancelled) 1194 | end 1195 | return task 1196 | end 1197 | 1198 | ---@param params gindent.kit.LSP.DocumentFormattingParams 1199 | function Client:textDocument_formatting(params) 1200 | local that, _, request_id, reject_ = self, nil, nil, nil 1201 | local task = AsyncTask.new(function(resolve, reject) 1202 | reject_ = reject 1203 | _, request_id = self.client:request('textDocument/formatting', params, function(err, res) 1204 | if err then 1205 | reject(err) 1206 | else 1207 | resolve(res) 1208 | end 1209 | end) 1210 | end) 1211 | function task.cancel() 1212 | that.client:cancel_request(request_id) 1213 | reject_(LSP.ErrorCodes.RequestCancelled) 1214 | end 1215 | return task 1216 | end 1217 | 1218 | ---@param params gindent.kit.LSP.DocumentRangeFormattingParams 1219 | function Client:textDocument_rangeFormatting(params) 1220 | local that, _, request_id, reject_ = self, nil, nil, nil 1221 | local task = AsyncTask.new(function(resolve, reject) 1222 | reject_ = reject 1223 | _, request_id = self.client:request('textDocument/rangeFormatting', params, function(err, res) 1224 | if err then 1225 | reject(err) 1226 | else 1227 | resolve(res) 1228 | end 1229 | end) 1230 | end) 1231 | function task.cancel() 1232 | that.client:cancel_request(request_id) 1233 | reject_(LSP.ErrorCodes.RequestCancelled) 1234 | end 1235 | return task 1236 | end 1237 | 1238 | ---@param params gindent.kit.LSP.DocumentRangesFormattingParams 1239 | function Client:textDocument_rangesFormatting(params) 1240 | local that, _, request_id, reject_ = self, nil, nil, nil 1241 | local task = AsyncTask.new(function(resolve, reject) 1242 | reject_ = reject 1243 | _, request_id = self.client:request('textDocument/rangesFormatting', params, function(err, res) 1244 | if err then 1245 | reject(err) 1246 | else 1247 | resolve(res) 1248 | end 1249 | end) 1250 | end) 1251 | function task.cancel() 1252 | that.client:cancel_request(request_id) 1253 | reject_(LSP.ErrorCodes.RequestCancelled) 1254 | end 1255 | return task 1256 | end 1257 | 1258 | ---@param params gindent.kit.LSP.DocumentOnTypeFormattingParams 1259 | function Client:textDocument_onTypeFormatting(params) 1260 | local that, _, request_id, reject_ = self, nil, nil, nil 1261 | local task = AsyncTask.new(function(resolve, reject) 1262 | reject_ = reject 1263 | _, request_id = self.client:request('textDocument/onTypeFormatting', params, function(err, res) 1264 | if err then 1265 | reject(err) 1266 | else 1267 | resolve(res) 1268 | end 1269 | end) 1270 | end) 1271 | function task.cancel() 1272 | that.client:cancel_request(request_id) 1273 | reject_(LSP.ErrorCodes.RequestCancelled) 1274 | end 1275 | return task 1276 | end 1277 | 1278 | ---@param params gindent.kit.LSP.RenameParams 1279 | function Client:textDocument_rename(params) 1280 | local that, _, request_id, reject_ = self, nil, nil, nil 1281 | local task = AsyncTask.new(function(resolve, reject) 1282 | reject_ = reject 1283 | _, request_id = self.client:request('textDocument/rename', params, function(err, res) 1284 | if err then 1285 | reject(err) 1286 | else 1287 | resolve(res) 1288 | end 1289 | end) 1290 | end) 1291 | function task.cancel() 1292 | that.client:cancel_request(request_id) 1293 | reject_(LSP.ErrorCodes.RequestCancelled) 1294 | end 1295 | return task 1296 | end 1297 | 1298 | ---@param params gindent.kit.LSP.PrepareRenameParams 1299 | function Client:textDocument_prepareRename(params) 1300 | local that, _, request_id, reject_ = self, nil, nil, nil 1301 | local task = AsyncTask.new(function(resolve, reject) 1302 | reject_ = reject 1303 | _, request_id = self.client:request('textDocument/prepareRename', params, function(err, res) 1304 | if err then 1305 | reject(err) 1306 | else 1307 | resolve(res) 1308 | end 1309 | end) 1310 | end) 1311 | function task.cancel() 1312 | that.client:cancel_request(request_id) 1313 | reject_(LSP.ErrorCodes.RequestCancelled) 1314 | end 1315 | return task 1316 | end 1317 | 1318 | ---@param params gindent.kit.LSP.ExecuteCommandParams 1319 | function Client:workspace_executeCommand(params) 1320 | local that, _, request_id, reject_ = self, nil, nil, nil 1321 | local task = AsyncTask.new(function(resolve, reject) 1322 | reject_ = reject 1323 | _, request_id = self.client:request('workspace/executeCommand', params, function(err, res) 1324 | if err then 1325 | reject(err) 1326 | else 1327 | resolve(res) 1328 | end 1329 | end) 1330 | end) 1331 | function task.cancel() 1332 | that.client:cancel_request(request_id) 1333 | reject_(LSP.ErrorCodes.RequestCancelled) 1334 | end 1335 | return task 1336 | end 1337 | 1338 | ---@param params gindent.kit.LSP.ApplyWorkspaceEditParams 1339 | function Client:workspace_applyEdit(params) 1340 | local that, _, request_id, reject_ = self, nil, nil, nil 1341 | local task = AsyncTask.new(function(resolve, reject) 1342 | reject_ = reject 1343 | _, request_id = self.client:request('workspace/applyEdit', params, function(err, res) 1344 | if err then 1345 | reject(err) 1346 | else 1347 | resolve(res) 1348 | end 1349 | end) 1350 | end) 1351 | function task.cancel() 1352 | that.client:cancel_request(request_id) 1353 | reject_(LSP.ErrorCodes.RequestCancelled) 1354 | end 1355 | return task 1356 | end 1357 | 1358 | return Client 1359 | -------------------------------------------------------------------------------- /lua/gindent/kit/LSP/DocumentSelector.lua: -------------------------------------------------------------------------------- 1 | local LanguageId = require('gindent.kit.LSP.LanguageId') 2 | 3 | -- NOTE 4 | --@alias gindent.kit.LSP.DocumentSelector gindent.kit.LSP.DocumentFilter[] 5 | --@alias gindent.kit.LSP.DocumentFilter (gindent.kit.LSP.TextDocumentFilter | gindent.kit.LSP.NotebookCellTextDocumentFilter) 6 | --@alias gindent.kit.LSP.TextDocumentFilter ({ language: string, scheme?: string, pattern?: string } | { language?: string, scheme: string, pattern?: string } | { language?: string, scheme?: string, pattern: string }) 7 | --@class gindent.kit.LSP.NotebookCellTextDocumentFilter 8 | --@field public notebook (string | gindent.kit.LSP.NotebookDocumentFilter) A filter that matches against the notebook
containing the notebook cell. If a string
value is provided it matches against the
notebook type. '*' matches every notebook. 9 | --@field public language? string A language id like `python`.

Will be matched against the language id of the
notebook cell document. '*' matches every language. 10 | --@alias gindent.kit.LSP.NotebookDocumentFilter ({ notebookType: string, scheme?: string, pattern?: string } | { notebookType?: string, scheme: string, pattern?: string } | { notebookType?: string, scheme?: string, pattern: string }) 11 | 12 | ---@alias gindent.kit.LSP.DocumentSelector.NormalizedFilter { notebook_type: string?, scheme: string?, pattern: string, language: string? } 13 | 14 | ---Normalize the filter. 15 | ---@param document_filter gindent.kit.LSP.DocumentFilter 16 | ---@return gindent.kit.LSP.DocumentSelector.NormalizedFilter | nil 17 | local function normalize_filter(document_filter) 18 | if document_filter.notebook then 19 | local filter = document_filter --[[@as gindent.kit.LSP.NotebookCellTextDocumentFilter]] 20 | if type(filter.notebook) == 'string' then 21 | return { 22 | notebook_type = nil, 23 | scheme = nil, 24 | pattern = filter.notebook, 25 | language = filter.language, 26 | } 27 | elseif filter.notebook then 28 | return { 29 | notebook_type = filter.notebook.notebookType, 30 | scheme = filter.notebook.scheme, 31 | pattern = filter.notebook.pattern, 32 | language = filter.language, 33 | } 34 | end 35 | else 36 | local filter = document_filter --[[@as gindent.kit.LSP.TextDocumentFilter]] 37 | return { 38 | notebook_type = nil, 39 | scheme = filter.scheme, 40 | pattern = filter.pattern, 41 | language = filter.language, 42 | } 43 | end 44 | end 45 | 46 | ---Return the document filter score. 47 | ---TODO: file-related buffer check is not implemented... 48 | ---TODO: notebook related function is not implemented... 49 | ---@param filter? gindent.kit.LSP.DocumentSelector.NormalizedFilter 50 | ---@param uri string 51 | ---@param language string 52 | ---@return integer 53 | local function score(filter, uri, language) 54 | if not filter then 55 | return 0 56 | end 57 | 58 | local s = 0 59 | 60 | if filter.scheme then 61 | if filter.scheme == '*' then 62 | s = 5 63 | elseif filter.scheme == uri:sub(1, #filter.scheme) then 64 | s = 10 65 | else 66 | return 0 67 | end 68 | end 69 | 70 | if filter.language then 71 | if filter.language == '*' then 72 | s = math.max(s, 5) 73 | elseif filter.language == language then 74 | s = 10 75 | else 76 | return 0 77 | end 78 | end 79 | 80 | if filter.pattern then 81 | if vim.glob.to_lpeg(filter.pattern):match(uri) ~= nil then 82 | s = 10 83 | else 84 | return 0 85 | end 86 | end 87 | 88 | return s 89 | end 90 | 91 | local DocumentSelector = {} 92 | 93 | ---Check buffer matches the selector. 94 | ---@see https://github.com/microsoft/vscode/blob/7241eea61021db926c052b657d577ef0d98f7dc7/src/vs/editor/common/languageSelector.ts#L29 95 | ---@param bufnr integer 96 | ---@param document_selector gindent.kit.LSP.DocumentSelector 97 | function DocumentSelector.score(bufnr, document_selector) 98 | local uri = vim.uri_from_bufnr(bufnr) 99 | local language = LanguageId.from_filetype(vim.api.nvim_buf_get_option(bufnr, 'filetype')) 100 | local r = 0 101 | for _, document_filter in ipairs(document_selector) do 102 | local filter = normalize_filter(document_filter) 103 | if filter then 104 | local s = score(filter, uri, language) 105 | if s == 10 then 106 | return 10 107 | end 108 | r = math.max(r, s) 109 | end 110 | end 111 | return r 112 | end 113 | 114 | return DocumentSelector 115 | -------------------------------------------------------------------------------- /lua/gindent/kit/LSP/LanguageId.lua: -------------------------------------------------------------------------------- 1 | local mapping = { 2 | ['sh'] = 'shellscript', 3 | ['javascript.tsx'] = 'javascriptreact', 4 | ['typescript.tsx'] = 'typescriptreact', 5 | } 6 | 7 | local LanguageId = {} 8 | 9 | function LanguageId.from_filetype(filetype) 10 | return mapping[filetype] or filetype 11 | end 12 | 13 | return LanguageId 14 | -------------------------------------------------------------------------------- /lua/gindent/kit/LSP/Position.lua: -------------------------------------------------------------------------------- 1 | local LSP = require('gindent.kit.LSP') 2 | 3 | local Position = {} 4 | 5 | ---Return the value is position or not. 6 | ---@param v any 7 | ---@return boolean 8 | function Position.is(v) 9 | local is = true 10 | is = is and (type(v) == 'table' and type(v.line) == 'number' and type(v.character) == 'number') 11 | return is 12 | end 13 | 14 | ---Create a cursor position. 15 | ---@param encoding? gindent.kit.LSP.PositionEncodingKind 16 | function Position.cursor(encoding) 17 | local r, c = unpack(vim.api.nvim_win_get_cursor(0)) 18 | local utf8 = { line = r - 1, character = c } 19 | if encoding == LSP.PositionEncodingKind.UTF8 then 20 | return utf8 21 | else 22 | local text = vim.api.nvim_get_current_line() 23 | if encoding == LSP.PositionEncodingKind.UTF32 then 24 | return Position.to(text, utf8, LSP.PositionEncodingKind.UTF8, LSP.PositionEncodingKind.UTF32) 25 | end 26 | return Position.to(text, utf8, LSP.PositionEncodingKind.UTF8, LSP.PositionEncodingKind.UTF16) 27 | end 28 | end 29 | 30 | ---Convert position to buffer position from specified encoding. 31 | ---@param bufnr integer 32 | ---@param position gindent.kit.LSP.Position 33 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 34 | function Position.to_buf(bufnr, position, from_encoding) 35 | from_encoding = from_encoding or LSP.PositionEncodingKind.UTF16 36 | local text = vim.api.nvim_buf_get_lines(bufnr, position.line, position.line + 1, false)[1] or '' 37 | return Position.to(text, position, from_encoding, LSP.PositionEncodingKind.UTF8) 38 | end 39 | 40 | ---Convert position to utf8 from specified encoding. 41 | ---@param text string 42 | ---@param position gindent.kit.LSP.Position 43 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 44 | ---@return gindent.kit.LSP.Position 45 | function Position.to_utf8(text, position, from_encoding) 46 | from_encoding = from_encoding or LSP.PositionEncodingKind.UTF16 47 | if from_encoding == LSP.PositionEncodingKind.UTF8 then 48 | return position 49 | end 50 | local ok, byteindex = pcall(vim.str_byteindex, text, position.character, from_encoding == LSP.PositionEncodingKind.UTF16) 51 | if ok then 52 | position = { line = position.line, character = byteindex } 53 | end 54 | return position 55 | end 56 | 57 | ---Convert position to utf16 from specified encoding. 58 | ---@param text string 59 | ---@param position gindent.kit.LSP.Position 60 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 61 | ---@return gindent.kit.LSP.Position 62 | function Position.to_utf16(text, position, from_encoding) 63 | local utf8 = Position.to_utf8(text, position, from_encoding) 64 | for index = utf8.character, 0, -1 do 65 | local ok, _, utf16index = pcall(vim.str_utfindex, text, index) 66 | if ok then 67 | position = { line = utf8.line, character = utf16index } 68 | break 69 | end 70 | end 71 | return position 72 | end 73 | 74 | ---Convert position to utf32 from specified encoding. 75 | ---@param text string 76 | ---@param position gindent.kit.LSP.Position 77 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 78 | ---@return gindent.kit.LSP.Position 79 | function Position.to_utf32(text, position, from_encoding) 80 | local utf8 = Position.to_utf8(text, position, from_encoding) 81 | for index = utf8.character, 0, -1 do 82 | local ok, utf32index = pcall(vim.str_utfindex, text, index) 83 | if ok then 84 | position = { line = utf8.line, character = utf32index } 85 | break 86 | end 87 | end 88 | return position 89 | end 90 | 91 | ---Convert position to specified encoding from specified encoding. 92 | ---@param text string 93 | ---@param position gindent.kit.LSP.Position 94 | ---@param from_encoding gindent.kit.LSP.PositionEncodingKind 95 | ---@param to_encoding gindent.kit.LSP.PositionEncodingKind 96 | function Position.to(text, position, from_encoding, to_encoding) 97 | if to_encoding == LSP.PositionEncodingKind.UTF8 then 98 | return Position.to_utf8(text, position, from_encoding) 99 | elseif to_encoding == LSP.PositionEncodingKind.UTF16 then 100 | return Position.to_utf16(text, position, from_encoding) 101 | elseif to_encoding == LSP.PositionEncodingKind.UTF32 then 102 | return Position.to_utf32(text, position, from_encoding) 103 | end 104 | error('LSP.Position: Unsupported encoding: ' .. to_encoding) 105 | end 106 | 107 | return Position 108 | -------------------------------------------------------------------------------- /lua/gindent/kit/LSP/Range.lua: -------------------------------------------------------------------------------- 1 | local Position = require('gindent.kit.LSP.Position') 2 | 3 | local Range = {} 4 | 5 | ---Return the value is range or not. 6 | ---@param v any 7 | ---@return boolean 8 | function Range.is(v) 9 | return type(v) == 'table' and Position.is(v.start) and Position.is(v['end']) 10 | end 11 | 12 | ---Return the range is empty or not. 13 | ---@param range gindent.kit.LSP.Range 14 | ---@return boolean 15 | function Range.empty(range) 16 | return range.start.line == range['end'].line and range.start.character == range['end'].character 17 | end 18 | 19 | ---Return the range is empty or not. 20 | ---@param range gindent.kit.LSP.Range 21 | ---@return boolean 22 | function Range.contains(range) 23 | return range.start.line == range['end'].line and range.start.character == range['end'].character 24 | end 25 | 26 | ---Convert range to buffer range from specified encoding. 27 | ---@param bufnr integer 28 | ---@param range gindent.kit.LSP.Range 29 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 30 | ---@return gindent.kit.LSP.Range 31 | function Range.to_buf(bufnr, range, from_encoding) 32 | return { 33 | start = Position.to_buf(bufnr, range.start, from_encoding), 34 | ['end'] = Position.to_buf(bufnr, range['end'], from_encoding), 35 | } 36 | end 37 | 38 | ---Convert range to utf8 from specified encoding. 39 | ---@param text_start string 40 | ---@param range gindent.kit.LSP.Range 41 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 42 | ---@return gindent.kit.LSP.Range 43 | function Range.to_utf8(text_start, text_end, range, from_encoding) 44 | return { 45 | start = Position.to_utf8(text_start, range.start, from_encoding), 46 | ['end'] = Position.to_utf8(text_end, range['end'], from_encoding), 47 | } 48 | end 49 | 50 | ---Convert range to utf16 from specified encoding. 51 | ---@param text_start string 52 | ---@param range gindent.kit.LSP.Range 53 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 54 | ---@return gindent.kit.LSP.Range 55 | function Range.to_utf16(text_start, text_end, range, from_encoding) 56 | return { 57 | start = Position.to_utf16(text_start, range.start, from_encoding), 58 | ['end'] = Position.to_utf16(text_end, range['end'], from_encoding), 59 | } 60 | end 61 | 62 | ---Convert range to utf32 from specified encoding. 63 | ---@param text_start string 64 | ---@param range gindent.kit.LSP.Range 65 | ---@param from_encoding? gindent.kit.LSP.PositionEncodingKind 66 | ---@return gindent.kit.LSP.Range 67 | function Range.to_utf32(text_start, text_end, range, from_encoding) 68 | return { 69 | start = Position.to_utf32(text_start, range.start, from_encoding), 70 | ['end'] = Position.to_utf32(text_end, range['end'], from_encoding), 71 | } 72 | end 73 | 74 | return Range 75 | -------------------------------------------------------------------------------- /lua/gindent/kit/Spec/init.lua: -------------------------------------------------------------------------------- 1 | local kit = require('gindent.kit') 2 | local assert = require('luassert') 3 | 4 | ---@class gindent.Spec.SetupOption 5 | ---@field filetype? string 6 | ---@field noexpandtab? boolean 7 | ---@field shiftwidth? integer 8 | ---@field tabstop? integer 9 | 10 | ---@param buffer string|string[] 11 | local function parse_buffer(buffer) 12 | buffer = kit.to_array(buffer) 13 | 14 | for i, line in ipairs(buffer) do 15 | local s = line:find('|', 1, true) 16 | if s then 17 | buffer[i] = line:gsub('|', '') 18 | return buffer, { i, s - 1 } 19 | end 20 | end 21 | error('cursor position is not found.') 22 | end 23 | 24 | local Spec = {} 25 | 26 | ---Setup buffer. 27 | ---@param buffer string|string[] 28 | ---@param option? gindent.Spec.SetupOption 29 | function Spec.setup(buffer, option) 30 | option = option or {} 31 | 32 | vim.cmd.enew({ bang = true }) 33 | vim.cmd([[ set noswapfile ]]) 34 | vim.cmd([[ set virtualedit=onemore ]]) 35 | vim.cmd(([[ set shiftwidth=%s ]]):format(option.shiftwidth or 2)) 36 | vim.cmd(([[ set tabstop=%s ]]):format(option.tabstop or 2)) 37 | if option.noexpandtab then 38 | vim.cmd([[ set noexpandtab ]]) 39 | else 40 | vim.cmd([[ set expandtab ]]) 41 | end 42 | if option.filetype then 43 | vim.cmd(([[ set filetype=%s ]]):format(option.filetype)) 44 | end 45 | 46 | local lines, cursor = parse_buffer(buffer) 47 | vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) 48 | vim.api.nvim_win_set_cursor(0, cursor) 49 | end 50 | 51 | ---Expect buffer. 52 | function Spec.expect(buffer) 53 | local lines, cursor = parse_buffer(buffer) 54 | assert.are.same(lines, vim.api.nvim_buf_get_lines(0, 0, -1, false)) 55 | assert.are.same(cursor, vim.api.nvim_win_get_cursor(0)) 56 | end 57 | 58 | return Spec 59 | -------------------------------------------------------------------------------- /lua/gindent/kit/System/init.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: ignore 212 2 | 3 | local kit = require('gindent.kit') 4 | local Async = require('gindent.kit.Async') 5 | 6 | local System = {} 7 | 8 | ---@class gindent.kit.System.Buffer 9 | ---@field write fun(data: string) 10 | ---@field close fun() 11 | 12 | ---@class gindent.kit.System.Buffering 13 | ---@field create fun(self: any, callback: fun(data: string)): gindent.kit.System.Buffer 14 | 15 | ---@class gindent.kit.System.LineBuffering: gindent.kit.System.Buffering 16 | ---@field ignore_empty boolean 17 | System.LineBuffering = {} 18 | System.LineBuffering.__index = System.LineBuffering 19 | 20 | ---Create LineBuffering. 21 | ---@param option { ignore_empty?: boolean } 22 | function System.LineBuffering.new(option) 23 | return setmetatable({ 24 | ignore_empty = option.ignore_empty or false, 25 | }, System.LineBuffering) 26 | end 27 | 28 | ---Create LineBuffer object. 29 | function System.LineBuffering:create(callback) 30 | local buffer = {} 31 | return { 32 | write = function(data) 33 | data = (data:gsub('\r\n?', '\n')) 34 | table.insert(buffer, data) 35 | 36 | local has = false 37 | for i = #data, 1, -1 do 38 | if data:sub(i, i) == '\n' then 39 | has = true 40 | break 41 | end 42 | end 43 | 44 | if has then 45 | local texts = vim.split(table.concat(buffer, ''), '\n') 46 | buffer = texts[#texts] ~= '' and { table.remove(texts) } or {} 47 | for _, text in ipairs(texts) do 48 | if self.ignore_empty then 49 | if not text:match('^%s*$') then 50 | callback(text) 51 | end 52 | else 53 | callback(text) 54 | end 55 | end 56 | end 57 | end, 58 | close = function() 59 | if #buffer > 0 then 60 | callback(table.concat(buffer, '')) 61 | end 62 | end, 63 | } 64 | end 65 | 66 | ---@class gindent.kit.System.DelimiterBuffering: gindent.kit.System.Buffering 67 | ---@field delimiter string 68 | System.DelimiterBuffering = {} 69 | System.DelimiterBuffering.__index = System.DelimiterBuffering 70 | 71 | ---Create Buffering. 72 | ---@param option { delimiter: string } 73 | function System.DelimiterBuffering.new(option) 74 | return setmetatable({ 75 | delimiter = option.delimiter, 76 | }, System.DelimiterBuffering) 77 | end 78 | 79 | ---Create Delimiter object. 80 | function System.DelimiterBuffering:create(callback) 81 | local state = { 82 | buffer = {}, 83 | buffer_pos = 1, 84 | delimiter_pos = 1, 85 | match_pos = nil --[[@as integer?]], 86 | } 87 | 88 | local function len() 89 | local l = 0 90 | for i = 1, #state.buffer do 91 | l = l + #state.buffer[i] 92 | end 93 | return l 94 | end 95 | 96 | local function split(s, e) 97 | local before = {} 98 | local after = {} 99 | local off = 0 100 | for i = 1, #state.buffer do 101 | local l = #state.buffer[i] 102 | local sep_s = s - off 103 | local sep_e = e - off 104 | local buf_s = 1 105 | local buf_e = l 106 | 107 | if buf_e < sep_s then 108 | table.insert(before, state.buffer[i]) 109 | elseif sep_e < buf_s then 110 | table.insert(after, state.buffer[i]) 111 | else 112 | if buf_s < sep_s then 113 | table.insert(before, state.buffer[i]:sub(buf_s, sep_s - 1)) 114 | end 115 | if sep_e < buf_e then 116 | table.insert(after, state.buffer[i]:sub(sep_e + 1, buf_e)) 117 | end 118 | end 119 | 120 | off = off + l 121 | end 122 | return before, after 123 | end 124 | 125 | local function get(at) 126 | local off = 0 127 | for i = 1, #state.buffer do 128 | local l = #state.buffer[i] 129 | if at <= off + l then 130 | local idx = at - off 131 | return state.buffer[i]:sub(idx, idx) 132 | end 133 | off = off + l 134 | end 135 | return nil 136 | end 137 | 138 | local buffer_len = 0 139 | local delimiter_len = #self.delimiter 140 | local buffer 141 | buffer = { 142 | write = function(data) 143 | table.insert(state.buffer, data) 144 | buffer_len = len() 145 | 146 | while state.buffer_pos <= buffer_len do 147 | local b = get(state.buffer_pos) 148 | local d = self.delimiter:sub(state.delimiter_pos, state.delimiter_pos) 149 | if b == d then 150 | if state.delimiter_pos == delimiter_len then 151 | local before, after = split(state.match_pos, state.buffer_pos) 152 | callback(table.concat(before, '')) 153 | state.buffer = after 154 | state.buffer_pos = 1 155 | state.delimiter_pos = 1 156 | state.match_pos = nil 157 | buffer_len = len() 158 | else 159 | if state.delimiter_pos == 1 then 160 | state.match_pos = state.buffer_pos 161 | end 162 | state.buffer_pos = state.buffer_pos + 1 163 | state.delimiter_pos = state.delimiter_pos + 1 164 | end 165 | else 166 | state.buffer_pos = state.match_pos and state.match_pos + 1 or state.buffer_pos + 1 167 | state.delimiter_pos = 1 168 | state.match_pos = nil 169 | end 170 | end 171 | end, 172 | close = function() 173 | if #state.buffer > 0 then 174 | callback(table.concat(state.buffer, '')) 175 | end 176 | end, 177 | } 178 | return buffer 179 | end 180 | 181 | ---@class gindent.kit.System.PatternBuffering: gindent.kit.System.Buffering 182 | ---@field pattern string 183 | System.PatternBuffering = {} 184 | System.PatternBuffering.__index = System.PatternBuffering 185 | 186 | ---Create PatternBuffering. 187 | ---@param option { pattern: string } 188 | function System.PatternBuffering.new(option) 189 | return setmetatable({ 190 | pattern = option.pattern, 191 | }, System.PatternBuffering) 192 | end 193 | 194 | ---Create PatternBuffer object. 195 | function System.PatternBuffering:create(callback) 196 | local buffer = {} 197 | return { 198 | write = function(data) 199 | table.insert(buffer, data) 200 | while true do 201 | local text = table.concat(buffer, '') 202 | local s, e = text:find(self.pattern, 1, true) 203 | if s and e then 204 | callback(text:sub(1, s - 1)) 205 | if e < #text then 206 | buffer = { text:sub(e + 1) } 207 | else 208 | buffer = {} 209 | end 210 | else 211 | break 212 | end 213 | end 214 | end, 215 | close = function() 216 | if #buffer > 0 then 217 | callback(table.concat(buffer, '')) 218 | end 219 | end, 220 | } 221 | end 222 | 223 | ---@class gindent.kit.System.RawBuffering: gindent.kit.System.Buffering 224 | System.RawBuffering = {} 225 | System.RawBuffering.__index = System.RawBuffering 226 | 227 | ---Create RawBuffering. 228 | function System.RawBuffering.new() 229 | return setmetatable({}, System.RawBuffering) 230 | end 231 | 232 | ---Create RawBuffer object. 233 | function System.RawBuffering:create(callback) 234 | return { 235 | write = function(data) 236 | callback(data) 237 | end, 238 | close = function() 239 | -- noop. 240 | end, 241 | } 242 | end 243 | 244 | ---Spawn a new process. 245 | ---@class gindent.kit.System.SpawnParams 246 | ---@field cwd string 247 | ---@field env? table 248 | ---@field input? string|string[] 249 | ---@field on_stdout? fun(data: string) 250 | ---@field on_stderr? fun(data: string) 251 | ---@field on_exit? fun(code: integer, signal: integer) 252 | ---@field buffering? gindent.kit.System.Buffering 253 | ---@param command string[] 254 | ---@param params gindent.kit.System.SpawnParams 255 | ---@return fun(signal?: integer) 256 | function System.spawn(command, params) 257 | command = vim 258 | .iter(command) 259 | :filter(function(c) 260 | return c ~= nil 261 | end) 262 | :totable() 263 | 264 | local cmd = command[1] 265 | local args = {} 266 | for i = 2, #command do 267 | table.insert(args, command[i]) 268 | end 269 | 270 | local env = params.env 271 | if not env then 272 | env = vim.fn.environ() 273 | env.NVIM = vim.v.servername 274 | env.NVIM_LISTEN_ADDRESS = nil 275 | end 276 | 277 | local env_pairs = {} 278 | for k, v in pairs(env) do 279 | table.insert(env_pairs, string.format('%s=%s', k, tostring(v))) 280 | end 281 | 282 | local buffering = params.buffering or System.RawBuffering.new() 283 | local stdout_buffer = buffering:create(function(text) 284 | if params.on_stdout then 285 | params.on_stdout(text) 286 | end 287 | end) 288 | local stderr_buffer = buffering:create(function(text) 289 | if params.on_stderr then 290 | params.on_stderr(text) 291 | end 292 | end) 293 | 294 | local close --[[@type fun(signal?: integer): gindent.kit.Async.AsyncTask]] 295 | local stdin = params.input and assert(vim.uv.new_pipe()) 296 | local stdout = assert(vim.uv.new_pipe()) 297 | local stderr = assert(vim.uv.new_pipe()) 298 | local process = vim.uv.spawn(vim.fn.exepath(cmd), { 299 | cwd = vim.fs.normalize(params.cwd), 300 | env = env_pairs, 301 | hide = true, 302 | args = args, 303 | stdio = { stdin, stdout, stderr }, 304 | detached = false, 305 | verbatim = false, 306 | } --[[@as any]], function(code, signal) 307 | stdout_buffer.close() 308 | stderr_buffer.close() 309 | close():next(function() 310 | if params.on_exit then 311 | params.on_exit(code, signal) 312 | end 313 | end) 314 | end) 315 | stdout:read_start(function(err, data) 316 | if err then 317 | error(err) 318 | end 319 | if data then 320 | stdout_buffer.write(data) 321 | end 322 | end) 323 | stderr:read_start(function(err, data) 324 | if err then 325 | error(err) 326 | end 327 | if data then 328 | stderr_buffer.write(data) 329 | end 330 | end) 331 | 332 | local stdin_closing = Async.new(function(resolve) 333 | if stdin then 334 | for _, input in ipairs(kit.to_array(params.input)) do 335 | stdin:write(input) 336 | end 337 | stdin:shutdown(function() 338 | stdin:close(resolve) 339 | end) 340 | else 341 | resolve() 342 | end 343 | end) 344 | 345 | close = function(signal) 346 | local closing = { stdin_closing } 347 | table.insert( 348 | closing, 349 | Async.new(function(resolve) 350 | if not stdout:is_closing() then 351 | stdout:close(resolve) 352 | else 353 | resolve() 354 | end 355 | end) 356 | ) 357 | table.insert( 358 | closing, 359 | Async.new(function(resolve) 360 | if not stderr:is_closing() then 361 | stderr:close(resolve) 362 | else 363 | resolve() 364 | end 365 | end) 366 | ) 367 | table.insert( 368 | closing, 369 | Async.new(function(resolve) 370 | if signal and process:is_active() then 371 | process:kill(signal) 372 | end 373 | if process and not process:is_closing() then 374 | process:close(resolve) 375 | else 376 | resolve() 377 | end 378 | end) 379 | ) 380 | 381 | local closing_task = Async.resolve() 382 | for _, task in ipairs(closing) do 383 | closing_task = closing_task:next(function() 384 | return task 385 | end) 386 | end 387 | return closing_task 388 | end 389 | 390 | return function(signal) 391 | close(signal) 392 | end 393 | end 394 | 395 | return System 396 | -------------------------------------------------------------------------------- /lua/gindent/kit/Vim/FloatingWindow.lua: -------------------------------------------------------------------------------- 1 | local kit = require('gindent.kit') 2 | 3 | ---@alias gindent.kit.Vim.FloatingWindow.WindowKind 'main' | 'scrollbar_track' | 'scrollbar_thumb' 4 | 5 | ---@class gindent.kit.Vim.FloatingWindow.BorderSize 6 | ---@field public top integer 7 | ---@field public left integer 8 | ---@field public right integer 9 | ---@field public bottom integer 10 | ---@field public h integer 11 | ---@field public v integer 12 | 13 | ---@class gindent.kit.Vim.FloatingWindow.ContentSize 14 | ---@field public width integer 15 | ---@field public height integer 16 | 17 | ---@class gindent.kit.Vim.FloatingWindow.WindowConfig 18 | ---@field public row integer 0-indexed utf-8 19 | ---@field public col integer 0-indexed utf-8 20 | ---@field public width integer 21 | ---@field public height integer 22 | ---@field public border? string | string[] 23 | ---@field public anchor? "NW" | "NE" | "SW" | "SE" 24 | ---@field public style? string 25 | ---@field public zindex? integer 26 | 27 | ---@class gindent.kit.Vim.FloatingWindow.Viewport 28 | ---@field public row integer 29 | ---@field public col integer 30 | ---@field public inner_width integer window inner width 31 | ---@field public inner_height integer window inner height 32 | ---@field public outer_width integer window outer width that includes border and scrollbar width 33 | ---@field public outer_height integer window outer height that includes border width 34 | ---@field public border_size gindent.kit.Vim.FloatingWindow.BorderSize 35 | ---@field public content_size gindent.kit.Vim.FloatingWindow.ContentSize 36 | ---@field public scrollbar boolean 37 | ---@field public ui_width integer 38 | ---@field public ui_height integer 39 | ---@field public border string | string[] | nil 40 | ---@field public zindex integer 41 | 42 | ---@class gindent.kit.Vim.FloatingWindow.Config 43 | ---@field public markdown? boolean 44 | 45 | ---@class gindent.kit.Vim.FloatingWindow 46 | ---@field private _augroup string 47 | ---@field private _config gindent.kit.Vim.FloatingWindow.Config 48 | ---@field private _buf_option table 49 | ---@field private _win_option table 50 | ---@field private _buf integer 51 | ---@field private _scrollbar_track_buf integer 52 | ---@field private _scrollbar_thumb_buf integer 53 | ---@field private _win? integer 54 | ---@field private _scrollbar_track_win? integer 55 | ---@field private _scrollbar_thumb_win? integer 56 | local FloatingWindow = {} 57 | FloatingWindow.__index = FloatingWindow 58 | 59 | ---Returns true if the window is visible 60 | ---@param win? integer 61 | ---@return boolean 62 | local function is_visible(win) 63 | if not win then 64 | return false 65 | end 66 | if not vim.api.nvim_win_is_valid(win) then 67 | return false 68 | end 69 | return true 70 | end 71 | 72 | ---Show the window 73 | ---@param win? integer 74 | ---@param buf integer 75 | ---@param win_config gindent.kit.Vim.FloatingWindow.WindowConfig 76 | ---@return integer 77 | local function show_or_move(win, buf, win_config) 78 | local border_size = FloatingWindow.get_border_size(win_config.border) 79 | if win_config.anchor == 'NE' then 80 | win_config.col = win_config.col - win_config.width - border_size.right - border_size.left 81 | elseif win_config.anchor == 'SW' then 82 | win_config.row = win_config.row - win_config.height - border_size.top - border_size.bottom 83 | elseif win_config.anchor == 'SE' then 84 | win_config.row = win_config.row - win_config.height - border_size.top - border_size.bottom 85 | win_config.col = win_config.col - win_config.width - border_size.right - border_size.left 86 | end 87 | win_config.anchor = 'NW' 88 | 89 | if is_visible(win) then 90 | vim.api.nvim_win_set_config(win --[=[@as integer]=], { 91 | relative = 'editor', 92 | row = win_config.row, 93 | col = win_config.col, 94 | width = win_config.width, 95 | height = win_config.height, 96 | anchor = 'NW', 97 | style = win_config.style, 98 | border = win_config.border, 99 | zindex = win_config.zindex, 100 | }) 101 | return win --[=[@as integer]=] 102 | else 103 | return vim.api.nvim_open_win(buf, false, { 104 | noautocmd = true, 105 | relative = 'editor', 106 | row = win_config.row, 107 | col = win_config.col, 108 | width = win_config.width, 109 | height = win_config.height, 110 | anchor = 'NW', 111 | style = win_config.style, 112 | border = win_config.border, 113 | zindex = win_config.zindex, 114 | }) 115 | end 116 | end 117 | 118 | ---Hide the window 119 | ---@param win integer 120 | local function hide(win) 121 | if is_visible(win) then 122 | vim.api.nvim_win_hide(win) 123 | end 124 | end 125 | 126 | ---Get border size. 127 | ---@param border nil | string | string[] 128 | ---@return gindent.kit.Vim.FloatingWindow.BorderSize 129 | function FloatingWindow.get_border_size(border) 130 | local maybe_border_size = (function() 131 | if not border then 132 | return { top = 0, right = 0, bottom = 0, left = 0 } 133 | end 134 | if type(border) == 'string' then 135 | if border == 'none' then 136 | return { top = 0, right = 0, bottom = 0, left = 0 } 137 | elseif border == 'single' then 138 | return { top = 1, right = 1, bottom = 1, left = 1 } 139 | elseif border == 'double' then 140 | return { top = 2, right = 2, bottom = 2, left = 2 } 141 | elseif border == 'rounded' then 142 | return { top = 1, right = 1, bottom = 1, left = 1 } 143 | elseif border == 'solid' then 144 | return { top = 1, right = 1, bottom = 1, left = 1 } 145 | elseif border == 'shadow' then 146 | return { top = 0, right = 1, bottom = 1, left = 0 } 147 | end 148 | return { top = 0, right = 0, bottom = 0, left = 0 } 149 | end 150 | local chars = border --[=[@as string[]]=] 151 | while #chars < 8 do 152 | chars = kit.concat(chars, chars) 153 | end 154 | return { 155 | top = vim.api.nvim_strwidth(chars[2]), 156 | right = vim.api.nvim_strwidth(chars[4]), 157 | bottom = vim.api.nvim_strwidth(chars[6]), 158 | left = vim.api.nvim_strwidth(chars[8]), 159 | } 160 | end)() 161 | maybe_border_size.v = maybe_border_size.top + maybe_border_size.bottom 162 | maybe_border_size.h = maybe_border_size.left + maybe_border_size.right 163 | return maybe_border_size 164 | end 165 | 166 | ---Get content size. 167 | ---@param params { bufnr: integer, wrap: boolean, max_inner_width: integer, markdown?: boolean } 168 | ---@return gindent.kit.Vim.FloatingWindow.ContentSize 169 | function FloatingWindow.get_content_size(params) 170 | --- compute content width. 171 | local content_width --[=[@as integer]=] 172 | do 173 | local max_text_width = 0 174 | for _, text in ipairs(vim.api.nvim_buf_get_lines(params.bufnr, 0, -1, false)) do 175 | local text_width = math.max(1, vim.api.nvim_strwidth(text)) 176 | if params.markdown then 177 | local j = 1 178 | local s, e = text:find('%b[]%b()', j) 179 | if s then 180 | text_width = text_width - (#text:match('%b[]', j) - 2) 181 | j = e + 1 182 | end 183 | end 184 | max_text_width = math.max(max_text_width, text_width) 185 | end 186 | content_width = max_text_width 187 | end 188 | 189 | --- compute content height. 190 | local content_height --[=[@as integer]=] 191 | do 192 | if params.wrap then 193 | local max_width = math.min(params.max_inner_width, content_width) 194 | local height = 0 195 | for _, text in ipairs(vim.api.nvim_buf_get_lines(params.bufnr, 0, -1, false)) do 196 | local text_width = math.max(1, vim.api.nvim_strwidth(text)) 197 | height = height + math.max(1, math.ceil(text_width / max_width)) 198 | end 199 | content_height = height 200 | else 201 | content_height = vim.api.nvim_buf_line_count(params.bufnr) 202 | end 203 | 204 | for _, extmark in 205 | ipairs(vim.api.nvim_buf_get_extmarks(params.bufnr, -1, 0, -1, { 206 | details = true, 207 | })) 208 | do 209 | if extmark[4] and extmark[4].virt_lines then 210 | content_height = content_height + #extmark[4].virt_lines 211 | end 212 | end 213 | end 214 | 215 | return { 216 | width = content_width, 217 | height = content_height, 218 | } 219 | end 220 | 221 | ---Guess viewport information. 222 | ---@param params { border_size: gindent.kit.Vim.FloatingWindow.BorderSize, content_size: gindent.kit.Vim.FloatingWindow.ContentSize, max_outer_width: integer, max_outer_height: integer } 223 | ---@return { inner_width: integer, inner_height: integer, outer_width: integer, outer_height: integer, scrollbar: boolean } 224 | function FloatingWindow.compute_restricted_size(params) 225 | local inner_size = { 226 | width = math.min(params.content_size.width, params.max_outer_width - params.border_size.h), 227 | height = math.min(params.content_size.height, params.max_outer_height - params.border_size.v), 228 | } 229 | 230 | local scrollbar = inner_size.height < params.content_size.height 231 | 232 | return { 233 | outer_width = inner_size.width + params.border_size.h + (scrollbar and 1 or 0), 234 | outer_height = inner_size.height + params.border_size.v, 235 | inner_width = inner_size.width, 236 | inner_height = inner_size.height, 237 | scrollbar = scrollbar, 238 | } 239 | end 240 | 241 | ---Create window. 242 | ---@return gindent.kit.Vim.FloatingWindow 243 | function FloatingWindow.new() 244 | return setmetatable({ 245 | _augroup = vim.api.nvim_create_augroup(('gindent.kit.Vim.FloatingWindow:%s'):format(kit.unique_id()), { 246 | clear = true, 247 | }), 248 | _config = { 249 | markdown = false, 250 | }, 251 | _win_option = {}, 252 | _buf_option = {}, 253 | _buf = vim.api.nvim_create_buf(false, true), 254 | _scrollbar_track_buf = vim.api.nvim_create_buf(false, true), 255 | _scrollbar_thumb_buf = vim.api.nvim_create_buf(false, true), 256 | }, FloatingWindow) 257 | end 258 | 259 | ---Get config. 260 | ---@return gindent.kit.Vim.FloatingWindow.Config 261 | function FloatingWindow:get_config() 262 | return self._config 263 | end 264 | 265 | ---Set config. 266 | ---@param config gindent.kit.Vim.FloatingWindow.Config 267 | function FloatingWindow:set_config(config) 268 | self._config = kit.merge(config, self._config) 269 | end 270 | 271 | ---Set window option. 272 | ---@param key string 273 | ---@param value any 274 | ---@param kind? gindent.kit.Vim.FloatingWindow.WindowKind 275 | function FloatingWindow:set_win_option(key, value, kind) 276 | kind = kind or 'main' 277 | self._win_option[kind] = self._win_option[kind] or {} 278 | self._win_option[kind][key] = value 279 | self:_update_option() 280 | end 281 | 282 | ---Get window option. 283 | ---@param key string 284 | ---@param kind? gindent.kit.Vim.FloatingWindow.WindowKind 285 | ---@return any 286 | function FloatingWindow:get_win_option(key, kind) 287 | kind = kind or 'main' 288 | local win = ({ 289 | main = self._win, 290 | scrollbar_track = self._scrollbar_track_win, 291 | scrollbar_thumb = self._scrollbar_thumb_win, 292 | })[kind] --[=[@as integer]=] 293 | if not is_visible(win) then 294 | return self._win_option[kind] and self._win_option[kind][key] 295 | end 296 | return vim.api.nvim_get_option_value(key, { win = win }) or vim.api.nvim_get_option_value(key, { scope = 'global' }) 297 | end 298 | 299 | ---Set buffer option. 300 | ---@param key string 301 | ---@param value any 302 | ---@param kind? gindent.kit.Vim.FloatingWindow.WindowKind 303 | function FloatingWindow:set_buf_option(key, value, kind) 304 | kind = kind or 'main' 305 | self._buf_option[kind] = self._buf_option[kind] or {} 306 | self._buf_option[kind][key] = value 307 | self:_update_option() 308 | end 309 | 310 | ---Get window option. 311 | ---@param key string 312 | ---@param kind? gindent.kit.Vim.FloatingWindow.WindowKind 313 | ---@return any 314 | function FloatingWindow:get_buf_option(key, kind) 315 | kind = kind or 'main' 316 | local buf = ({ 317 | main = self._buf, 318 | scrollbar_track = self._scrollbar_track_buf, 319 | scrollbar_thumb = self._scrollbar_thumb_buf, 320 | })[kind] --[=[@as integer]=] 321 | if not buf then 322 | return self._buf_option[kind] and self._buf_option[kind][key] 323 | end 324 | return vim.api.nvim_get_option_value(key, { buf = buf }) or vim.api.nvim_get_option_value(key, { scope = 'global' }) 325 | end 326 | 327 | ---Returns the related bufnr. 328 | ---@param kind? gindent.kit.Vim.FloatingWindow.WindowKind 329 | ---@return integer 330 | function FloatingWindow:get_buf(kind) 331 | if kind == 'scrollbar_track' then 332 | return self._scrollbar_track_buf 333 | elseif kind == 'scrollbar_thumb' then 334 | return self._scrollbar_thumb_buf 335 | end 336 | return self._buf 337 | end 338 | 339 | ---Returns the current win. 340 | ---@param kind? gindent.kit.Vim.FloatingWindow.WindowKind 341 | ---@return integer? 342 | function FloatingWindow:get_win(kind) 343 | if kind == 'scrollbar_track' then 344 | return self._scrollbar_track_win 345 | elseif kind == 'scrollbar_thumb' then 346 | return self._scrollbar_thumb_win 347 | end 348 | return self._win 349 | end 350 | 351 | ---Show the window 352 | ---@param win_config gindent.kit.Vim.FloatingWindow.WindowConfig 353 | function FloatingWindow:show(win_config) 354 | local zindex = win_config.zindex or 1000 355 | 356 | self._win = show_or_move(self._win, self._buf, { 357 | row = win_config.row, 358 | col = win_config.col, 359 | width = win_config.width, 360 | height = win_config.height, 361 | anchor = win_config.anchor, 362 | style = win_config.style, 363 | border = win_config.border, 364 | zindex = zindex, 365 | }) 366 | 367 | vim.api.nvim_clear_autocmds({ group = self._augroup }) 368 | vim.api.nvim_create_autocmd({ 'WinResized', 'WinScrolled' }, { 369 | group = self._augroup, 370 | callback = function() 371 | self:_update_scrollbar() 372 | end, 373 | }) 374 | 375 | self:_update_scrollbar() 376 | self:_update_option() 377 | end 378 | 379 | ---Hide the window 380 | function FloatingWindow:hide() 381 | vim.api.nvim_clear_autocmds({ group = self._augroup }) 382 | hide(self._win) 383 | hide(self._scrollbar_track_win) 384 | hide(self._scrollbar_thumb_win) 385 | end 386 | 387 | ---Scroll the window. 388 | ---@param delta integer 389 | function FloatingWindow:scroll(delta) 390 | if not is_visible(self._win) then 391 | return 392 | end 393 | vim.api.nvim_win_call(self._win, function() 394 | local topline = vim.fn.getwininfo(self._win)[1].height 395 | topline = topline + delta 396 | topline = math.max(topline, 1) 397 | topline = math.min(topline, vim.api.nvim_buf_line_count(self._buf) - vim.api.nvim_win_get_height(self._win) + 1) 398 | vim.api.nvim_command(('normal! %szt'):format(topline)) 399 | end) 400 | end 401 | 402 | ---Returns true if the window is visible 403 | function FloatingWindow:is_visible() 404 | return is_visible(self._win) 405 | end 406 | 407 | ---Get window viewport. 408 | ---NOTE: this method can only be called if window is showing. 409 | ---@return gindent.kit.Vim.FloatingWindow.Viewport 410 | function FloatingWindow:get_viewport() 411 | if not self:is_visible() then 412 | error('this method can only be called if window is showing.') 413 | end 414 | 415 | local win_config = vim.api.nvim_win_get_config(self:get_win() --[[@as integer]]) 416 | local win_position = vim.api.nvim_win_get_position(self:get_win() --[[@as integer]]) 417 | local border_size = FloatingWindow.get_border_size(win_config.border) 418 | local content_size = FloatingWindow.get_content_size({ 419 | bufnr = self:get_buf(), 420 | wrap = self:get_win_option('wrap'), 421 | max_inner_width = win_config.width, 422 | markdown = self:get_config().markdown, 423 | }) 424 | local scrollbar = win_config.height < content_size.height 425 | 426 | local ui_width = border_size.h + (scrollbar and 1 or 0) 427 | local ui_height = border_size.v 428 | return { 429 | row = win_position[1], 430 | col = win_position[2], 431 | inner_width = win_config.width, 432 | inner_height = win_config.height, 433 | outer_width = win_config.width + ui_width, 434 | outer_height = win_config.height + ui_height, 435 | ui_width = ui_width, 436 | ui_height = ui_height, 437 | border_size = border_size, 438 | content_size = content_size, 439 | scrollbar = scrollbar, 440 | border = win_config.border, 441 | zindex = win_config.zindex, 442 | } 443 | end 444 | 445 | ---Update scrollbar. 446 | function FloatingWindow:_update_scrollbar() 447 | if is_visible(self._win) then 448 | local viewport = self:get_viewport() 449 | if viewport.scrollbar then 450 | do 451 | self._scrollbar_track_win = show_or_move(self._scrollbar_track_win, self._scrollbar_track_buf, { 452 | row = viewport.row + viewport.border_size.top, 453 | col = viewport.col + viewport.outer_width - 1, 454 | width = 1, 455 | height = viewport.inner_height, 456 | style = 'minimal', 457 | zindex = viewport.zindex + 1, 458 | }) 459 | end 460 | do 461 | local topline = vim.fn.getwininfo(self._win)[1].topline 462 | local ratio = topline / (viewport.content_size.height - viewport.inner_height) 463 | local thumb_height = viewport.inner_height / viewport.content_size.height * viewport.inner_height 464 | local thumb_row = (viewport.inner_height - thumb_height) * ratio 465 | thumb_row = math.floor(math.min(viewport.inner_height - thumb_height, thumb_row)) 466 | self._scrollbar_thumb_win = show_or_move(self._scrollbar_thumb_win, self._scrollbar_thumb_buf, { 467 | row = viewport.row + viewport.border_size.top + thumb_row, 468 | col = viewport.col + viewport.outer_width - 1, 469 | width = 1, 470 | height = math.ceil(thumb_height), 471 | style = 'minimal', 472 | zindex = viewport.zindex + 2, 473 | }) 474 | end 475 | return 476 | end 477 | end 478 | hide(self._scrollbar_track_win) 479 | hide(self._scrollbar_thumb_win) 480 | end 481 | 482 | ---Update options. 483 | function FloatingWindow:_update_option() 484 | -- update buf. 485 | for kind, buf in pairs({ 486 | main = self._buf, 487 | scrollbar_track = self._scrollbar_track_buf, 488 | scrollbar_thumb = self._scrollbar_thumb_buf, 489 | }) do 490 | for k, v in pairs(self._buf_option[kind] or {}) do 491 | if vim.api.nvim_get_option_value(k, { buf = buf }) ~= v then 492 | vim.api.nvim_set_option_value(k, v, { buf = buf }) 493 | end 494 | end 495 | end 496 | 497 | -- update win. 498 | for kind, win in pairs({ 499 | main = self._win, 500 | scrollbar_track = self._scrollbar_track_win, 501 | scrollbar_thumb = self._scrollbar_thumb_win, 502 | }) do 503 | if is_visible(win) then 504 | for k, v in pairs(self._win_option[kind] or {}) do 505 | if vim.api.nvim_get_option_value(k, { win = win }) ~= v then 506 | vim.api.nvim_set_option_value(k, v, { win = win }) 507 | end 508 | end 509 | end 510 | end 511 | end 512 | 513 | return FloatingWindow 514 | -------------------------------------------------------------------------------- /lua/gindent/kit/Vim/Keymap.lua: -------------------------------------------------------------------------------- 1 | local kit = require('gindent.kit') 2 | local Async = require('gindent.kit.Async') 3 | 4 | local buf = vim.api.nvim_create_buf(false, true) 5 | 6 | ---@alias gindent.kit.Vim.Keymap.Keys { keys: string, remap?: boolean } 7 | ---@alias gindent.kit.Vim.Keymap.KeysSpecifier string|gindent.kit.Vim.Keymap.Keys 8 | 9 | ---@param keys gindent.kit.Vim.Keymap.KeysSpecifier 10 | ---@return gindent.kit.Vim.Keymap.Keys 11 | local function to_keys(keys) 12 | if type(keys) == 'table' then 13 | return keys 14 | end 15 | return { keys = keys, remap = false } 16 | end 17 | 18 | local Keymap = {} 19 | 20 | _G.kit = _G.kit or {} 21 | _G.kit.Vim = _G.kit.Vim or {} 22 | _G.kit.Vim.Keymap = _G.kit.Vim.Keymap or {} 23 | _G.kit.Vim.Keymap.callbacks = _G.kit.Vim.Keymap.callbacks or {} 24 | 25 | ---Replace termcodes. 26 | ---@param keys string 27 | ---@return string 28 | function Keymap.termcodes(keys) 29 | return vim.api.nvim_replace_termcodes(keys, true, true, true) 30 | end 31 | 32 | ---Normalize keycode. 33 | function Keymap.normalize(s) 34 | local desc = 'gindent.kit.Vim.Keymap.normalize' 35 | vim.api.nvim_buf_set_keymap(buf, 't', s, '.', { desc = desc }) 36 | for _, map in ipairs(vim.api.nvim_buf_get_keymap(buf, 't')) do 37 | if map.desc == desc then 38 | vim.api.nvim_buf_del_keymap(buf, 't', s) 39 | return map.lhs --[[@as string]] 40 | end 41 | end 42 | vim.api.nvim_buf_del_keymap(buf, 't', s) 43 | return s 44 | end 45 | 46 | ---Set callback for consuming next typeahead. 47 | ---@param callback fun() 48 | ---@return gindent.kit.Async.AsyncTask 49 | function Keymap.next(callback) 50 | return Keymap.send(''):next(callback) 51 | end 52 | 53 | ---Send keys. 54 | ---@param keys gindent.kit.Vim.Keymap.KeysSpecifier|gindent.kit.Vim.Keymap.KeysSpecifier[] 55 | ---@param no_insert? boolean 56 | ---@return gindent.kit.Async.AsyncTask 57 | function Keymap.send(keys, no_insert) 58 | local unique_id = kit.unique_id() 59 | return Async.new(function(resolve, _) 60 | _G.kit.Vim.Keymap.callbacks[unique_id] = resolve 61 | 62 | local callback = Keymap.termcodes(('lua require("gindent.kit.Vim.Keymap")._resolve(%s)'):format(unique_id)) 63 | if no_insert then 64 | for _, keys_ in ipairs(kit.to_array(keys)) do 65 | keys_ = to_keys(keys_) 66 | vim.api.nvim_feedkeys(keys_.keys, keys_.remap and 'm' or 'n', true) 67 | end 68 | vim.api.nvim_feedkeys(callback, 'n', true) 69 | else 70 | vim.api.nvim_feedkeys(callback, 'in', true) 71 | for _, keys_ in ipairs(kit.reverse(kit.to_array(keys))) do 72 | keys_ = to_keys(keys_) 73 | vim.api.nvim_feedkeys(keys_.keys, 'i' .. (keys_.remap and 'm' or 'n'), true) 74 | end 75 | end 76 | end):catch(function() 77 | _G.kit.Vim.Keymap.callbacks[unique_id] = nil 78 | end) 79 | end 80 | 81 | ---Return sendabke keys with callback function. 82 | ---@param callback fun(...: any): any 83 | ---@return string 84 | function Keymap.to_sendable(callback) 85 | local unique_id = kit.unique_id() 86 | _G.kit.Vim.Keymap.callbacks[unique_id] = function() 87 | Async.run(callback) 88 | end 89 | return Keymap.termcodes(('lua require("gindent.kit.Vim.Keymap")._resolve(%s)'):format(unique_id)) 90 | end 91 | 92 | ---Test spec helper. 93 | ---@param spec fun(): any 94 | function Keymap.spec(spec) 95 | local task = Async.resolve():next(function() 96 | return Async.run(spec) 97 | end) 98 | vim.api.nvim_feedkeys('', 'x', true) 99 | task:sync(5000) 100 | collectgarbage('collect') 101 | vim.wait(200) 102 | end 103 | 104 | ---Resolve running keys. 105 | ---@param unique_id integer 106 | function Keymap._resolve(unique_id) 107 | _G.kit.Vim.Keymap.callbacks[unique_id]() 108 | _G.kit.Vim.Keymap.callbacks[unique_id] = nil 109 | end 110 | 111 | return Keymap 112 | -------------------------------------------------------------------------------- /lua/gindent/kit/Vim/RegExp.lua: -------------------------------------------------------------------------------- 1 | local RegExp = {} 2 | 3 | ---@type table 4 | RegExp._cache = {} 5 | 6 | ---Create a RegExp object. 7 | ---@param pattern string 8 | ---@return { match_str: fun(self, text: string) } 9 | function RegExp.get(pattern) 10 | if not RegExp._cache[pattern] then 11 | RegExp._cache[pattern] = vim.regex(pattern) 12 | end 13 | return RegExp._cache[pattern] 14 | end 15 | 16 | ---Grep and substitute text. 17 | ---@param text string 18 | ---@param pattern string 19 | ---@param replacement string 20 | ---@return string 21 | function RegExp.gsub(text, pattern, replacement) 22 | return vim.fn.substitute(text, pattern, replacement, 'g') 23 | end 24 | 25 | ---Match pattern in text for specified position. 26 | ---@param text string 27 | ---@param pattern string 28 | ---@param pos integer 1-origin index 29 | ---@return string?, integer?, integer? 1-origin-index 30 | function RegExp.extract_at(text, pattern, pos) 31 | local before_text = text:sub(1, pos - 1) 32 | local after_text = text:sub(pos) 33 | local b_s, _ = RegExp.get(pattern .. '$'):match_str(before_text) 34 | local _, a_e = RegExp.get('^' .. pattern):match_str(after_text) 35 | if b_s or a_e then 36 | b_s = b_s or #before_text 37 | a_e = #before_text + (a_e or 0) 38 | return text:sub(b_s + 1, a_e), b_s + 1, a_e + 1 39 | end 40 | end 41 | 42 | return RegExp 43 | -------------------------------------------------------------------------------- /lua/gindent/kit/Vim/Syntax.lua: -------------------------------------------------------------------------------- 1 | local Syntax = {} 2 | 3 | ---Return the specified position is in the specified syntax. 4 | ---@param cursor { [1]: integer, [2]: integer } 5 | ---@param groups string[] 6 | function Syntax.within(cursor, groups) 7 | for _, group in ipairs(Syntax.get_syntax_groups(cursor)) do 8 | if vim.tbl_contains(groups, group) then 9 | return true 10 | end 11 | end 12 | return false 13 | end 14 | 15 | ---Get all syntax groups for specified position. 16 | ---NOTE: This function accepts 0-origin cursor position. 17 | ---@param cursor { [1]: integer, [2]: integer } 18 | ---@return string[] 19 | function Syntax.get_syntax_groups(cursor) 20 | local treesitter = Syntax.get_vim_syntax_groups(cursor) 21 | if #treesitter > 0 then 22 | return treesitter 23 | end 24 | return Syntax.get_treesitter_syntax_groups(cursor) -- it might be heavy. 25 | end 26 | 27 | ---Get vim's syntax groups for specified position. 28 | ---NOTE: This function accepts 0-origin cursor position. 29 | ---@param cursor { [1]: integer, [2]: integer } 30 | ---@return string[] 31 | function Syntax.get_vim_syntax_groups(cursor) 32 | local unique = {} 33 | local groups = {} 34 | for _, syntax_id in ipairs(vim.fn.synstack(cursor[1] + 1, cursor[2] + 1)) do 35 | local name = vim.fn.synIDattr(vim.fn.synIDtrans(syntax_id), 'name') 36 | if not unique[name] then 37 | unique[name] = true 38 | table.insert(groups, name) 39 | end 40 | end 41 | for _, syntax_id in ipairs(vim.fn.synstack(cursor[1] + 1, cursor[2] + 1)) do 42 | local name = vim.fn.synIDattr(syntax_id, 'name') 43 | if not unique[name] then 44 | unique[name] = true 45 | table.insert(groups, name) 46 | end 47 | end 48 | return groups 49 | end 50 | 51 | ---Get tree-sitter's syntax groups for specified position. 52 | ---NOTE: This function accepts 0-origin cursor position. 53 | ---@param cursor { [1]: integer, [2]: integer } 54 | ---@return string[] 55 | function Syntax.get_treesitter_syntax_groups(cursor) 56 | local groups = {} 57 | for _, capture in ipairs(vim.treesitter.get_captures_at_pos(0, cursor[1], cursor[2])) do 58 | table.insert(groups, ('@%s'):format(capture.capture)) 59 | end 60 | return groups 61 | end 62 | 63 | return Syntax 64 | -------------------------------------------------------------------------------- /lua/gindent/kit/init.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: ignore 512 2 | 3 | local kit = {} 4 | 5 | ---Clear table. 6 | ---@param tbl table 7 | kit.clear = require('table.clear') or function(tbl) 8 | for k, _ in pairs(tbl) do 9 | tbl[k] = nil 10 | end 11 | end 12 | 13 | ---Create gabage collection detector. 14 | ---@param callback fun(...: any): any 15 | ---@return userdata 16 | function kit.gc(callback) 17 | local gc = newproxy(true) 18 | if vim.is_thread() or os.getenv('NODE_ENV') == 'test' then 19 | getmetatable(gc).__gc = callback 20 | else 21 | getmetatable(gc).__gc = vim.schedule_wrap(callback) 22 | end 23 | return gc 24 | end 25 | 26 | ---Safe version of vim.schedule. 27 | ---@param fn fun(...: any): any 28 | function kit.safe_schedule(fn) 29 | if vim.is_thread() then 30 | local timer = assert(vim.uv.new_timer()) 31 | timer:start(0, 0, function() 32 | timer:stop() 33 | timer:close() 34 | fn() 35 | end) 36 | else 37 | vim.schedule(fn) 38 | end 39 | end 40 | 41 | ---Safe version of vim.schedule_wrap. 42 | ---@param fn fun(...: any): any 43 | function kit.safe_schedule_wrap(fn) 44 | if vim.is_thread() then 45 | return function(...) 46 | local args = { ... } 47 | local timer = assert(vim.uv.new_timer()) 48 | timer:start(0, 0, function() 49 | timer:stop() 50 | timer:close() 51 | fn(unpack(args)) 52 | end) 53 | end 54 | else 55 | return vim.schedule_wrap(fn) 56 | end 57 | end 58 | 59 | ---Fast version of vim.schedule. 60 | ---@param fn fun(): any 61 | function kit.fast_schedule(fn) 62 | if vim.in_fast_event() then 63 | kit.safe_schedule(fn) 64 | else 65 | fn() 66 | end 67 | end 68 | 69 | ---Safe version of vim.schedule_wrap. 70 | ---@generic A 71 | ---@param fn fun(...: A) 72 | ---@return fun(...: A) 73 | function kit.fast_schedule_wrap(fn) 74 | return function(...) 75 | local args = { ... } 76 | kit.fast_schedule(function() 77 | fn(unpack(args)) 78 | end) 79 | end 80 | end 81 | 82 | ---Find up directory. 83 | ---@param path string 84 | ---@param markers string[] 85 | function kit.findup(path, markers) 86 | path = vim.fs.normalize(path) 87 | if vim.fn.filereadable(path) == 1 then 88 | path = vim.fs.dirname(path) 89 | end 90 | while path ~= '/' do 91 | for _, marker in ipairs(markers) do 92 | local target = vim.fs.joinpath(path, (marker:gsub('/', ''))) 93 | if vim.fn.isdirectory(target) == 1 or vim.fn.filereadable(target) == 1 then 94 | return path 95 | end 96 | end 97 | path = vim.fs.dirname(path) 98 | end 99 | end 100 | 101 | do 102 | _G.kit = _G.kit or {} 103 | _G.kit.unique_id = 0 104 | 105 | ---Create unique id. 106 | ---@return integer 107 | kit.unique_id = function() 108 | _G.kit.unique_id = _G.kit.unique_id + 1 109 | return _G.kit.unique_id 110 | end 111 | end 112 | 113 | ---Map array. 114 | ---@deprecated 115 | ---@param array table 116 | ---@param fn fun(item: unknown, index: integer): unknown 117 | ---@return unknown[] 118 | function kit.map(array, fn) 119 | local new_array = {} 120 | for i, item in ipairs(array) do 121 | table.insert(new_array, fn(item, i)) 122 | end 123 | return new_array 124 | end 125 | 126 | ---@generic T 127 | ---@deprecated 128 | ---@param value T? 129 | ---@param default T 130 | function kit.default(value, default) 131 | if value == nil then 132 | return default 133 | end 134 | return value 135 | end 136 | 137 | ---Get object path with default value. 138 | ---@generic T 139 | ---@param value table 140 | ---@param path integer|string|(string|integer)[] 141 | ---@param default? T 142 | ---@return T 143 | function kit.get(value, path, default) 144 | local result = value 145 | for _, key in ipairs(kit.to_array(path)) do 146 | if type(result) == 'table' then 147 | result = result[key] 148 | else 149 | return default 150 | end 151 | end 152 | if result == nil then 153 | return default 154 | end 155 | return result 156 | end 157 | 158 | ---Set object path with new value. 159 | ---@param value table 160 | ---@param path integer|string|(string|integer)[] 161 | ---@param new_value any 162 | function kit.set(value, path, new_value) 163 | local current = value 164 | for i = 1, #path - 1 do 165 | local key = path[i] 166 | if type(current[key]) ~= 'table' then 167 | error('The specified path is not a table.') 168 | end 169 | current = current[key] 170 | end 171 | current[path[#path]] = new_value 172 | end 173 | 174 | ---Create debounced callback. 175 | ---@generic T: fun(...: any): nil 176 | ---@param callback T 177 | ---@param timeout_ms integer 178 | ---@return T 179 | function kit.debounce(callback, timeout_ms) 180 | local timer = assert(vim.uv.new_timer()) 181 | return setmetatable({ 182 | timeout_ms = timeout_ms, 183 | is_running = function() 184 | return timer:is_active() 185 | end, 186 | stop = function() 187 | timer:stop() 188 | end, 189 | }, { 190 | __call = function(self, ...) 191 | local arguments = { ... } 192 | 193 | self.running = true 194 | timer:stop() 195 | timer:start(self.timeout_ms, 0, function() 196 | self.running = false 197 | timer:stop() 198 | callback(unpack(arguments)) 199 | end) 200 | end, 201 | }) 202 | end 203 | 204 | ---Create throttled callback. 205 | ---First call will be called immediately. 206 | ---@generic T: fun(...: any): nil 207 | ---@param callback T 208 | ---@param timeout_ms integer 209 | function kit.throttle(callback, timeout_ms) 210 | local timer = assert(vim.uv.new_timer()) 211 | local arguments = nil 212 | local last_time = (vim.uv.hrtime() / 1000000) - timeout_ms - 1 213 | return setmetatable({ 214 | timeout_ms = timeout_ms, 215 | is_running = function() 216 | return timer:is_active() 217 | end, 218 | stop = function() 219 | timer:stop() 220 | end, 221 | }, { 222 | __call = function(self, ...) 223 | arguments = { ... } 224 | 225 | if self.is_running() then 226 | timer:stop() 227 | end 228 | local delay_ms = self.timeout_ms - ((vim.uv.hrtime() / 1000000) - last_time) 229 | if delay_ms > 0 then 230 | timer:start(delay_ms, 0, function() 231 | timer:stop() 232 | last_time = (vim.uv.hrtime() / 1000000) 233 | callback(unpack(arguments)) 234 | end) 235 | else 236 | last_time = (vim.uv.hrtime() / 1000000) 237 | callback(unpack(arguments)) 238 | end 239 | end, 240 | }) 241 | end 242 | 243 | do 244 | ---@generic T 245 | ---@param target T 246 | ---@param seen table 247 | ---@return T 248 | local function do_clone(target, seen) 249 | if type(target) ~= 'table' then 250 | return target 251 | end 252 | if seen[target] then 253 | return seen[target] 254 | end 255 | if kit.is_array(target) then 256 | local new_tbl = {} 257 | seen[target] = new_tbl 258 | for k, v in ipairs(target) do 259 | new_tbl[k] = do_clone(v, seen) 260 | end 261 | return new_tbl 262 | else 263 | local new_tbl = {} 264 | local meta = getmetatable(target) 265 | if meta then 266 | setmetatable(new_tbl, meta) 267 | end 268 | seen[target] = new_tbl 269 | for k, v in pairs(target) do 270 | new_tbl[k] = do_clone(v, seen) 271 | end 272 | return new_tbl 273 | end 274 | end 275 | 276 | ---Clone object. 277 | ---@generic T 278 | ---@param target T 279 | ---@return T 280 | function kit.clone(target) 281 | return do_clone(target, {}) 282 | end 283 | end 284 | 285 | ---Merge two tables. 286 | ---@generic T: any[] 287 | ---NOTE: This doesn't merge array-like table. 288 | ---@param tbl1 T 289 | ---@param tbl2 T 290 | ---@return T 291 | function kit.merge(tbl1, tbl2) 292 | local is_dict1 = kit.is_dict(tbl1) and getmetatable(tbl1) == nil 293 | local is_dict2 = kit.is_dict(tbl2) and getmetatable(tbl2) == nil 294 | if is_dict1 and is_dict2 then 295 | local new_tbl = {} 296 | for k, v in pairs(tbl2) do 297 | if tbl1[k] ~= vim.NIL then 298 | new_tbl[k] = kit.merge(tbl1[k], v) 299 | end 300 | end 301 | for k, v in pairs(tbl1) do 302 | if tbl2[k] == nil then 303 | if v ~= vim.NIL then 304 | new_tbl[k] = kit.merge(v, {}) 305 | else 306 | new_tbl[k] = nil 307 | end 308 | end 309 | end 310 | return new_tbl 311 | end 312 | 313 | -- premitive like values. 314 | if tbl1 == vim.NIL then 315 | return nil 316 | elseif tbl1 == nil then 317 | return kit.merge(tbl2, {}) -- clone & prevent vim.NIL 318 | end 319 | return tbl1 320 | end 321 | 322 | ---Concatenate two tables. 323 | ---NOTE: This doesn't concatenate dict-like table. 324 | ---@param tbl1 table 325 | ---@param ... table 326 | ---@return table 327 | function kit.concat(tbl1, ...) 328 | local new_tbl = {} 329 | 330 | local off = 0 331 | for _, item in pairs(tbl1) do 332 | new_tbl[off + 1] = item 333 | off = off + 1 334 | end 335 | 336 | for _, tbl2 in ipairs({ ... }) do 337 | for _, item in pairs(kit.to_array(tbl2)) do 338 | new_tbl[off + 1] = item 339 | off = off + 1 340 | end 341 | end 342 | return new_tbl 343 | end 344 | 345 | ---Slice the array. 346 | ---@generic T: any[] 347 | ---@param array T 348 | ---@param s integer 349 | ---@param e integer 350 | ---@return T 351 | function kit.slice(array, s, e) 352 | if not kit.is_array(array) then 353 | error('[kit] specified value is not an array.') 354 | end 355 | local new_array = {} 356 | for i = s, e do 357 | table.insert(new_array, array[i]) 358 | end 359 | return new_array 360 | end 361 | 362 | ---The value to array. 363 | ---@param value any 364 | ---@return table 365 | function kit.to_array(value) 366 | if type(value) == 'table' then 367 | if kit.is_array(value) then 368 | return value 369 | end 370 | end 371 | return { value } 372 | end 373 | 374 | ---Check the value is array. 375 | ---@param value any 376 | ---@return boolean 377 | function kit.is_array(value) 378 | if type(value) ~= 'table' then 379 | return false 380 | end 381 | 382 | for k, _ in pairs(value) do 383 | if type(k) ~= 'number' then 384 | return false 385 | end 386 | end 387 | return true 388 | end 389 | 390 | ---Check the value is dict. 391 | ---@param value any 392 | ---@return boolean 393 | function kit.is_dict(value) 394 | return type(value) == 'table' and (not kit.is_array(value) or kit.is_empty(value)) 395 | end 396 | 397 | ---Check the value is empty. 398 | ---@param value any 399 | ---@return boolean 400 | function kit.is_empty(value) 401 | if type(value) ~= 'table' then 402 | return false 403 | end 404 | for _ in pairs(value) do 405 | return false 406 | end 407 | if #value == 0 then 408 | return true 409 | end 410 | return true 411 | end 412 | 413 | ---Reverse the array. 414 | ---@param array table 415 | ---@return table 416 | function kit.reverse(array) 417 | if not kit.is_array(array) then 418 | error('[kit] specified value is not an array.') 419 | end 420 | 421 | local new_array = {} 422 | for i = #array, 1, -1 do 423 | table.insert(new_array, array[i]) 424 | end 425 | return new_array 426 | end 427 | 428 | ---String dedent. 429 | function kit.dedent(s) 430 | local lines = vim.split(s, '\n') 431 | if lines[1]:match('^%s*$') then 432 | table.remove(lines, 1) 433 | end 434 | if lines[#lines]:match('^%s*$') then 435 | table.remove(lines, #lines) 436 | end 437 | local base_indent = lines[1]:match('^%s*') 438 | for i, line in ipairs(lines) do 439 | lines[i] = line:gsub('^' .. base_indent, '') 440 | end 441 | return table.concat(lines, '\n') 442 | end 443 | 444 | return kit 445 | -------------------------------------------------------------------------------- /plugin/gindent.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_gindent') 2 | finish 3 | end 4 | let g:loaded_gindent = v:true 5 | 6 | let g:gindent = get(g:, 'gindent', {}) 7 | let g:gindent.enabled = get(g:gindent, 'enabled', { -> v:false }) 8 | 9 | augroup gindent 10 | autocmd! 11 | autocmd FileType * call timer_start(200, { -> gindent#apply() }) 12 | augroup END 13 | 14 | call gindent#register_preset('*', gindent#preset#default#get()) 15 | call gindent#register_preset('lua', gindent#preset#lua#get()) 16 | call gindent#register_preset('vim', gindent#preset#vim#get()) 17 | call gindent#register_preset('vimspec', gindent#preset#vim#get()) 18 | 19 | --------------------------------------------------------------------------------