951 lines
33 KiB
Lua
951 lines
33 KiB
Lua
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 <q>
|
|
----------------------------------------------------------------------------
|
|
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',
|
|
'<cmd>lua Jump_to_file_location_at_cursor(true)<CR>',
|
|
{ noremap = true, silent = true }
|
|
)
|
|
vim.api.nvim_buf_set_keymap(
|
|
args.buf,
|
|
'n',
|
|
'go',
|
|
'<cmd>lua Jump_to_file_location_at_cursor(false)<CR>',
|
|
{ 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' }, '<C-q>', '<cmd>CompileClose<cr><cmd>wqa<cr>')
|
|
|
|
----------------------------------------------------------------------------
|
|
-- 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:
|
|
----------------------------------------------------------------------------
|