├── README.md ├── doc ├── dansa.txt └── tags ├── lua ├── dansa.lua └── dansa │ └── kit │ ├── App │ ├── Cache.lua │ ├── Character.lua │ ├── Command.lua │ ├── Config.lua │ └── Event.lua │ ├── Async │ ├── AsyncTask.lua │ ├── ScheduledTimer.lua │ ├── Worker.lua │ └── init.lua │ ├── IO │ └── init.lua │ ├── LSP │ ├── Client.lua │ ├── DocumentSelector.lua │ ├── LanguageId.lua │ ├── Position.lua │ ├── Range.lua │ └── init.lua │ ├── RPC │ └── JSON │ │ └── init.lua │ ├── Spec │ └── init.lua │ ├── System │ └── init.lua │ ├── Vim │ ├── FloatingWindow.lua │ ├── Keymap.lua │ ├── RegExp.lua │ ├── Syntax.lua │ └── WinSaveView.lua │ └── init.lua └── plugin └── dansa.lua /README.md: -------------------------------------------------------------------------------- 1 | # nvim-dansa 2 | 3 | Guess the indent from lines of around. 4 | 5 | # usage 6 | 7 | ```lua 8 | local dansa = require('dansa') 9 | 10 | -- global settings. 11 | dansa.setup({ 12 | -- The offset to specify how much lines to use. 13 | scan_offset = 100, 14 | 15 | -- The count for cut-off the indent candidate. 16 | cutoff_count = 5, 17 | 18 | -- The settings for tab-indentation or when it cannot be guessed. 19 | default = { 20 | expandtab = true, 21 | space = { 22 | shiftwidth = 2, 23 | }, 24 | tab = { 25 | shiftwidth = 4, 26 | } 27 | } 28 | }) 29 | 30 | -- per filetype settings. 31 | dansa.setup.filetype('go', { 32 | default = { 33 | expandtab = false, 34 | tab = { 35 | shiftwidth = 4, 36 | } 37 | } 38 | }) 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /doc/dansa.txt: -------------------------------------------------------------------------------- 1 | *nvim-dansa* *dansa* 2 | 3 | Guess indent from lines of around. 4 | 5 | 6 | 7 | ============================================================================== 8 | CONTENTS *dansa-contents* 9 | 10 | USAGE |dansa-usage| 11 | FUNCTION |dansa-function| 12 | COMMAND |dansa-command| 13 | TYPINGS |dansa-typings| 14 | 15 | 16 | 17 | ============================================================================== 18 | USAGE *dansa-usage* 19 | 20 | >lua 21 | local dansa = require('dansa') 22 | 23 | -- global settings. 24 | dansa.setup({ 25 | -- Specify enabled or disabled. 26 | enabled = true, 27 | 28 | -- The offset to specify how much lines to use. 29 | scan_offset = 100, 30 | 31 | -- The count for cut-off the indent candidate. 32 | cutoff_count = 5, 33 | 34 | -- The settings for tab-indentation or when it cannot be guessed. 35 | default = { 36 | expandtab = true, 37 | space = { 38 | shiftwidth = 2, 39 | }, 40 | tab = { 41 | shiftwidth = 4, 42 | } 43 | } 44 | }) 45 | 46 | -- per filetype settings. 47 | dansa.setup.filetype('go', { 48 | default = { 49 | expandtab = false, 50 | tab = { 51 | shiftwidth = 4, 52 | } 53 | } 54 | }) 55 | < 56 | 57 | 58 | ============================================================================== 59 | FUNCTION *dansa-function* 60 | 61 | require('dansa').setup(config)~ 62 | - `config`: dansa.kit.App.Config.Schema 63 | - The configuration object. 64 | 65 | require('dansa').setup.filetype(filetype, config)~ 66 | - `filetype`: string|string[] 67 | - Specify filetype to configure. 68 | - `config`: dansa.kit.App.Config.Schema 69 | - The configuration object. 70 | 71 | ============================================================================== 72 | COMMAND *dansa-command* 73 | 74 | Dansa~ 75 | 76 | Guess & apply & show applied configuration. 77 | 78 | Dansa [style]~ 79 | 80 | Set indent to specified style. 81 | 82 | - 8: 8space indent. 83 | - 4: 4space indent. 84 | - 2: 2space indent. 85 | - tab: tab indent. 86 | 87 | 88 | ============================================================================== 89 | TYPINGS *dansa-typings* 90 | 91 | >lua 92 | ---@class dansa.kit.App.Config.Schema 93 | ---@field public enabled boolean|fun(): boolean 94 | ---@field public cutoff_count integer 95 | ---@field public scan_offset integer 96 | ---@field public default { expandtab: boolean, space: { shiftwidth: integer }, tab: { shiftwidth: integer } } 97 | < 98 | 99 | 100 | ============================================================================== 101 | vim:tw=78:ts=4:et:ft=help:norl: 102 | 103 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | dansa dansa.txt /*dansa* 2 | dansa-command dansa.txt /*dansa-command* 3 | dansa-contents dansa.txt /*dansa-contents* 4 | dansa-function dansa.txt /*dansa-function* 5 | dansa-typings dansa.txt /*dansa-typings* 6 | dansa-usage dansa.txt /*dansa-usage* 7 | nvim-dansa dansa.txt /*nvim-dansa* 8 | -------------------------------------------------------------------------------- /lua/dansa.lua: -------------------------------------------------------------------------------- 1 | local kit = require('dansa.kit') 2 | local Config = require('dansa.kit.App.Config') 3 | local Syntax = require('dansa.kit.Vim.Syntax') 4 | 5 | ---@class dansa.kit.App.Config.Schema 6 | ---@field public enabled boolean|fun(): boolean 7 | ---@field public cutoff_count integer 8 | ---@field public scan_offset integer 9 | ---@field public ignored_groups_pattern? string[] 10 | ---@field public default { expandtab: boolean, space: { shiftwidth: integer }, tab: { shiftwidth: integer } } 11 | 12 | ---@param a string 13 | ---@param b string 14 | ---@return string 15 | local function get_space_indent_diff(a, b) 16 | if #a > #b then 17 | a, b = b, a 18 | end 19 | return (b:gsub('^' .. vim.pesc(a), '')) 20 | end 21 | 22 | local dansa = { 23 | config = Config.new({ 24 | enabled = true, 25 | scan_offset = 100, 26 | cutoff_count = 5, 27 | default = { 28 | expandtab = true, 29 | space = { 30 | shiftwidth = 2, 31 | }, 32 | tab = { 33 | shiftwidth = 4 34 | } 35 | }, 36 | ignored_groups_pattern = { '.*comment.*', '.*string.*' } 37 | }) 38 | } 39 | 40 | dansa.setup = dansa.config:create_setup_interface() 41 | 42 | ---Guess indent information. 43 | ---@param bufnr integer 44 | ---@return table 45 | function dansa.guess(bufnr) 46 | local cur_row0 = vim.api.nvim_win_get_cursor(0)[1] 47 | local max_row0 = vim.api.nvim_buf_line_count(0) - 1 48 | local start_row = math.max(0, cur_row0 - dansa.config:get().scan_offset - 1) 49 | local end_row = 1 + math.min(max_row0, cur_row0 + dansa.config:get().scan_offset) 50 | local lines = vim.iter(ipairs(vim.api.nvim_buf_get_lines(bufnr, start_row, end_row, false))):map(function(i, line) 51 | return { 52 | row0 = start_row + i - 1, 53 | line = line, 54 | } 55 | end):totable() 56 | 57 | -- Remove blank lines. 58 | for i = #lines, 1, -1 do 59 | if lines[i].line == '' then 60 | table.remove(lines, i) 61 | end 62 | end 63 | 64 | ---@type table 65 | local guessing = {} 66 | for i = 2, #lines do 67 | local prev = lines[i - 1] 68 | local prev_line = prev.line 69 | local prev_white = prev_line:match('^%s+') or '' 70 | local is_prev_tab = not not prev_white:match('^\t+$') 71 | local curr = lines[i] 72 | local curr_line = curr.line 73 | local curr_white = curr_line:match('^%s+') or '' 74 | local is_curr_tab = not not curr_white:match('^\t+$') 75 | 76 | local in_ignored_group = false 77 | local groups = {} 78 | groups = kit.concat(groups, Syntax.get_treesitter_syntax_groups({ prev.row0, 0 })) 79 | for _, group in ipairs(groups) do 80 | for _, pattern in ipairs(dansa.config:get().ignored_groups_pattern or {}) do 81 | if group:lower():match(pattern) then 82 | in_ignored_group = true 83 | break 84 | end 85 | end 86 | if in_ignored_group then 87 | break 88 | end 89 | end 90 | 91 | if not in_ignored_group then 92 | if is_curr_tab then 93 | guessing['\t'] = guessing['\t'] or 0 94 | guessing['\t'] = guessing['\t'] + 1 95 | else 96 | if is_prev_tab then -- tab -> space -> ? 97 | if lines[i + 1] then -- tab -> space -> ? 98 | local next = lines[i + 1] 99 | local next_line = next.line 100 | local next_white = next_line:match('^%s+') or '' 101 | local is_next_tab = not not next_white:match('^\t+$') 102 | if not is_next_tab then -- tab -> space -> space 103 | local diff = get_space_indent_diff(curr_white, next_white) 104 | if diff ~= '' then 105 | guessing[diff] = guessing[diff] or 0 106 | guessing[diff] = guessing[diff] + 1 107 | end 108 | else -- tab -> space -> tab 109 | -- ignore 110 | end 111 | else -- tab -> space -> none 112 | -- ignore 113 | end 114 | else -- space -> space 115 | local diff = get_space_indent_diff(prev_white, curr_white) 116 | if diff ~= '' then 117 | guessing[diff] = guessing[diff] or 0 118 | guessing[diff] = guessing[diff] + 1 119 | end 120 | end 121 | end 122 | end 123 | end 124 | 125 | local fixed_guessing = {} 126 | for indent, count in pairs(guessing) do 127 | if count >= dansa.config:get().cutoff_count then 128 | fixed_guessing[indent] = count 129 | end 130 | end 131 | return fixed_guessing 132 | end 133 | 134 | return dansa 135 | -------------------------------------------------------------------------------- /lua/dansa/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 dansa.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/dansa/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.upper = {} 13 | string.gsub('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '.', function(char) 14 | Character.upper[string.byte(char)] = char 15 | end) 16 | 17 | ---@type table 18 | Character.digit = {} 19 | string.gsub('1234567890', '.', function(char) 20 | Character.digit[string.byte(char)] = char 21 | end) 22 | 23 | ---@type table 24 | Character.white = {} 25 | string.gsub(' \t\n', '.', function(char) 26 | Character.white[string.byte(char)] = char 27 | end) 28 | 29 | ---Return specified byte is alpha or not. 30 | ---@param byte integer 31 | ---@return boolean 32 | function Character.is_alpha(byte) 33 | return not not (Character.alpha[byte] ~= nil or (byte and Character.alpha[byte + 32] ~= nil)) 34 | end 35 | 36 | ---Return specified byte is digit or not. 37 | ---@param byte integer 38 | ---@return boolean 39 | function Character.is_digit(byte) 40 | return Character.digit[byte] ~= nil 41 | end 42 | 43 | ---Return specified byte is alpha or not. 44 | ---@param byte integer 45 | ---@return boolean 46 | function Character.is_alnum(byte) 47 | return Character.is_alpha(byte) or Character.is_digit(byte) 48 | end 49 | 50 | ---Return specified byte is alpha or not. 51 | ---@param byte integer 52 | ---@return boolean 53 | function Character.is_upper(byte) 54 | return Character.upper[byte] ~= nil 55 | end 56 | 57 | ---Return specified byte is alpha or not. 58 | ---@param byte integer 59 | ---@return boolean 60 | function Character.is_lower(byte) 61 | return Character.alpha[byte] ~= nil 62 | end 63 | 64 | ---Return specified byte is white or not. 65 | ---@param byte integer 66 | ---@return boolean 67 | function Character.is_white(byte) 68 | return Character.white[byte] ~= nil 69 | end 70 | 71 | ---Return specified byte is symbol or not. 72 | ---@param byte integer 73 | ---@return boolean 74 | function Character.is_symbol(byte) 75 | return not Character.is_alnum(byte) and not Character.is_white(byte) 76 | end 77 | 78 | ---@param a integer 79 | ---@param b integer 80 | function Character.match_ignorecase(a, b) 81 | if a == b then 82 | return true 83 | elseif Character.is_alpha(a) and Character.is_alpha(b) then 84 | return (a == b + 32) or (a == b - 32) 85 | end 86 | return false 87 | end 88 | 89 | ---@param text string 90 | ---@param index integer 91 | ---@return boolean 92 | function Character.is_semantic_index(text, index) 93 | if index <= 1 then 94 | return true 95 | end 96 | 97 | local prev = string.byte(text, index - 1) 98 | local curr = string.byte(text, index) 99 | 100 | if not Character.is_upper(prev) and Character.is_upper(curr) then 101 | return true 102 | end 103 | if Character.is_symbol(curr) or Character.is_white(curr) then 104 | return true 105 | end 106 | if not Character.is_alpha(prev) and Character.is_alpha(curr) then 107 | return true 108 | end 109 | if not Character.is_digit(prev) and Character.is_digit(curr) then 110 | return true 111 | end 112 | return false 113 | end 114 | 115 | ---@param text string 116 | ---@param current_index integer 117 | ---@return integer 118 | function Character.get_next_semantic_index(text, current_index) 119 | for i = current_index + 1, #text do 120 | if Character.is_semantic_index(text, i) then 121 | return i 122 | end 123 | end 124 | return #text + 1 125 | end 126 | 127 | return Character 128 | -------------------------------------------------------------------------------- /lua/dansa/kit/App/Command.lua: -------------------------------------------------------------------------------- 1 | ---@class dansa.kit.App.Command.SubCommand.Argument 2 | ---@field public complete? fun(prefix: string):string[] 3 | ---@field public required? boolean 4 | 5 | ---@class dansa.kit.App.Command.SubCommandSpecifier 6 | ---@field public desc? string 7 | ---@field public args? table 8 | ---@field public execute fun(params: dansa.kit.App.Command.ExecuteParams, arguments: table) 9 | 10 | ---@class dansa.kit.App.Command.SubCommand: dansa.kit.App.Command.SubCommandSpecifier 11 | ---@field public name string 12 | ---@field public args table 13 | 14 | ---@class dansa.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 dansa.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 dansa.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/dansa/kit/App/Config.lua: -------------------------------------------------------------------------------- 1 | local kit = require('dansa.kit') 2 | local Cache = require('dansa.kit.App.Cache') 3 | 4 | ---@class dansa.kit.App.Config.Schema 5 | 6 | ---@alias dansa.kit.App.Config.SchemaInternal dansa.kit.App.Config.Schema|{ revision: integer } 7 | 8 | ---@class dansa.kit.App.Config 9 | ---@field private _cache dansa.kit.App.Cache 10 | ---@field private _default dansa.kit.App.Config.SchemaInternal 11 | ---@field private _global dansa.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 dansa.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 dansa.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 dansa.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 dansa.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 dansa.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: dansa.kit.App.Config.Schema)|{ filetype: fun(filetypes: string|string[], config: dansa.kit.App.Config.Schema), buffer: fun(bufnr: integer, config: dansa.kit.App.Config.Schema) } 79 | function Config:create_setup_interface() 80 | return setmetatable({ 81 | ---@param filetypes string|string[] 82 | ---@param config dansa.kit.App.Config.Schema 83 | filetype = function(filetypes, config) 84 | self:filetype(filetypes, config) 85 | end, 86 | ---@param bufnr integer 87 | ---@param config dansa.kit.App.Config.Schema 88 | buffer = function(bufnr, config) 89 | self:buffer(bufnr, config) 90 | end, 91 | }, { 92 | ---@param config dansa.kit.App.Config.Schema 93 | __call = function(_, config) 94 | self:global(config) 95 | end, 96 | }) 97 | end 98 | 99 | return Config 100 | -------------------------------------------------------------------------------- /lua/dansa/kit/App/Event.lua: -------------------------------------------------------------------------------- 1 | ---@class dansa.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/dansa/kit/Async/AsyncTask.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: invisible 2 | local uv = require('luv') 3 | local kit = require('dansa.kit') 4 | 5 | local is_thread = vim.is_thread() 6 | 7 | ---@class dansa.kit.Async.AsyncTask 8 | ---@field private value any 9 | ---@field private status dansa.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 dansa.kit.Async.AsyncTask 18 | ---@param status dansa.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 dansa.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 dansa.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 dansa.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 dansa.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 dansa.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('dansa.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: dansa.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 dansa.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 dansa.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/dansa/kit/Async/ScheduledTimer.lua: -------------------------------------------------------------------------------- 1 | ---@class dansa.kit.Async.ScheduledTimer 2 | ---@field private _timer uv.uv_timer_t 3 | ---@field private _running boolean 4 | ---@field private _revision integer 5 | local ScheduledTimer = {} 6 | ScheduledTimer.__index = ScheduledTimer 7 | 8 | ---Create new timer. 9 | function ScheduledTimer.new() 10 | return setmetatable({ 11 | _timer = assert(vim.uv.new_timer()), 12 | _running = false, 13 | _revision = 0, 14 | }, ScheduledTimer) 15 | end 16 | 17 | ---Check if timer is running. 18 | ---@return boolean 19 | function ScheduledTimer:is_running() 20 | return self._running 21 | end 22 | 23 | ---Start timer. 24 | function ScheduledTimer:start(ms, repeat_ms, callback) 25 | self._timer:stop() 26 | self._running = true 27 | self._revision = self._revision + 1 28 | local revision = self._revision 29 | 30 | local on_tick 31 | local tick 32 | 33 | on_tick = function() 34 | if revision ~= self._revision then 35 | return 36 | end 37 | if vim.in_fast_event() then 38 | vim.schedule(tick) 39 | else 40 | tick() 41 | end 42 | end 43 | 44 | tick = function() 45 | if revision ~= self._revision then 46 | return 47 | end 48 | callback() -- `callback()` can restart timer, so it need to check revision here again. 49 | if revision ~= self._revision then 50 | return 51 | end 52 | if repeat_ms ~= 0 then 53 | self._timer:start(repeat_ms, 0, on_tick) 54 | else 55 | self._running = false 56 | end 57 | end 58 | 59 | if ms == 0 then 60 | on_tick() 61 | return 62 | end 63 | self._timer:start(ms, 0, on_tick) 64 | end 65 | 66 | ---Stop timer. 67 | function ScheduledTimer:stop() 68 | self._timer:stop() 69 | self._running = false 70 | self._revision = self._revision + 1 71 | end 72 | 73 | return ScheduledTimer 74 | -------------------------------------------------------------------------------- /lua/dansa/kit/Async/Worker.lua: -------------------------------------------------------------------------------- 1 | local uv = require('luv') 2 | local AsyncTask = require('dansa.kit.Async.AsyncTask') 3 | 4 | ---@class dansa.kit.Async.WorkerOption 5 | ---@field public runtimepath string[] 6 | 7 | ---@class dansa.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 dansa.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('dansa.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/dansa/kit/Async/init.lua: -------------------------------------------------------------------------------- 1 | local AsyncTask = require('dansa.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 dansa.kit.Async.AsyncTask[] 13 | ---@return dansa.kit.Async.AsyncTask 14 | function Async.all(tasks) 15 | return AsyncTask.all(tasks) 16 | end 17 | 18 | ---Alias of AsyncTask.race. 19 | ---@param tasks dansa.kit.Async.AsyncTask[] 20 | ---@return dansa.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 dansa.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 dansa.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 dansa.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 dansa.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 dansa.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 dansa.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 dansa.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): dansa.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/dansa/kit/IO/init.lua: -------------------------------------------------------------------------------- 1 | local uv = vim.uv 2 | local Async = require('dansa.kit.Async') 3 | 4 | local bytes = { 5 | backslash = string.byte('\\'), 6 | slash = string.byte('/'), 7 | tilde = string.byte('~'), 8 | dot = string.byte('.'), 9 | } 10 | 11 | ---@param path string 12 | ---@return string 13 | local function sep(path) 14 | for i = 1, #path do 15 | local c = path:byte(i) 16 | if c == bytes.slash then 17 | return path 18 | end 19 | if c == bytes.backslash then 20 | return (path:gsub('\\', '/')) 21 | end 22 | end 23 | return path 24 | end 25 | 26 | local home = sep(assert(vim.uv.os_homedir())) 27 | 28 | ---@see https://github.com/luvit/luvit/blob/master/deps/fs.lua 29 | local IO = {} 30 | 31 | ---@class dansa.kit.IO.UV.Stat 32 | ---@field public dev integer 33 | ---@field public mode integer 34 | ---@field public nlink integer 35 | ---@field public uid integer 36 | ---@field public gid integer 37 | ---@field public rdev integer 38 | ---@field public ino integer 39 | ---@field public size integer 40 | ---@field public blksize integer 41 | ---@field public blocks integer 42 | ---@field public flags integer 43 | ---@field public gen integer 44 | ---@field public atime { sec: integer, nsec: integer } 45 | ---@field public mtime { sec: integer, nsec: integer } 46 | ---@field public ctime { sec: integer, nsec: integer } 47 | ---@field public birthtime { sec: integer, nsec: integer } 48 | ---@field public type string 49 | 50 | ---@enum dansa.kit.IO.UV.AccessMode 51 | IO.AccessMode = { 52 | r = 'r', 53 | rs = 'rs', 54 | sr = 'sr', 55 | ['r+'] = 'r+', 56 | ['rs+'] = 'rs+', 57 | ['sr+'] = 'sr+', 58 | w = 'w', 59 | wx = 'wx', 60 | xw = 'xw', 61 | ['w+'] = 'w+', 62 | ['wx+'] = 'wx+', 63 | ['xw+'] = 'xw+', 64 | a = 'a', 65 | ax = 'ax', 66 | xa = 'xa', 67 | ['a+'] = 'a+', 68 | ['ax+'] = 'ax+', 69 | ['xa+'] = 'xa+', 70 | } 71 | 72 | ---@enum dansa.kit.IO.WalkStatus 73 | IO.WalkStatus = { 74 | SkipDir = 1, 75 | Break = 2, 76 | } 77 | 78 | ---@type fun(path: string): dansa.kit.Async.AsyncTask 79 | local uv_fs_stat = Async.promisify(uv.fs_stat) 80 | 81 | ---@type fun(path: string): dansa.kit.Async.AsyncTask 82 | local uv_fs_unlink = Async.promisify(uv.fs_unlink) 83 | 84 | ---@type fun(path: string): dansa.kit.Async.AsyncTask 85 | local uv_fs_rmdir = Async.promisify(uv.fs_rmdir) 86 | 87 | ---@type fun(path: string, mode: integer): dansa.kit.Async.AsyncTask 88 | local uv_fs_mkdir = Async.promisify(uv.fs_mkdir) 89 | 90 | ---@type fun(from: string, to: string, option?: { excl?: boolean, ficlone?: boolean, ficlone_force?: boolean }): dansa.kit.Async.AsyncTask 91 | local uv_fs_copyfile = Async.promisify(uv.fs_copyfile) 92 | 93 | ---@type fun(path: string, flags: dansa.kit.IO.UV.AccessMode, mode: integer): dansa.kit.Async.AsyncTask 94 | local uv_fs_open = Async.promisify(uv.fs_open) 95 | 96 | ---@type fun(fd: userdata): dansa.kit.Async.AsyncTask 97 | local uv_fs_close = Async.promisify(uv.fs_close) 98 | 99 | ---@type fun(fd: userdata, chunk_size: integer, offset?: integer): dansa.kit.Async.AsyncTask 100 | local uv_fs_read = Async.promisify(uv.fs_read) 101 | 102 | ---@type fun(fd: userdata, content: string, offset?: integer): dansa.kit.Async.AsyncTask 103 | local uv_fs_write = Async.promisify(uv.fs_write) 104 | 105 | ---@type fun(fd: userdata, offset: integer): dansa.kit.Async.AsyncTask 106 | local uv_fs_ftruncate = Async.promisify(uv.fs_ftruncate) 107 | 108 | ---@type fun(path: string): dansa.kit.Async.AsyncTask 109 | local uv_fs_scandir = Async.promisify(uv.fs_scandir) 110 | 111 | ---@type fun(path: string): dansa.kit.Async.AsyncTask 112 | local uv_fs_realpath = Async.promisify(uv.fs_realpath) 113 | 114 | ---Return if the path is directory. 115 | ---@param path string 116 | ---@return dansa.kit.Async.AsyncTask 117 | function IO.is_directory(path) 118 | path = IO.normalize(path) 119 | return Async.run(function() 120 | return uv_fs_stat(path) 121 | :catch(function() 122 | return {} 123 | end) 124 | :await().type == 'directory' 125 | end) 126 | end 127 | 128 | ---Return if the path is exists. 129 | ---@param path string 130 | ---@return dansa.kit.Async.AsyncTask 131 | function IO.exists(path) 132 | path = IO.normalize(path) 133 | return Async.run(function() 134 | return uv_fs_stat(path) 135 | :next(function() 136 | return true 137 | end) 138 | :catch(function() 139 | return false 140 | end) 141 | :await() 142 | end) 143 | end 144 | 145 | ---Get realpath. 146 | ---@param path string 147 | ---@return dansa.kit.Async.AsyncTask 148 | function IO.realpath(path) 149 | path = IO.normalize(path) 150 | return Async.run(function() 151 | return IO.normalize(uv_fs_realpath(path):await()) 152 | end) 153 | end 154 | 155 | ---Return file stats or throw error. 156 | ---@param path string 157 | ---@return dansa.kit.Async.AsyncTask 158 | function IO.stat(path) 159 | path = IO.normalize(path) 160 | return Async.run(function() 161 | return uv_fs_stat(path):await() 162 | end) 163 | end 164 | 165 | ---Read file. 166 | ---@param path string 167 | ---@param chunk_size? integer 168 | ---@return dansa.kit.Async.AsyncTask 169 | function IO.read_file(path, chunk_size) 170 | path = IO.normalize(path) 171 | chunk_size = chunk_size or 1024 172 | return Async.run(function() 173 | local stat = uv_fs_stat(path):await() 174 | local fd = uv_fs_open(path, IO.AccessMode.r, tonumber('755', 8)):await() 175 | local ok, res = pcall(function() 176 | local chunks = {} 177 | local offset = 0 178 | while offset < stat.size do 179 | local chunk = uv_fs_read(fd, math.min(chunk_size, stat.size - offset), offset):await() 180 | if not chunk then 181 | break 182 | end 183 | table.insert(chunks, chunk) 184 | offset = offset + #chunk 185 | end 186 | return table.concat(chunks, ''):sub(1, stat.size - 1) -- remove EOF. 187 | end) 188 | uv_fs_close(fd):await() 189 | if not ok then 190 | error(res) 191 | end 192 | return res 193 | end) 194 | end 195 | 196 | ---Write file. 197 | ---@param path string 198 | ---@param content string 199 | ---@param chunk_size? integer 200 | function IO.write_file(path, content, chunk_size) 201 | path = IO.normalize(path) 202 | content = content .. '\n' -- add EOF. 203 | chunk_size = chunk_size or 1024 204 | return Async.run(function() 205 | local fd = uv_fs_open(path, IO.AccessMode.w, tonumber('755', 8)):await() 206 | local ok, err = pcall(function() 207 | local offset = 0 208 | while offset < #content do 209 | local chunk = content:sub(offset + 1, offset + chunk_size) 210 | offset = offset + uv_fs_write(fd, chunk, offset):await() 211 | end 212 | uv_fs_ftruncate(fd, offset):await() 213 | end) 214 | uv_fs_close(fd):await() 215 | if not ok then 216 | error(err) 217 | end 218 | end) 219 | end 220 | 221 | ---Create directory. 222 | ---@param path string 223 | ---@param mode integer 224 | ---@param option? { recursive?: boolean } 225 | function IO.mkdir(path, mode, option) 226 | path = IO.normalize(path) 227 | option = option or {} 228 | option.recursive = option.recursive or false 229 | return Async.run(function() 230 | if not option.recursive then 231 | uv_fs_mkdir(path, mode):await() 232 | else 233 | local not_exists = {} 234 | local current = path 235 | while current ~= '/' do 236 | local stat = uv_fs_stat(current):catch(function() end):await() 237 | if stat then 238 | break 239 | end 240 | table.insert(not_exists, 1, current) 241 | current = IO.dirname(current) 242 | end 243 | for _, dir in ipairs(not_exists) do 244 | uv_fs_mkdir(dir, mode):await() 245 | end 246 | end 247 | end) 248 | end 249 | 250 | ---Remove file or directory. 251 | ---@param start_path string 252 | ---@param option? { recursive?: boolean } 253 | function IO.rm(start_path, option) 254 | start_path = IO.normalize(start_path) 255 | option = option or {} 256 | option.recursive = option.recursive or false 257 | return Async.run(function() 258 | local stat = uv_fs_stat(start_path):await() 259 | if stat.type == 'directory' then 260 | local children = IO.scandir(start_path):await() 261 | if not option.recursive and #children > 0 then 262 | error(('IO.rm: `%s` is a directory and not empty.'):format(start_path)) 263 | end 264 | IO.walk(start_path, function(err, entry) 265 | if err then 266 | error('IO.rm: ' .. tostring(err)) 267 | end 268 | if entry.type == 'directory' then 269 | uv_fs_rmdir(entry.path):await() 270 | else 271 | uv_fs_unlink(entry.path):await() 272 | end 273 | end, { postorder = true }):await() 274 | else 275 | uv_fs_unlink(start_path):await() 276 | end 277 | end) 278 | end 279 | 280 | ---Copy file or directory. 281 | ---@param from any 282 | ---@param to any 283 | ---@param option? { recursive?: boolean } 284 | ---@return dansa.kit.Async.AsyncTask 285 | function IO.cp(from, to, option) 286 | from = IO.normalize(from) 287 | to = IO.normalize(to) 288 | option = option or {} 289 | option.recursive = option.recursive or false 290 | return Async.run(function() 291 | local stat = uv_fs_stat(from):await() 292 | if stat.type == 'directory' then 293 | if not option.recursive then 294 | error(('IO.cp: `%s` is a directory.'):format(from)) 295 | end 296 | local from_pat = ('^%s'):format(vim.pesc(from)) 297 | IO.walk(from, function(err, entry) 298 | if err then 299 | error('IO.cp: ' .. tostring(err)) 300 | end 301 | local new_path = entry.path:gsub(from_pat, to) 302 | if entry.type == 'directory' then 303 | IO.mkdir(new_path, tonumber(stat.mode, 10), { recursive = true }):await() 304 | else 305 | uv_fs_copyfile(entry.path, new_path):await() 306 | end 307 | end):await() 308 | else 309 | uv_fs_copyfile(from, to):await() 310 | end 311 | end) 312 | end 313 | 314 | ---Walk directory entries recursively. 315 | ---@param start_path string 316 | ---@param callback fun(err: string|nil, entry: { path: string, type: string }): dansa.kit.IO.WalkStatus? 317 | ---@param option? { postorder?: boolean } 318 | function IO.walk(start_path, callback, option) 319 | start_path = IO.normalize(start_path) 320 | option = option or {} 321 | option.postorder = option.postorder or false 322 | return Async.run(function() 323 | local function walk_pre(dir) 324 | local ok, iter_entries = pcall(function() 325 | return IO.iter_scandir(dir.path):await() 326 | end) 327 | if not ok then 328 | return callback(iter_entries, dir) 329 | end 330 | local status = callback(nil, dir) 331 | if status == IO.WalkStatus.SkipDir then 332 | return 333 | elseif status == IO.WalkStatus.Break then 334 | return status 335 | end 336 | for entry in iter_entries do 337 | if entry.type == 'directory' then 338 | if walk_pre(entry) == IO.WalkStatus.Break then 339 | return IO.WalkStatus.Break 340 | end 341 | else 342 | if callback(nil, entry) == IO.WalkStatus.Break then 343 | return IO.WalkStatus.Break 344 | end 345 | end 346 | end 347 | end 348 | 349 | local function walk_post(dir) 350 | local ok, iter_entries = pcall(function() 351 | return IO.iter_scandir(dir.path):await() 352 | end) 353 | if not ok then 354 | return callback(iter_entries, dir) 355 | end 356 | for entry in iter_entries do 357 | if entry.type == 'directory' then 358 | if walk_post(entry) == IO.WalkStatus.Break then 359 | return IO.WalkStatus.Break 360 | end 361 | else 362 | if callback(nil, entry) == IO.WalkStatus.Break then 363 | return IO.WalkStatus.Break 364 | end 365 | end 366 | end 367 | return callback(nil, dir) 368 | end 369 | 370 | if not IO.is_directory(start_path) then 371 | error(('IO.walk: `%s` is not a directory.'):format(start_path)) 372 | end 373 | if option.postorder then 374 | walk_post({ path = start_path, type = 'directory' }) 375 | else 376 | walk_pre({ path = start_path, type = 'directory' }) 377 | end 378 | end) 379 | end 380 | 381 | ---Scan directory entries. 382 | ---@param path string 383 | ---@return dansa.kit.Async.AsyncTask 384 | function IO.scandir(path) 385 | path = IO.normalize(path) 386 | return Async.run(function() 387 | local fd = uv_fs_scandir(path):await() 388 | local entries = {} 389 | while true do 390 | local name, type = uv.fs_scandir_next(fd) 391 | if not name then 392 | break 393 | end 394 | table.insert(entries, { 395 | type = type, 396 | path = IO.join(path, name), 397 | }) 398 | end 399 | return entries 400 | end) 401 | end 402 | 403 | ---Scan directory entries. 404 | ---@param path any 405 | ---@return dansa.kit.Async.AsyncTask 406 | function IO.iter_scandir(path) 407 | path = IO.normalize(path) 408 | return Async.run(function() 409 | local fd = uv_fs_scandir(path):await() 410 | return function() 411 | local name, type = uv.fs_scandir_next(fd) 412 | if name then 413 | return { 414 | type = type, 415 | path = IO.join(path, name), 416 | } 417 | end 418 | end 419 | end) 420 | end 421 | 422 | ---Return normalized path. 423 | ---@param path string 424 | ---@return string 425 | function IO.normalize(path) 426 | path = sep(path) 427 | 428 | -- remove trailing slash. 429 | if #path > 1 and path:byte(-1) == bytes.slash then 430 | path = path:sub(1, -2) 431 | end 432 | 433 | -- homedir. 434 | if path:byte(1) == bytes.tilde and path:byte(2) == bytes.slash then 435 | path = (path:gsub('^~/', home)) 436 | end 437 | 438 | -- absolute. 439 | if IO.is_absolute(path) then 440 | return path 441 | end 442 | 443 | -- resolve relative path. 444 | return IO.join(IO.cwd(), path) 445 | end 446 | 447 | do 448 | local cache = { 449 | raw = nil, 450 | fix = nil 451 | } 452 | 453 | ---Return the current working directory. 454 | ---@return string 455 | function IO.cwd() 456 | local cwd = assert(uv.cwd()) 457 | if cache.raw == cwd then 458 | return cache.fix 459 | end 460 | cache.raw = cwd 461 | cache.fix = sep(cwd) 462 | return cache.fix 463 | end 464 | end 465 | 466 | do 467 | local cache_pat = {} 468 | 469 | ---Join the paths. 470 | ---@param base string 471 | ---@vararg string 472 | ---@return string 473 | function IO.join(base, ...) 474 | base = sep(base) 475 | 476 | -- remove trailing slash. 477 | -- ./ → ./ 478 | -- aaa/ → aaa 479 | if not (base == './' or base == '../') and base:byte(-1) == bytes.slash then 480 | base = base:sub(1, -2) 481 | end 482 | 483 | for i = 1, select('#', ...) do 484 | local path = sep(select(i, ...)) 485 | local path_s = 1 486 | if path:byte(path_s) == bytes.dot and path:byte(path_s + 1) == bytes.slash then 487 | path_s = path_s + 2 488 | end 489 | local up_count = 0 490 | while path:byte(path_s) == bytes.dot and path:byte(path_s + 1) == bytes.dot and path:byte(path_s + 2) == bytes.slash do 491 | up_count = up_count + 1 492 | path_s = path_s + 3 493 | end 494 | if path_s > 1 then 495 | cache_pat[path_s] = cache_pat[path_s] or ('^%s'):format(('.'):rep(path_s - 2)) 496 | end 497 | 498 | -- optimize for avoiding new string creation. 499 | if path_s == 1 then 500 | base = ('%s/%s'):format(IO.dirname(base, up_count), path) 501 | else 502 | base = path:gsub(cache_pat[path_s], IO.dirname(base, up_count)) 503 | end 504 | end 505 | return base 506 | end 507 | end 508 | 509 | ---Return the path of the current working directory. 510 | ---@param path string 511 | ---@param level? integer 512 | ---@return string 513 | function IO.dirname(path, level) 514 | path = sep(path) 515 | level = level or 1 516 | 517 | if level == 0 then 518 | return path 519 | end 520 | 521 | for i = #path - 1, 1, -1 do 522 | if path:byte(i) == bytes.slash then 523 | if level == 1 then 524 | return path:sub(1, i - 1) 525 | end 526 | level = level - 1 527 | end 528 | end 529 | return path 530 | end 531 | 532 | ---Return the path is absolute or not. 533 | ---@param path string 534 | ---@return boolean 535 | function IO.is_absolute(path) 536 | path = sep(path) 537 | return path:byte(1) == bytes.slash or path:match('^%a:/') 538 | end 539 | 540 | return IO 541 | -------------------------------------------------------------------------------- /lua/dansa/kit/LSP/Client.lua: -------------------------------------------------------------------------------- 1 | local LSP = require('dansa.kit.LSP') 2 | local AsyncTask = require('dansa.kit.Async.AsyncTask') 3 | 4 | ---@class dansa.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 dansa.kit.LSP.Client 12 | function Client.new(client) 13 | local self = setmetatable({}, Client) 14 | self.client = client 15 | return self 16 | end 17 | 18 | ---Send request. 19 | ---@param method string 20 | ---@param params table 21 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 22 | function Client:request(method, params) 23 | local that, _, request_id, reject_ = self, nil, nil, nil 24 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 25 | local task = AsyncTask.new(function(resolve, reject) 26 | reject_ = reject 27 | _, request_id = self.client:request(method, params, function(err, res) 28 | if err then 29 | reject(err) 30 | else 31 | resolve(res) 32 | end 33 | end) 34 | end) 35 | function task.cancel() 36 | that.client:cancel_request(request_id) 37 | reject_(LSP.ErrorCodes.RequestCancelled) 38 | end 39 | return task 40 | end 41 | 42 | ---@param params dansa.kit.LSP.ImplementationParams 43 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 44 | function Client:textDocument_implementation(params) 45 | local that, _, request_id, reject_ = self, nil, nil, nil 46 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 47 | local task = AsyncTask.new(function(resolve, reject) 48 | reject_ = reject 49 | _, request_id = self.client:request('textDocument/implementation', params, function(err, res) 50 | if err then 51 | reject(err) 52 | else 53 | resolve(res) 54 | end 55 | end) 56 | end) 57 | function task.cancel() 58 | that.client:cancel_request(request_id) 59 | reject_(LSP.ErrorCodes.RequestCancelled) 60 | end 61 | return task 62 | end 63 | 64 | ---@param params dansa.kit.LSP.TypeDefinitionParams 65 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 66 | function Client:textDocument_typeDefinition(params) 67 | local that, _, request_id, reject_ = self, nil, nil, nil 68 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 69 | local task = AsyncTask.new(function(resolve, reject) 70 | reject_ = reject 71 | _, request_id = self.client:request('textDocument/typeDefinition', params, function(err, res) 72 | if err then 73 | reject(err) 74 | else 75 | resolve(res) 76 | end 77 | end) 78 | end) 79 | function task.cancel() 80 | that.client:cancel_request(request_id) 81 | reject_(LSP.ErrorCodes.RequestCancelled) 82 | end 83 | return task 84 | end 85 | 86 | ---@param params nil 87 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 88 | function Client:workspace_workspaceFolders(params) 89 | local that, _, request_id, reject_ = self, nil, nil, nil 90 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 91 | local task = AsyncTask.new(function(resolve, reject) 92 | reject_ = reject 93 | _, request_id = self.client:request('workspace/workspaceFolders', params, function(err, res) 94 | if err then 95 | reject(err) 96 | else 97 | resolve(res) 98 | end 99 | end) 100 | end) 101 | function task.cancel() 102 | that.client:cancel_request(request_id) 103 | reject_(LSP.ErrorCodes.RequestCancelled) 104 | end 105 | return task 106 | end 107 | 108 | ---@param params dansa.kit.LSP.ConfigurationParams 109 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 110 | function Client:workspace_configuration(params) 111 | local that, _, request_id, reject_ = self, nil, nil, nil 112 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 113 | local task = AsyncTask.new(function(resolve, reject) 114 | reject_ = reject 115 | _, request_id = self.client:request('workspace/configuration', params, function(err, res) 116 | if err then 117 | reject(err) 118 | else 119 | resolve(res) 120 | end 121 | end) 122 | end) 123 | function task.cancel() 124 | that.client:cancel_request(request_id) 125 | reject_(LSP.ErrorCodes.RequestCancelled) 126 | end 127 | return task 128 | end 129 | 130 | ---@param params dansa.kit.LSP.DocumentColorParams 131 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 132 | function Client:textDocument_documentColor(params) 133 | local that, _, request_id, reject_ = self, nil, nil, nil 134 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 135 | local task = AsyncTask.new(function(resolve, reject) 136 | reject_ = reject 137 | _, request_id = self.client:request('textDocument/documentColor', params, function(err, res) 138 | if err then 139 | reject(err) 140 | else 141 | resolve(res) 142 | end 143 | end) 144 | end) 145 | function task.cancel() 146 | that.client:cancel_request(request_id) 147 | reject_(LSP.ErrorCodes.RequestCancelled) 148 | end 149 | return task 150 | end 151 | 152 | ---@param params dansa.kit.LSP.ColorPresentationParams 153 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 154 | function Client:textDocument_colorPresentation(params) 155 | local that, _, request_id, reject_ = self, nil, nil, nil 156 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 157 | local task = AsyncTask.new(function(resolve, reject) 158 | reject_ = reject 159 | _, request_id = self.client:request('textDocument/colorPresentation', params, function(err, res) 160 | if err then 161 | reject(err) 162 | else 163 | resolve(res) 164 | end 165 | end) 166 | end) 167 | function task.cancel() 168 | that.client:cancel_request(request_id) 169 | reject_(LSP.ErrorCodes.RequestCancelled) 170 | end 171 | return task 172 | end 173 | 174 | ---@param params dansa.kit.LSP.FoldingRangeParams 175 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 176 | function Client:textDocument_foldingRange(params) 177 | local that, _, request_id, reject_ = self, nil, nil, nil 178 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 179 | local task = AsyncTask.new(function(resolve, reject) 180 | reject_ = reject 181 | _, request_id = self.client:request('textDocument/foldingRange', params, function(err, res) 182 | if err then 183 | reject(err) 184 | else 185 | resolve(res) 186 | end 187 | end) 188 | end) 189 | function task.cancel() 190 | that.client:cancel_request(request_id) 191 | reject_(LSP.ErrorCodes.RequestCancelled) 192 | end 193 | return task 194 | end 195 | 196 | ---@param params nil 197 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 198 | function Client:workspace_foldingRange_refresh(params) 199 | local that, _, request_id, reject_ = self, nil, nil, nil 200 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 201 | local task = AsyncTask.new(function(resolve, reject) 202 | reject_ = reject 203 | _, request_id = self.client:request('workspace/foldingRange/refresh', 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 dansa.kit.LSP.DeclarationParams 219 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 220 | function Client:textDocument_declaration(params) 221 | local that, _, request_id, reject_ = self, nil, nil, nil 222 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 223 | local task = AsyncTask.new(function(resolve, reject) 224 | reject_ = reject 225 | _, request_id = self.client:request('textDocument/declaration', params, function(err, res) 226 | if err then 227 | reject(err) 228 | else 229 | resolve(res) 230 | end 231 | end) 232 | end) 233 | function task.cancel() 234 | that.client:cancel_request(request_id) 235 | reject_(LSP.ErrorCodes.RequestCancelled) 236 | end 237 | return task 238 | end 239 | 240 | ---@param params dansa.kit.LSP.SelectionRangeParams 241 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 242 | function Client:textDocument_selectionRange(params) 243 | local that, _, request_id, reject_ = self, nil, nil, nil 244 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 245 | local task = AsyncTask.new(function(resolve, reject) 246 | reject_ = reject 247 | _, request_id = self.client:request('textDocument/selectionRange', params, function(err, res) 248 | if err then 249 | reject(err) 250 | else 251 | resolve(res) 252 | end 253 | end) 254 | end) 255 | function task.cancel() 256 | that.client:cancel_request(request_id) 257 | reject_(LSP.ErrorCodes.RequestCancelled) 258 | end 259 | return task 260 | end 261 | 262 | ---@param params dansa.kit.LSP.WorkDoneProgressCreateParams 263 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 264 | function Client:window_workDoneProgress_create(params) 265 | local that, _, request_id, reject_ = self, nil, nil, nil 266 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 267 | local task = AsyncTask.new(function(resolve, reject) 268 | reject_ = reject 269 | _, request_id = self.client:request('window/workDoneProgress/create', params, function(err, res) 270 | if err then 271 | reject(err) 272 | else 273 | resolve(res) 274 | end 275 | end) 276 | end) 277 | function task.cancel() 278 | that.client:cancel_request(request_id) 279 | reject_(LSP.ErrorCodes.RequestCancelled) 280 | end 281 | return task 282 | end 283 | 284 | ---@param params dansa.kit.LSP.CallHierarchyPrepareParams 285 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 286 | function Client:textDocument_prepareCallHierarchy(params) 287 | local that, _, request_id, reject_ = self, nil, nil, nil 288 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 289 | local task = AsyncTask.new(function(resolve, reject) 290 | reject_ = reject 291 | _, request_id = self.client:request('textDocument/prepareCallHierarchy', params, function(err, res) 292 | if err then 293 | reject(err) 294 | else 295 | resolve(res) 296 | end 297 | end) 298 | end) 299 | function task.cancel() 300 | that.client:cancel_request(request_id) 301 | reject_(LSP.ErrorCodes.RequestCancelled) 302 | end 303 | return task 304 | end 305 | 306 | ---@param params dansa.kit.LSP.CallHierarchyIncomingCallsParams 307 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 308 | function Client:callHierarchy_incomingCalls(params) 309 | local that, _, request_id, reject_ = self, nil, nil, nil 310 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 311 | local task = AsyncTask.new(function(resolve, reject) 312 | reject_ = reject 313 | _, request_id = self.client:request('callHierarchy/incomingCalls', params, function(err, res) 314 | if err then 315 | reject(err) 316 | else 317 | resolve(res) 318 | end 319 | end) 320 | end) 321 | function task.cancel() 322 | that.client:cancel_request(request_id) 323 | reject_(LSP.ErrorCodes.RequestCancelled) 324 | end 325 | return task 326 | end 327 | 328 | ---@param params dansa.kit.LSP.CallHierarchyOutgoingCallsParams 329 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 330 | function Client:callHierarchy_outgoingCalls(params) 331 | local that, _, request_id, reject_ = self, nil, nil, nil 332 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 333 | local task = AsyncTask.new(function(resolve, reject) 334 | reject_ = reject 335 | _, request_id = self.client:request('callHierarchy/outgoingCalls', params, function(err, res) 336 | if err then 337 | reject(err) 338 | else 339 | resolve(res) 340 | end 341 | end) 342 | end) 343 | function task.cancel() 344 | that.client:cancel_request(request_id) 345 | reject_(LSP.ErrorCodes.RequestCancelled) 346 | end 347 | return task 348 | end 349 | 350 | ---@param params dansa.kit.LSP.SemanticTokensParams 351 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 352 | function Client:textDocument_semanticTokens_full(params) 353 | local that, _, request_id, reject_ = self, nil, nil, nil 354 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 355 | local task = AsyncTask.new(function(resolve, reject) 356 | reject_ = reject 357 | _, request_id = self.client:request('textDocument/semanticTokens/full', params, function(err, res) 358 | if err then 359 | reject(err) 360 | else 361 | resolve(res) 362 | end 363 | end) 364 | end) 365 | function task.cancel() 366 | that.client:cancel_request(request_id) 367 | reject_(LSP.ErrorCodes.RequestCancelled) 368 | end 369 | return task 370 | end 371 | 372 | ---@param params dansa.kit.LSP.SemanticTokensDeltaParams 373 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 374 | function Client:textDocument_semanticTokens_full_delta(params) 375 | local that, _, request_id, reject_ = self, nil, nil, nil 376 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 377 | local task = AsyncTask.new(function(resolve, reject) 378 | reject_ = reject 379 | _, request_id = self.client:request('textDocument/semanticTokens/full/delta', params, function(err, res) 380 | if err then 381 | reject(err) 382 | else 383 | resolve(res) 384 | end 385 | end) 386 | end) 387 | function task.cancel() 388 | that.client:cancel_request(request_id) 389 | reject_(LSP.ErrorCodes.RequestCancelled) 390 | end 391 | return task 392 | end 393 | 394 | ---@param params dansa.kit.LSP.SemanticTokensRangeParams 395 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 396 | function Client:textDocument_semanticTokens_range(params) 397 | local that, _, request_id, reject_ = self, nil, nil, nil 398 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 399 | local task = AsyncTask.new(function(resolve, reject) 400 | reject_ = reject 401 | _, request_id = self.client:request('textDocument/semanticTokens/range', params, function(err, res) 402 | if err then 403 | reject(err) 404 | else 405 | resolve(res) 406 | end 407 | end) 408 | end) 409 | function task.cancel() 410 | that.client:cancel_request(request_id) 411 | reject_(LSP.ErrorCodes.RequestCancelled) 412 | end 413 | return task 414 | end 415 | 416 | ---@param params nil 417 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 418 | function Client:workspace_semanticTokens_refresh(params) 419 | local that, _, request_id, reject_ = self, nil, nil, nil 420 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 421 | local task = AsyncTask.new(function(resolve, reject) 422 | reject_ = reject 423 | _, request_id = self.client:request('workspace/semanticTokens/refresh', 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 dansa.kit.LSP.ShowDocumentParams 439 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 440 | function Client:window_showDocument(params) 441 | local that, _, request_id, reject_ = self, nil, nil, nil 442 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 443 | local task = AsyncTask.new(function(resolve, reject) 444 | reject_ = reject 445 | _, request_id = self.client:request('window/showDocument', params, function(err, res) 446 | if err then 447 | reject(err) 448 | else 449 | resolve(res) 450 | end 451 | end) 452 | end) 453 | function task.cancel() 454 | that.client:cancel_request(request_id) 455 | reject_(LSP.ErrorCodes.RequestCancelled) 456 | end 457 | return task 458 | end 459 | 460 | ---@param params dansa.kit.LSP.LinkedEditingRangeParams 461 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 462 | function Client:textDocument_linkedEditingRange(params) 463 | local that, _, request_id, reject_ = self, nil, nil, nil 464 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 465 | local task = AsyncTask.new(function(resolve, reject) 466 | reject_ = reject 467 | _, request_id = self.client:request('textDocument/linkedEditingRange', params, function(err, res) 468 | if err then 469 | reject(err) 470 | else 471 | resolve(res) 472 | end 473 | end) 474 | end) 475 | function task.cancel() 476 | that.client:cancel_request(request_id) 477 | reject_(LSP.ErrorCodes.RequestCancelled) 478 | end 479 | return task 480 | end 481 | 482 | ---@param params dansa.kit.LSP.CreateFilesParams 483 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 484 | function Client:workspace_willCreateFiles(params) 485 | local that, _, request_id, reject_ = self, nil, nil, nil 486 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 487 | local task = AsyncTask.new(function(resolve, reject) 488 | reject_ = reject 489 | _, request_id = self.client:request('workspace/willCreateFiles', params, function(err, res) 490 | if err then 491 | reject(err) 492 | else 493 | resolve(res) 494 | end 495 | end) 496 | end) 497 | function task.cancel() 498 | that.client:cancel_request(request_id) 499 | reject_(LSP.ErrorCodes.RequestCancelled) 500 | end 501 | return task 502 | end 503 | 504 | ---@param params dansa.kit.LSP.RenameFilesParams 505 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 506 | function Client:workspace_willRenameFiles(params) 507 | local that, _, request_id, reject_ = self, nil, nil, nil 508 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 509 | local task = AsyncTask.new(function(resolve, reject) 510 | reject_ = reject 511 | _, request_id = self.client:request('workspace/willRenameFiles', params, function(err, res) 512 | if err then 513 | reject(err) 514 | else 515 | resolve(res) 516 | end 517 | end) 518 | end) 519 | function task.cancel() 520 | that.client:cancel_request(request_id) 521 | reject_(LSP.ErrorCodes.RequestCancelled) 522 | end 523 | return task 524 | end 525 | 526 | ---@param params dansa.kit.LSP.DeleteFilesParams 527 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 528 | function Client:workspace_willDeleteFiles(params) 529 | local that, _, request_id, reject_ = self, nil, nil, nil 530 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 531 | local task = AsyncTask.new(function(resolve, reject) 532 | reject_ = reject 533 | _, request_id = self.client:request('workspace/willDeleteFiles', params, function(err, res) 534 | if err then 535 | reject(err) 536 | else 537 | resolve(res) 538 | end 539 | end) 540 | end) 541 | function task.cancel() 542 | that.client:cancel_request(request_id) 543 | reject_(LSP.ErrorCodes.RequestCancelled) 544 | end 545 | return task 546 | end 547 | 548 | ---@param params dansa.kit.LSP.MonikerParams 549 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 550 | function Client:textDocument_moniker(params) 551 | local that, _, request_id, reject_ = self, nil, nil, nil 552 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 553 | local task = AsyncTask.new(function(resolve, reject) 554 | reject_ = reject 555 | _, request_id = self.client:request('textDocument/moniker', params, function(err, res) 556 | if err then 557 | reject(err) 558 | else 559 | resolve(res) 560 | end 561 | end) 562 | end) 563 | function task.cancel() 564 | that.client:cancel_request(request_id) 565 | reject_(LSP.ErrorCodes.RequestCancelled) 566 | end 567 | return task 568 | end 569 | 570 | ---@param params dansa.kit.LSP.TypeHierarchyPrepareParams 571 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 572 | function Client:textDocument_prepareTypeHierarchy(params) 573 | local that, _, request_id, reject_ = self, nil, nil, nil 574 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 575 | local task = AsyncTask.new(function(resolve, reject) 576 | reject_ = reject 577 | _, request_id = self.client:request('textDocument/prepareTypeHierarchy', params, function(err, res) 578 | if err then 579 | reject(err) 580 | else 581 | resolve(res) 582 | end 583 | end) 584 | end) 585 | function task.cancel() 586 | that.client:cancel_request(request_id) 587 | reject_(LSP.ErrorCodes.RequestCancelled) 588 | end 589 | return task 590 | end 591 | 592 | ---@param params dansa.kit.LSP.TypeHierarchySupertypesParams 593 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 594 | function Client:typeHierarchy_supertypes(params) 595 | local that, _, request_id, reject_ = self, nil, nil, nil 596 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 597 | local task = AsyncTask.new(function(resolve, reject) 598 | reject_ = reject 599 | _, request_id = self.client:request('typeHierarchy/supertypes', params, function(err, res) 600 | if err then 601 | reject(err) 602 | else 603 | resolve(res) 604 | end 605 | end) 606 | end) 607 | function task.cancel() 608 | that.client:cancel_request(request_id) 609 | reject_(LSP.ErrorCodes.RequestCancelled) 610 | end 611 | return task 612 | end 613 | 614 | ---@param params dansa.kit.LSP.TypeHierarchySubtypesParams 615 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 616 | function Client:typeHierarchy_subtypes(params) 617 | local that, _, request_id, reject_ = self, nil, nil, nil 618 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 619 | local task = AsyncTask.new(function(resolve, reject) 620 | reject_ = reject 621 | _, request_id = self.client:request('typeHierarchy/subtypes', params, function(err, res) 622 | if err then 623 | reject(err) 624 | else 625 | resolve(res) 626 | end 627 | end) 628 | end) 629 | function task.cancel() 630 | that.client:cancel_request(request_id) 631 | reject_(LSP.ErrorCodes.RequestCancelled) 632 | end 633 | return task 634 | end 635 | 636 | ---@param params dansa.kit.LSP.InlineValueParams 637 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 638 | function Client:textDocument_inlineValue(params) 639 | local that, _, request_id, reject_ = self, nil, nil, nil 640 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 641 | local task = AsyncTask.new(function(resolve, reject) 642 | reject_ = reject 643 | _, request_id = self.client:request('textDocument/inlineValue', 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 nil 659 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 660 | function Client:workspace_inlineValue_refresh(params) 661 | local that, _, request_id, reject_ = self, nil, nil, nil 662 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 663 | local task = AsyncTask.new(function(resolve, reject) 664 | reject_ = reject 665 | _, request_id = self.client:request('workspace/inlineValue/refresh', params, function(err, res) 666 | if err then 667 | reject(err) 668 | else 669 | resolve(res) 670 | end 671 | end) 672 | end) 673 | function task.cancel() 674 | that.client:cancel_request(request_id) 675 | reject_(LSP.ErrorCodes.RequestCancelled) 676 | end 677 | return task 678 | end 679 | 680 | ---@param params dansa.kit.LSP.InlayHintParams 681 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 682 | function Client:textDocument_inlayHint(params) 683 | local that, _, request_id, reject_ = self, nil, nil, nil 684 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 685 | local task = AsyncTask.new(function(resolve, reject) 686 | reject_ = reject 687 | _, request_id = self.client:request('textDocument/inlayHint', params, function(err, res) 688 | if err then 689 | reject(err) 690 | else 691 | resolve(res) 692 | end 693 | end) 694 | end) 695 | function task.cancel() 696 | that.client:cancel_request(request_id) 697 | reject_(LSP.ErrorCodes.RequestCancelled) 698 | end 699 | return task 700 | end 701 | 702 | ---@param params dansa.kit.LSP.InlayHint 703 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 704 | function Client:inlayHint_resolve(params) 705 | local that, _, request_id, reject_ = self, nil, nil, nil 706 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 707 | local task = AsyncTask.new(function(resolve, reject) 708 | reject_ = reject 709 | _, request_id = self.client:request('inlayHint/resolve', params, function(err, res) 710 | if err then 711 | reject(err) 712 | else 713 | resolve(res) 714 | end 715 | end) 716 | end) 717 | function task.cancel() 718 | that.client:cancel_request(request_id) 719 | reject_(LSP.ErrorCodes.RequestCancelled) 720 | end 721 | return task 722 | end 723 | 724 | ---@param params nil 725 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 726 | function Client:workspace_inlayHint_refresh(params) 727 | local that, _, request_id, reject_ = self, nil, nil, nil 728 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 729 | local task = AsyncTask.new(function(resolve, reject) 730 | reject_ = reject 731 | _, request_id = self.client:request('workspace/inlayHint/refresh', params, function(err, res) 732 | if err then 733 | reject(err) 734 | else 735 | resolve(res) 736 | end 737 | end) 738 | end) 739 | function task.cancel() 740 | that.client:cancel_request(request_id) 741 | reject_(LSP.ErrorCodes.RequestCancelled) 742 | end 743 | return task 744 | end 745 | 746 | ---@param params dansa.kit.LSP.DocumentDiagnosticParams 747 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 748 | function Client:textDocument_diagnostic(params) 749 | local that, _, request_id, reject_ = self, nil, nil, nil 750 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 751 | local task = AsyncTask.new(function(resolve, reject) 752 | reject_ = reject 753 | _, request_id = self.client:request('textDocument/diagnostic', params, function(err, res) 754 | if err then 755 | reject(err) 756 | else 757 | resolve(res) 758 | end 759 | end) 760 | end) 761 | function task.cancel() 762 | that.client:cancel_request(request_id) 763 | reject_(LSP.ErrorCodes.RequestCancelled) 764 | end 765 | return task 766 | end 767 | 768 | ---@param params dansa.kit.LSP.WorkspaceDiagnosticParams 769 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 770 | function Client:workspace_diagnostic(params) 771 | local that, _, request_id, reject_ = self, nil, nil, nil 772 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 773 | local task = AsyncTask.new(function(resolve, reject) 774 | reject_ = reject 775 | _, request_id = self.client:request('workspace/diagnostic', params, function(err, res) 776 | if err then 777 | reject(err) 778 | else 779 | resolve(res) 780 | end 781 | end) 782 | end) 783 | function task.cancel() 784 | that.client:cancel_request(request_id) 785 | reject_(LSP.ErrorCodes.RequestCancelled) 786 | end 787 | return task 788 | end 789 | 790 | ---@param params nil 791 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 792 | function Client:workspace_diagnostic_refresh(params) 793 | local that, _, request_id, reject_ = self, nil, nil, nil 794 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 795 | local task = AsyncTask.new(function(resolve, reject) 796 | reject_ = reject 797 | _, request_id = self.client:request('workspace/diagnostic/refresh', params, function(err, res) 798 | if err then 799 | reject(err) 800 | else 801 | resolve(res) 802 | end 803 | end) 804 | end) 805 | function task.cancel() 806 | that.client:cancel_request(request_id) 807 | reject_(LSP.ErrorCodes.RequestCancelled) 808 | end 809 | return task 810 | end 811 | 812 | ---@param params dansa.kit.LSP.InlineCompletionParams 813 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 814 | function Client:textDocument_inlineCompletion(params) 815 | local that, _, request_id, reject_ = self, nil, nil, nil 816 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 817 | local task = AsyncTask.new(function(resolve, reject) 818 | reject_ = reject 819 | _, request_id = self.client:request('textDocument/inlineCompletion', params, function(err, res) 820 | if err then 821 | reject(err) 822 | else 823 | resolve(res) 824 | end 825 | end) 826 | end) 827 | function task.cancel() 828 | that.client:cancel_request(request_id) 829 | reject_(LSP.ErrorCodes.RequestCancelled) 830 | end 831 | return task 832 | end 833 | 834 | ---@param params dansa.kit.LSP.RegistrationParams 835 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 836 | function Client:client_registerCapability(params) 837 | local that, _, request_id, reject_ = self, nil, nil, nil 838 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 839 | local task = AsyncTask.new(function(resolve, reject) 840 | reject_ = reject 841 | _, request_id = self.client:request('client/registerCapability', params, function(err, res) 842 | if err then 843 | reject(err) 844 | else 845 | resolve(res) 846 | end 847 | end) 848 | end) 849 | function task.cancel() 850 | that.client:cancel_request(request_id) 851 | reject_(LSP.ErrorCodes.RequestCancelled) 852 | end 853 | return task 854 | end 855 | 856 | ---@param params dansa.kit.LSP.UnregistrationParams 857 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 858 | function Client:client_unregisterCapability(params) 859 | local that, _, request_id, reject_ = self, nil, nil, nil 860 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 861 | local task = AsyncTask.new(function(resolve, reject) 862 | reject_ = reject 863 | _, request_id = self.client:request('client/unregisterCapability', 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 dansa.kit.LSP.InitializeParams 879 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 880 | function Client:initialize(params) 881 | local that, _, request_id, reject_ = self, nil, nil, nil 882 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 883 | local task = AsyncTask.new(function(resolve, reject) 884 | reject_ = reject 885 | _, request_id = self.client:request('initialize', params, function(err, res) 886 | if err then 887 | reject(err) 888 | else 889 | resolve(res) 890 | end 891 | end) 892 | end) 893 | function task.cancel() 894 | that.client:cancel_request(request_id) 895 | reject_(LSP.ErrorCodes.RequestCancelled) 896 | end 897 | return task 898 | end 899 | 900 | ---@param params nil 901 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 902 | function Client:shutdown(params) 903 | local that, _, request_id, reject_ = self, nil, nil, nil 904 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 905 | local task = AsyncTask.new(function(resolve, reject) 906 | reject_ = reject 907 | _, request_id = self.client:request('shutdown', params, function(err, res) 908 | if err then 909 | reject(err) 910 | else 911 | resolve(res) 912 | end 913 | end) 914 | end) 915 | function task.cancel() 916 | that.client:cancel_request(request_id) 917 | reject_(LSP.ErrorCodes.RequestCancelled) 918 | end 919 | return task 920 | end 921 | 922 | ---@param params dansa.kit.LSP.ShowMessageRequestParams 923 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 924 | function Client:window_showMessageRequest(params) 925 | local that, _, request_id, reject_ = self, nil, nil, nil 926 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 927 | local task = AsyncTask.new(function(resolve, reject) 928 | reject_ = reject 929 | _, request_id = self.client:request('window/showMessageRequest', params, function(err, res) 930 | if err then 931 | reject(err) 932 | else 933 | resolve(res) 934 | end 935 | end) 936 | end) 937 | function task.cancel() 938 | that.client:cancel_request(request_id) 939 | reject_(LSP.ErrorCodes.RequestCancelled) 940 | end 941 | return task 942 | end 943 | 944 | ---@param params dansa.kit.LSP.WillSaveTextDocumentParams 945 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 946 | function Client:textDocument_willSaveWaitUntil(params) 947 | local that, _, request_id, reject_ = self, nil, nil, nil 948 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 949 | local task = AsyncTask.new(function(resolve, reject) 950 | reject_ = reject 951 | _, request_id = self.client:request('textDocument/willSaveWaitUntil', params, function(err, res) 952 | if err then 953 | reject(err) 954 | else 955 | resolve(res) 956 | end 957 | end) 958 | end) 959 | function task.cancel() 960 | that.client:cancel_request(request_id) 961 | reject_(LSP.ErrorCodes.RequestCancelled) 962 | end 963 | return task 964 | end 965 | 966 | ---@param params dansa.kit.LSP.CompletionParams 967 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 968 | function Client:textDocument_completion(params) 969 | local that, _, request_id, reject_ = self, nil, nil, nil 970 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 971 | local task = AsyncTask.new(function(resolve, reject) 972 | reject_ = reject 973 | _, request_id = self.client:request('textDocument/completion', params, function(err, res) 974 | if err then 975 | reject(err) 976 | else 977 | resolve(res) 978 | end 979 | end) 980 | end) 981 | function task.cancel() 982 | that.client:cancel_request(request_id) 983 | reject_(LSP.ErrorCodes.RequestCancelled) 984 | end 985 | return task 986 | end 987 | 988 | ---@param params dansa.kit.LSP.CompletionItem 989 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 990 | function Client:completionItem_resolve(params) 991 | local that, _, request_id, reject_ = self, nil, nil, nil 992 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 993 | local task = AsyncTask.new(function(resolve, reject) 994 | reject_ = reject 995 | _, request_id = self.client:request('completionItem/resolve', params, function(err, res) 996 | if err then 997 | reject(err) 998 | else 999 | resolve(res) 1000 | end 1001 | end) 1002 | end) 1003 | function task.cancel() 1004 | that.client:cancel_request(request_id) 1005 | reject_(LSP.ErrorCodes.RequestCancelled) 1006 | end 1007 | return task 1008 | end 1009 | 1010 | ---@param params dansa.kit.LSP.HoverParams 1011 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1012 | function Client:textDocument_hover(params) 1013 | local that, _, request_id, reject_ = self, nil, nil, nil 1014 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1015 | local task = AsyncTask.new(function(resolve, reject) 1016 | reject_ = reject 1017 | _, request_id = self.client:request('textDocument/hover', params, function(err, res) 1018 | if err then 1019 | reject(err) 1020 | else 1021 | resolve(res) 1022 | end 1023 | end) 1024 | end) 1025 | function task.cancel() 1026 | that.client:cancel_request(request_id) 1027 | reject_(LSP.ErrorCodes.RequestCancelled) 1028 | end 1029 | return task 1030 | end 1031 | 1032 | ---@param params dansa.kit.LSP.SignatureHelpParams 1033 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1034 | function Client:textDocument_signatureHelp(params) 1035 | local that, _, request_id, reject_ = self, nil, nil, nil 1036 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1037 | local task = AsyncTask.new(function(resolve, reject) 1038 | reject_ = reject 1039 | _, request_id = self.client:request('textDocument/signatureHelp', params, function(err, res) 1040 | if err then 1041 | reject(err) 1042 | else 1043 | resolve(res) 1044 | end 1045 | end) 1046 | end) 1047 | function task.cancel() 1048 | that.client:cancel_request(request_id) 1049 | reject_(LSP.ErrorCodes.RequestCancelled) 1050 | end 1051 | return task 1052 | end 1053 | 1054 | ---@param params dansa.kit.LSP.DefinitionParams 1055 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1056 | function Client:textDocument_definition(params) 1057 | local that, _, request_id, reject_ = self, nil, nil, nil 1058 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1059 | local task = AsyncTask.new(function(resolve, reject) 1060 | reject_ = reject 1061 | _, request_id = self.client:request('textDocument/definition', params, function(err, res) 1062 | if err then 1063 | reject(err) 1064 | else 1065 | resolve(res) 1066 | end 1067 | end) 1068 | end) 1069 | function task.cancel() 1070 | that.client:cancel_request(request_id) 1071 | reject_(LSP.ErrorCodes.RequestCancelled) 1072 | end 1073 | return task 1074 | end 1075 | 1076 | ---@param params dansa.kit.LSP.ReferenceParams 1077 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1078 | function Client:textDocument_references(params) 1079 | local that, _, request_id, reject_ = self, nil, nil, nil 1080 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1081 | local task = AsyncTask.new(function(resolve, reject) 1082 | reject_ = reject 1083 | _, request_id = self.client:request('textDocument/references', 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 dansa.kit.LSP.DocumentHighlightParams 1099 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1100 | function Client:textDocument_documentHighlight(params) 1101 | local that, _, request_id, reject_ = self, nil, nil, nil 1102 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1103 | local task = AsyncTask.new(function(resolve, reject) 1104 | reject_ = reject 1105 | _, request_id = self.client:request('textDocument/documentHighlight', params, function(err, res) 1106 | if err then 1107 | reject(err) 1108 | else 1109 | resolve(res) 1110 | end 1111 | end) 1112 | end) 1113 | function task.cancel() 1114 | that.client:cancel_request(request_id) 1115 | reject_(LSP.ErrorCodes.RequestCancelled) 1116 | end 1117 | return task 1118 | end 1119 | 1120 | ---@param params dansa.kit.LSP.DocumentSymbolParams 1121 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1122 | function Client:textDocument_documentSymbol(params) 1123 | local that, _, request_id, reject_ = self, nil, nil, nil 1124 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1125 | local task = AsyncTask.new(function(resolve, reject) 1126 | reject_ = reject 1127 | _, request_id = self.client:request('textDocument/documentSymbol', params, function(err, res) 1128 | if err then 1129 | reject(err) 1130 | else 1131 | resolve(res) 1132 | end 1133 | end) 1134 | end) 1135 | function task.cancel() 1136 | that.client:cancel_request(request_id) 1137 | reject_(LSP.ErrorCodes.RequestCancelled) 1138 | end 1139 | return task 1140 | end 1141 | 1142 | ---@param params dansa.kit.LSP.CodeActionParams 1143 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1144 | function Client:textDocument_codeAction(params) 1145 | local that, _, request_id, reject_ = self, nil, nil, nil 1146 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1147 | local task = AsyncTask.new(function(resolve, reject) 1148 | reject_ = reject 1149 | _, request_id = self.client:request('textDocument/codeAction', params, function(err, res) 1150 | if err then 1151 | reject(err) 1152 | else 1153 | resolve(res) 1154 | end 1155 | end) 1156 | end) 1157 | function task.cancel() 1158 | that.client:cancel_request(request_id) 1159 | reject_(LSP.ErrorCodes.RequestCancelled) 1160 | end 1161 | return task 1162 | end 1163 | 1164 | ---@param params dansa.kit.LSP.CodeAction 1165 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1166 | function Client:codeAction_resolve(params) 1167 | local that, _, request_id, reject_ = self, nil, nil, nil 1168 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1169 | local task = AsyncTask.new(function(resolve, reject) 1170 | reject_ = reject 1171 | _, request_id = self.client:request('codeAction/resolve', params, function(err, res) 1172 | if err then 1173 | reject(err) 1174 | else 1175 | resolve(res) 1176 | end 1177 | end) 1178 | end) 1179 | function task.cancel() 1180 | that.client:cancel_request(request_id) 1181 | reject_(LSP.ErrorCodes.RequestCancelled) 1182 | end 1183 | return task 1184 | end 1185 | 1186 | ---@param params dansa.kit.LSP.WorkspaceSymbolParams 1187 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1188 | function Client:workspace_symbol(params) 1189 | local that, _, request_id, reject_ = self, nil, nil, nil 1190 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1191 | local task = AsyncTask.new(function(resolve, reject) 1192 | reject_ = reject 1193 | _, request_id = self.client:request('workspace/symbol', params, function(err, res) 1194 | if err then 1195 | reject(err) 1196 | else 1197 | resolve(res) 1198 | end 1199 | end) 1200 | end) 1201 | function task.cancel() 1202 | that.client:cancel_request(request_id) 1203 | reject_(LSP.ErrorCodes.RequestCancelled) 1204 | end 1205 | return task 1206 | end 1207 | 1208 | ---@param params dansa.kit.LSP.WorkspaceSymbol 1209 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1210 | function Client:workspaceSymbol_resolve(params) 1211 | local that, _, request_id, reject_ = self, nil, nil, nil 1212 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1213 | local task = AsyncTask.new(function(resolve, reject) 1214 | reject_ = reject 1215 | _, request_id = self.client:request('workspaceSymbol/resolve', params, function(err, res) 1216 | if err then 1217 | reject(err) 1218 | else 1219 | resolve(res) 1220 | end 1221 | end) 1222 | end) 1223 | function task.cancel() 1224 | that.client:cancel_request(request_id) 1225 | reject_(LSP.ErrorCodes.RequestCancelled) 1226 | end 1227 | return task 1228 | end 1229 | 1230 | ---@param params dansa.kit.LSP.CodeLensParams 1231 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1232 | function Client:textDocument_codeLens(params) 1233 | local that, _, request_id, reject_ = self, nil, nil, nil 1234 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1235 | local task = AsyncTask.new(function(resolve, reject) 1236 | reject_ = reject 1237 | _, request_id = self.client:request('textDocument/codeLens', params, function(err, res) 1238 | if err then 1239 | reject(err) 1240 | else 1241 | resolve(res) 1242 | end 1243 | end) 1244 | end) 1245 | function task.cancel() 1246 | that.client:cancel_request(request_id) 1247 | reject_(LSP.ErrorCodes.RequestCancelled) 1248 | end 1249 | return task 1250 | end 1251 | 1252 | ---@param params dansa.kit.LSP.CodeLens 1253 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1254 | function Client:codeLens_resolve(params) 1255 | local that, _, request_id, reject_ = self, nil, nil, nil 1256 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1257 | local task = AsyncTask.new(function(resolve, reject) 1258 | reject_ = reject 1259 | _, request_id = self.client:request('codeLens/resolve', params, function(err, res) 1260 | if err then 1261 | reject(err) 1262 | else 1263 | resolve(res) 1264 | end 1265 | end) 1266 | end) 1267 | function task.cancel() 1268 | that.client:cancel_request(request_id) 1269 | reject_(LSP.ErrorCodes.RequestCancelled) 1270 | end 1271 | return task 1272 | end 1273 | 1274 | ---@param params nil 1275 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1276 | function Client:workspace_codeLens_refresh(params) 1277 | local that, _, request_id, reject_ = self, nil, nil, nil 1278 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1279 | local task = AsyncTask.new(function(resolve, reject) 1280 | reject_ = reject 1281 | _, request_id = self.client:request('workspace/codeLens/refresh', params, function(err, res) 1282 | if err then 1283 | reject(err) 1284 | else 1285 | resolve(res) 1286 | end 1287 | end) 1288 | end) 1289 | function task.cancel() 1290 | that.client:cancel_request(request_id) 1291 | reject_(LSP.ErrorCodes.RequestCancelled) 1292 | end 1293 | return task 1294 | end 1295 | 1296 | ---@param params dansa.kit.LSP.DocumentLinkParams 1297 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1298 | function Client:textDocument_documentLink(params) 1299 | local that, _, request_id, reject_ = self, nil, nil, nil 1300 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1301 | local task = AsyncTask.new(function(resolve, reject) 1302 | reject_ = reject 1303 | _, request_id = self.client:request('textDocument/documentLink', 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 dansa.kit.LSP.DocumentLink 1319 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1320 | function Client:documentLink_resolve(params) 1321 | local that, _, request_id, reject_ = self, nil, nil, nil 1322 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1323 | local task = AsyncTask.new(function(resolve, reject) 1324 | reject_ = reject 1325 | _, request_id = self.client:request('documentLink/resolve', params, function(err, res) 1326 | if err then 1327 | reject(err) 1328 | else 1329 | resolve(res) 1330 | end 1331 | end) 1332 | end) 1333 | function task.cancel() 1334 | that.client:cancel_request(request_id) 1335 | reject_(LSP.ErrorCodes.RequestCancelled) 1336 | end 1337 | return task 1338 | end 1339 | 1340 | ---@param params dansa.kit.LSP.DocumentFormattingParams 1341 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1342 | function Client:textDocument_formatting(params) 1343 | local that, _, request_id, reject_ = self, nil, nil, nil 1344 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1345 | local task = AsyncTask.new(function(resolve, reject) 1346 | reject_ = reject 1347 | _, request_id = self.client:request('textDocument/formatting', params, function(err, res) 1348 | if err then 1349 | reject(err) 1350 | else 1351 | resolve(res) 1352 | end 1353 | end) 1354 | end) 1355 | function task.cancel() 1356 | that.client:cancel_request(request_id) 1357 | reject_(LSP.ErrorCodes.RequestCancelled) 1358 | end 1359 | return task 1360 | end 1361 | 1362 | ---@param params dansa.kit.LSP.DocumentRangeFormattingParams 1363 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1364 | function Client:textDocument_rangeFormatting(params) 1365 | local that, _, request_id, reject_ = self, nil, nil, nil 1366 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1367 | local task = AsyncTask.new(function(resolve, reject) 1368 | reject_ = reject 1369 | _, request_id = self.client:request('textDocument/rangeFormatting', params, function(err, res) 1370 | if err then 1371 | reject(err) 1372 | else 1373 | resolve(res) 1374 | end 1375 | end) 1376 | end) 1377 | function task.cancel() 1378 | that.client:cancel_request(request_id) 1379 | reject_(LSP.ErrorCodes.RequestCancelled) 1380 | end 1381 | return task 1382 | end 1383 | 1384 | ---@param params dansa.kit.LSP.DocumentRangesFormattingParams 1385 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1386 | function Client:textDocument_rangesFormatting(params) 1387 | local that, _, request_id, reject_ = self, nil, nil, nil 1388 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1389 | local task = AsyncTask.new(function(resolve, reject) 1390 | reject_ = reject 1391 | _, request_id = self.client:request('textDocument/rangesFormatting', params, function(err, res) 1392 | if err then 1393 | reject(err) 1394 | else 1395 | resolve(res) 1396 | end 1397 | end) 1398 | end) 1399 | function task.cancel() 1400 | that.client:cancel_request(request_id) 1401 | reject_(LSP.ErrorCodes.RequestCancelled) 1402 | end 1403 | return task 1404 | end 1405 | 1406 | ---@param params dansa.kit.LSP.DocumentOnTypeFormattingParams 1407 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1408 | function Client:textDocument_onTypeFormatting(params) 1409 | local that, _, request_id, reject_ = self, nil, nil, nil 1410 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1411 | local task = AsyncTask.new(function(resolve, reject) 1412 | reject_ = reject 1413 | _, request_id = self.client:request('textDocument/onTypeFormatting', params, function(err, res) 1414 | if err then 1415 | reject(err) 1416 | else 1417 | resolve(res) 1418 | end 1419 | end) 1420 | end) 1421 | function task.cancel() 1422 | that.client:cancel_request(request_id) 1423 | reject_(LSP.ErrorCodes.RequestCancelled) 1424 | end 1425 | return task 1426 | end 1427 | 1428 | ---@param params dansa.kit.LSP.RenameParams 1429 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1430 | function Client:textDocument_rename(params) 1431 | local that, _, request_id, reject_ = self, nil, nil, nil 1432 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1433 | local task = AsyncTask.new(function(resolve, reject) 1434 | reject_ = reject 1435 | _, request_id = self.client:request('textDocument/rename', params, function(err, res) 1436 | if err then 1437 | reject(err) 1438 | else 1439 | resolve(res) 1440 | end 1441 | end) 1442 | end) 1443 | function task.cancel() 1444 | that.client:cancel_request(request_id) 1445 | reject_(LSP.ErrorCodes.RequestCancelled) 1446 | end 1447 | return task 1448 | end 1449 | 1450 | ---@param params dansa.kit.LSP.PrepareRenameParams 1451 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1452 | function Client:textDocument_prepareRename(params) 1453 | local that, _, request_id, reject_ = self, nil, nil, nil 1454 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1455 | local task = AsyncTask.new(function(resolve, reject) 1456 | reject_ = reject 1457 | _, request_id = self.client:request('textDocument/prepareRename', params, function(err, res) 1458 | if err then 1459 | reject(err) 1460 | else 1461 | resolve(res) 1462 | end 1463 | end) 1464 | end) 1465 | function task.cancel() 1466 | that.client:cancel_request(request_id) 1467 | reject_(LSP.ErrorCodes.RequestCancelled) 1468 | end 1469 | return task 1470 | end 1471 | 1472 | ---@param params dansa.kit.LSP.ExecuteCommandParams 1473 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1474 | function Client:workspace_executeCommand(params) 1475 | local that, _, request_id, reject_ = self, nil, nil, nil 1476 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1477 | local task = AsyncTask.new(function(resolve, reject) 1478 | reject_ = reject 1479 | _, request_id = self.client:request('workspace/executeCommand', params, function(err, res) 1480 | if err then 1481 | reject(err) 1482 | else 1483 | resolve(res) 1484 | end 1485 | end) 1486 | end) 1487 | function task.cancel() 1488 | that.client:cancel_request(request_id) 1489 | reject_(LSP.ErrorCodes.RequestCancelled) 1490 | end 1491 | return task 1492 | end 1493 | 1494 | ---@param params dansa.kit.LSP.ApplyWorkspaceEditParams 1495 | ---@return dansa.kit.Async.AsyncTask|{cancel: fun()} 1496 | function Client:workspace_applyEdit(params) 1497 | local that, _, request_id, reject_ = self, nil, nil, nil 1498 | ---@type dansa.kit.Async.AsyncTask|{cancel: fun()} 1499 | local task = AsyncTask.new(function(resolve, reject) 1500 | reject_ = reject 1501 | _, request_id = self.client:request('workspace/applyEdit', params, function(err, res) 1502 | if err then 1503 | reject(err) 1504 | else 1505 | resolve(res) 1506 | end 1507 | end) 1508 | end) 1509 | function task.cancel() 1510 | that.client:cancel_request(request_id) 1511 | reject_(LSP.ErrorCodes.RequestCancelled) 1512 | end 1513 | return task 1514 | end 1515 | 1516 | ---Send notification. 1517 | ---@param method string 1518 | ---@param params table 1519 | function Client:notify(method, params) 1520 | self.client:notify(method, params) 1521 | end 1522 | 1523 | ---@param params dansa.kit.LSP.DidChangeWorkspaceFoldersParams 1524 | function Client:workspace_didChangeWorkspaceFolders(params) 1525 | self.client:notify('workspace/didChangeWorkspaceFolders', params) 1526 | end 1527 | 1528 | ---@param params dansa.kit.LSP.WorkDoneProgressCancelParams 1529 | function Client:window_workDoneProgress_cancel(params) 1530 | self.client:notify('window/workDoneProgress/cancel', params) 1531 | end 1532 | 1533 | ---@param params dansa.kit.LSP.CreateFilesParams 1534 | function Client:workspace_didCreateFiles(params) 1535 | self.client:notify('workspace/didCreateFiles', params) 1536 | end 1537 | 1538 | ---@param params dansa.kit.LSP.RenameFilesParams 1539 | function Client:workspace_didRenameFiles(params) 1540 | self.client:notify('workspace/didRenameFiles', params) 1541 | end 1542 | 1543 | ---@param params dansa.kit.LSP.DeleteFilesParams 1544 | function Client:workspace_didDeleteFiles(params) 1545 | self.client:notify('workspace/didDeleteFiles', params) 1546 | end 1547 | 1548 | ---@param params dansa.kit.LSP.DidOpenNotebookDocumentParams 1549 | function Client:notebookDocument_didOpen(params) 1550 | self.client:notify('notebookDocument/didOpen', params) 1551 | end 1552 | 1553 | ---@param params dansa.kit.LSP.DidChangeNotebookDocumentParams 1554 | function Client:notebookDocument_didChange(params) 1555 | self.client:notify('notebookDocument/didChange', params) 1556 | end 1557 | 1558 | ---@param params dansa.kit.LSP.DidSaveNotebookDocumentParams 1559 | function Client:notebookDocument_didSave(params) 1560 | self.client:notify('notebookDocument/didSave', params) 1561 | end 1562 | 1563 | ---@param params dansa.kit.LSP.DidCloseNotebookDocumentParams 1564 | function Client:notebookDocument_didClose(params) 1565 | self.client:notify('notebookDocument/didClose', params) 1566 | end 1567 | 1568 | ---@param params dansa.kit.LSP.InitializedParams 1569 | function Client:initialized(params) 1570 | self.client:notify('initialized', params) 1571 | end 1572 | 1573 | ---@param params nil 1574 | function Client:exit(params) 1575 | self.client:notify('exit', params) 1576 | end 1577 | 1578 | ---@param params dansa.kit.LSP.DidChangeConfigurationParams 1579 | function Client:workspace_didChangeConfiguration(params) 1580 | self.client:notify('workspace/didChangeConfiguration', params) 1581 | end 1582 | 1583 | ---@param params dansa.kit.LSP.ShowMessageParams 1584 | function Client:window_showMessage(params) 1585 | self.client:notify('window/showMessage', params) 1586 | end 1587 | 1588 | ---@param params dansa.kit.LSP.LogMessageParams 1589 | function Client:window_logMessage(params) 1590 | self.client:notify('window/logMessage', params) 1591 | end 1592 | 1593 | ---@param params dansa.kit.LSP.LSPAny 1594 | function Client:telemetry_event(params) 1595 | self.client:notify('telemetry/event', params) 1596 | end 1597 | 1598 | ---@param params dansa.kit.LSP.DidOpenTextDocumentParams 1599 | function Client:textDocument_didOpen(params) 1600 | self.client:notify('textDocument/didOpen', params) 1601 | end 1602 | 1603 | ---@param params dansa.kit.LSP.DidChangeTextDocumentParams 1604 | function Client:textDocument_didChange(params) 1605 | self.client:notify('textDocument/didChange', params) 1606 | end 1607 | 1608 | ---@param params dansa.kit.LSP.DidCloseTextDocumentParams 1609 | function Client:textDocument_didClose(params) 1610 | self.client:notify('textDocument/didClose', params) 1611 | end 1612 | 1613 | ---@param params dansa.kit.LSP.DidSaveTextDocumentParams 1614 | function Client:textDocument_didSave(params) 1615 | self.client:notify('textDocument/didSave', params) 1616 | end 1617 | 1618 | ---@param params dansa.kit.LSP.WillSaveTextDocumentParams 1619 | function Client:textDocument_willSave(params) 1620 | self.client:notify('textDocument/willSave', params) 1621 | end 1622 | 1623 | ---@param params dansa.kit.LSP.DidChangeWatchedFilesParams 1624 | function Client:workspace_didChangeWatchedFiles(params) 1625 | self.client:notify('workspace/didChangeWatchedFiles', params) 1626 | end 1627 | 1628 | ---@param params dansa.kit.LSP.PublishDiagnosticsParams 1629 | function Client:textDocument_publishDiagnostics(params) 1630 | self.client:notify('textDocument/publishDiagnostics', params) 1631 | end 1632 | 1633 | ---@param params dansa.kit.LSP.SetTraceParams 1634 | function Client:dollar_setTrace(params) 1635 | self.client:notify('$/setTrace', params) 1636 | end 1637 | 1638 | ---@param params dansa.kit.LSP.LogTraceParams 1639 | function Client:dollar_logTrace(params) 1640 | self.client:notify('$/logTrace', params) 1641 | end 1642 | 1643 | ---@param params dansa.kit.LSP.CancelParams 1644 | function Client:dollar_cancelRequest(params) 1645 | self.client:notify('$/cancelRequest', params) 1646 | end 1647 | 1648 | ---@param params dansa.kit.LSP.ProgressParams 1649 | function Client:dollar_progress(params) 1650 | self.client:notify('$/progress', params) 1651 | end 1652 | 1653 | return Client 1654 | -------------------------------------------------------------------------------- /lua/dansa/kit/LSP/DocumentSelector.lua: -------------------------------------------------------------------------------- 1 | local LanguageId = require('dansa.kit.LSP.LanguageId') 2 | 3 | -- NOTE 4 | --@alias dansa.kit.LSP.DocumentSelector dansa.kit.LSP.DocumentFilter[] 5 | --@alias dansa.kit.LSP.DocumentFilter (dansa.kit.LSP.TextDocumentFilter | dansa.kit.LSP.NotebookCellTextDocumentFilter) 6 | --@alias dansa.kit.LSP.TextDocumentFilter ({ language: string, scheme?: string, pattern?: string } | { language?: string, scheme: string, pattern?: string } | { language?: string, scheme?: string, pattern: string }) 7 | --@class dansa.kit.LSP.NotebookCellTextDocumentFilter 8 | --@field public notebook (string | dansa.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 dansa.kit.LSP.NotebookDocumentFilter ({ notebookType: string, scheme?: string, pattern?: string } | { notebookType?: string, scheme: string, pattern?: string } | { notebookType?: string, scheme?: string, pattern: string }) 11 | 12 | ---@alias dansa.kit.LSP.DocumentSelector.NormalizedFilter { notebook_type: string?, scheme: string?, pattern: string, language: string? } 13 | 14 | ---Normalize the filter. 15 | ---@param document_filter dansa.kit.LSP.DocumentFilter 16 | ---@return dansa.kit.LSP.DocumentSelector.NormalizedFilter | nil 17 | local function normalize_filter(document_filter) 18 | if document_filter.notebook then 19 | local filter = document_filter --[[@as dansa.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 dansa.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? dansa.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 dansa.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/dansa/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/dansa/kit/LSP/Position.lua: -------------------------------------------------------------------------------- 1 | local LSP = require('dansa.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? dansa.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 dansa.kit.LSP.Position 33 | ---@param from_encoding? dansa.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 dansa.kit.LSP.Position 43 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 44 | ---@return dansa.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 dansa.kit.LSP.Position 60 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 61 | ---@return dansa.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 dansa.kit.LSP.Position 77 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 78 | ---@return dansa.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 dansa.kit.LSP.Position 94 | ---@param from_encoding dansa.kit.LSP.PositionEncodingKind 95 | ---@param to_encoding dansa.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/dansa/kit/LSP/Range.lua: -------------------------------------------------------------------------------- 1 | local Position = require('dansa.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 dansa.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 dansa.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 dansa.kit.LSP.Range 29 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 30 | ---@return dansa.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 dansa.kit.LSP.Range 41 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 42 | ---@return dansa.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 dansa.kit.LSP.Range 53 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 54 | ---@return dansa.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 dansa.kit.LSP.Range 65 | ---@param from_encoding? dansa.kit.LSP.PositionEncodingKind 66 | ---@return dansa.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/dansa/kit/RPC/JSON/init.lua: -------------------------------------------------------------------------------- 1 | local kit = require('dansa.kit') 2 | local Async = require('dansa.kit.Async') 3 | 4 | ---@class dansa.kit.RPC.JSON.Transport 5 | ---@field send fun(self: dansa.kit.RPC.JSON.Transport, data: table): dansa.kit.Async.AsyncTask 6 | ---@field on_message fun(self: dansa.kit.RPC.JSON.Transport, callback: fun(data: table)) 7 | ---@field start fun(self: dansa.kit.RPC.JSON.Transport) 8 | ---@field close fun(self: dansa.kit.RPC.JSON.Transport): dansa.kit.Async.AsyncTask 9 | 10 | ---@class dansa.kit.RPC.JSON.Transport.LineDelimitedPipe: dansa.kit.RPC.JSON.Transport 11 | ---@field private _buffer dansa.kit.buffer.Buffer 12 | ---@field private _reader uv.uv_pipe_t 13 | ---@field private _writer uv.uv_pipe_t 14 | ---@field private _on_message fun(data: table) 15 | local LineDelimitedPipe = {} 16 | LineDelimitedPipe.__index = LineDelimitedPipe 17 | 18 | ---Create new LineDelimitedPipe instance. 19 | ---@param reader uv.uv_pipe_t 20 | ---@param writer uv.uv_pipe_t 21 | function LineDelimitedPipe.new(reader, writer) 22 | return setmetatable({ 23 | _buffer = kit.buffer(), 24 | _reader = reader, 25 | _writer = writer, 26 | _on_message = nil, 27 | }, LineDelimitedPipe) 28 | end 29 | 30 | ---Send data. 31 | ---@param message table 32 | ---@return dansa.kit.Async.AsyncTask 33 | function LineDelimitedPipe:send(message) 34 | return Async.new(function(resolve, reject) 35 | self._writer:write(vim.json.encode(message) .. '\n', function(err) 36 | if err then 37 | return reject(err) 38 | else 39 | resolve() 40 | end 41 | end) 42 | end) 43 | end 44 | 45 | ---Set message callback. 46 | ---@param callback fun(data: table) 47 | function LineDelimitedPipe:on_message(callback) 48 | self._on_message = callback 49 | end 50 | 51 | ---Start transport. 52 | function LineDelimitedPipe:start() 53 | self._reader:read_start(function(err, data) 54 | if err then 55 | return 56 | end 57 | self._buffer.put(data) 58 | 59 | local found = data:find('\n', 1, true) 60 | if found then 61 | for i, byte in self._buffer.iter_bytes() do 62 | if byte == 10 then 63 | local message = vim.json.decode(self._buffer.get(i - 1), { object = true, array = true }) 64 | self._buffer.skip(1) 65 | self._on_message(message) 66 | end 67 | end 68 | end 69 | end) 70 | end 71 | 72 | ---Close transport. 73 | ---@return dansa.kit.Async.AsyncTask 74 | function LineDelimitedPipe:close() 75 | self._reader:read_stop() 76 | 77 | local p = Async.resolve() 78 | p = p:next(function() 79 | if not self._reader:is_closing() and self._reader:is_active() then 80 | return Async.new(function(resolve) 81 | self._reader:close(resolve) 82 | end) 83 | end 84 | end) 85 | p = p:next(function() 86 | if not self._writer:is_closing() and self._writer:is_active() then 87 | return Async.new(function(resolve) 88 | self._writer:close(resolve) 89 | end) 90 | end 91 | end) 92 | return p 93 | end 94 | 95 | ---@class dansa.kit.RPC.JSON.RPC 96 | ---@field private _transport dansa.kit.RPC.JSON.Transport 97 | ---@field private _next_requet_id number 98 | ---@field private _pending_callbacks table 99 | ---@field private _on_request_map table 100 | ---@field private _on_notification_map table 101 | local RPC = { 102 | Transport = { 103 | LineDelimitedPipe = LineDelimitedPipe, 104 | }, 105 | } 106 | RPC.__index = RPC 107 | 108 | ---Create new RPC instance. 109 | ---@param params { transport: dansa.kit.RPC.JSON.Transport } 110 | function RPC.new(params) 111 | return setmetatable({ 112 | _transport = params.transport, 113 | _next_requet_id = 0, 114 | _pending_callbacks = {}, 115 | _on_request_map = {}, 116 | _on_notification_map = {}, 117 | }, RPC) 118 | end 119 | 120 | ---Start RPC. 121 | function RPC:start() 122 | self._transport:on_message(function(data) 123 | if data.id then 124 | if data.method then 125 | -- request. 126 | local request_callback = self._on_request_map[data.method] 127 | if request_callback then 128 | Async.resolve():next(function() 129 | return request_callback(data) 130 | end):dispatch(function(res) 131 | -- request success. 132 | self._transport:send({ 133 | jsonrpc = '2.0', 134 | id = data.id, 135 | result = res, 136 | }) 137 | end, function(err) 138 | -- request failure. 139 | self._transport:send({ 140 | jsonrpc = '2.0', 141 | id = data.id, 142 | error = { 143 | code = -32603, 144 | message = tostring(err), 145 | }, 146 | }) 147 | end) 148 | else 149 | -- request not found. 150 | self._transport:send({ 151 | jsonrpc = "2.0", 152 | id = data.id, 153 | error = { 154 | code = -32601, 155 | message = ('Method not found: %s'):format(data.method), 156 | }, 157 | }) 158 | end 159 | else 160 | -- response. 161 | local pending_callback = self._pending_callbacks[data.id] 162 | if pending_callback then 163 | pending_callback(data) 164 | self._pending_callbacks[data.id] = nil 165 | end 166 | end 167 | else 168 | -- notification. 169 | local notification_callbacks = self._on_notification_map[data.method] 170 | if notification_callbacks then 171 | for _, callback in ipairs(notification_callbacks) do 172 | pcall(callback, { params = data.params }) 173 | end 174 | end 175 | end 176 | end) 177 | self._transport:start() 178 | end 179 | 180 | ---Close RPC. 181 | ---@return dansa.kit.Async.AsyncTask 182 | function RPC:close() 183 | return self._transport:close() 184 | end 185 | 186 | ---Set request callback. 187 | ---@param method string 188 | ---@param callback fun(ctx: { params: table }): table 189 | function RPC:on_request(method, callback) 190 | if self._on_request_map[method] then 191 | error('Method already exists: ' .. method) 192 | end 193 | self._on_request_map[method] = callback 194 | end 195 | 196 | ---Set notification callback. 197 | ---@param method string 198 | ---@param callback fun(ctx: { params: table }) 199 | function RPC:on_notification(method, callback) 200 | if not self._on_notification_map[method] then 201 | self._on_notification_map[method] = {} 202 | end 203 | table.insert(self._on_notification_map[method], callback) 204 | end 205 | 206 | ---Request. 207 | ---@param method string 208 | ---@param params table 209 | ---@return dansa.kit.Async.AsyncTask| { cancel: fun() } 210 | function RPC:request(method, params) 211 | self._next_requet_id = self._next_requet_id + 1 212 | 213 | local request_id = self._next_requet_id 214 | 215 | local p = Async.new(function(resolve, reject) 216 | self._pending_callbacks[request_id] = function(response) 217 | if response.error then 218 | reject(response.error) 219 | else 220 | resolve(response.result) 221 | end 222 | end 223 | self._transport:send({ 224 | jsonrpc = '2.0', 225 | id = request_id, 226 | method = method, 227 | params = params, 228 | }) 229 | end) 230 | 231 | ---@diagnostic disable-next-line: inject-field 232 | p.cancel = function() 233 | self._pending_callbacks[request_id] = nil 234 | end 235 | 236 | return p 237 | end 238 | 239 | ---Notify. 240 | ---@param method string 241 | ---@param params table 242 | function RPC:notify(method, params) 243 | self._transport:send({ 244 | jsonrpc = '2.0', 245 | method = method, 246 | params = params, 247 | }) 248 | end 249 | 250 | return RPC 251 | -------------------------------------------------------------------------------- /lua/dansa/kit/Spec/init.lua: -------------------------------------------------------------------------------- 1 | local kit = require('dansa.kit') 2 | local assert = require('luassert') 3 | 4 | ---@class dansa.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? dansa.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/dansa/kit/System/init.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: ignore 212 2 | 3 | local kit = require('dansa.kit') 4 | local Async = require('dansa.kit.Async') 5 | 6 | local bytes = { 7 | ['\n'] = 10, 8 | ['\r'] = 13, 9 | } 10 | 11 | local System = {} 12 | 13 | ---@class dansa.kit.System.Buffer 14 | ---@field write fun(data: string) 15 | ---@field close fun() 16 | 17 | ---@class dansa.kit.System.Buffering 18 | ---@field create fun(self: any, callback: fun(data: string)): dansa.kit.System.Buffer 19 | 20 | ---@class dansa.kit.System.LineBuffering: dansa.kit.System.Buffering 21 | ---@field ignore_empty boolean 22 | System.LineBuffering = {} 23 | System.LineBuffering.__index = System.LineBuffering 24 | 25 | ---Create LineBuffering. 26 | ---@param option { ignore_empty?: boolean } 27 | function System.LineBuffering.new(option) 28 | return setmetatable({ 29 | ignore_empty = option.ignore_empty or false, 30 | }, System.LineBuffering) 31 | end 32 | 33 | ---Create LineBuffer object. 34 | ---@param callback fun(data: string) 35 | function System.LineBuffering:create(callback) 36 | local callback_wrapped = callback 37 | if self.ignore_empty then 38 | ---@param data string 39 | function callback_wrapped(data) 40 | if data:find('%g') then 41 | return callback(data) 42 | end 43 | end 44 | end 45 | 46 | local buffer = kit.buffer() 47 | local iter = buffer.iter_bytes() 48 | ---@type dansa.kit.System.Buffer 49 | return { 50 | write = function(data) 51 | buffer.put(data) 52 | local found = true 53 | while found do 54 | found = false 55 | for i, byte in iter do 56 | if byte == bytes['\n'] then 57 | if buffer.peek(i - 1) == bytes['\r'] then 58 | callback_wrapped(buffer.get(i - 2)) 59 | buffer.skip(2) 60 | else 61 | callback_wrapped(buffer.get(i - 1)) 62 | buffer.skip(1) 63 | end 64 | iter = buffer.iter_bytes() 65 | found = true 66 | break 67 | end 68 | end 69 | if not found then 70 | break 71 | end 72 | end 73 | end, 74 | close = function() 75 | for byte, i in buffer.iter_bytes() do 76 | if byte == bytes['\n'] then 77 | if buffer.peek(i - 1) == bytes['\r'] then 78 | callback_wrapped(buffer.get(i - 2)) 79 | buffer.skip(2) 80 | else 81 | callback_wrapped(buffer.get(i - 1)) 82 | buffer.skip(1) 83 | end 84 | end 85 | end 86 | callback_wrapped(buffer.get()) 87 | end, 88 | } 89 | end 90 | 91 | ---@class dansa.kit.System.DelimiterBuffering: dansa.kit.System.Buffering 92 | ---@field delimiter string 93 | System.DelimiterBuffering = {} 94 | System.DelimiterBuffering.__index = System.DelimiterBuffering 95 | 96 | ---Create Buffering. 97 | ---@param option { delimiter: string } 98 | function System.DelimiterBuffering.new(option) 99 | return setmetatable({ 100 | delimiter = option.delimiter, 101 | }, System.DelimiterBuffering) 102 | end 103 | 104 | ---Create Delimiter object. 105 | function System.DelimiterBuffering:create(callback) 106 | local state = { 107 | buffer = {}, 108 | buffer_pos = 1, 109 | delimiter_pos = 1, 110 | match_pos = nil --[[@as integer?]], 111 | } 112 | 113 | local function len() 114 | local l = 0 115 | for i = 1, #state.buffer do 116 | l = l + #state.buffer[i] 117 | end 118 | return l 119 | end 120 | 121 | local function split(s, e) 122 | local before = {} 123 | local after = {} 124 | local off = 0 125 | for i = 1, #state.buffer do 126 | local l = #state.buffer[i] 127 | local sep_s = s - off 128 | local sep_e = e - off 129 | local buf_s = 1 130 | local buf_e = l 131 | 132 | if buf_e < sep_s then 133 | table.insert(before, state.buffer[i]) 134 | elseif sep_e < buf_s then 135 | table.insert(after, state.buffer[i]) 136 | else 137 | if buf_s < sep_s then 138 | table.insert(before, state.buffer[i]:sub(buf_s, sep_s - 1)) 139 | end 140 | if sep_e < buf_e then 141 | table.insert(after, state.buffer[i]:sub(sep_e + 1, buf_e)) 142 | end 143 | end 144 | 145 | off = off + l 146 | end 147 | return before, after 148 | end 149 | 150 | local function get(at) 151 | local off = 0 152 | for i = 1, #state.buffer do 153 | local l = #state.buffer[i] 154 | if at <= off + l then 155 | local idx = at - off 156 | return state.buffer[i]:sub(idx, idx) 157 | end 158 | off = off + l 159 | end 160 | return nil 161 | end 162 | 163 | local buffer_len = 0 164 | local delimiter_len = #self.delimiter 165 | local buffer 166 | buffer = { 167 | write = function(data) 168 | table.insert(state.buffer, data) 169 | buffer_len = len() 170 | 171 | while state.buffer_pos <= buffer_len do 172 | local b = get(state.buffer_pos) 173 | local d = self.delimiter:sub(state.delimiter_pos, state.delimiter_pos) 174 | if b == d then 175 | if state.delimiter_pos == delimiter_len then 176 | local before, after = split(state.match_pos, state.buffer_pos) 177 | callback(table.concat(before, '')) 178 | state.buffer = after 179 | state.buffer_pos = 1 180 | state.delimiter_pos = 1 181 | state.match_pos = nil 182 | buffer_len = len() 183 | else 184 | if state.delimiter_pos == 1 then 185 | state.match_pos = state.buffer_pos 186 | end 187 | state.buffer_pos = state.buffer_pos + 1 188 | state.delimiter_pos = state.delimiter_pos + 1 189 | end 190 | else 191 | state.buffer_pos = state.match_pos and state.match_pos + 1 or state.buffer_pos + 1 192 | state.delimiter_pos = 1 193 | state.match_pos = nil 194 | end 195 | end 196 | end, 197 | close = function() 198 | if #state.buffer > 0 then 199 | callback(table.concat(state.buffer, '')) 200 | end 201 | end, 202 | } 203 | return buffer 204 | end 205 | 206 | ---@class dansa.kit.System.RawBuffering: dansa.kit.System.Buffering 207 | System.RawBuffering = {} 208 | System.RawBuffering.__index = System.RawBuffering 209 | 210 | ---Create RawBuffering. 211 | function System.RawBuffering.new() 212 | return setmetatable({}, System.RawBuffering) 213 | end 214 | 215 | ---Create RawBuffer object. 216 | function System.RawBuffering:create(callback) 217 | return { 218 | write = function(data) 219 | callback(data) 220 | end, 221 | close = function() 222 | -- noop. 223 | end, 224 | } 225 | end 226 | 227 | ---Spawn a new process. 228 | ---@class dansa.kit.System.SpawnParams 229 | ---@field cwd string 230 | ---@field env? table 231 | ---@field input? string|string[] 232 | ---@field on_stdout? fun(data: string) 233 | ---@field on_stderr? fun(data: string) 234 | ---@field on_exit? fun(code: integer, signal: integer) 235 | ---@field buffering? dansa.kit.System.Buffering 236 | ---@param command string[] 237 | ---@param params dansa.kit.System.SpawnParams 238 | ---@return fun(signal?: integer) 239 | function System.spawn(command, params) 240 | command = vim 241 | .iter(command) 242 | :filter(function(c) 243 | return c ~= nil 244 | end) 245 | :totable() 246 | 247 | local cmd = command[1] 248 | local args = {} 249 | for i = 2, #command do 250 | table.insert(args, command[i]) 251 | end 252 | 253 | local env = params.env 254 | if not env then 255 | env = vim.fn.environ() 256 | env.NVIM = vim.v.servername 257 | env.NVIM_LISTEN_ADDRESS = nil 258 | end 259 | 260 | local env_pairs = {} 261 | for k, v in pairs(env) do 262 | table.insert(env_pairs, string.format('%s=%s', k, tostring(v))) 263 | end 264 | 265 | local buffering = params.buffering or System.RawBuffering.new() 266 | local stdout_buffer = buffering:create(function(text) 267 | if params.on_stdout then 268 | params.on_stdout(text) 269 | end 270 | end) 271 | local stderr_buffer = buffering:create(function(text) 272 | if params.on_stderr then 273 | params.on_stderr(text) 274 | end 275 | end) 276 | 277 | local close --[[@type fun(signal?: integer): dansa.kit.Async.AsyncTask]] 278 | local stdin = params.input and assert(vim.uv.new_pipe()) 279 | local stdout = assert(vim.uv.new_pipe()) 280 | local stderr = assert(vim.uv.new_pipe()) 281 | local process = vim.uv.spawn(vim.fn.exepath(cmd), { 282 | cwd = vim.fs.normalize(params.cwd), 283 | env = env_pairs, 284 | hide = true, 285 | args = args, 286 | stdio = { stdin, stdout, stderr }, 287 | detached = false, 288 | verbatim = false, 289 | } --[[@as any]], function(code, signal) 290 | stdout_buffer.close() 291 | stderr_buffer.close() 292 | close():next(function() 293 | if params.on_exit then 294 | params.on_exit(code, signal) 295 | end 296 | end) 297 | end) 298 | stdout:read_start(function(err, data) 299 | if err then 300 | error(err) 301 | end 302 | if data then 303 | stdout_buffer.write(data) 304 | end 305 | end) 306 | stderr:read_start(function(err, data) 307 | if err then 308 | error(err) 309 | end 310 | if data then 311 | stderr_buffer.write(data) 312 | end 313 | end) 314 | 315 | local stdin_closing = Async.new(function(resolve) 316 | if stdin then 317 | for _, input in ipairs(kit.to_array(params.input)) do 318 | stdin:write(input) 319 | end 320 | stdin:shutdown(function() 321 | stdin:close(resolve) 322 | end) 323 | else 324 | resolve() 325 | end 326 | end) 327 | 328 | close = function(signal) 329 | local closing = { stdin_closing } 330 | table.insert( 331 | closing, 332 | Async.new(function(resolve) 333 | if not stdout:is_closing() then 334 | stdout:close(resolve) 335 | else 336 | resolve() 337 | end 338 | end) 339 | ) 340 | table.insert( 341 | closing, 342 | Async.new(function(resolve) 343 | if not stderr:is_closing() then 344 | stderr:close(resolve) 345 | else 346 | resolve() 347 | end 348 | end) 349 | ) 350 | table.insert( 351 | closing, 352 | Async.new(function(resolve) 353 | if signal and process:is_active() then 354 | process:kill(signal) 355 | end 356 | if process and not process:is_closing() then 357 | process:close(resolve) 358 | else 359 | resolve() 360 | end 361 | end) 362 | ) 363 | 364 | local closing_task = Async.resolve() 365 | for _, task in ipairs(closing) do 366 | closing_task = closing_task:next(function() 367 | return task 368 | end) 369 | end 370 | return closing_task 371 | end 372 | 373 | return function(signal) 374 | close(signal) 375 | end 376 | end 377 | 378 | return System 379 | -------------------------------------------------------------------------------- /lua/dansa/kit/Vim/FloatingWindow.lua: -------------------------------------------------------------------------------- 1 | local kit = require('dansa.kit') 2 | 3 | ---@alias dansa.kit.Vim.FloatingWindow.WindowKind 'main' | 'scrollbar_track' | 'scrollbar_thumb' 4 | 5 | ---@class dansa.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 dansa.kit.Vim.FloatingWindow.ContentSize 14 | ---@field public width integer 15 | ---@field public height integer 16 | 17 | ---@class dansa.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 dansa.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 dansa.kit.Vim.FloatingWindow.BorderSize 35 | ---@field public content_size dansa.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 dansa.kit.Vim.FloatingWindow.Config 43 | ---@field public markdown? boolean 44 | 45 | ---@class dansa.kit.Vim.FloatingWindow 46 | ---@field private _augroup string 47 | ---@field private _config dansa.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 dansa.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 dansa.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 dansa.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: dansa.kit.Vim.FloatingWindow.BorderSize, content_size: dansa.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 dansa.kit.Vim.FloatingWindow 243 | function FloatingWindow.new() 244 | return setmetatable({ 245 | _augroup = vim.api.nvim_create_augroup(('dansa.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 dansa.kit.Vim.FloatingWindow.Config 261 | function FloatingWindow:get_config() 262 | return self._config 263 | end 264 | 265 | ---Set config. 266 | ---@param config dansa.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? dansa.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? dansa.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? dansa.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? dansa.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? dansa.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? dansa.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 dansa.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 dansa.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/dansa/kit/Vim/Keymap.lua: -------------------------------------------------------------------------------- 1 | local kit = require('dansa.kit') 2 | local Async = require('dansa.kit.Async') 3 | 4 | local buf = vim.api.nvim_create_buf(false, true) 5 | 6 | ---@alias dansa.kit.Vim.Keymap.Keys { keys: string, remap?: boolean } 7 | ---@alias dansa.kit.Vim.Keymap.KeysSpecifier string|dansa.kit.Vim.Keymap.Keys 8 | 9 | ---@param keys dansa.kit.Vim.Keymap.KeysSpecifier 10 | ---@return dansa.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 = 'dansa.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 dansa.kit.Async.AsyncTask 49 | function Keymap.next(callback) 50 | return Keymap.send(''):next(callback) 51 | end 52 | 53 | ---Send keys. 54 | ---@param keys dansa.kit.Vim.Keymap.KeysSpecifier|dansa.kit.Vim.Keymap.KeysSpecifier[] 55 | ---@param no_insert? boolean 56 | ---@return dansa.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("dansa.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("dansa.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/dansa/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/dansa/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_treesitter_syntax_groups(cursor) 21 | if #treesitter > 0 then 22 | return treesitter 23 | end 24 | return Syntax.get_vim_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/dansa/kit/Vim/WinSaveView.lua: -------------------------------------------------------------------------------- 1 | ---@class dansa.kit.Vim.WinSaveView 2 | ---@field private _mode string 3 | ---@field private _view table 4 | ---@field private _cmd string 5 | ---@field private _win number 6 | ---@field private _cur table 7 | local WinSaveView = {} 8 | WinSaveView.__index = WinSaveView 9 | 10 | ---Create WinSaveView. 11 | function WinSaveView.new() 12 | return setmetatable({ 13 | _mode = vim.api.nvim_get_mode().mode, 14 | _view = vim.fn.winsaveview(), 15 | _cmd = vim.fn.winrestcmd(), 16 | _win = vim.api.nvim_get_current_win(), 17 | _cur = vim.api.nvim_win_get_cursor(0), 18 | }, WinSaveView) 19 | end 20 | 21 | function WinSaveView:restore() 22 | vim.api.nvim_set_current_win(self._win) 23 | 24 | -- restore modes. 25 | if vim.api.nvim_get_mode().mode ~= self._mode then 26 | if self._mode == 'i' then 27 | vim.cmd.startinsert() 28 | elseif vim.tbl_contains({ 'v', 'V', vim.keycode('') }, self._mode) then 29 | vim.cmd.normal({ 'gv', bang = true }) 30 | end 31 | end 32 | 33 | vim.api.nvim_win_set_cursor(0, self._cur) 34 | vim.cmd(self._cmd) 35 | vim.fn.winrestview(self._view) 36 | end 37 | 38 | return WinSaveView 39 | -------------------------------------------------------------------------------- /lua/dansa/kit/init.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: ignore 512 2 | 3 | ---@class dansa.kit.buffer.Buffer 4 | ---@field put fun(data: string) 5 | ---@field get fun(byte_size?: integer): string 6 | ---@field len integer 7 | ---@field skip fun(byte_size: integer) 8 | ---@field peek fun(index: integer): integer? 9 | ---@field clear fun() 10 | ---@field reserve fun(byte_size: integer) 11 | ---@field iter_bytes fun(): fun(): integer, integer 12 | 13 | local kit = {} 14 | 15 | do 16 | local buffer = package.preload['string.buffer'] and require('string.buffer') 17 | if buffer then 18 | ---Create buffer object. 19 | ---@return dansa.kit.buffer.Buffer 20 | function kit.buffer_jit() 21 | local buf = buffer.new() 22 | local ptr, len = buf:ref() 23 | ---@type dansa.kit.buffer.Buffer 24 | return setmetatable({ 25 | put = function(data) 26 | buf:put(data) 27 | ptr, len = buf:ref() 28 | end, 29 | get = function(byte_size) 30 | local o = buf:get(byte_size) 31 | ptr, len = buf:ref() 32 | return o 33 | end, 34 | len = function() 35 | return len 36 | end, 37 | skip = function(byte_size) 38 | buf:skip(byte_size) 39 | ptr, len = buf:ref() 40 | end, 41 | clear = function() 42 | buf:reset() 43 | ptr, len = buf:ref() 44 | end, 45 | peek = function(index) 46 | if index < 1 or index > len then 47 | return nil 48 | end 49 | return ptr[index - 1] 50 | end, 51 | reserve = function(byte_size) 52 | buf:reserve(byte_size) 53 | end, 54 | iter_bytes = function() 55 | local i = 0 56 | return function() 57 | if i < len then 58 | local byte = ptr[i] 59 | i = i + 1 60 | return i, byte 61 | end 62 | end 63 | end, 64 | }, { 65 | __tostring = function() 66 | return buf:tostring() 67 | end, 68 | }) 69 | end 70 | end 71 | end 72 | 73 | ---Create buffer object. 74 | ---@return dansa.kit.buffer.Buffer 75 | function kit.buffer_tbl() 76 | local buf = {} 77 | ---@type dansa.kit.buffer.Buffer 78 | local buffer 79 | buffer = setmetatable({ 80 | put = function(data) 81 | table.insert(buf, data) 82 | end, 83 | get = function(byte_size) 84 | if byte_size == nil then 85 | local data = table.concat(buf) 86 | kit.clear(buf) 87 | return data 88 | end 89 | if byte_size == 0 then 90 | return '' 91 | end 92 | 93 | local data = {} 94 | local off = 0 95 | local i = 1 96 | while i <= #buf do 97 | local b = buf[i] 98 | if off + #b >= byte_size then 99 | local data_size = byte_size - off 100 | if #b == data_size then 101 | table.insert(data, b) 102 | table.remove(buf, i) 103 | else 104 | table.insert(data, b:sub(1, data_size)) 105 | buf[i] = b:sub(data_size + 1) 106 | end 107 | break 108 | end 109 | i = i + 1 110 | off = off + #b 111 | end 112 | return table.concat(data) 113 | end, 114 | len = function() 115 | local len = 0 116 | for _, data in ipairs(buf) do 117 | len = len + #data 118 | end 119 | return len 120 | end, 121 | skip = function(byte_size) 122 | buffer.get(byte_size) 123 | end, 124 | peek = function(index) 125 | local i = 1 126 | while i <= #buf do 127 | if index <= #buf[i] then 128 | return buf[i]:byte(index) 129 | end 130 | index = index - #buf[i] 131 | i = i + 1 132 | end 133 | end, 134 | clear = function() 135 | kit.clear(buf) 136 | end, 137 | reserve = function() 138 | -- noop 139 | end, 140 | iter_bytes = function() 141 | local i = 1 142 | local j = 1 143 | local c = 1 144 | return function() 145 | while i <= #buf do 146 | local data = buf[i] 147 | if j <= #data then 148 | local byte = data:byte(j) 149 | j = j + 1 150 | c = c + 1 151 | return (c - 1), byte 152 | end 153 | i = i + 1 154 | j = 1 155 | end 156 | return nil 157 | end 158 | end, 159 | }, { 160 | __tostring = function() 161 | return table.concat(buf) 162 | end, 163 | }) 164 | return buffer 165 | end 166 | 167 | ---Create buffer object. 168 | ---@return dansa.kit.buffer.Buffer 169 | kit.buffer = function() 170 | return kit.buffer_jit and kit.buffer_jit() or kit.buffer_tbl() 171 | end 172 | 173 | do 174 | local clear = package.preload['table.clear'] and require('table.clear') 175 | 176 | ---Clear table. 177 | ---@generic T: table 178 | ---@param tbl T 179 | ---@return T 180 | kit.clear = function(tbl) 181 | if type(tbl) ~= 'table' then 182 | return tbl 183 | end 184 | if clear then 185 | clear(tbl) 186 | else 187 | for k, _ in pairs(tbl) do 188 | tbl[k] = nil 189 | end 190 | end 191 | return tbl 192 | end 193 | end 194 | 195 | ---Check shallow equals. 196 | ---@param a any 197 | ---@param b any 198 | ---@return boolean 199 | function kit.shallow_equals(a, b) 200 | if type(a) ~= type(b) then 201 | return false 202 | end 203 | if type(a) ~= 'table' then 204 | return a == b 205 | end 206 | for k, v in pairs(a) do 207 | if v ~= b[k] then 208 | return false 209 | end 210 | end 211 | for k, v in pairs(b) do 212 | if v ~= a[k] then 213 | return false 214 | end 215 | end 216 | return true 217 | end 218 | 219 | ---Create gabage collection detector. 220 | ---@param callback fun(...: any): any 221 | ---@return userdata 222 | function kit.gc(callback) 223 | local gc = newproxy(true) 224 | if vim.is_thread() or os.getenv('NODE_ENV') == 'test' then 225 | getmetatable(gc).__gc = callback 226 | else 227 | getmetatable(gc).__gc = vim.schedule_wrap(callback) 228 | end 229 | return gc 230 | end 231 | 232 | ---Safe version of vim.schedule. 233 | ---@param fn fun(...: any): any 234 | function kit.safe_schedule(fn) 235 | if vim.is_thread() then 236 | local timer = assert(vim.uv.new_timer()) 237 | timer:start(0, 0, function() 238 | timer:stop() 239 | timer:close() 240 | fn() 241 | end) 242 | else 243 | vim.schedule(fn) 244 | end 245 | end 246 | 247 | ---Safe version of vim.schedule_wrap. 248 | ---@param fn fun(...: any): any 249 | function kit.safe_schedule_wrap(fn) 250 | if vim.is_thread() then 251 | return function(...) 252 | local args = { ... } 253 | local timer = assert(vim.uv.new_timer()) 254 | timer:start(0, 0, function() 255 | timer:stop() 256 | timer:close() 257 | fn(unpack(args)) 258 | end) 259 | end 260 | else 261 | return vim.schedule_wrap(fn) 262 | end 263 | end 264 | 265 | ---Fast version of vim.schedule. 266 | ---@param fn fun(): any 267 | function kit.fast_schedule(fn) 268 | if vim.in_fast_event() then 269 | kit.safe_schedule(fn) 270 | else 271 | fn() 272 | end 273 | end 274 | 275 | ---Safe version of vim.schedule_wrap. 276 | ---@generic A 277 | ---@param fn fun(...: A) 278 | ---@return fun(...: A) 279 | function kit.fast_schedule_wrap(fn) 280 | return function(...) 281 | local args = { ... } 282 | kit.fast_schedule(function() 283 | fn(unpack(args)) 284 | end) 285 | end 286 | end 287 | 288 | ---Find up directory. 289 | ---@param path string 290 | ---@param markers string[] 291 | function kit.findup(path, markers) 292 | path = vim.fs.normalize(path) 293 | if vim.fn.filereadable(path) == 1 then 294 | path = vim.fs.dirname(path) 295 | end 296 | while path ~= '/' do 297 | for _, marker in ipairs(markers) do 298 | local target = vim.fs.joinpath(path, (marker:gsub('/', ''))) 299 | if vim.fn.isdirectory(target) == 1 or vim.fn.filereadable(target) == 1 then 300 | return path 301 | end 302 | end 303 | path = vim.fs.dirname(path) 304 | end 305 | end 306 | 307 | do 308 | _G.kit = _G.kit or {} 309 | _G.kit.unique_id = 0 310 | 311 | ---Create unique id. 312 | ---@return integer 313 | kit.unique_id = function() 314 | _G.kit.unique_id = _G.kit.unique_id + 1 315 | return _G.kit.unique_id 316 | end 317 | end 318 | 319 | ---Map array. 320 | ---@deprecated 321 | ---@param array table 322 | ---@param fn fun(item: unknown, index: integer): unknown 323 | ---@return unknown[] 324 | function kit.map(array, fn) 325 | local new_array = {} 326 | for i, item in ipairs(array) do 327 | table.insert(new_array, fn(item, i)) 328 | end 329 | return new_array 330 | end 331 | 332 | ---@generic T 333 | ---@deprecated 334 | ---@param value T? 335 | ---@param default T 336 | function kit.default(value, default) 337 | if value == nil then 338 | return default 339 | end 340 | return value 341 | end 342 | 343 | ---Get object path with default value. 344 | ---@generic T 345 | ---@param value table 346 | ---@param path integer|string|(string|integer)[] 347 | ---@param default? T 348 | ---@return T 349 | function kit.get(value, path, default) 350 | local result = value 351 | for _, key in ipairs(kit.to_array(path)) do 352 | if type(result) == 'table' then 353 | result = result[key] 354 | else 355 | return default 356 | end 357 | end 358 | if result == nil then 359 | return default 360 | end 361 | return result 362 | end 363 | 364 | ---Set object path with new value. 365 | ---@param value table 366 | ---@param path integer|string|(string|integer)[] 367 | ---@param new_value any 368 | function kit.set(value, path, new_value) 369 | local current = value 370 | for i = 1, #path - 1 do 371 | local key = path[i] 372 | if type(current[key]) ~= 'table' then 373 | error('The specified path is not a table.') 374 | end 375 | current = current[key] 376 | end 377 | current[path[#path]] = new_value 378 | end 379 | 380 | ---Create debounced callback. 381 | ---@generic T: fun(...: any): nil 382 | ---@param callback T 383 | ---@param timeout_ms integer 384 | ---@return T 385 | function kit.debounce(callback, timeout_ms) 386 | local timer = assert(vim.uv.new_timer()) 387 | return setmetatable({ 388 | timeout_ms = timeout_ms, 389 | is_running = function() 390 | return timer:is_active() 391 | end, 392 | stop = function() 393 | timer:stop() 394 | end, 395 | }, { 396 | __call = function(self, ...) 397 | local arguments = { ... } 398 | 399 | self.running = true 400 | timer:stop() 401 | timer:start(self.timeout_ms, 0, function() 402 | self.running = false 403 | timer:stop() 404 | callback(unpack(arguments)) 405 | end) 406 | end, 407 | }) 408 | end 409 | 410 | ---Create throttled callback. 411 | ---First call will be called immediately. 412 | ---@generic T: fun(...: any): nil 413 | ---@param callback T 414 | ---@param timeout_ms integer 415 | function kit.throttle(callback, timeout_ms) 416 | local timer = assert(vim.uv.new_timer()) 417 | local arguments = nil 418 | local last_time = (vim.uv.hrtime() / 1000000) - timeout_ms - 1 419 | return setmetatable({ 420 | timeout_ms = timeout_ms, 421 | is_running = function() 422 | return timer:is_active() 423 | end, 424 | stop = function() 425 | timer:stop() 426 | end, 427 | }, { 428 | __call = function(self, ...) 429 | arguments = { ... } 430 | 431 | if self.is_running() then 432 | timer:stop() 433 | end 434 | local delay_ms = self.timeout_ms - ((vim.uv.hrtime() / 1000000) - last_time) 435 | if delay_ms > 0 then 436 | timer:start(delay_ms, 0, function() 437 | timer:stop() 438 | last_time = (vim.uv.hrtime() / 1000000) 439 | callback(unpack(arguments)) 440 | end) 441 | else 442 | last_time = (vim.uv.hrtime() / 1000000) 443 | callback(unpack(arguments)) 444 | end 445 | end, 446 | }) 447 | end 448 | 449 | do 450 | ---@generic T 451 | ---@param target T 452 | ---@param seen table 453 | ---@return T 454 | local function do_clone(target, seen) 455 | if type(target) ~= 'table' then 456 | return target 457 | end 458 | if seen[target] then 459 | return seen[target] 460 | end 461 | if kit.is_array(target) then 462 | local new_tbl = {} 463 | seen[target] = new_tbl 464 | for k, v in ipairs(target) do 465 | new_tbl[k] = do_clone(v, seen) 466 | end 467 | return new_tbl 468 | else 469 | local new_tbl = {} 470 | local meta = getmetatable(target) 471 | if meta then 472 | setmetatable(new_tbl, meta) 473 | end 474 | seen[target] = new_tbl 475 | for k, v in pairs(target) do 476 | new_tbl[k] = do_clone(v, seen) 477 | end 478 | return new_tbl 479 | end 480 | end 481 | 482 | ---Clone object. 483 | ---@generic T 484 | ---@param target T 485 | ---@return T 486 | function kit.clone(target) 487 | return do_clone(target, {}) 488 | end 489 | end 490 | 491 | ---Merge two tables. 492 | ---@generic T: any[] 493 | ---NOTE: This doesn't merge array-like table. 494 | ---@param tbl1 T 495 | ---@param tbl2 T 496 | ---@return T 497 | function kit.merge(tbl1, tbl2) 498 | local is_dict1 = kit.is_dict(tbl1) and getmetatable(tbl1) == nil 499 | local is_dict2 = kit.is_dict(tbl2) and getmetatable(tbl2) == nil 500 | if is_dict1 and is_dict2 then 501 | local new_tbl = {} 502 | for k, v in pairs(tbl2) do 503 | if tbl1[k] ~= vim.NIL then 504 | new_tbl[k] = kit.merge(tbl1[k], v) 505 | end 506 | end 507 | for k, v in pairs(tbl1) do 508 | if tbl2[k] == nil then 509 | if v ~= vim.NIL then 510 | new_tbl[k] = kit.merge(v, {}) 511 | else 512 | new_tbl[k] = nil 513 | end 514 | end 515 | end 516 | return new_tbl 517 | end 518 | 519 | -- premitive like values. 520 | if tbl1 == vim.NIL then 521 | return nil 522 | elseif tbl1 == nil then 523 | return kit.merge(tbl2, {}) -- clone & prevent vim.NIL 524 | end 525 | return tbl1 526 | end 527 | 528 | ---Concatenate two tables. 529 | ---NOTE: This doesn't concatenate dict-like table. 530 | ---@param tbl1 table 531 | ---@param ... table 532 | ---@return table 533 | function kit.concat(tbl1, ...) 534 | local new_tbl = {} 535 | 536 | local off = 0 537 | for _, item in pairs(tbl1) do 538 | new_tbl[off + 1] = item 539 | off = off + 1 540 | end 541 | 542 | for _, tbl2 in ipairs({ ... }) do 543 | for _, item in pairs(kit.to_array(tbl2)) do 544 | new_tbl[off + 1] = item 545 | off = off + 1 546 | end 547 | end 548 | return new_tbl 549 | end 550 | 551 | ---Slice the array. 552 | ---@generic T: any[] 553 | ---@param array T 554 | ---@param s integer 555 | ---@param e integer 556 | ---@return T 557 | function kit.slice(array, s, e) 558 | if not kit.is_array(array) then 559 | error('[kit] specified value is not an array.') 560 | end 561 | local new_array = {} 562 | for i = s, e do 563 | table.insert(new_array, array[i]) 564 | end 565 | return new_array 566 | end 567 | 568 | ---The value to array. 569 | ---@param value any 570 | ---@return table 571 | function kit.to_array(value) 572 | if type(value) == 'table' then 573 | if kit.is_array(value) then 574 | return value 575 | end 576 | end 577 | return { value } 578 | end 579 | 580 | ---Check the value is array. 581 | ---@param value any 582 | ---@return boolean 583 | function kit.is_array(value) 584 | if type(value) ~= 'table' then 585 | return false 586 | end 587 | 588 | for k, _ in pairs(value) do 589 | if type(k) ~= 'number' then 590 | return false 591 | end 592 | end 593 | return true 594 | end 595 | 596 | ---Check the value is dict. 597 | ---@param value any 598 | ---@return boolean 599 | function kit.is_dict(value) 600 | return type(value) == 'table' and (not kit.is_array(value) or kit.is_empty(value)) 601 | end 602 | 603 | ---Check the value is empty. 604 | ---@param value any 605 | ---@return boolean 606 | function kit.is_empty(value) 607 | if type(value) ~= 'table' then 608 | return false 609 | end 610 | for _ in pairs(value) do 611 | return false 612 | end 613 | if #value == 0 then 614 | return true 615 | end 616 | return true 617 | end 618 | 619 | ---Reverse the array. 620 | ---@param array table 621 | ---@return table 622 | function kit.reverse(array) 623 | if not kit.is_array(array) then 624 | error('[kit] specified value is not an array.') 625 | end 626 | 627 | local new_array = {} 628 | for i = #array, 1, -1 do 629 | table.insert(new_array, array[i]) 630 | end 631 | return new_array 632 | end 633 | 634 | ---String dedent. 635 | function kit.dedent(s) 636 | local lines = vim.split(s, '\n') 637 | if lines[1]:match('^%s*$') then 638 | table.remove(lines, 1) 639 | end 640 | if lines[#lines]:match('^%s*$') then 641 | table.remove(lines, #lines) 642 | end 643 | local base_indent = lines[1]:match('^%s*') 644 | for i, line in ipairs(lines) do 645 | lines[i] = line:gsub('^' .. base_indent, '') 646 | end 647 | return table.concat(lines, '\n') 648 | end 649 | 650 | return kit 651 | -------------------------------------------------------------------------------- /plugin/dansa.lua: -------------------------------------------------------------------------------- 1 | if vim.g.loaded_dansa then 2 | return 3 | end 4 | vim.g.loaded_dansa = true 5 | 6 | local dansa = require "dansa" 7 | 8 | ---Get enabled 9 | ---@return boolean 10 | local function enabled() 11 | local e = dansa.config:get() 12 | if type(e) == 'function' then 13 | return e() 14 | end 15 | return not not e 16 | end 17 | 18 | ---Debug print 19 | local function debug_print(...) 20 | if vim.g.dansa_debug then 21 | vim.print(...) 22 | end 23 | end 24 | vim.g.dansa_debug = false 25 | 26 | ---Set expandtab/shiftwidth/tabstop. 27 | ---@param bufnr integer 28 | ---@param is_tab boolean 29 | ---@param shiftwidth integer 30 | local function set(bufnr, is_tab, shiftwidth) 31 | if is_tab then 32 | vim.bo[bufnr].expandtab = false 33 | vim.bo[bufnr].shiftwidth = shiftwidth 34 | vim.bo[bufnr].tabstop = shiftwidth 35 | else 36 | vim.bo[bufnr].expandtab = true 37 | vim.bo[bufnr].shiftwidth = shiftwidth 38 | vim.bo[bufnr].tabstop = shiftwidth 39 | end 40 | end 41 | 42 | ---Apply indent settings for buf. 43 | ---@param bufnr integer 44 | local function apply(bufnr) 45 | local guess = dansa.guess(bufnr) 46 | 47 | if #vim.tbl_keys(guess) == 0 then 48 | local is_editorconfig = type(vim.b.editorconfig) and vim.b.editorconfig or vim.g.editorconfig or true 49 | if is_editorconfig then 50 | debug_print({ 51 | name = vim.api.nvim_buf_get_name(bufnr), 52 | type = 'editorconfig', 53 | guess = guess, 54 | }) 55 | require('editorconfig').config(bufnr) 56 | else 57 | debug_print({ 58 | name = vim.api.nvim_buf_get_name(bufnr), 59 | type = 'fallback', 60 | guess = guess, 61 | }) 62 | vim.bo[bufnr].expandtab = dansa.config:get().default.expandtab 63 | if dansa.config:get().default.expandtab then 64 | vim.bo[bufnr].shiftwidth = dansa.config:get().default.space.shiftwidth 65 | vim.bo[bufnr].tabstop = dansa.config:get().default.space.shiftwidth 66 | else 67 | vim.bo[bufnr].shiftwidth = dansa.config:get().default.tab.shiftwidth 68 | vim.bo[bufnr].tabstop = dansa.config:get().default.tab.shiftwidth 69 | end 70 | end 71 | return 72 | end 73 | 74 | debug_print({ 75 | name = vim.api.nvim_buf_get_name(bufnr), 76 | type = 'guess', 77 | guess = guess, 78 | }) 79 | local current = { indent = '', count = -1 } 80 | for indent, count in pairs(guess) do 81 | if current.count < count then 82 | current.indent = indent 83 | current.count = count 84 | end 85 | end 86 | 87 | if current.indent == '\t' then 88 | set(bufnr, true, dansa.config:get().default.tab.shiftwidth) 89 | else 90 | set(bufnr, false, #current.indent) 91 | end 92 | end 93 | 94 | vim.api.nvim_create_autocmd({ 'FileType' }, { 95 | group = vim.api.nvim_create_augroup('dansa', { 96 | clear = true, 97 | }), 98 | callback = function() 99 | if enabled() then 100 | local bufnr = vim.api.nvim_get_current_buf() 101 | vim.schedule(function() 102 | if not vim.api.nvim_buf_is_valid(bufnr) then 103 | return 104 | end 105 | apply(bufnr) 106 | end) 107 | end 108 | end 109 | }) 110 | 111 | vim.api.nvim_create_user_command('Dansa', function(ctx) 112 | local buf = vim.api.nvim_get_current_buf() 113 | if ctx.fargs[1] == '8' then 114 | set(buf, false, 8) 115 | elseif ctx.fargs[1] == '4' then 116 | set(buf, false, 4) 117 | elseif ctx.fargs[1] == '2' then 118 | set(buf, false, 2) 119 | elseif ctx.fargs[1] == 'tab' then 120 | set(buf, true, 4) 121 | else 122 | apply(vim.api.nvim_get_current_buf()) 123 | end 124 | vim.api.nvim_echo({ 125 | { '[dansa]', 'Special' }, 126 | { ' ', 'Normal' }, 127 | 128 | -- style. 129 | { 'style=', 'Normal' }, 130 | { vim.bo[0].expandtab and 'space' or 'tab', 'String' }, 131 | { ', ', 'Normal' }, 132 | 133 | -- shiftwidth. 134 | { 'shiftwidth=', 'Normal' }, 135 | { tostring(vim.bo[0].shiftwidth), 'String' }, 136 | { ', ', 'Normal' }, 137 | 138 | -- tabstop. 139 | { 'tabstop=', 'Normal' }, 140 | { tostring(vim.bo[0].tabstop), 'String' }, 141 | }, false, {}) 142 | end, { 143 | nargs = '*', 144 | complete = function() 145 | return { 146 | '8', 147 | '4', 148 | '2', 149 | 'tab', 150 | } 151 | end 152 | }) 153 | --------------------------------------------------------------------------------