-- 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', 'close', { noremap = true, silent = true }) vim.api.nvim_buf_set_keymap(term.bufnr, 't', '', '', { 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', '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', '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', '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.__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' }))