169 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			4.9 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
 | |
| 
 | |
| -- 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
 |