local function augroup(name) return vim.api.nvim_create_augroup('kyren-' .. name, { clear = true }) end ---------------------------------------------------------------------------- -- NOTE: Highlight when yanking text ---------------------------------------------------------------------------- vim.api.nvim_create_autocmd('TextYankPost', { group = augroup('highlight-yank'), callback = function() vim.highlight.on_yank() end, }) vim.api.nvim_create_autocmd('BufEnter', { desc = 'Disable newlines on commented lines from continuing the comment', group = augroup('disable-comments-continuation'), callback = function() -- vim.opt_local.formatoptions:remove('r') -- no comments on enter vim.opt_local.formatoptions:remove('o') -- no comments on `o` or `O` end, }) -- NOTE: highlight %v, %s etc in go string literals vim.api.nvim_create_autocmd({ 'BufEnter', 'TextChanged', 'InsertLeave' }, { pattern = '*.go', callback = function() local query = vim.treesitter.query.parse('go', '(interpreted_string_literal) @string') local parser = vim.treesitter.get_parser(0, 'go') if not parser then return end local tree = parser:parse()[1] local root = tree:root() local bufnr = vim.api.nvim_get_current_buf() for id, node in query:iter_captures(root, bufnr, 0, -1) do if query.captures[id] == 'string' then local start_row, start_col, end_row, end_col = node:range() -- Get the text of the string literal local text = vim.api.nvim_buf_get_text(bufnr, start_row, start_col, end_row, end_col, {})[1] -- Highlight only the parts matching `%[a-z]` for match_start, match_end in text:gmatch('()%%[a-z]()') do vim.api.nvim_buf_add_highlight( bufnr, -1, '@lsp.type.formatSpecifier.go', -- Higlight group start_row, start_col + match_start - 1, start_col + match_end - 1 ) end end end end, }) ---------------------------------------------------------------------------- -- NOTE: Snacks snippet to notify LSP servers when renaming files in oil.nvim ---------------------------------------------------------------------------- vim.api.nvim_create_autocmd('User', { pattern = 'OilActionsPost', callback = function(event) if event.data.actions.type == 'move' then Snacks.rename.on_rename_file(event.data.actions.src_url, event.data.actions.dest_url) end end, }) ---------------------------------------------------------------------------- -- NOTE: Check if we need to reload the file when it changed ---------------------------------------------------------------------------- vim.api.nvim_create_autocmd({ 'FocusGained', 'TermClose', 'TermLeave' }, { group = augroup('checktime'), callback = function() if vim.o.buftype ~= 'nofile' then vim.cmd('checktime') end end, }) ---------------------------------------------------------------------------- -- NOTE: Close some filetypes with ---------------------------------------------------------------------------- vim.api.nvim_create_autocmd('FileType', { group = augroup('close_with_q'), pattern = { 'PlenaryTestPopup', 'checkhealth', 'dbout', 'gitsigns-blame', 'grug-far', 'help', 'lspinfo', 'neotest-output', 'neotest-output-panel', 'neotest-summary', 'notify', 'qf', 'startuptime', 'tsplayground', }, callback = function(event) vim.bo[event.buf].buflisted = false vim.schedule(function() vim.keymap.set('n', 'q', function() vim.cmd('close') pcall(vim.api.nvim_buf_delete, event.buf, { force = true }) end, { buffer = event.buf, silent = true, desc = 'Quit buffer', }) end) end, }) ---------------------------------------------------------------------------- -- NOTE: Format and organize imports on file save ---------------------------------------------------------------------------- vim.api.nvim_create_autocmd('BufWritePre', { group = augroup('java-auto-format'), pattern = '*.java', callback = function(event) if vim.g.disable_autoformat or vim.b[event.buf].disable_autoformat then return end require('jdtls').organize_imports() vim.lsp.buf.format({ async = false }) end, }) ---------------------------------------------------------------------------- -- NOTE: show unused zig variables as "DiagnosticUnnecessary" instead of error ---------------------------------------------------------------------------- local orig_underline_show = vim.diagnostic.handlers.underline.show local custom_ns = vim.api.nvim_create_namespace('custom_unused_ns') vim.diagnostic.handlers.underline.show = function(namespace, bufnr, diagnostics, opts) local normal_diags = {} local custom_diags = {} for _, diag in ipairs(diagnostics) do if diag.message:find('unused', 1, true) then table.insert(custom_diags, diag) else table.insert(normal_diags, diag) end end if #normal_diags > 0 and orig_underline_show ~= nil then orig_underline_show(namespace, bufnr, normal_diags, opts) end for _, diag in ipairs(custom_diags) do vim.highlight.range( bufnr, custom_ns, 'DiagnosticUnnecessary', { diag.lnum, diag.col }, { diag.end_lnum, diag.end_col }, { inclusive = false } ) end end local orig_underline_hide = vim.diagnostic.handlers.underline.hide vim.diagnostic.handlers.underline.hide = function(namespace, bufnr) if orig_underline_hide ~= nil then orig_underline_hide(namespace, bufnr) end vim.api.nvim_buf_clear_namespace(bufnr, custom_ns, 0, -1) end -- NOTE: template for C/C++ files vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufEnter' }, { pattern = { '*.c', '*.h', '*.cpp' }, callback = function() local buf = vim.api.nvim_get_current_buf() local line_count = vim.api.nvim_buf_line_count(buf) local is_empty = line_count == 1 and vim.api.nvim_buf_get_lines(buf, 0, 1, false)[1] == '' if not is_empty then return end local ext = vim.fn.expand('%:e') local cwd = vim.fn.getcwd() local project_name = vim.fn.fnamemodify(cwd, ':t') local copyright = nil local lines = nil if project_name == 'krypton' then copyright = { '// Copyright (c) Kyren223', '// Licensed under the MIT license (https://opensource.org/license/mit/)', '', } local c_template = { '#include "base.h"', '', } lines = { c = c_template, h = { '#ifndef ' .. string.upper(vim.fn.expand('%:t:r')) .. '_H', '#define ' .. string.upper(vim.fn.expand('%:t:r')) .. '_H', '', '#include "generated/' .. vim.fn.expand('%:t:r') .. '.meta.h"', '', '#endif', }, cpp = c_template, } end if not lines then return end local content = lines[ext] if copyright then content = vim.list_extend(copyright, content) end vim.api.nvim_buf_set_lines(0, 0, -1, false, content) end, }) ---------------------------------------------------------------------------- -- NOTE: Function to highlight TODO/NOTE patterns ---------------------------------------------------------------------------- local todo_ns = vim.api.nvim_create_namespace('todo_highlight') local groups = { todo = { keywords = { 'TODO', 'WIP' }, hl = '@comment.todo.comment', }, warning = { keywords = { 'WARN', 'WARNING', 'HACK', 'SECURITY', 'SECURE', 'IMPORTANT' }, hl = '@comment.warning.comment', }, error = { keywords = { 'FIX', 'FIXME', 'ERROR', 'UNSAFE', 'SAFETY' }, hl = '@comment.error.comment', }, perf = { keywords = { 'PERF', 'OPTIMIZE', 'STUDY' }, hl = '@comment.perf.comment', }, note = { keywords = { 'NOTE', 'INFO', 'DOCS', 'TEST' }, hl = '@comment.note.comment', }, } local scopes = { bracket = '@punctuation.bracket.comment', constant = '@constant.comment', delimiter = '@punctuation.delimiter.comment', } function RenderTodoHighlights(bufnr) vim.api.nvim_buf_clear_namespace(bufnr, todo_ns, 0, -1) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) for i, line in ipairs(lines) do for _, cfg in pairs(groups) do for _, kw in ipairs(cfg.keywords) do for s, e in line:gmatch('()' .. kw .. '()') do -- 1) highlight keyword vim.api.nvim_buf_add_highlight(bufnr, todo_ns, cfg.hl, i - 1, s - 1, e - 1) local next_char = line:sub(e, e) -- 2) highlight (...) if it directly follows keyword if next_char == '(' then local ps = e local pe = nil for j = e + 1, math.min(#line, e + 100) do if line:sub(j, j) == ')' then pe = j break end end if pe then vim.api.nvim_buf_add_highlight(bufnr, todo_ns, scopes.bracket, i - 1, ps - 1, ps) vim.api.nvim_buf_add_highlight(bufnr, todo_ns, scopes.bracket, i - 1, pe - 1, pe) if pe - ps > 1 then vim.api.nvim_buf_add_highlight(bufnr, todo_ns, scopes.constant, i - 1, ps, pe - 1) end e = pe end end -- -- 3) highlight ":" if directly after keyword or after parens local colon_pos = nil if line:sub(e, e) == ':' then colon_pos = e end if colon_pos then vim.api.nvim_buf_add_highlight( bufnr, todo_ns, scopes.delimiter, i - 1, colon_pos - 1, colon_pos ) end end end end end end vim.api.nvim_create_autocmd({ 'VimEnter', 'BufRead', 'BufWinEnter', 'BufWritePost', 'TextChanged', 'TextChangedI' }, { nested = true, callback = function(args) local bufnr = args.buf RenderTodoHighlights(bufnr) end, }) ---------------------------------------------------------------------------- -- NOTE: Jump to a file location from a "path:line:column" in terminal buffers ---------------------------------------------------------------------------- function Jump_to_file_location(win, path, lnum, col, focus) if win then vim.api.nvim_win_call(win, function() vim.cmd('edit ' .. path) vim.api.nvim_win_set_cursor(0, { tonumber(lnum), tonumber(col) - 1 }) end) if focus then vim.api.nvim_set_current_win(win) end else vim.cmd('edit ' .. path) vim.api.nvim_win_set_cursor(0, { tonumber(lnum), tonumber(col) - 1 }) end end function Jump_to_file_location_at_cursor(focus) local line = vim.api.nvim_get_current_line() local path, lnum, col = line:match('([^:]+):(%d+):(%d+):') if not (path and lnum) then vim.cmd('normal! gd') return end local main_win = nil for _, win in ipairs(vim.api.nvim_list_wins()) do local buf = vim.api.nvim_win_get_buf(win) if vim.api.nvim_buf_get_option(buf, 'buftype') ~= 'terminal' then main_win = win break end end Jump_to_file_location(main_win, path, lnum, col, focus) end vim.api.nvim_create_autocmd('TermOpen', { callback = function(args) vim.api.nvim_buf_set_keymap( args.buf, 'n', 'gd', 'lua Jump_to_file_location_at_cursor(true)', { noremap = true, silent = true } ) vim.api.nvim_buf_set_keymap( args.buf, 'n', 'go', 'lua Jump_to_file_location_at_cursor(false)', { noremap = true, silent = true } ) end, }) ---------------------------------------------------------------------------- -- NOTE: Compile a project by opening a terminal, inspired by Casey's workflow ---------------------------------------------------------------------------- function Compile_project(command) local x = 2 local y = 1 local filter = function(win) return vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_config(win).relative == '' end local current = vim.api.nvim_get_current_win() if not filter(current) then return end local pos = vim.api.nvim_win_get_position(current) local is_build = nil -- if pos[x] == 0 then -- is_build = function(pos) -- return pos ~= 0 -- end -- else -- is_build = function(pos) -- return pos == 0 -- end -- end -- NOTE: for new monitor setup, temporary if pos[x] < 160 then is_build = function(pos_x) return pos_x > 160 end else is_build = function(pos_x) return pos_x == 0 end end -- vim.notify('Current window: ' .. vim.inspect(current) .. ' ' .. vim.inspect(pos), vim.log.levels.INFO) local windows = vim.tbl_filter(filter, vim.api.nvim_list_wins()) -- vim.notify('Valid windows: ' .. vim.inspect(windows), vim.log.levels.INFO) local old_build_win = -1 local new_build_win = -1 for _, win in ipairs(windows) do local pos = vim.api.nvim_win_get_position(win) -- vim.notify('Window id: ' .. vim.inspect(win) .. ' Window pos: ' .. vim.inspect(pos), vim.log.levels.INFO) -- vim.notify( -- 'is build: ' .. vim.inspect(is_build(pos[1])) .. ' y == 0: ' .. vim.inspect(pos[2] == 0), -- vim.log.levels.INFO -- ) if vim.api.nvim_win_get_buf(win) == BuildTerminalBuf then old_build_win = win end if is_build(pos[x]) and pos[y] == 0 then new_build_win = win -- vim.notify('SET BUILD_WIN: ' .. vim.inspect(build_win), vim.log.levels.INFO) end end if old_build_win == new_build_win then old_build_win = -1 end if new_build_win == -1 then vim.cmd('botright vsplit') -- vim.cmd('vertical resize ' .. math.floor(vim.o.columns * 0.395)) -- vim.cmd('vertical resize ' .. math.floor(vim.o.columns * 0.46)) new_build_win = vim.api.nvim_get_current_win() -- vim.notify('SET BUILD_WIN: ' .. vim.inspect(build_win), vim.log.levels.INFO) end if not filter(new_build_win) then return end -- local build_pos = vim.api.nvim_win_get_position(build_win) -- vim.notify( -- 'Build ID: ' .. vim.inspect(build_win) .. ' Build pos: ' .. vim.inspect(build_pos), -- vim.log.levels.INFO -- ) vim.api.nvim_set_current_win(new_build_win) local build_buf = vim.api.nvim_win_get_buf(new_build_win) if vim.api.nvim_win_get_buf(current) == BuildTerminalBuf then -- NOTE(kyren): switch buffers vim.api.nvim_win_set_buf(current, build_buf) end local old_terminal = BuildTerminalBuf BuildTerminalBuf = vim.api.nvim_create_buf(false, true) vim.bo[BuildTerminalBuf].filetype = 'build_terminal' vim.api.nvim_buf_call(BuildTerminalBuf, function() -- vim.fn.termopen(command, vim.empty_dict()) -- local job_id = vim.fn.termopen(vim.o.shell) local job_id = vim.fn.jobstart(vim.o.shell, { term = true }) if job_id > 0 then vim.fn.chansend(job_id, command) end end) local previous_buffer = vim.api.nvim_win_get_buf(new_build_win) vim.api.nvim_win_set_buf(new_build_win, BuildTerminalBuf) if old_build_win ~= -1 then vim.api.nvim_win_set_buf(old_build_win, previous_buffer) end if old_terminal then vim.api.nvim_buf_delete(old_terminal, { force = true }) end vim.api.nvim_set_current_win(current) end vim.api.nvim_create_user_command('CompileClose', function() if BuildTerminalBuf ~= nil and vim.api.nvim_buf_is_valid(BuildTerminalBuf) then vim.api.nvim_buf_delete(BuildTerminalBuf, { force = true }) end end, {}) vim.keymap.set({ 'i', 'n', 'v' }, '', 'CompileClosewqa') ---------------------------------------------------------------------------- -- NOTE: Jump to next error based on compile() ---------------------------------------------------------------------------- function JumpToDiagnostic(direction, is_error, is_warning) vim.defer_fn(function() vim.cmd('w') end, 10) vim.defer_fn(function() -- vim.notify("Running JumpToError after delay", "warn") if BuildTerminalBuf == nil or not vim.api.nvim_buf_is_valid(BuildTerminalBuf) then -- vim.notify("BuildTerminalBuf not ready", "warn") return end local warning_parsers = { function(line) -- Clang: ABS:LINE:COL: warning: ... local file, lnum = line:match('^(.-):(%d+):%d+:%s+warning:') if file and lnum then return { file = file, line = tonumber(lnum) } end end, } local error_parsers = { function(line) -- Clang: ABS:LINE:COL: error: ... local file, lnum = line:match('^(.-):(%d+):%d+:%s+error:') if file and lnum then return { file = file, line = tonumber(lnum) } end end, function(line) -- MSVC: PATH(LINE): error ... -- Z:\home\kyren\projects\krypton\src\krypton\libkrypton.c(22): error C2220: ... -- Z:\home\kyren\projects\krypton\src\krypton\libkrypton.c(22): warning C4018: ... local file, lnum, kind = line:match('^(.-)%((%d+)%)%:%s*(%a+)') if file and lnum and kind then kind = kind:lower() -- Convert Z:\home\... to /home/... -- Only if it matches the expected WSL mount pattern local wsl_path = file:gsub('^Z:\\', '/'):gsub('\\', '/') return { file = wsl_path, line = tonumber(lnum), kind = kind, } end return nil end, } local lines = vim.api.nvim_buf_get_lines(BuildTerminalBuf, 0, -1, false) -- vim.notify('Build buffer line count: ' .. #lines, 'warn') local diagnostics = {} for _, line in ipairs(lines) do if is_warning then for _, f in ipairs(warning_parsers) do local warning = f(line) if warning then table.insert(diagnostics, warning) break end end end if is_error then for _, f in ipairs(error_parsers) do local error = f(line) if error then table.insert(diagnostics, error) break end end end end -- vim.notify('Diagnostics count: ' .. #diagnostics, 'warn') if #diagnostics == 0 then return end local win = 0 local current_file = vim.api.nvim_buf_get_name(win) local filtered = {} for _, e in ipairs(diagnostics) do if e.file == current_file then table.insert(filtered, e) end end -- vim.notify("Filtered errors: " .. #filtered, "warn") if #filtered == 0 then return end table.sort(filtered, function(a, b) return a.line < b.line end) local cursor_line = vim.api.nvim_win_get_cursor(win)[1] if direction == 1 then for _, e in ipairs(filtered) do if e.line > cursor_line then vim.api.nvim_win_set_cursor(win, { e.line, 0 }) return end end vim.api.nvim_win_set_cursor(win, { filtered[1].line, 0 }) else for i = #filtered, 1, -1 do if filtered[i].line < cursor_line then vim.api.nvim_win_set_cursor(win, { filtered[i].line, 0 }) return end end vim.api.nvim_win_set_cursor(win, { filtered[#filtered].line, 0 }) end end, 0) -- in milliseconds vim.defer_fn(function() if vim.g.project_compile_cmd then Compile_project(vim.g.project_compile_cmd) else vim.notify('vim.g.project_compile_cmd missing', 'warn') end end, 100) -- in milliseconds end function JumpToNextError() return JumpToDiagnostic(1, true, false) end function JumpToPrevError() return JumpToDiagnostic(-1, true, false) end function JumpToNextWarning() return JumpToDiagnostic(1, true, true) end function JumpToPrevWarning() return JumpToDiagnostic(-1, true, true) end ---------------------------------------------------------------------------- -- NOTE: Run arbitrary code (if trusted) when opening a project ---------------------------------------------------------------------------- local init_trusted_paths = { '/home/kyren/projects/krypton', } function Run_nvim_lua() -- NOTE(kyren): I use cwd instead of partial path for better security local cwd = vim.fn.getcwd() if vim.tbl_contains(init_trusted_paths, cwd) then local init_path = cwd .. '/.nvim.lua' if vim.fn.filereadable(init_path) == 1 then vim.cmd('source ' .. init_path) end end end vim.api.nvim_create_autocmd({ 'VimEnter', 'BufWritePost' }, { callback = function() vim.schedule(function() vim.wait(100) Run_nvim_lua() end) end, }) ---------------------------------------------------------------------------- -- NOTE: utility to create a new "k memory" ---------------------------------------------------------------------------- local function create_and_or_open_memory() vim.ui.input({ prompt = 'Enter title: ' }, function(title) if not title or title == '' then return end local sanitized_title = title:gsub('%s+', '-') local cmd = { 'k', 'memory', sanitized_title, '--output-path', '--edit=false', '--no-rebuild-self' } local output_lines = {} vim.notify('Memory command: ' .. vim.inspect(cmd)) vim.fn.jobstart(cmd, { stdout_buffered = true, stderr_buffered = true, on_stdout = function(_, data) if data then for _, line in ipairs(data) do if line ~= '' then table.insert(output_lines, line) end end end end, on_stderr = function(_, data) if data then for _, line in ipairs(data) do if line ~= '' then table.insert(output_lines, line) end end end end, on_exit = function(_, exit_code) if exit_code == 0 then local target_file = output_lines[#output_lines] vim.notify(vim.inspect(output_lines)) if target_file and target_file ~= '' then -- Trim any trailing carriage returns or spaces target_file = target_file:gsub('%s+$', '') vim.schedule(function() vim.cmd('edit ' .. vim.fn.fnameescape(target_file)) end) else vim.notify('Error: CLI succeeded but stdout was empty!', vim.log.levels.ERROR) end else vim.api.nvim_err_writeln("Error: 'k memory' failed with exit code " .. exit_code) end end, }) end) end -- 5. Create the Neovim user command (:MyCliCreate) vim.api.nvim_create_user_command('KMemory', create_and_or_open_memory, {}) ---------------------------------------------------------------------------- -- NOTE: Generate java template ---------------------------------------------------------------------------- local function generate_java_boilerplate() local buf_name = vim.api.nvim_buf_get_name(0) if buf_name == '' or vim.bo.filetype ~= 'java' or vim.api.nvim_buf_line_count(0) > 1 or vim.api.nvim_buf_get_lines(0, 0, 1, false)[1] ~= '' then return end local filepath = buf_name -- Find where the standard Java source roots begin local source_root = filepath:match('.*/src/main/java/(.*)') or filepath:match('.*/src/test/java/(.*)') if not source_root then return -- Not inside a standard Java project directory structure end -- Separate the file name from the package directory paths local package_path = source_root:match('(.*)/.*%.java$') local class_name = source_root:match('.*/(.*)%.java$') or source_root:match('(.*)%.java$') if not class_name then return end local lines = {} -- Generate the package declaration if it's not in the default root package if package_path then local package_name = package_path:gsub('/', '.') table.insert(lines, 'package ' .. package_name .. ';') table.insert(lines, '') end -- Generate the standard class declaration boilerplate table.insert(lines, 'public final class ' .. class_name .. ' {') table.insert(lines, ' ') table.insert(lines, '}') vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) local line = 4 local column = 4 pcall(vim.api.nvim_win_set_cursor, 0, { line, column }) -- put cursor inside the class end vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufEnter' }, { pattern = '*.java', callback = generate_java_boilerplate, }) ---------------------------------------------------------------------------- -- NOTE: Show NullAway Jspecify diagnostics inside nvim ---------------------------------------------------------------------------- local nullaway_ns = vim.api.nvim_create_namespace('nullaway_diagnostics') -- TOGGLE: When true, isolates "[NullAway]" errors and strips the "[NullAway]" -- tag prefix from the inline diagnostic text. local strip_nullaway_prefix = true local debounce_timer = nil local active_job_id = nil local DEBOUNCE_MS = 800 vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI', 'BufEnter', 'BufWritePost' }, { pattern = '*.java', callback = function() -- vim.notify("[NullAway] Event triggered, managing build window...", vim.log.levels.DEBUG) local bufnr = vim.api.nvim_get_current_buf() local current_file = vim.api.nvim_buf_get_name(bufnr) local gradlew_search = vim.fs.find('gradlew', { upward = true, path = vim.fs.dirname(current_file) }) if #gradlew_search == 0 then return end local gradlew_path = gradlew_search[1] local project_root = vim.fs.dirname(gradlew_path) if debounce_timer then debounce_timer:stop() debounce_timer:close() debounce_timer = nil end debounce_timer = vim.uv.new_timer() debounce_timer:start( DEBOUNCE_MS, 0, vim.schedule_wrap(function() if active_job_id then vim.fn.jobstop(active_job_id) active_job_id = nil end local diagnostics = {} active_job_id = vim.fn.jobstart({ gradlew_path, 'compileJava', '--console=plain' }, { cwd = project_root, stdout_buffered = true, stderr_buffered = true, on_stderr = function(_, data) if not data then return end local function process_entry(index) local line = data[index] if line:match('^%s') then return end if not line:find('error: %[NullAway%]') then return end local file_path, lnum_str, msg = line:match('^(.-):(%d+):%s+error:%s+(.*)') if not (file_path and lnum_str and msg) then return end if vim.fs.normalize(file_path) ~= vim.fs.normalize(current_file) then return end -- Clean up the diagnostic string based on the toggle configuration if strip_nullaway_prefix then msg = msg:gsub('^%[NullAway%]%s*', '') end local col = 0 local end_col = nil for offset = 1, 3 do local lookahead = data[index + offset] if lookahead then local indent = lookahead:match('^(%s*)%^') if indent then col = #indent local param_token = msg:match("parameter '([^']+)'") if param_token then end_col = col + #param_token end break end end end table.insert(diagnostics, { lnum = tonumber(lnum_str) - 1, col = col, end_col = end_col, severity = vim.diagnostic.severity.ERROR, message = msg, source = 'NullAway', }) end for i = 1, #data do process_entry(i) end end, on_exit = function(job_id, exit_code) if job_id ~= active_job_id then return end active_job_id = nil vim.schedule(function() vim.diagnostic.reset(nullaway_ns, bufnr) if #diagnostics > 0 then vim.diagnostic.set(nullaway_ns, bufnr, diagnostics) -- vim.notify("[NullAway] Live check placed " .. #diagnostics .. " error(s).", vim.log.levels.INFO) else -- vim.notify("[NullAway] Live check clean.", vim.log.levels.INFO) end end) end, }) end) ) end, }) ---------------------------------------------------------------------------- -- NOTE: LSP hover ---------------------------------------------------------------------------- require('lsphover') ---------------------------------------------------------------------------- -- NOTE: ----------------------------------------------------------------------------