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 build.dir = build_dir echo('Processing compile_commands.json with compdb ...', 'DiagnosticInfo') local compile_commands = current_dir .. '/compile_commands.json' -- Post-process compile_commands.json with compdb as an async job to avoid -- blocking the user interface vim.fn.jobstart( 'compdb -p ' .. build.dir .. ' list > ' .. compile_commands, { -- Restart clangd language server -- TODO: Configure cmake language server? on_exit = function() vim.cmd [[ LspRestart clangd ]] echo('Build directory selected: ' .. dirname, 'DiagnosticInfo') end, -- Display any error messages to the user on_stderr = function(_, output, _) -- Remove any lines containing a compdb warning local error = {} local warning = 'WARNING:compdb' for _, line in ipairs(output) do if string.sub(line, 1, #warning) ~= warning then table.insert(error, line) end end -- Display the error message if there was one if table.maxn(error) > 0 then echo(vim.fn.join(error, '\n'), 'Error') return end end, stderr_buffered = true, } ) 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 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, }) return build