nvim/ftplugin/cpp.lua
Jeremie Fraeys 02e26b00b7
Some checks are pending
Check Lua Formatting in MyRepo / Stylua Check (push) Waiting to run
chore(nvim): reinitialize with working config
2026-02-07 21:06:45 -05:00

256 lines
7.7 KiB
Lua

vim.opt_local.cinoptions = vim.opt_local.cinoptions + '0{1' -- Places opening brace on the same line as function signature
vim.opt_local.smartindent = true -- Automatically adds indentation where needed
vim.opt_local.tabstop = 4 -- Set tab stop to 4 spaces
vim.opt_local.shiftwidth = 4 -- Set shift width to 4 spaces
vim.opt_local.softtabstop = 4 -- Use spaces instead of tabs
vim.api.nvim_create_user_command('RunCpp', function(opts)
vim.cmd('w') -- Save the current file
local filename = vim.fn.expand('%') -- Get current file name
local output = 'bin/' .. vim.fn.expand('%:r'):match('([^/]+)$') .. '.out' -- Output file with .out extension
-- Use provided compiler or default to 'clang++'
local compiler = opts.args ~= '' and opts.args or 'clang++'
-- Ensure the output directory exists
vim.fn.mkdir('bin', 'p')
-- Compile and run the C++ code
vim.cmd('!' .. compiler .. ' ' .. filename .. ' -o ' .. output .. ' && ./' .. output)
end, { nargs = '?' }) -- Accepts one optional argument for the compiler
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, extra_args)
local args = extra_args or ''
vim.cmd('!cmake --build ' .. vim.fn.fnameescape(build_dir) .. ' ' .. args)
end
local function cmake_ctest(build_dir)
vim.cmd('!ctest --test-dir ' .. vim.fn.fnameescape(build_dir) .. ' --output-on-failure')
end
local function select_cmake_preset()
local lines = vim.fn.systemlist('cmake --list-presets')
local presets = {}
for _, line in ipairs(lines) do
local name = line:match('^%s*"([^"]+)"')
if name then
table.insert(presets, name)
end
end
if #presets == 0 then
return
end
vim.ui.select(presets, { prompt = 'CMake preset' }, function(choice)
if choice then
vim.g.cmake_preset = choice
end
end)
end
local function cmake_configure_with_preset()
local preset = vim.g.cmake_preset
if preset and preset ~= '' then
vim.cmd('!cmake --preset ' .. preset)
return true
end
return false
end
local function cmake_build_with_preset()
local preset = vim.g.cmake_preset
if preset and preset ~= '' then
vim.cmd('!cmake --build --preset ' .. preset)
return true
end
return false
end
local function toggle_debugger_repl()
local dbg = vim.__cpp_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.__cpp_dbg_term then
vim.__cpp_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.__cpp_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 = { 'cpp', 'cc', 'cxx', 'c' }
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>cS', select_cmake_preset, vim.tbl_extend('force', opts, { desc = 'CMake: Select preset' }))
vim.keymap.set('n', '<leader>cb', function()
if cmake_configure_with_preset() then
cmake_build_with_preset()
return
end
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>cr', function()
local cmd = vim.fn.input('Run command: ', './')
if cmd == '' then
return
end
vim.cmd('!' .. cmd)
end, vim.tbl_extend('force', opts, { desc = 'CMake: Run (prompt)' }))
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.__cpp_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' }))
vim.keymap.set('n', '<leader>ca', function()
local build_dir = cmake_build_dir('build-asan')
local flags = "-DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS='-fsanitize=address -fno-omit-frame-pointer' -DCMAKE_CXX_FLAGS='-fsanitize=address -fno-omit-frame-pointer'"
cmake_configure(build_dir, flags)
cmake_build(build_dir)
end, vim.tbl_extend('force', opts, { desc = 'CMake: Build (ASan)' }))