local M = {}
function M.setup(opts)
opts = opts or {}
local config = {
enable_diagnostics = true,
enable_hover = true,
enable_references = true,
keymaps = {
goto_definition = '<leader>gd',
find_references = '<leader>gr',
show_hover = '<leader>K',
},
window = {
width = 80,
height = 20,
border = 'rounded',
},
}
config = vim.tbl_deep_extend('force', config, opts)
vim.g.code_navigator_config = config
vim.api.nvim_create_user_command('CodeNavDefinition', M.navigate_to_definition, {})
vim.api.nvim_set_keymap('n', config.keymaps.goto_definition, '<cmd>CodeNavDefinition<CR>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('n', config.keymaps.find_references, '<cmd>lua require("code_navigator").find_references()<CR>', { noremap = true, silent = true })
vim.api.nvim_set_keymap('n', config.keymaps.show_hover, '<cmd>lua require("code_navigator").show_hover()<CR>', { noremap = true, silent = true })
local group = vim.api.nvim_create_augroup('CodeNavigator', { clear = true })
vim.api.nvim_create_autocmd('DiagnosticChanged', {
group = group,
callback = function()
if config.enable_diagnostics then
config:validate()
end
end,
})
vim.notify('CodeNavigator: Setup complete', vim.log.levels.INFO)
end
function M.navigate_to_definition()
local bufnr = vim.api.nvim_get_current_buf()
local cursor = vim.api.nvim_win_get_cursor(0)
local row, col = cursor[1], cursor[2]
local config = Config:new(vim.g.code_navigator_config)
config:merge({ buffer = bufnr, position = { row, col } })
vim.lsp.buf.definition({
on_list = function(items)
if #items > 0 then
vim.api.nvim_win_set_cursor(0, { items[1].lnum, items[1].col - 1 })
local buffer = Buffer:new(bufnr)
local content = buffer:get_content()
vim.notify('Navigated to definition', vim.log.levels.INFO)
vim.lsp.util.show_line_diagnostics()
end
end,
})
end
function M.find_references()
local bufnr = vim.api.nvim_get_current_buf()
local cursor = vim.api.nvim_win_get_cursor(0)
local row, col = cursor[1], cursor[2]
local config = Config:new(vim.g.code_navigator_config)
config:merge({ buffer = bufnr, position = { row, col } })
vim.lsp.buf.references(nil, {
on_list = function(items)
local win = Window:new(vim.g.code_navigator_config.window)
win:focus()
local lines = {}
vim.api.nvim_buf_set_lines(win.bufnr, 0, -1, false, lines)
win:resize(#lines)
vim.notify(string.format('Found %d references', #items), vim.log.levels.INFO)
end,
})
end
function M.show_hover()
local bufnr = vim.api.nvim_get_current_buf()
local cursor = vim.api.nvim_win_get_cursor(0)
local row, col = cursor[1], cursor[2]
local config = Config:new(vim.g.code_navigator_config)
config:merge({ buffer = bufnr, position = { row, col } })
vim.lsp.buf.hover({
on_list = function(items)
local win = Window:new(vim.g.code_navigator_config.window)
win:focus()
vim.defer_fn(function()
win:close()
end, 3000)
vim.notify('Showing hover information', vim.log.levels.INFO)
end,
})
end
function M.list_diagnostics()
local bufnr = vim.api.nvim_get_current_buf()
local diagnostics = vim.diagnostic.get(bufnr)
if #diagnostics > 0 then
local win = Window:new(vim.g.code_navigator_config.window)
win:focus()
local lines = {}
vim.api.nvim_buf_set_lines(win.bufnr, 0, -1, false, lines)
win:resize(#lines)
vim.notify(string.format('Found %d diagnostics', #diagnostics), vim.log.levels.INFO)
end
end
function M.format_buffer()
local bufnr = vim.api.nvim_get_current_buf()
local buffer = Buffer:new(bufnr)
local content = buffer:get_content()
vim.lsp.buf.format({
bufnr = bufnr,
async = false,
})
buffer:set_content(content)
buffer:save()
vim.notify('Buffer formatted', vim.log.levels.INFO)
end
Config = {}
Config.__index = Config
function Config:new(opts)
opts = opts or {}
local instance = {
data = opts,
valid = false,
}
setmetatable(instance, Config)
return instance
end
function Config:merge(new_opts)
if not new_opts then
return self
end
self.data = vim.tbl_deep_extend('force', self.data, new_opts)
self.valid = false
return self
end
function Config:validate()
if self.valid then
return true
end
local required_keys = { 'enable_diagnostics', 'enable_hover', 'enable_references' }
for _, key in ipairs(required_keys) do
if self.data[key] == nil then
vim.notify(string.format('Missing required config key: %s', key), vim.log.levels.ERROR)
return false
end
end
self.valid = true
return true
end
function Config:get(key)
return self.data[key]
end
Buffer = {}
Buffer.__index = Buffer
function Buffer:new(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local instance = {
bufnr = bufnr,
name = vim.api.nvim_buf_get_name(bufnr),
}
setmetatable(instance, Buffer)
return instance
end
function Buffer:get_content()
local lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false)
return table.concat(lines, '\n')
end
function Buffer:set_content(content)
local lines = vim.split(content, '\n')
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, lines)
return self
end
function Buffer:save()
vim.api.nvim_buf_call(self.bufnr, function()
vim.cmd('write')
end)
return self
end
Window = {}
Window.__index = Window
function Window:new(opts)
opts = opts or {}
local bufnr = vim.api.nvim_create_buf(false, true)
local width = opts.width or 80
local height = opts.height or 20
local instance = {
bufnr = bufnr,
width = width,
height = height,
}
setmetatable(instance, Window)
return instance
end
function Window:focus()
vim.api.nvim_set_current_buf(self.bufnr)
return self
end
function Window:close()
vim.api.nvim_buf_delete(self.bufnr, { force = true })
return self
end
function Window:resize(new_height)
if new_height then
self.height = new_height
end
return self
end
local function log_debug(message)
if vim.g.code_navigator_config and vim.g.code_navigator_config.debug then
vim.notify(message, vim.log.levels.DEBUG)
end
end
local function get_cursor_word()
return vim.fn.expand('<cword>')
end
local function is_lsp_available()
return #vim.lsp.get_active_clients() > 0
end
local function on_definition_found(items)
log_debug('Definition found')
return items[1]
end
local function on_references_found(items)
log_debug(string.format('Found %d references', #items))
return items
end
local function on_hover_shown(result)
log_debug('Hover information displayed')
return result
end
local function chain_example()
local config = Config:new({ debug = true })
:merge({ enable_diagnostics = false })
:validate()
local buffer = Buffer:new()
:get_content()
local window = Window:new({ width = 100, height = 30 })
:focus()
:resize(40)
:close()
end
local handlers = {
definition = M.navigate_to_definition,
references = M.find_references,
hover = M.show_hover,
diagnostics = M.list_diagnostics,
format = M.format_buffer,
}
local function dispatch_handler(handler_name)
if handlers[handler_name] then
handlers[handler_name]()
end
end
local function with_lsp_client(callback)
if is_lsp_available() then
callback()
else
vim.notify('No LSP client available', vim.log.levels.WARN)
end
end
local function safe_navigate()
with_lsp_client(function()
M.navigate_to_definition()
end)
end
local autocmd_callbacks = {
on_save = function()
M.format_buffer()
end,
on_change = function()
M.list_diagnostics()
end,
}
local function conditional_example(should_format)
local buffer = Buffer:new()
if should_format then
M.format_buffer()
else
buffer:get_content()
end
end
local MetaModule = {}
MetaModule.__index = MetaModule
function MetaModule:create()
local instance = {}
setmetatable(instance, MetaModule)
return instance
end
function MetaModule:process()
Config:new():validate()
end
local function call_method(obj, method_name, ...)
return obj[method_name](obj, ...)
end
local registered_callbacks = {}
local function register_callback(name, callback)
registered_callbacks[name] = callback
end
local function trigger_callback(name, ...)
if registered_callbacks[name] then
registered_callbacks[name](...)
end
end
register_callback('format', M.format_buffer)
register_callback('diagnostics', M.list_diagnostics)
local actions = {
gd = M.navigate_to_definition,
gr = M.find_references,
K = M.show_hover,
advanced = {
format = M.format_buffer,
diagnostics = M.list_diagnostics,
},
}
local function execute_action(key)
if actions[key] then
actions[key]()
end
end
local function complex_chain()
local config = Config:new()
if config:validate() then
config:merge({ debug = true })
end
return config:get('debug')
end
return M