diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fcb23b1..4d2d019 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,16 +13,12 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v15 - with: - nix_path: nixpkgs=channel:nixos-unstable - extra_nix_config: | - access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - - - name: Check teal files + - name: Setup dependencies run: | - nix-shell --pure --run "make ensure" + sudo apt update + sudo apt install --yes neovim luarocks - name: Run tests run: | - nix-shell --pure --run "make test" + luarocks init + luarocks test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d14386a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/luarocks +/lua_modules +/.luarocks diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..a2b3447 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +column_width = 100 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferSingle" +call_parentheses = "Always" diff --git a/Makefile b/Makefile deleted file mode 100644 index 7f0468b..0000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -build: - tl build - -check: - tl check teal/**/*.tl - -ensure: build - git diff --exit-code -- lua - -test: - ./run_tests.sh - -nix-build: - nix-shell --pure --run "tl build" - -nix-test: - nix-shell --pure --run "./run_tests.sh" - -nix-debug: - nix-shell --pure --run "nvim --clean -u min.lua" diff --git a/lua/notifier/config.lua b/lua/notifier/config.lua index bd546b8..55eb305 100644 --- a/lua/notifier/config.lua +++ b/lua/notifier/config.lua @@ -1,83 +1,74 @@ -local ConfigModule = {Config = {Notify = {}, }, } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -ConfigModule.NS_NAME = "Notifier" -ConfigModule.NS_ID = vim.api.nvim_create_namespace("notifier") - -ConfigModule.config = { - ignore_messages = {}, - status_width = function() +---@class Notifier.NotifyCfg +---@field clear_time integer Time to wait before poping the notification +---@field min_level integer Minimum log level to consider + +---@class Notifier.Config +---@field ignore_messages {string: boolean} TODO +---@field status_width (integer|fun(): integer) Width or function to compute width +---@field components string[] Components to activate +---@field notify Notifier.NotifyCfg Configuration for the notify component +---@field component_name_recall boolean Whether to recall the component name in the notifier UI +---@field debug boolean +---@field zindex integer zindex of the UI floating window + +local M = { + ---@type Notifier.Config + config = { + ignore_messages = {}, + status_width = function() local tw = vim.o.textwidth local cols = vim.o.columns if tw > 0 and tw < cols then - return math.floor((cols - tw) * 0.7) + return math.floor((cols - tw) * 0.7) else - return math.floor(cols / 3) + return math.floor(cols / 3) end - end, - components = { "nvim", "lsp" }, - notify = { + end, + components = { 'nvim', 'lsp' }, + notify = { clear_time = 5000, min_level = vim.log.levels.INFO, - }, - component_name_recall = false, - debug = false, - zindex = 50, + }, + component_name_recall = false, + debug = false, + zindex = 50, + } } -function ConfigModule.update(other) - ConfigModule.config = vim.tbl_deep_extend("force", ConfigModule.config, other or {}) +M.NS_NAME = 'Notifier' +M.NS_ID = vim.api.nvim_create_namespace('notifier') + +--- Updates the configuration to match @p other +---@param other Notifier.Config The new configuration +function M.update(other) + M.config = vim.tbl_deep_extend('force', M.config, other or {}) end -function ConfigModule.has_component(compname) - return vim.tbl_contains(ConfigModule.config.components, compname) +--- Checks whether a component is enabled +---@param compname string The component name to check +---@return boolean enabled Whether the componenent is enabled +function M.has_component(compname) + return vim.tbl_contains(M.config.components, compname) end +--- Creates and sets a highlight group. +---@param name string Short name of the highlight group +---@param options any Options to nvim_set_hl +---@return string hlgroup Name of the created highlight group +---@private local function hl_group(name, options) - local hl_name = ConfigModule.NS_NAME .. name - vim.api.nvim_set_hl(0, hl_name, options) - return hl_name + local hl_name = M.NS_NAME .. name + vim.api.nvim_set_hl(0, hl_name, options) + return hl_name end - -ConfigModule.HL_CONTENT_DIM = hl_group("ContentDim", { link = "Comment", default = true }) -ConfigModule.HL_CONTENT = hl_group("Content", { link = "Normal", default = true }) -ConfigModule.HL_TITLE = hl_group("Title", { link = "Title", default = true }) -ConfigModule.HL_ICON = hl_group("Icon", { link = "Title", default = true }) - +M.HL_CONTENT_DIM = hl_group('ContentDim', { link = 'Comment', default = true }) +M.HL_CONTENT = hl_group('Content', { link = 'Normal', default = true }) +M.HL_TITLE = hl_group('Title', { link = 'Title', default = true }) +M.HL_ICON = hl_group('Icon', { link = 'Title', default = true }) if vim.api.nvim_win_set_hl_ns then - vim.api.nvim_set_hl(ConfigModule.NS_ID, "NormalFloat", { bg = "NONE" }) + vim.api.nvim_set_hl(M.NS_ID, 'NormalFloat', { bg = 'NONE' }) end -return ConfigModule +return M diff --git a/lua/notifier/init.lua b/lua/notifier/init.lua index 361b298..894e71d 100644 --- a/lua/notifier/init.lua +++ b/lua/notifier/init.lua @@ -1,117 +1,140 @@ local api = vim.api -local status = require("notifier.status") -local config = require("notifier.config") - - - +local status = require('notifier.status') +local config = require('notifier.config') +local M = {} +---@class Notifier.NotifyMsg +---@field msg string The message +---@field level integer Message level +---@field opts {[string]: any} Options for the notification +---@type Notifier.NotifyMsg[] local notify_msg_cache = {} +--- Reimplementation of vim.notify +---@param msg string Message +---@param level integer Level (see vim.log.Level) +---@param opts {[string]:any}? Options +---@param no_cache boolean? Whether to add the current notification in the cache local function notify(msg, level, opts, no_cache) - level = level or vim.log.levels.INFO - opts = opts or {} - if level >= config.config.notify.min_level then - status.push("nvim", { mandat = msg, title = opts.title, icon = opts.icon }) - if not no_cache then - table.insert(notify_msg_cache, { msg = msg, level = level, opts = opts }) - end - local lifetime = config.config.notify.clear_time - if lifetime > 0 then - vim.defer_fn(function() status.pop("nvim") end, lifetime) - end - end + level = level or vim.log.levels.INFO + opts = opts or {} + if level >= config.config.notify.min_level then + status.push('nvim', { mandat = msg, title = opts.title, icon = opts.icon }) + if not no_cache then + table.insert(notify_msg_cache, { msg = msg, level = level, opts = opts }) + end + local lifetime = config.config.notify.clear_time + if lifetime > 0 then + vim.defer_fn(function() + status.pop('nvim') + end, lifetime) + end + end end - - - - - local commands = { - Clear = { - opts = {}, - func = function() - status.clear("nvim") - end, - }, - Replay = { - opts = { - bang = true, - }, - func = function(args) - if args.bang then - local list = {} - for _, msg in ipairs(notify_msg_cache) do - list[#list + 1] = { - text = msg.msg, - } - end - - vim.fn.setqflist(list, 'r') - else - for _, msg in ipairs(notify_msg_cache) do - notify(msg.msg, msg.level, msg.opts, true) - end - end - end, - }, -} - -return { - notify = function(msg, level, opts) - notify(msg, level, opts) - end, - setup = function(user_config) - api.nvim_create_augroup(config.NS_NAME, { - clear = true, - }) - - config.update(user_config) - - if config.has_component("nvim") then - vim.notify = function(msg, level, opts) - notify(msg, level, opts) - end - end - - for cname, def in pairs(commands) do - api.nvim_create_user_command(config.NS_NAME .. cname, def.func, def.opts) + Clear = { + opts = {}, + func = function() + status.clear('nvim') + end, + }, + Replay = { + opts = { + bang = true, + }, + func = function(args) + if args.bang then + ---@type any[] + local list = {} + for _, msg in ipairs(notify_msg_cache) do + list[#list + 1] = { + text = msg.msg, + } + end + + vim.fn.setqflist(list, 'r') + else + for _, msg in ipairs(notify_msg_cache) do + notify(msg.msg, msg.level, msg.opts, true) + end end + end, + }, +} - if config.has_component("lsp") then - local lsp_storage = {} - - - vim.lsp.handlers["$/progress"] = function(_, params, ctx) - if not params then return end +M.notify = notify - local value = params.value +--- Sets up notifier +---@param user_config Notifier.Config User configuration +function M.setup(user_config) + api.nvim_create_augroup(config.NS_NAME, { + clear = true, + }) - local client = vim.lsp.get_client_by_id(ctx.client_id) - if value.kind == "end" then - status.pop("lsp", client.name) - lsp_storage[params.token] = nil - elseif value.kind == "report" then - local msg = lsp_storage[params.token] - if not msg then error("Report without begin ?") end + config.update(user_config) - msg.opt = value.message or msg.opt + if config.has_component('nvim') then + ---@diagnostic disable-next-line:duplicate-set-field + vim.notify = function(msg, level, opts) + notify(msg, level, opts) + end + end + + for cname, def in pairs(commands) do + api.nvim_create_user_command(config.NS_NAME .. cname, def.func, def.opts) + end + + if config.has_component('lsp') then + ---@type {[string]: Notifier.Message} + local lsp_storage = {} + + --- Progress handler for LSP + ---@param _ any + ---@param params any? + ---@param ctx any + ---@diagnostic disable-next-line:duplicate-set-field + vim.lsp.handlers['$/progress'] = function(_, params, ctx) + if not params then + return + end - status.push("lsp", msg, client.name) - else - lsp_storage[params.token] = { mandat = value.title, opt = value.message, dim = true } - status.push("lsp", lsp_storage[params.token], client.name) - end - end + ---@type {kind: string, message: string, title: string} + local value = params.value + + local client = vim.lsp.get_client_by_id(ctx.client_id) + if value.kind == 'end' then + status.pop('lsp', client.name) + lsp_storage[params.token] = nil + elseif value.kind == 'report' then + local msg = lsp_storage[params.token] + if not msg then + error('Report without begin ?') + end + + msg.opt = value.message or msg.opt + + status.push('lsp', msg, client.name) + else + lsp_storage[params.token] = { mandat = value.title, opt = value.message, dim = true } + status.push('lsp', lsp_storage[params.token], client.name) end + end + end + + api.nvim_create_autocmd('VimResized', { + group = config.NS_NAME, + callback = function() + status._delete_win() + end, + }) +end - api.nvim_create_autocmd("VimResized", { - group = config.NS_NAME, - callback = function() - status._delete_win() - end, - }) - end, -} +return M +-- notify = function(msg, level, opts) +-- notify(msg, level, opts) +-- end, +-- ---@type function(Notifier.Config) +-- } diff --git a/lua/notifier/status.lua b/lua/notifier/status.lua index e6106b2..4842309 100644 --- a/lua/notifier/status.lua +++ b/lua/notifier/status.lua @@ -1,318 +1,338 @@ local api = vim.api -local cfg = require("notifier.config") +local cfg = require('notifier.config') local displayw = vim.fn.strdisplaywidth +---@class Notifier.Message +---@field mandat string Mandatory part of the message +---@field opt string? Optional part of the message +---@field dim boolean Whether to dim the message +---@field title string? Optional title for the message +---@field icon string? Optional icon of the message - -local StatusModule = {} - - - - - - - - - - - - - - -StatusModule.buf_nr = nil -StatusModule.win_nr = nil -StatusModule.active = {} +local M = { + win_nr = nil, + buf_nr = nil, + active = {} +} local function get_status_width() - local w = cfg.config.status_width - if type(w) == "function" then - return w() - else - return w - end + local w = cfg.config.status_width + if type(w) == 'function' then + return w() + else + return w + end end -function StatusModule._create_win() - if not StatusModule.win_nr or not api.nvim_win_is_valid(StatusModule.win_nr) then - if not StatusModule.buf_nr or not api.nvim_buf_is_valid(StatusModule.buf_nr) then - StatusModule.buf_nr = api.nvim_create_buf(false, true); - end - local border - if cfg.config.debug then - border = "single" - else - border = "none" - end - local success, win_nr = pcall(api.nvim_open_win, StatusModule.buf_nr, false, { - focusable = false, - style = "minimal", - border = border, - noautocmd = true, - relative = "editor", - anchor = "SE", - width = get_status_width(), - height = 3, - row = vim.o.lines - vim.o.cmdheight - 1, - col = vim.o.columns, - zindex = cfg.config.zindex, - }) - - if success then - StatusModule.win_nr = win_nr - if api.nvim_win_set_hl_ns then - api.nvim_win_set_hl_ns(StatusModule.win_nr, cfg.NS_ID) - end +--- Creates the status window if not already created +---@private +local function create_win() + if not M.win_nr or not api.nvim_win_is_valid(M.win_nr) then + if not M.buf_nr or not api.nvim_buf_is_valid(M.buf_nr) then + M.buf_nr = api.nvim_create_buf(false, true) + end + + ---@type string + local border + if cfg.config.debug then + border = 'single' + else + border = 'none' + end + + local success + success, M.win_nr = pcall(api.nvim_open_win, M.buf_nr, false, { + focusable = false, + style = 'minimal', + border = border, + noautocmd = true, + relative = 'editor', + anchor = 'SE', + width = get_status_width(), + height = 3, + row = vim.o.lines - vim.o.cmdheight - 1, + col = vim.o.columns, + zindex = cfg.config.zindex, + }) + + if success then + if api.nvim_win_set_hl_ns then + api.nvim_win_set_hl_ns(M.win_nr, cfg.NS_ID) end - end + end + end end -function StatusModule._ui_valid() - return StatusModule.win_nr and api.nvim_win_is_valid(StatusModule.win_nr) and - StatusModule.buf_nr and api.nvim_buf_is_valid(StatusModule.buf_nr) +--- Checks if the UI is valid, including the UI buffer +---@return boolean valid Whether the UI is valid +---@private +function M._ui_valid() + return M.win_nr and api.nvim_win_is_valid(M.win_nr) and M.buf_nr and api.nvim_buf_is_valid(M.buf_nr) end -function StatusModule._delete_win() - if StatusModule.win_nr and api.nvim_win_is_valid(StatusModule.win_nr) then - api.nvim_win_close(StatusModule.win_nr, true) - end - StatusModule.win_nr = nil +--- Closes the status window +local function delete_win() + if M.win_nr and api.nvim_win_is_valid(M.win_nr) then + api.nvim_win_close(M.win_nr, true) + end + M.win_nr = nil end +--- Pads @p src to fit in @p width +---@param src string The string to pad +---@param width integer Width to fit +---@return string padded The argument padded with spaces to fit in width local function adjust_width(src, width) - return vim.fn["repeat"](" ", width - displayw(src)) .. src + return vim.fn['repeat'](' ', width - displayw(src)) .. src end - - - - - - -function StatusModule.redraw() - StatusModule._create_win() - - if not StatusModule._ui_valid() then return end - - local lines = {} - local hl_infos = {} - local width = get_status_width() - - - - local function push_line(title, content) - local message_lines = vim.split(content.mandat, '\n', { plain = true, trimempty = true }) - - - local inner_width = width - (displayw(title) + 1) - if content.icon then - inner_width = inner_width - (displayw(content.icon) + 1) +--- Redraws the notifier UI +local function redraw() + create_win() + + if not M._ui_valid() then + return + end + + local lines = {} + local hl_infos = {} + local width = get_status_width() + + local function push_line(title, content) + local message_lines = vim.split(content.mandat, '\n', { plain = true, trimempty = true }) + + local inner_width = width - (displayw(title) + 1) + if content.icon then + inner_width = inner_width - (displayw(content.icon) + 1) + end + + if cfg.config.debug then + vim.pretty_print(message_lines) + end + + ---@type string[] + local tmp_lines = {} + + local maxlen = 0 + + for _, line in ipairs(message_lines) do + ---@type string + local tmp_line + local words = vim.split(line, '%s', { trimempty = true }) + + for _, w in ipairs(words) do + ---@type string + local tmp + if not tmp_line then + tmp = w + else + tmp = tmp_line .. ' ' .. w + end + + if displayw(tmp) > inner_width then + tmp_lines[#tmp_lines + 1] = tmp_line + maxlen = math.max(maxlen, displayw(tmp_line)) + tmp_line = w + else + tmp_line = tmp + end end - if cfg.config.debug then - vim.pretty_print(message_lines) + tmp_lines[#tmp_lines + 1] = tmp_line + maxlen = math.max(maxlen, displayw(tmp_line)) + end + + message_lines = tmp_lines + + if cfg.config.debug then + vim.pretty_print(message_lines) + end + + for i, line in ipairs(message_lines) do + ---@type integer + local right_pad_len = maxlen - displayw(line) + + ---@type string + local fmt_msg + if content.opt and i == #message_lines then + local tmp = string.format('%s (%s)', line, content.opt) + if displayw(tmp) > inner_width - right_pad_len then + fmt_msg = adjust_width(line, inner_width - right_pad_len) + else + fmt_msg = adjust_width(tmp, inner_width - right_pad_len) + end + else + fmt_msg = adjust_width(line, inner_width - right_pad_len) end - - local tmp_lines = {} - local maxlen = 0 - - for _, line in ipairs(message_lines) do - local tmp_line - local words = vim.split(line, '%s', { trimempty = true }) - - for _, w in ipairs(words) do - local tmp - if not tmp_line then - tmp = w - else - tmp = tmp_line .. ' ' .. w - end - - if displayw(tmp) > inner_width then - tmp_lines[#tmp_lines + 1] = tmp_line - maxlen = math.max(maxlen, displayw(tmp_line)) - tmp_line = w - else - tmp_line = tmp - end - end - - tmp_lines[#tmp_lines + 1] = tmp_line - maxlen = math.max(maxlen, displayw(tmp_line)) + ---@type string + local formatted + if i == 1 then + local right_pad = vim.fn['repeat'](' ', right_pad_len) + if content.icon then + formatted = string.format('%s%s %s %s', fmt_msg, right_pad, title, content.icon) + else + formatted = string.format('%s%s %s', fmt_msg, right_pad, title) + end + else + formatted = fmt_msg end - message_lines = tmp_lines - if cfg.config.debug then - vim.pretty_print(message_lines) + vim.pretty_print(formatted) end - for i, line in ipairs(message_lines) do - - - - local right_pad_len = maxlen - displayw(line) - - - local fmt_msg - if content.opt and i == #message_lines then - - local tmp = string.format("%s (%s)", line, content.opt) - if displayw(tmp) > inner_width - right_pad_len then - fmt_msg = adjust_width(line, inner_width - right_pad_len) - else - fmt_msg = adjust_width(tmp, inner_width - right_pad_len) - end - else - fmt_msg = adjust_width(line, inner_width - right_pad_len) - end - - local formatted - if i == 1 then - local right_pad = vim.fn["repeat"](' ', right_pad_len) - if content.icon then - formatted = string.format("%s%s %s %s", fmt_msg, right_pad, title, content.icon) - else - formatted = string.format("%s%s %s", fmt_msg, right_pad, title) - end - else - formatted = fmt_msg - end - - if cfg.config.debug then - vim.pretty_print(formatted) - end - - - table.insert(lines, formatted) - if i == 1 then - table.insert(hl_infos, { name = title, dim = content.dim, icon = content.icon }) - else - table.insert(hl_infos, { name = "", icon = "", dim = content.dim }) - end + table.insert(lines, formatted) + if i == 1 then + table.insert(hl_infos, { name = title, dim = content.dim, icon = content.icon }) + else + table.insert(hl_infos, { name = '', icon = '', dim = content.dim }) end - end - - - for _, compname in ipairs(cfg.config.components) do - local msgs = StatusModule.active[compname] or {} - local is_tbl = vim.tbl_islist(msgs) - - for name, msg in pairs(msgs) do - - local rname = msg.title - if not rname and is_tbl then - rname = compname - elseif not is_tbl then - rname = name - end - - if cfg.config.component_name_recall and not is_tbl then - rname = string.format("%s:%s", compname, rname) - end - - push_line(rname, msg) + end + end + + for _, compname in ipairs(cfg.config.components) do + local msgs = M.active[compname] or {} + local is_tbl = vim.tbl_islist(msgs) + + for name, msg in pairs(msgs) do + local rname = msg.title + if not rname and is_tbl then + rname = compname + elseif not is_tbl then + rname = name end - end - - if #lines > 0 then - api.nvim_buf_clear_namespace(StatusModule.buf_nr, cfg.NS_ID, 0, -1) - api.nvim_buf_set_lines(StatusModule.buf_nr, 0, -1, false, lines) - - for i = 1, #hl_infos do - local hl_group - if hl_infos[i].dim then - hl_group = cfg.HL_CONTENT_DIM - else - hl_group = cfg.HL_CONTENT - end + if cfg.config.component_name_recall and not is_tbl then + rname = string.format('%s:%s', compname, rname) + end + push_line(rname, msg) + end + end + if #lines > 0 then + api.nvim_buf_clear_namespace(M.buf_nr, cfg.NS_ID, 0, -1) + api.nvim_buf_set_lines(M.buf_nr, 0, -1, false, lines) - local title_start_offset = #lines[i] - #hl_infos[i].name - if hl_infos[i].icon then - title_start_offset = title_start_offset - (#hl_infos[i].icon + 1) - end + for i = 1, #hl_infos do + local hl_group + if hl_infos[i].dim then + hl_group = cfg.HL_CONTENT_DIM + else + hl_group = cfg.HL_CONTENT + end - local title_stop_offset - if hl_infos[i].icon then - title_stop_offset = #lines[i] - #hl_infos[i].icon - 1 - else - title_stop_offset = -1 - end - api.nvim_buf_add_highlight(StatusModule.buf_nr, cfg.NS_ID, hl_group, i - 1, 0, title_start_offset - 1) - api.nvim_buf_add_highlight(StatusModule.buf_nr, cfg.NS_ID, cfg.HL_TITLE, i - 1, title_start_offset, title_stop_offset) + local title_start_offset = #lines[i] - #hl_infos[i].name + if hl_infos[i].icon then + title_start_offset = title_start_offset - (#hl_infos[i].icon + 1) + end - if hl_infos[i].icon then - api.nvim_buf_add_highlight(StatusModule.buf_nr, cfg.NS_ID, cfg.HL_ICON, i - 1, title_stop_offset + 1, -1) - end + local title_stop_offset + if hl_infos[i].icon then + title_stop_offset = #lines[i] - #hl_infos[i].icon - 1 + else + title_stop_offset = -1 end + api.nvim_buf_add_highlight( + M.buf_nr, + cfg.NS_ID, + hl_group, + i - 1, + 0, + title_start_offset - 1 + ) + api.nvim_buf_add_highlight( + M.buf_nr, + cfg.NS_ID, + cfg.HL_TITLE, + i - 1, + title_start_offset, + title_stop_offset + ) + + if hl_infos[i].icon then + api.nvim_buf_add_highlight( + M.buf_nr, + cfg.NS_ID, + cfg.HL_ICON, + i - 1, + title_stop_offset + 1, + -1 + ) + end + end - api.nvim_win_set_height(StatusModule.win_nr, #lines) - else - StatusModule._delete_win() - end + api.nvim_win_set_height(M.win_nr, #lines) + else + delete_win() + end end -function StatusModule._ensure_valid(msg) - if msg.icon and displayw(msg.icon) == 0 then - msg.icon = nil - end +function M._ensure_valid(msg) + if msg.icon and displayw(msg.icon) == 0 then + msg.icon = nil + end - if msg.title and displayw(msg.title) == 0 then - msg.title = nil - end + if msg.title and displayw(msg.title) == 0 then + msg.title = nil + end - if msg.title and string.find(msg.title, "\n") then - error("Message title cannot contain newlines") - end + if msg.title and string.find(msg.title, '\n') then + error('Message title cannot contain newlines') + end - if msg.icon and string.find(msg.icon, "\n") then - error("Message icon cannot contain newlines") - end + if msg.icon and string.find(msg.icon, '\n') then + error('Message icon cannot contain newlines') + end - if msg.opt and string.find(msg.opt, "\n") then - error("Message optional part cannot contain newlines") - end + if msg.opt and string.find(msg.opt, '\n') then + error('Message optional part cannot contain newlines') + end - return true + return true end -function StatusModule.push(component, content, title) - if not StatusModule.active[component] then - StatusModule.active[component] = {} - end - - if type(content) == "string" then - content = { mandat = content } - end - - content = content - if StatusModule._ensure_valid(content) then - if title then - StatusModule.active[component][title] = content - else - table.insert(StatusModule.active[component], content) - end - StatusModule.redraw() - end +--- Push a new content into a given component +---@param component string Component to put the message int +---@param content string|Notifier.Message Message to display +---@param title string? Subcomponent title +function M.push(component, content, title) + if not M.active[component] then + M.active[component] = {} + end + + if type(content) == 'string' then + content = { mandat = content } + end + + content = content + if M._ensure_valid(content) then + if title then + M.active[component][title] = content + else + table.insert(M.active[component], content) + end + redraw() + end end -function StatusModule.pop(component, title) - if not StatusModule.active[component] then return end - - if title then - StatusModule.active[component][title] = nil - else - table.remove(StatusModule.active[component]) - end - StatusModule.redraw() +function M.pop(component, title) + if not M.active[component] then + return + end + + if title then + M.active[component][title] = nil + else + table.remove(M.active[component]) + end + redraw() end -function StatusModule.clear(component) - StatusModule.active[component] = nil - StatusModule.redraw() +function M.clear(component) + M.active[component] = nil + redraw() end -return StatusModule +return M diff --git a/min.lua b/min.lua index 0b83c6f..f80d4b4 100644 --- a/min.lua +++ b/min.lua @@ -1 +1,11 @@ -vim.o.runtimepath='.,' .. vim.fn.expand "$VIMRUNTIME" + +vim.o.runtimepath = '.,' .. vim.fn.expand('$VIMRUNTIME') + +-- Now integrate luarocks to get the test dependencies +local lrpath = vim.fn.systemlist { "luarocks", "path", "--lr-path" }[1] +local lrcpath = vim.fn.systemlist { "luarocks", "path", "--lr-cpath" }[1] + +package.path = package.path .. ";" .. lrpath +package.cpath = package.cpath .. ";" .. lrcpath + +require 'luarocks.loader' diff --git a/notifier.nvim-dev-1.rockspec b/notifier.nvim-dev-1.rockspec new file mode 100644 index 0000000..4f65d06 --- /dev/null +++ b/notifier.nvim-dev-1.rockspec @@ -0,0 +1,30 @@ +package = "notifier.nvim" +rockspec_format = "3.0" + +version = "dev-1" +source = { + url = "git+ssh://git@github.com/vigoux/notifier.nvim.git" +} +description = { + detailed = "![Showcase](https://user-images.githubusercontent.com/39092278/186714682-f51ea665-6fca-4442-bad8-8cc7fda2f138.gif)", + homepage = "www.github.com/vigoux/notifier.nvim", + license = "BSD 3-Clause" +} +build = { + type = "builtin", + modules = { + ["notifier.config"] = "lua/notifier/config.lua", + ["notifier.init"] = "lua/notifier/init.lua", + ["notifier.status"] = "lua/notifier/status.lua" + }, + copy_directories = { + "tests" + } +} +test_dependencies = { + "busted >= 2.1.2" +} +test = { + type = "command", + command = "find tests -type f -exec './{}' ';'" +} diff --git a/run_tests_busted.sh b/run_tests_busted.sh new file mode 100755 index 0000000..4c7c894 --- /dev/null +++ b/run_tests_busted.sh @@ -0,0 +1,7 @@ +#!/bin/sh +BUSTED_VERSION="2.1.2-3" +luarocks install busted "$BUSTED_VERSION" +luarocks config --scope project lua_version 5.1 +nvim --headless --clean -u min.lua \ + -c "lua package.path='lua_modules/share/lua/5.1/?.lua;lua_modules/share/lua/5.1/?/init.lua;'..package.path;package.cpath='lua_modules/lib/lua/5.1/?.so;'..package.cpath;local k,l,_=pcall(require,'luarocks.loader') _=k and l.add_context('busted','$BUSTED_VERSION')" \ + -l "lua_modules/lib/luarocks/rocks-5.1/busted/$BUSTED_VERSION/bin/busted" "$@" diff --git a/shell.nix b/shell.nix deleted file mode 100644 index d5e5076..0000000 --- a/shell.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ pkgs ? import {} }: -pkgs.mkShell { - buildInputs = [ - pkgs.neovim - pkgs.stylua - pkgs.git - pkgs.luajitPackages.busted - pkgs.luajitPackages.tl - ]; -} diff --git a/teal/notifier/config.tl b/teal/notifier/config.tl deleted file mode 100644 index 2823ec9..0000000 --- a/teal/notifier/config.tl +++ /dev/null @@ -1,83 +0,0 @@ -local record ConfigModule - record Config - ignore_messages: {string:boolean} - status_width: integer|function():integer - components: {string} - record Notify - clear_time: integer - min_level: integer - end - notify: Notify - component_name_recall: boolean - - -- For... debug purposes - debug: boolean - - zindex: integer - end - - config: Config - - update: function(Config) - has_component: function(string): boolean - - NS_NAME: string - NS_ID: vim.api.NSId - - -- Highlight groups - HL_CONTENT_DIM: string - HL_CONTENT: string - HL_TITLE: string - HL_ICON: string -end - -ConfigModule.NS_NAME = "Notifier" -ConfigModule.NS_ID = vim.api.nvim_create_namespace "notifier" - -ConfigModule.config = { - ignore_messages = {}, - status_width = function(): integer - local tw = vim.o.textwidth - local cols = vim.o.columns - if tw > 0 and tw < cols then - return math.floor((cols - tw) * 0.7) - else - return math.floor(cols / 3) - end - end, - components = { "nvim", "lsp" }, - notify = { - clear_time = 5000, - min_level = vim.log.levels.INFO - } as ConfigModule.Config.Notify, - component_name_recall = false, - debug = false, - zindex = 50, -} - -function ConfigModule.update(other: ConfigModule.Config) - ConfigModule.config = vim.tbl_deep_extend("force", ConfigModule.config as {string:any}, other as {string:any} or {}) as ConfigModule.Config -end - -function ConfigModule.has_component(compname: string): boolean - return vim.tbl_contains(ConfigModule.config.components, compname) -end - -local function hl_group(name: string, options: vim.api.SetHlOptions): string - local hl_name = ConfigModule.NS_NAME .. name - vim.api.nvim_set_hl(0, hl_name, options) - return hl_name -end - --- Global highlight definitions -ConfigModule.HL_CONTENT_DIM = hl_group("ContentDim", { link = "Comment", default = true }) -ConfigModule.HL_CONTENT = hl_group("Content", { link = "Normal", default = true }) -ConfigModule.HL_TITLE = hl_group("Title", { link = "Title", default = true }) -ConfigModule.HL_ICON = hl_group("Icon", { link = "Title", default = true }) - --- Namespaced highlight definitions -if vim.api.nvim_win_set_hl_ns then - vim.api.nvim_set_hl(ConfigModule.NS_ID, "NormalFloat", { bg = "NONE" }) -end - -return ConfigModule diff --git a/teal/notifier/init.tl b/teal/notifier/init.tl deleted file mode 100644 index 4f109d0..0000000 --- a/teal/notifier/init.tl +++ /dev/null @@ -1,117 +0,0 @@ -local api = vim.api -local status = require "notifier.status" -local config = require "notifier.config" - -local record NotifyOptions - title: string|nil - icon: string|nil -end - -local notify_msg_cache = {} - -local function notify(msg: string, level: integer, opts: NotifyOptions, no_cache: boolean) - level = level or vim.log.levels.INFO - opts = opts or {} - if level >= config.config.notify.min_level then - status.push("nvim", { mandat = msg, title = opts.title, icon = opts.icon }) - if not no_cache then - table.insert(notify_msg_cache, { msg = msg, level = level, opts = opts }) - end - local lifetime = config.config.notify.clear_time - if lifetime > 0 then - vim.defer_fn(function() status.pop "nvim" end, lifetime) - end - end -end - -local record CommandDef - func: function(vim.api.UserCommandArgs) - opts: vim.api.CreateUserCommandOptions -end - -local commands: {string:CommandDef} = { - Clear = { - opts = {}, - func = function() - status.clear "nvim" - end - }, - Replay = { - opts = { - bang = true - }, - func = function(args: vim.api.UserCommandArgs) - if args.bang then - local list = {} - for _, msg in ipairs(notify_msg_cache) do - list[#list + 1] = { - text = msg.msg, - } - end - - vim.fn.setqflist(list, 'r') - else - for _, msg in ipairs(notify_msg_cache) do - notify(msg.msg, msg.level, msg.opts, true) - end - end - end - } -} - -return { - notify = function(msg: string, level: integer, opts: NotifyOptions) - notify(msg, level, opts) - end, - setup = function(user_config: config.Config) - api.nvim_create_augroup(config.NS_NAME, { - clear = true - }) - - config.update(user_config) - - if config.has_component "nvim" then - vim.notify = function(msg:string, level:integer, opts: {any:any}) - notify(msg, level, opts as NotifyOptions) - end - end - - for cname, def in pairs(commands) do - api.nvim_create_user_command(config.NS_NAME .. cname, def.func, def.opts) - end - - if config.has_component "lsp" then - local lsp_storage: {string|integer: Message} = {} - - -- We'll plug into the lsp handler - vim.lsp.handlers["$/progress"] = function(_: any, params: vim.lsp.ProgressParams, ctx: vim.lsp.HandlerCtx) - if not params then return end - - local value = params.value - - local client: vim.lsp.Client = vim.lsp.get_client_by_id(ctx.client_id) - if value.kind == "end" then - status.pop("lsp", client.name) - lsp_storage[params.token] = nil - elseif value.kind == "report" then - local msg = lsp_storage[params.token] - if not msg then error "Report without begin ?" end - - msg.opt = value.message or msg.opt - - status.push("lsp", msg, client.name) - else -- begin - lsp_storage[params.token] = { mandat = value.title, opt = value.message, dim=true } - status.push("lsp", lsp_storage[params.token], client.name) - end - end - end - - api.nvim_create_autocmd("VimResized", { - group = config.NS_NAME, - callback = function() - status._delete_win() - end - }) - end -} diff --git a/teal/notifier/status.tl b/teal/notifier/status.tl deleted file mode 100644 index 79f8e94..0000000 --- a/teal/notifier/status.tl +++ /dev/null @@ -1,318 +0,0 @@ -local api = vim.api -local cfg = require"notifier.config" -local displayw = vim.fn.strdisplaywidth - -local type Component = {string|integer:Message} - -local record StatusModule - active: {string:Component} - buf_nr: vim.api.BufNr|nil - win_nr: vim.api.WinNr|nil - - -- API functions - redraw: function() - - -- Private functions - _ensure_valid: function(Message): boolean - _create_win: function() - _delete_win: function() - _ui_valid: function(): boolean -end - -StatusModule.buf_nr = nil -StatusModule.win_nr = nil -StatusModule.active = {} - -local function get_status_width(): integer - local w = cfg.config.status_width - if w is function(): integer then - return w() - else - return w - end -end - -function StatusModule._create_win() - if not StatusModule.win_nr or not api.nvim_win_is_valid(StatusModule.win_nr) then - if not StatusModule.buf_nr or not api.nvim_buf_is_valid(StatusModule.buf_nr) then - StatusModule.buf_nr = api.nvim_create_buf(false, true); - end - local border: string - if cfg.config.debug then - border = "single" - else - border = "none" - end - local success, win_nr = pcall(api.nvim_open_win, StatusModule.buf_nr, false, { - focusable = false, - style = "minimal", - border = border, - noautocmd = true, - relative = "editor", - anchor = "SE", - width = get_status_width(), - height = 3, - row = vim.o.lines - vim.o.cmdheight - 1, - col = vim.o.columns, - zindex = cfg.config.zindex, - }) - - if success then - StatusModule.win_nr = win_nr - if api.nvim_win_set_hl_ns then - api.nvim_win_set_hl_ns(StatusModule.win_nr, cfg.NS_ID) - end - end - end -end - -function StatusModule._ui_valid(): boolean - return StatusModule.win_nr and api.nvim_win_is_valid(StatusModule.win_nr) - and StatusModule.buf_nr and api.nvim_buf_is_valid(StatusModule.buf_nr) -end - -function StatusModule._delete_win() - if StatusModule.win_nr and api.nvim_win_is_valid(StatusModule.win_nr) then - api.nvim_win_close(StatusModule.win_nr, true) - end - StatusModule.win_nr = nil -end - -local function adjust_width(src: string, width: integer): string - return vim.fn["repeat"](" ", width - displayw(src)) .. src -end - -local record HlInfo - name: string - icon: string - dim: boolean -end - -function StatusModule.redraw() - StatusModule._create_win() - - if not StatusModule._ui_valid() then return end - - local lines: {string} = {} - local hl_infos: {integer:HlInfo} = {} - local width = get_status_width() - - -- This is the main "drawing" function, handling en ensure the correct placement of everything in - -- the message - local function push_line(title: string, content: Message) - local message_lines = vim.split(content.mandat, '\n', { plain = true, trimempty = true }) - - -- Compute the available room for messages - local inner_width: integer = width - (displayw(title) + 1) - if content.icon then - inner_width = inner_width - (displayw(content.icon) + 1) -- One space plus the icon which is of size 1 cell - end - - if cfg.config.debug then - vim.pretty_print(message_lines) - end - - -- Now go over each line, and if it does not fit break the line in multiple parts - local tmp_lines: {string} = {} - local maxlen: integer = 0 - - for _, line in ipairs(message_lines) do - local tmp_line: string - local words = vim.split(line, '%s', { trimempty = true }) - - for _, w in ipairs(words) do - local tmp: string - if not tmp_line then - tmp = w - else - tmp = tmp_line .. ' ' .. w - end - - if displayw(tmp) > inner_width then - tmp_lines[#tmp_lines + 1] = tmp_line - maxlen = math.max(maxlen, displayw(tmp_line)) - tmp_line = w - else - tmp_line = tmp - end - end - - tmp_lines[#tmp_lines + 1] = tmp_line - maxlen = math.max(maxlen, displayw(tmp_line)) - end - - message_lines = tmp_lines - - if cfg.config.debug then - vim.pretty_print(message_lines) - end - - for i,line in ipairs(message_lines) do - -- This is where we handle multiline notifications - - -- For a given line, the amount of space to be left-alligned in the message - local right_pad_len = maxlen - displayw(line) - - -- Try to render optional message part and see if it fits - local fmt_msg: string - if content.opt and i == #message_lines then - -- The optional parts of messages is drawn with the last line - local tmp = string.format("%s (%s)", line, content.opt) - if displayw(tmp) > inner_width - right_pad_len then - fmt_msg = adjust_width(line, inner_width - right_pad_len) - else - fmt_msg = adjust_width(tmp, inner_width - right_pad_len) - end - else - fmt_msg = adjust_width(line, inner_width - right_pad_len) - end - - local formatted: string - if i == 1 then - local right_pad: string = vim.fn["repeat"](' ', right_pad_len) - if content.icon then - formatted = string.format("%s%s %s %s", fmt_msg, right_pad, title, content.icon) - else - formatted = string.format("%s%s %s", fmt_msg, right_pad, title) - end - else - formatted = fmt_msg - end - - if cfg.config.debug then - vim.pretty_print(formatted) - end - - -- TODO(vigoux): right allign multiline messages instead of push like this - table.insert(lines, formatted) - if i == 1 then - table.insert(hl_infos, { name = title, dim = content.dim, icon = content.icon }) - else - table.insert(hl_infos, { name = "", icon = "", dim = content.dim }) - end - end - end - - -- For each component, print the messages - for _, compname in ipairs(cfg.config.components) do - local msgs = StatusModule.active[compname] or {} - local is_tbl = vim.tbl_islist(msgs) - - for name, msg in pairs(msgs) do - -- Resolve notification name - local rname: string = msg.title - if not rname and is_tbl then - rname = compname - elseif not is_tbl then - rname = name as string -- This is actually a string here, as checks by tbl_islist - end - - if cfg.config.component_name_recall and not is_tbl then - rname = string.format("%s:%s", compname, rname) - end - - push_line(rname, msg) - end - end - - - if #lines > 0 then - api.nvim_buf_clear_namespace(StatusModule.buf_nr, cfg.NS_ID, 0, -1) - api.nvim_buf_set_lines(StatusModule.buf_nr, 0, -1, false, lines) - -- Then highlight the lines - for i = 1, #hl_infos do - local hl_group: string - if hl_infos[i].dim then - hl_group = cfg.HL_CONTENT_DIM - else - hl_group = cfg.HL_CONTENT - end - - -- Here the highlighting is done in byte indexes, so we have to correct the byte offset - -- compared to the cell offsets - local title_start_offset: integer = #lines[i] - #hl_infos[i].name - if hl_infos[i].icon then - title_start_offset = title_start_offset - (#hl_infos[i].icon + 1) - end - - local title_stop_offset: integer - if hl_infos[i].icon then - title_stop_offset = #lines[i] - #hl_infos[i].icon - 1 - else - title_stop_offset = -1 - end - api.nvim_buf_add_highlight(StatusModule.buf_nr, cfg.NS_ID, hl_group, i - 1, 0, title_start_offset - 1) - api.nvim_buf_add_highlight(StatusModule.buf_nr, cfg.NS_ID, cfg.HL_TITLE, i - 1, title_start_offset, title_stop_offset) - - if hl_infos[i].icon then - api.nvim_buf_add_highlight(StatusModule.buf_nr, cfg.NS_ID, cfg.HL_ICON, i - 1, title_stop_offset + 1, -1) - end - end - - api.nvim_win_set_height(StatusModule.win_nr, #lines) - else - StatusModule._delete_win() - end -end - -function StatusModule._ensure_valid(msg: Message): boolean - if msg.icon and displayw(msg.icon) == 0 then - msg.icon = nil - end - - if msg.title and displayw(msg.title) == 0 then - msg.title = nil - end - - if msg.title and string.find(msg.title, "\n") then - error "Message title cannot contain newlines" - end - - if msg.icon and string.find(msg.icon, "\n") then - error "Message icon cannot contain newlines" - end - - if msg.opt and string.find(msg.opt, "\n") then - error "Message optional part cannot contain newlines" - end - - return true -end - -function StatusModule.push(component: string, content: Message|string, title: string|nil) - if not StatusModule.active[component] then - StatusModule.active[component] = {} - end - - if content is string then - content = { mandat = content } - end - - content = content as Message - if StatusModule._ensure_valid(content) then - if title then - StatusModule.active[component][title] = content - else - table.insert(StatusModule.active[component] as {integer:Message}, content) - end - StatusModule.redraw() - end -end - -function StatusModule.pop(component: string, title: string|nil) - if not StatusModule.active[component] then return end - - if title then - StatusModule.active[component][title] = nil - else - table.remove(StatusModule.active[component] as {integer:Message}) - end - StatusModule.redraw() -end - -function StatusModule.clear(component: string) - StatusModule.active[component] = nil - StatusModule.redraw() -end - -return StatusModule diff --git a/tests/notify.lua b/tests/notify.lua old mode 100644 new mode 100755 index 820943f..68766a8 --- a/tests/notify.lua +++ b/tests/notify.lua @@ -1,6 +1,9 @@ -local notifier = require 'notifier' -local status = require 'notifier.status' -require 'busted.runner' { output = 'TAP', shuffle = true } +#!/usr/bin/env -S nvim --clean -u ./min.lua -l + +require 'busted.runner'() + +local notifier = require('notifier') +local status = require('notifier.status') local function assert_no_status() assert.Falsy(status._ui_valid()) @@ -15,44 +18,42 @@ local function assert_status(lines) assert.are.Same(get_status_lines(), lines) end -notifier.setup { - components = { "nvim" }, +notifier.setup({ + components = { 'nvim' }, notify = { min_level = vim.log.levels.INFO, }, - status_width = 40 -} + status_width = 40, +}) describe('notify', function() - after_each(function() - status.clear "nvim" + status.clear('nvim') end) it('works', function() - vim.notify "test" - assert_status { - ' test nvim' - } + vim.notify('test') + assert_status({ + ' test nvim', + }) end) it('min_level is respected (level == min_level)', function() - vim.notify("test INFO", vim.log.levels.INFO) - assert_status { - ' test INFO nvim' - } + vim.notify('test INFO', vim.log.levels.INFO) + assert_status({ + ' test INFO nvim', + }) end) it('min_level is respected (level < min_level)', function() - vim.notify("test DEBUG", vim.log.levels.DEBUG) + vim.notify('test DEBUG', vim.log.levels.DEBUG) assert_no_status() end) it('min_level is respected (level > min_level)', function() - vim.notify("test WARN", vim.log.levels.WARN) - assert_status { - ' test WARN nvim' - } + vim.notify('test WARN', vim.log.levels.WARN) + assert_status({ + ' test WARN nvim', + }) end) - end) diff --git a/tests/status.lua b/tests/status.lua old mode 100644 new mode 100755 index ee84cdb..5069088 --- a/tests/status.lua +++ b/tests/status.lua @@ -1,6 +1,9 @@ -local notifier = require 'notifier' -local status = require 'notifier.status' -require 'busted.runner' { output = 'TAP', shuffle = true } +#!/usr/bin/env -S nvim --clean -u ./min.lua -l + +require 'busted.runner'() + +local notifier = require('notifier') +local status = require('notifier.status') local function get_status_lines() assert.Truthy(status._ui_valid()) @@ -12,53 +15,52 @@ local function assert_status(lines) end notifier.setup { - components = { "test" }, - status_width = 40 + components = { 'test' }, + status_width = 40, } describe('status window', function() - after_each(function() - status.clear "test" + status.clear('test') end) it('works', function() - status.push("test", "test") - assert_status { - ' test test' - } + status.push('test', 'test') + assert_status({ + ' test test', + }) end) it('handles multiline notifications #8', function() - status.push("test", "test\ntest") - assert_status { + status.push('test', 'test\ntest') + assert_status({ ' test test', - ' test' - } + ' test', + }) end) it('right alligns notifications', function() - status.push("test", "test with more text\ntest") - assert_status { + status.push('test', 'test with more text\ntest') + assert_status({ ' test with more text test', ' test', - } + }) - status.push("test", "test\ntest") - assert_status { + status.push('test', 'test\ntest') + assert_status({ ' test with more text test', ' test', ' test test', - ' test' - } + ' test', + }) end) it('handles very long notifications #11', function() status.push('test', 'very long notification that should wrap correctly otherwise that is a bug') - assert_status { + assert_status({ ' very long notification that should test', ' wrap correctly otherwise that is a', - ' bug' - } + ' bug', + }) end) end) diff --git a/tlconfig.lua b/tlconfig.lua deleted file mode 100644 index eee9a92..0000000 --- a/tlconfig.lua +++ /dev/null @@ -1,10 +0,0 @@ -return { - gen_target = '5.1', - gen_compat = 'off', - global_env_def = 'types', - include_dir = { - 'types', 'teal', - }, - source_dir = 'teal', - build_dir = "lua", -} diff --git a/types/types.d.tl b/types/types.d.tl deleted file mode 100644 index d235498..0000000 --- a/types/types.d.tl +++ /dev/null @@ -1,189 +0,0 @@ -global unpack: function({T}, number, number): T... - -global record Message - mandat: string - dim: boolean - title: string|nil - icon: string|nil - opt: string|nil -end - -global record vim - record o - textwidth: integer - lines: integer - columns: integer - cmdheight: integer - end - - record BufNr - end - - record QfItem - bufnr: BufNr - filename: string - - lnum: integer - end_lnum: integer - col: integer - end_col: integer - - text: string - type: string - valid: integer - - - -- Not really useful stuff - nr: integer - vcol: integer - module: string - pattern: string - end - - - record fn - exists: function(string): integer - strdisplaywidth: function(string): integer - setqflist: function({QfItem}, string, {string:boolean}) - ["repeat"]: function(T, integer): T - end - - record api - type BufNr = integer - type WinNr = integer - type NSId = integer - - record SetHlOptions - link: string - default: boolean - bg: string - end - - record OpenWinOptions - relative: string - win: WinNr|nil - anchor: string|nil - width: integer - height: integer - bufpos: {integer,integer} - row: integer - col: integer - focusable: boolean - external: boolean - zindex: integer|nil - style: string|nil - border: string|nil - noautocmd: boolean - end - - record CreateAugroupOptions - clear: boolean - end - - record UserCommandArgs - args: {string} - bang: boolean - end - - record CreateUserCommandOptions - bang: boolean - end - - record CreateAutocmdOptions - group: string|integer|nil - pattern: string|{string}|nil - buffer: BufNr|nil - desc: string|nil - callback: function()|string|nil - command: string|nil - once: boolean|nil - nested: boolean|nil - end - - nvim_buf_add_highlight: function(BufNr, NSId, string, integer, integer, integer) - nvim_buf_clear_namespace: function(BufNr, NSId, integer, integer) - nvim_buf_is_valid: function(BufNr): boolean - nvim_buf_set_lines: function(BufNr, integer, integer, boolean, {string}) - nvim_create_augroup: function(string, CreateAugroupOptions) - nvim_create_autocmd: function(string|{string}, CreateAutocmdOptions) - nvim_create_buf: function(boolean, boolean): BufNr - nvim_create_namespace: function(string): NSId - nvim_create_user_command: function(string, function(UserCommandArgs), CreateUserCommandOptions) - nvim_open_win: function(BufNr, boolean, OpenWinOptions): WinNr - nvim_set_hl: function(NSId, string, SetHlOptions) - nvim_win_close: function(WinNr, boolean) - nvim_win_is_valid: function(WinNr): boolean - nvim_win_set_height: function(WinNr, integer) - nvim_win_set_hl_ns: function(WinNr, NSId) - end - - record log - record levels - TRACE: integer - DEBUG: integer - INFO: integer - WARN: integer - ERROR: integer - OFF: integer - end - end - - record lsp - record ClientId - end - - enum WorkDoneProgressKind - 'begin' - 'report' - 'end' - end - - record WorkDoneProgress - kind: WorkDoneProgressKind - message: string - title: string - cancellable: boolean - percentage: integer - end - - record HandlerCtx - method: string - client_id: ClientId - bufnr: BufNr - params: any - end - - record ProgressParams - token: string|integer - value: WorkDoneProgress - end - - record Client - id: ClientId - name: string - end - - handlers: {string: function(any, any, HandlerCtx)} - get_client_by_id: function(ClientId): Client - end - - record SplitOptions - plain: boolean - trimempty: boolean - end - - print: function(any) - - notify: function(string, integer, {any:any}) - defer_fn: function(function(), integer) - - tbl_contains: function({T}, T): boolean - tbl_deep_extend: function(string, ...: table): table - tbl_islist: function({any:any}): boolean - - pretty_print: function(any) - - split: function(string, string, SplitOptions): {string} - - schedule: function(function()) -end