nvim/ftplugin/c.lua
Jeremie Fraeys 6c762c436a
Some checks failed
Luacheck / luacheck (push) Failing after 4s
StyLua / stylua (push) Failing after 2s
fix: endx typo in lsp-servers.lua
2026-02-08 15:08:04 -05:00

206 lines
5.6 KiB
Lua

-- Indentation settings
vim.bo.expandtab = true -- Use spaces instead of tabs
vim.bo.tabstop = 4 -- Number of spaces per tab
vim.bo.shiftwidth = 4 -- Number of spaces for autoindent
vim.bo.softtabstop = 4 -- Number of spaces for editing
vim.bo.formatoptions = vim.bo.formatoptions:gsub('o', '') -- Remove 'o'
-- Format on save using Conform
local augroup = vim.api.nvim_create_augroup('CFormat', { clear = true })
vim.api.nvim_create_autocmd('BufWritePre', {
group = augroup,
pattern = '*.c',
callback = function()
if vim.fn.exists(':ConformFormat') == 2 then
vim.cmd('ConformFormat')
end
end,
})
local function cmake_build_dir(name)
local dir = vim.fn.getcwd() .. '/' .. (name or 'build')
if vim.fn.isdirectory(dir) == 0 then
vim.fn.mkdir(dir, 'p')
end
return dir
end
local function cmake_configure(build_dir, extra_args)
local args = extra_args or ''
local bin_dir = vim.fn.getcwd() .. '/bin'
vim.fn.mkdir(bin_dir, 'p')
local out_args = ' -DCMAKE_RUNTIME_OUTPUT_DIRECTORY='
.. vim.fn.shellescape(bin_dir)
.. ' -DCMAKE_LIBRARY_OUTPUT_DIRECTORY='
.. vim.fn.shellescape(bin_dir)
.. ' -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY='
.. vim.fn.shellescape(bin_dir)
vim.cmd('!cmake -S . -B ' .. vim.fn.fnameescape(build_dir) .. out_args .. ' ' .. args)
end
local function cmake_build(build_dir)
vim.cmd('!cmake --build ' .. vim.fn.fnameescape(build_dir))
end
local function cmake_ctest(build_dir)
vim.cmd('!ctest --test-dir ' .. vim.fn.fnameescape(build_dir) .. ' --output-on-failure')
end
local function toggle_debugger_repl()
local dbg = vim.__c_dbg_term
if dbg and dbg.is_open and dbg:is_open() then
dbg:close()
return
end
local ok_term, term_mod = pcall(require, 'toggleterm.terminal')
if not ok_term then
return
end
local cmd
if vim.fn.has('mac') == 1 then
cmd = 'lldb'
else
cmd = 'gdb'
end
if not vim.__c_dbg_term then
vim.__c_dbg_term = term_mod.Terminal:new({
cmd = cmd,
direction = 'vertical',
size = function()
return math.floor(vim.o.columns * 0.4)
end,
hidden = true,
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<CR>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<Esc><Esc>', '<C-\\><C-n>', { noremap = true, silent = true })
end,
})
end
vim.__c_dbg_term:open()
vim.cmd('wincmd p')
end
local opts = { buffer = true, silent = true }
local function switch_source_header()
local bufnr = vim.api.nvim_get_current_buf()
local function edit_file(fname)
if not fname or fname == '' then
return
end
vim.cmd('edit ' .. vim.fn.fnameescape(fname))
end
local clangd
for _, client in ipairs(vim.lsp.get_clients({ bufnr = bufnr })) do
if client.name == 'clangd' then
clangd = client
break
end
end
if clangd then
clangd.request('textDocument/switchSourceHeader', { uri = vim.uri_from_bufnr(bufnr) }, function(err, result)
if err then
vim.notify('clangd: switchSourceHeader failed', vim.log.levels.WARN)
return
end
if not result then
vim.notify('No corresponding header/source found', vim.log.levels.INFO)
return
end
vim.schedule(function()
edit_file(vim.uri_to_fname(result))
end)
end, bufnr)
return
end
local file = vim.api.nvim_buf_get_name(bufnr)
if file == '' then
return
end
local root = file:gsub('%.[^%.]+$', '')
local ext = vim.fn.fnamemodify(file, ':e')
local candidates
if ext == 'h' or ext == 'hpp' or ext == 'hh' or ext == 'hxx' then
candidates = { 'c', 'cpp', 'cc', 'cxx' }
elseif ext == 'c' then
candidates = { 'h' }
else
candidates = { 'h', 'hpp', 'hh', 'hxx' }
end
for _, e in ipairs(candidates) do
local cand = root .. '.' .. e
if vim.fn.filereadable(cand) == 1 then
edit_file(cand)
return
end
end
vim.notify('No corresponding header/source found', vim.log.levels.INFO)
end
local function memory_profile_command(run_cmd)
if vim.fn.has('mac') == 1 then
return 'leaks --atExit -- ' .. run_cmd
end
return 'valgrind --leak-check=full --track-origins=yes ' .. run_cmd
end
vim.keymap.set('n', '<leader>cb', function()
local build_dir = cmake_build_dir('build')
cmake_configure(build_dir)
cmake_build(build_dir)
end, vim.tbl_extend('force', opts, { desc = 'CMake: Build' }))
vim.keymap.set('n', '<leader>ct', function()
local build_dir = cmake_build_dir('build')
cmake_ctest(build_dir)
end, vim.tbl_extend('force', opts, { desc = 'CMake: Test' }))
vim.keymap.set(
'n',
'<leader>ch',
switch_source_header,
vim.tbl_extend('force', opts, { desc = 'C: Switch header/source' })
)
vim.keymap.set(
'n',
'<leader>cg',
toggle_debugger_repl,
vim.tbl_extend('force', opts, { desc = 'C: Toggle debugger REPL' })
)
vim.keymap.set('n', '<leader>cm', function()
local cmd = vim.fn.input('Memory profile run: ', './bin/')
if cmd == '' then
return
end
vim.cmd('!' .. memory_profile_command(cmd))
end, vim.tbl_extend('force', opts, { desc = 'C: Memory profile (leaks/valgrind)' }))
vim.keymap.set('n', '<leader>Rr', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Toggle' }))
vim.keymap.set('n', '<leader>Rf', function()
local cmd = vim.fn.input('Run: ', './bin/')
if cmd == '' then
return
end
vim.cmd('!' .. cmd)
end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Run' }))
vim.keymap.set('n', '<leader>Ri', function()
local dbg = vim.__c_dbg_term
if dbg and dbg.send and dbg.is_open and dbg:is_open() then
dbg:send('\x03')
end
end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Interrupt' }))