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', 'close', { noremap = true, silent = true }) vim.api.nvim_buf_set_keymap(term.bufnr, 't', '', '', { 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', 'cS', select_cmake_preset, vim.tbl_extend('force', opts, { desc = 'CMake: Select preset' })) vim.keymap.set('n', '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', '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', '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', 'ch', switch_source_header, vim.tbl_extend('force', opts, { desc = 'C++: Switch header/source' }) ) vim.keymap.set( 'n', 'cg', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'C++: Toggle debugger REPL' }) ) vim.keymap.set('n', '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', 'Rr', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Toggle' })) vim.keymap.set('n', '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', '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', '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)' }))