Turns out vim.schedule() only defers calling the function until main event loop has some free cycles but can still block the UI, just a bit later. This patch replaces the external command invoked in the callback passed into vim.schedule() with vim.fn.jobstart() and moves the other code into job callbacks.
173 lines
5.0 KiB
Lua
173 lines
5.0 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
|
|
|
|
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', '<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
|
|
|
|
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
|