nvim/plugin/build.lua

185 lines
5.6 KiB
Lua

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
local compile_commands = current_dir .. '/compile_commands.json'
if vim.fn.executable('compdb') == 1 then
echo('Processing compile_commands.json with compdb ...', 'DiagnosticInfo')
-- 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
on_exit = function()
-- This is a callback so doesn't run on the main thread which causes
-- issues for running commands, so schedule that on the main thread.
vim.schedule(function()
vim.cmd('LspRestart')
echo('Build directory selected: ' .. dirname, 'DiagnosticInfo')
end)
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,
}
)
else
vim.uv.fs_copyfile(build_dir .. '/compile_commands.json', compile_commands, nil,
function()
-- This is a callback so doesn't run on the main thread which causes
-- issues for running commands, so schedule that on the main thread.
vim.schedule(function()
vim.cmd('LspRestart')
echo('Build directory selected: ' .. dirname, 'DiagnosticInfo')
end)
end
)
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', '<CR>', 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
-- Create :BuildDir command
vim.api.nvim_create_user_command('BuildDir', function(opts)
build.set_dir(opts.fargs[1])
end, {
bang = true, nargs = '?', complete = build.list_dirs,
})
-- Create :Build command
vim.api.nvim_create_user_command('Build', function(opts)
build.run(opts.fargs)
end, {
bang = true, nargs = '*', complete = build.list_targets,
})
return build