diff --git a/after/ftplugin/c.lua b/after/ftplugin/c.lua new file mode 100644 index 0000000..cd1cc3b --- /dev/null +++ b/after/ftplugin/c.lua @@ -0,0 +1 @@ +require('build').create_commands() diff --git a/after/ftplugin/cpp.lua b/after/ftplugin/cpp.lua index 1411d13..54962ef 100644 --- a/after/ftplugin/cpp.lua +++ b/after/ftplugin/cpp.lua @@ -1 +1,3 @@ vim.bo.commentstring = '//%s' + +require('build').create_commands() diff --git a/init.lua b/init.lua index 0ce3f8e..8bf6da7 100644 --- a/init.lua +++ b/init.lua @@ -2,6 +2,7 @@ require('settings') require('mappings') require('netrw') require('autocmds') +require('build') local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.loop.fs_stat(lazypath) then diff --git a/lua/build.lua b/lua/build.lua new file mode 100644 index 0000000..8e32593 --- /dev/null +++ b/lua/build.lua @@ -0,0 +1,161 @@ +local build = {} + +build.dir = nil + +local function echo(message, highlight) + vim.api.nvim_echo({ { message, highlight }, }, true, {}) +end + +function build.set_dir(dirname) + if not dirname then + build.select_dir() + return + end + local current_dir = vim.fn.getcwd() + local build_dir = current_dir .. '/' .. dirname + -- print(build_dir) + + if vim.fn.isdirectory(build_dir) == 0 then + echo('directory does not exist: ' .. build_dir, 'Error') + return + end + if vim.fn.filereadable(build_dir .. '/compile_commands.json') == 0 then + echo('compile_commands.json not found in: ' .. build_dir, 'Error') + return + end + + build.dir = build_dir + + -- Invoke async function to avoid blocking the UI + vim.schedule(function() + echo('Processing compile_commands.json with compdb ...', 'DiagnosticInfo') + + -- Post-process compile_commands.json with compdb + local compile_commands = current_dir .. '/compile_commands.json' + local output = vim.fn.systemlist( + 'compdb -p ' .. build.dir .. ' list > ' .. compile_commands) + if table.maxn(output) > 0 then + echo(vim.fn.join(output, '\n'), 'Error') + return + end + + -- TODO: Configure cmake language server? + + -- Restart clangd language server + vim.cmd [[ LspRestart clangd ]] + + echo('Build directory selected: ' .. dirname, 'DiagnosticInfo') + end) +end + +function build.select_dir(callback) + local dirs = build.list_dirs() + callback = callback or nil + + if table.maxn(dirs) == 1 then + build.set_dir(dirs[1]) + return + end + + if vim.fn.exists(':Telescope') > 0 then + -- Create a Telescope picker with custom action. + require('telescope.pickers').new({}, { + prompt_title = 'Select build directory', + finder = require('telescope.finders').new_table({ results = dirs }), + sorter = require("telescope.config").values.generic_sorter({}), + attach_mappings = function(bufnr, map) + -- Override the default confirm action + map('i', '', function() + -- Get the select build directory + local state = require('telescope.actions.state') + build.set_dir(state.get_selected_entry().value) + -- Close the Telescope floating window + require('telescope.actions').close(bufnr) + -- Invoke the callback if provided + if callback then callback() end + end) + return true + end, + layout_config = { + height = function(_, _, max_lines) + -- Limit the height based on the number of dirs + return math.min(max_lines, table.maxn(dirs) + 5) + end + } + }):find() + else + -- Prompt user to choose dir with to inputlist + local choices = {} + for index, choice in ipairs(dirs) do + table.insert(choices, tostring(index) .. ': ' .. choice) + end + local index = vim.fn.inputlist(choices) + build.dir = dirs[index] + if callback then + callback() + end + end +end + +function build.run(args) + -- Check if build dir set, if not prompt user to select one + if not build.dir then + build.select_dir(function() build.run(args) end) + return + end + local command = nil + if vim.fn.filereadable(build.dir .. '/build.ninja') then + command = 'ninja' + elseif vim.fn.filereadable(build.dir .. '/Makefile') then + command = 'make' + else + echo('could not find build.ninja or Makefile in: ' .. build.dir) + return + end + command = command .. ' -C ' .. build.dir .. ' ' .. vim.fn.join(args, ' ') + -- TODO: Figure out how to make the terminal window close on success + -- TODO: Running the terminal in a togglable floating window might be nice + vim.fn.execute('split | terminal ' .. command) +end + +function build.list_dirs() + local dirs = vim.fn.globpath('.', 'build*') + dirs = vim.fn.substitute(dirs, '\\.\\/', '', 'g') + return vim.fn.split(dirs) +end + +function build.list_targets() + local targets = {} + if not build.dir then + return targets + end + if vim.fn.filereadable(build.dir .. '/build.ninja') then + -- Query ninja for the list of targets + targets = vim.fn.systemlist('ninja -C ' .. build.dir .. ' -t targets') + for index, target in ipairs(targets) do + targets[index] = string.sub(target, 1, string.find(target, ':') - 1) + end + end + -- TODO: Support make, it's much less straight forwards than ninja. + return targets +end + +function build.create_commands() + local buffer = vim.api.nvim_get_current_buf() + + -- Create :BuildDir command + vim.api.nvim_buf_create_user_command(buffer, 'BuildDir', function(opts) + build.set_dir(opts.fargs[1]) + end, { + bang = true, nargs = '?', complete = build.list_dirs, + }) + + -- Create :Build command + vim.api.nvim_buf_create_user_command(buffer, 'Build', function(opts) + build.run(opts.fargs) + end, { + bang = true, nargs = '*', complete = build.list_targets, + }) +end + +return build diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua index e58a028..8ab3867 100644 --- a/lua/plugins/telescope.lua +++ b/lua/plugins/telescope.lua @@ -4,6 +4,7 @@ return { 'nvim-lua/plenary.nvim', 'nvim-telescope/telescope-fzy-native.nvim', 'nvim-tree/nvim-web-devicons', + 'axkirillov/easypick.nvim', }, config = function() local telescope = require('telescope') @@ -21,6 +22,9 @@ return { [''] = 'preview_scrolling_right', } }, + layout_config = { + height = 0.7, + } }, }) telescope.load_extension('fzy_native') @@ -33,5 +37,8 @@ return { vim.keymap.set('n', 'b', ':Telescope buffers', opts) vim.keymap.set('n', 'ht', ':Telescope help_tags', opts) + require('easypick').setup({ + pickers = { }, + }) end }