From 54c147c88537a682e5f926ea391c14ae31c80f82 Mon Sep 17 00:00:00 2001 From: Daniil Rozanov Date: Thu, 2 May 2024 00:15:28 +0300 Subject: feat: new commands and some refactoring --- lua/cmake/actions.lua | 198 +++++++++++++++++++++++++++++++++++---------- lua/cmake/autocmds.lua | 25 +++--- lua/cmake/capabilities.lua | 27 +++---- lua/cmake/commandline.lua | 148 +++++++++++++++++++++++++++++++++ lua/cmake/commands.lua | 109 ++++++++++++++++++------- lua/cmake/config.lua | 23 +++--- lua/cmake/fileapi.lua | 9 ++- lua/cmake/init.lua | 4 +- lua/cmake/project.lua | 20 ++++- lua/cmake/terminal.lua | 131 +++++++++++++++++------------- lua/cmake/utils.lua | 39 ++++----- lua/cmake/variants.lua | 2 +- 12 files changed, 535 insertions(+), 200 deletions(-) create mode 100644 lua/cmake/commandline.lua diff --git a/lua/cmake/actions.lua b/lua/cmake/actions.lua index 8c5a300..972e07e 100644 --- a/lua/cmake/actions.lua +++ b/lua/cmake/actions.lua @@ -5,13 +5,13 @@ local utils = require("cmake.utils") local constants = require("cmake.constants") local Path = require("plenary.path") -local uv = vim.uv or vim.loop +local uv = vim.uv local M = {} local default_generate_exe_opts = { notify = { - ok_message = "CMake build finished", + ok_message = "CMake generate finished", err_message = function(code) return "CMake generate failed with code " .. tostring(code) end, @@ -27,57 +27,161 @@ local default_build_exe_opts = { }, } -M.reset_project = function(opts) - require("cmake.project").setup(opts) +local _explain = function(command) + vim.notify( + table.concat({ + table.concat( + vim.iter(command.env or {}) + :map(function(k, v) + if v:find(" ") then + return k .. '="' .. v .. '"' + end + return k .. "=" .. v + end) + :totable(), + " " + ), + command.cmd, + command.args, + }, " "), + vim.log.levels.INFO + ) end -M.generate = function(opts) +--- Extends generate command by given options +---@param command table +---@param opts GenerateOpts +---@return table +local _extend_generate_command = function(command, opts) + opts = opts or {} + local new = vim.tbl_deep_extend("keep", command, {}) + return new +end + +--- Extends build command by given options +---@param command table +---@param opts BuildOpts +---@return table +local _extend_build_command = function(command, opts) + local new = vim.tbl_deep_extend("keep", command, {}) + if opts.j then + new.args = new.args .. " -j " .. tostring(opts.j) + end + if opts.clean then + new.args = new.args .. " --clean-first" + end + return new +end + +local _generate = function(option, opts) + opts = opts or {} + local main_path = function() + pr.create_fileapi_query({}, function() + vim.schedule(function() + t.cmake_execute(_extend_generate_command(option.generate_command, opts), default_generate_exe_opts) + end) + end) + end + if opts.fresh then + pr.clear_cache(main_path) + else + main_path() + end +end + +local _for_current_generate_option = function(func) local idx = pr.current_generate_option_idx() if not idx then - vim.notify("CMake: no project to generate") - return + vim.notify("CMake: no configuration to generate", vim.log.levels.WARN) + else + func(pr.current_generate_option()) end - pr.create_fileapi_query({ idx = idx }, function() - vim.schedule(function() - t.cmake_execute(pr.current_generate_option().generate_command, default_generate_exe_opts) - end) +end + +---@class GenerateOpts +---@field fresh boolean|nil + +--- Generate project with current generate option +--- @param opts GenerateOpts +M.generate = function(opts) + opts = opts or {} + _for_current_generate_option(function(option) + _generate(option, opts) + end) +end + +--- Generate project with current generate option +--- @param opts GenerateOpts +M.generate_explain = function(opts) + opts = opts or {} + _for_current_generate_option(function(option) + _explain(_extend_generate_command(option.generate_command, opts)) end) end +--- Generate project with current generate option +--- @param opts table|nil M.generate_select = function(opts) + opts = opts or {} local items = pr.generate_options(opts) vim.ui.select(items, { prompt = "Select configuration to generate:", format_item = function(item) return table.concat(item.name, config.variants_display.short.sep) end, - }, function(choice, idx) + }, function(_, idx) if not idx then return end pr.set_current_generate_option(idx) - pr.create_fileapi_query({ idx = idx }, function() - vim.schedule(function() - t.cmake_execute(choice.generate_command, default_generate_exe_opts) - end) - end) end) end -M.build = function(opts) - if not pr.current_build_option_idx() then - M.build_select(opts) +local _for_current_build_option = function(func) + local idx = pr.current_build_option() + if not idx then + vim.notify("CMake: no build configuration to generate", vim.log.levels.WARN) else - pr.create_fileapi_query({ idx = pr.current_build_option_idx() }, function() - vim.schedule(function() - t.cmake_execute(pr.current_build_option().command, default_build_exe_opts) - end) - end) + func(pr.current_build_option()) end end +local _build = function(option, opts) + opts = opts or {} + pr.create_fileapi_query({}, function() + vim.schedule(function() + t.cmake_execute(_extend_build_command(option.command, opts), default_build_exe_opts) + end) + end) +end + +---@class BuildOpts +---@field clean boolean|nil +---@field j number|nil +---@field target number|nil + +--- Build project with current build option +--- @param opts BuildOpts +M.build = function(opts) + opts = opts or {} + _for_current_build_option(function(option) + _build(option, opts) + end) +end + +--- Build project with current build option +--- @param opts BuildOpts +M.build_explain = function(opts) + opts = opts or {} + _for_current_build_option(function(option) + _explain(_extend_build_command(option.command, opts)) + end) +end + +---Change current build option +---@param opts any|nil M.build_select = function(opts) - local items = pr.current_generate_option(opts).build_options + local items = pr.current_generate_option().build_options vim.ui.select(items, { prompt = "Select build option to generate:", format_item = function(item) @@ -88,28 +192,34 @@ M.build_select = function(opts) return end pr.set_current_build_option(idx) - pr.create_fileapi_query({ idx = idx }, function() - vim.schedule(function() - t.cmake_execute(choice.command, default_build_exe_opts) - end) - end) end) end -M.run_tagret = function(opts) +local _run_target = function(opts) + local command = { + cmd = opts.path, + cwd = pr.current_directory(), + } + t.target_execute(command) +end + +---@class RunTargetOpts +---@field explain boolean|nil + +--- Run target +--- @param opts RunTargetOpts +M.run_target = function(opts) opts = opts or {} local _curr_exe_cmd = pr.current_executable_target() if not _curr_exe_cmd then - M.run_tagret_select(opts) + M.run_target_select(opts) else - local command = { - cmd = Path:new(pr.current_directory(), _curr_exe_cmd.path):make_relative(uv.cwd()), - } - t.target_execute(command) + _run_target({ path = _curr_exe_cmd.path }) end end -M.run_tagret_select = function(opts) +--- Select target to run +M.run_target_select = function(opts) opts = opts or {} opts.type = "EXECUTABLE" local items = pr.current_targets(opts) @@ -118,22 +228,20 @@ M.run_tagret_select = function(opts) format_item = function(item) return item.name end, - }, function(choice, idx) + }, function(_, idx) if not idx then return end pr.set_current_executable_target(idx) - local command = { - cmd = Path:new(pr.current_directory(), choice.path):make_relative(uv.cwd()), - } - t.target_execute(command) end) end +---Toggle CMake terminal window M.toggle = function() t.cmake_toggle() end +---Edit `.cmake-variants.yaml` file M.edit_variants = function() utils.file_exists(constants.variants_yaml_filename, function(variants_exists) if variants_exists then @@ -151,4 +259,8 @@ M.edit_variants = function() end) end +M.reset_project = function(opts) + require("cmake.project").setup(opts) +end + return M diff --git a/lua/cmake/autocmds.lua b/lua/cmake/autocmds.lua index deb193d..5bbfbe8 100644 --- a/lua/cmake/autocmds.lua +++ b/lua/cmake/autocmds.lua @@ -32,17 +32,20 @@ function autocmds.setup() --without CMakeLists.txt neovim starts like `nvim CMakeLists.txt`. In this case initial --setup will not make the affect and to correctry process the file save, we need to create --this autocommand so it reinitializes the project if it has not been done before. IMHO this - --is not the best way to do this - if config.generate_after_save then - vim.api.nvim_create_autocmd({ "BufEnter" }, { - group = cmake_nvim_augroup, - pattern = constants.cmakelists, - callback = function(args) - actions.reset_project({ first_time_only = true }) - end, - desc = "Set up project on open CMakeLists.txt if not set before", - }) - end + --is not the best way to do this. Also, if newly buffer associated with CMakeLists.txt will not + --be saved and just closed, but user will continue to use nvim, CMake commands still will be + --able while it sholdn't. Two options is give up or handle all this corner cases + -- + -- if config.generate_after_save then + -- vim.api.nvim_create_autocmd({ "BufEnter" }, { + -- group = cmake_nvim_augroup, + -- pattern = constants.cmakelists, + -- callback = function(args) + -- actions.reset_project({ first_time_only = true }) + -- end, + -- desc = "Set up project on open CMakeLists.txt if not set before", + -- }) + -- end end return autocmds diff --git a/lua/cmake/capabilities.lua b/lua/cmake/capabilities.lua index 36bc4b1..0b76fc0 100644 --- a/lua/cmake/capabilities.lua +++ b/lua/cmake/capabilities.lua @@ -38,27 +38,18 @@ function Capabilities.has_fileapi() return vim.tbl_get(Capabilities.json, "fileApi") ~= nil end --- TODO: make this async Capabilities.setup = function(callback) - local lines = {} vim.schedule(function() - vim.fn.jobstart({ config.cmake.cmake_path, "-E", "capabilities" }, { - on_stdout = function(_, data) - if data then - vim.list_extend(lines, data) + vim.system({ config.cmake.cmake_path, "-E", "capabilities" }, { text = true }, function(obj) + if obj.code == 0 then + Capabilities.json = vim.json.decode(obj.stdout) + if type(callback) == "function" then + callback() end - end, - on_exit = function(_, code, _) - if code == 0 then - Capabilities.json = vim.json.decode(table.concat(lines, "")) - if type(callback) == "function" then - callback() - end - else - vim.notify("error " .. tostring(code) .. ". 'cmake -E capabilities'", vim.log.levels.ERROR) - end - end, - }) + else + vim.notify("error " .. tostring(obj.code) .. ". 'cmake -E capabilities'", vim.log.levels.ERROR) + end + end) end) end diff --git a/lua/cmake/commandline.lua b/lua/cmake/commandline.lua new file mode 100644 index 0000000..55c671c --- /dev/null +++ b/lua/cmake/commandline.lua @@ -0,0 +1,148 @@ +local project = require("cmake.project") + +local M = {} + +-- from https://github.com/akinsho/toggleterm.nvim/blob/main/lua/toggleterm/commandline.lua + +-- Get a valid base path for a user provided path +-- and an optional search term +---@param typed_path string +---@return string|nil, string|nil +local get_path_parts = function(typed_path) + if vim.fn.isdirectory(typed_path ~= "" and typed_path or ".") == 1 then + -- The string is a valid path, we just need to drop trailing slashes to + -- ease joining the base path with the suggestions + return typed_path:gsub("/$", ""), nil + elseif typed_path:find("/", 2) ~= nil then + -- Maybe the typed path is looking for a nested directory + -- we need to make sure it has at least one slash in it, and that is not + -- from a root path + local base_path = vim.fn.fnamemodify(typed_path, ":h") + local search_term = vim.fn.fnamemodify(typed_path, ":t") + if vim.fn.isdirectory(base_path) then + return base_path, search_term + end + end + + return nil, nil +end + +local complete_path = function(typed_path) + -- Read the typed path as the base for the directory search + local base_path, search_term = get_path_parts(typed_path or "") + local safe_path = base_path ~= "" and base_path or "." + + local paths = vim.fn.readdir(safe_path, function(entry) + return vim.fn.isdirectory(safe_path .. "/" .. entry) + end) + + if not u.str_is_empty(search_term) then + paths = vim.tbl_filter(function(path) + return path:match("^" .. search_term .. "*") ~= nil + end, paths) + end + + return vim.tbl_map(function(path) + return u.concat_without_empty({ base_path, path }, "/") + end, paths) +end + +local generate_options = { + fresh = true, +} + +local complete_value = function(values, values_opts, match) + return function(typed_value) + typed_value = typed_value or "" + return vim.iter(values(values_opts)) + :filter(function(value) + return not typed_value or #typed_value == 0 or value[match]:match("^" .. typed_value .. "*") + end) + :map(function(value) + return value[match] + end) + :totable() + end +end + +local build_options = { + clean = true, + targets = complete_value(project.current_targets, {}, "name"), +} + +local install_options = { + explain = true, + -- component = complete_value(project.current_components, {}, "name"), + prefix = complete_path, +} + +---@param options table a dictionary of key to function +---@return fun(lead: string, command: string, _: number):table +local function complete(options) + ---@param lead string the leading portion of the argument currently being completed on + ---@param command string the entire command line + ---@param _ number the cursor position in it (byte index) + ---@return table + return function(lead, command, _) + local parts = vim.split(lead, "=") + local key = parts[1] + local value = parts[2] + if options[key] then + if type(options[key]) == "function" then + return vim.iter(options[key](value)) + :map(function(opt) + return key .. "=" .. opt + end) + :totable() + else + return {} + end + else + return vim.iter(options) + :filter(function(option, _) + return option:match(" " .. option .. "=") == nil + end) + :map(function(option, option_value) + if type(option_value) == "boolean" and option_value then + return option + else + return option .. "=" + end + end) + :totable() + end + end +end + +---Take a users command arguments in format 'key1=value key2' +---and parse this into a table of arguments +---@see https://stackoverflow.com/a/27007701 +---@param args string +---@return any +function M.parse(args) + local result = {} + if args then + for _, part in ipairs(vim.split(args, " ")) do + local arg = vim.split(part, "=") + local key, value = arg[1], arg[2] + if not value then + result[key] = true + else + if key == "targets" then + result[key] = vim.split(value, ",") + else + result[key] = value + end + end + end + end + return result +end + +M.cmake_generate_complete = complete(generate_options) + +M.cmake_build_complete = complete(build_options) + +M.cmake_install_complete = complete(install_options) + +return M diff --git a/lua/cmake/commands.lua b/lua/cmake/commands.lua index c2f6e75..d763958 100644 --- a/lua/cmake/commands.lua +++ b/lua/cmake/commands.lua @@ -1,39 +1,88 @@ +local commandline = require("cmake.commandline") +local actions = require("cmake.actions") + local M = {} local cmd = vim.api.nvim_create_user_command -M.register_commands = function() - cmd("CMakeGenerate", function() - require("cmake.actions").generate() - end, { desc = "Generate with last configuration" }) - - cmd("CMakeGenerateSelect", function() - require("cmake.actions").generate_select() - end, { desc = "Select configuration and generate" }) - - cmd("CMakeBuild", function() - require("cmake.actions").build() - end, { desc = "Build with last build option" }) +local prefix = "CMake" - cmd("CMakeBuildSelect", function() - require("cmake.actions").build_select() - end, { desc = "Select build option and build" }) +local commands = { + ["Generate"] = { + command = actions.generate, + parse = true, + default_opts = { fresh = false }, + cmd_opts = { + desc = "Generate with last configuration", + nargs = "*", + complete = commandline.cmake_generate_complete, + }, + }, + ["GenerateExplain"] = { + command = actions.generate_explain, + parse = true, + default_opts = { fresh = false }, + cmd_opts = { + desc = "Explain current generate command", + nargs = "*", + complete = commandline.cmake_generate_complete, + }, + }, + ["GenerateSelect"] = { + command = actions.generate_select, + cmd_opts = { desc = "Select generate configuration" }, + }, + ["Build"] = { + command = actions.build, + parse = true, + cmd_opts = { + desc = "Build with last configuration", + nargs = "*", + complete = commandline.cmake_build_complete, + }, + }, + ["BuildExplain"] = { + command = actions.build_explain, + parse = true, + cmd_opts = { + desc = "Explain current build command", + nargs = "*", + complete = commandline.cmake_build_complete, + }, + }, + ["BuildSelect"] = { + command = actions.build_select, + cmd_opts = { desc = "Select build configuration" }, + }, + -- ["Install"] = {}, + ["Run"] = { + command = actions.run_target, + cmd_opts = { desc = "Run current executable target", nargs = "*" }, + }, + ["RunSelect"] = { + command = actions.run_target_select, + cmd_opts = { desc = "Select executable target" }, + }, + ["Toggle"] = { + command = actions.toggle, + cmd_opts = { desc = "Toggle cmake terminal" }, + }, + ["EditVariants"] = { + command = actions.edit_variants, + cmd_opts = { desc = "Edit variants" }, + }, +} - cmd("CMakeRun", function() - require("cmake.actions").run_tagret() - end, { desc = "Select build option and build" }) - - cmd("CMakeRunSelect", function() - require("cmake.actions").run_tagret_select() - end, { desc = "Select build option and build" }) - - cmd("CMakeToggle", function() - require("cmake.actions").toggle() - end, { desc = "Toggle terminal with cmake command" }) - - cmd("CMakeEditVariants", function() - require("cmake.actions").edit_variants() - end, { desc = "Edit variants" }) +M.register_commands = function() + for k, v in pairs(commands) do + cmd(prefix .. k, function(opts) + local action_opts = v.default_opts or {} + if v.parse then + action_opts = vim.tbl_deep_extend("keep", commandline.parse(opts.args) or {}, action_opts) + end + v.command(action_opts) + end, v.cmd_opts) + end end return M diff --git a/lua/cmake/config.lua b/lua/cmake/config.lua index 9f7d04a..0cc95ca 100644 --- a/lua/cmake/config.lua +++ b/lua/cmake/config.lua @@ -22,25 +22,23 @@ local default_config = { }, }, }, - parallel_jobs = nil, --#(vim.uv or vim.loop).cpu_info(), - -- source_directory = "${workspaceFolder}", --TODO: not used + parallel_jobs = nil, }, save_before_build = true, generate_after_save = true, - terminal = { - direction = "horizontal", - display_name = "CMake", --TODO: not used + cmake_terminal = { + split = "below", + size = 15, close_on_exit = "success", - hidden = false, + open_on_start = true, clear_env = false, - focus = false, + enter = false, }, - runner_terminal = { - direction = "horizontal", - close_on_exit = false, - hidden = false, + target_terminal = { + split = "below", + size = 15, clear_env = false, - focus = false, + enter = true, }, notification = { after = "success", @@ -49,6 +47,7 @@ local default_config = { short = { sep = " × ", show = true }, long = { sep = " ❄ ", show = false }, }, + keybinds = {}, } local M = vim.deepcopy(default_config) diff --git a/lua/cmake/fileapi.lua b/lua/cmake/fileapi.lua index d33d477..1aa7de6 100644 --- a/lua/cmake/fileapi.lua +++ b/lua/cmake/fileapi.lua @@ -2,7 +2,6 @@ local capabilities = require("cmake.capabilities") local Path = require("plenary.path") local scan = require("plenary.scandir") local utils = require("cmake.utils") -local uv = vim.uv or vim.loop local query_path_suffix = { ".cmake", "api", "v1", "query", "client-cmake", "query.json" } local reply_dir_suffix = { ".cmake", "api", "v1", "reply" } @@ -15,7 +14,6 @@ function FileApi.create(path, callback) if not exists then if capabilities.json.fileApi then vim.schedule(function() - --TODO: change to async vim.fn.mkdir(Path:new(vim.fs.dirname(query)):absolute(), "p") utils.write_file(query, vim.json.encode(capabilities.json.fileApi), callback) end) @@ -35,6 +33,7 @@ function FileApi.read_reply(path, callback) return end local ret = { targets = {} } + --TODO: replace with uv scandir scan.scan_dir_async(reply_dir, { search_pattern = "index*", on_exit = function(results) @@ -47,8 +46,10 @@ function FileApi.read_reply(path, callback) if object.kind == "codemodel" then utils.read_file(Path:new(reply_dir, object.jsonFile):absolute(), function(codemodel_data) local codemodel = vim.json.decode(codemodel_data) - --FIX: this loop does not read all files if codemodel contains many targets. This is because libuv (or some external settings) forbids to open files - -- in async mode more than some limit number. Seems like the solution is to queue these calls and limit max number for opened files per time + --FIX: this loop does not read all files if codemodel contains many targets (is will crash actually). + --This is because libuv (or some external settings) forbids to open files + -- in async mode more than some limit number. Seems like the solution is + -- to queue these calls and limit max number for opened files per time for _, target in ipairs(codemodel.configurations[1].targets) do utils.read_file( Path:new(reply_dir, target.jsonFile):absolute(), diff --git a/lua/cmake/init.lua b/lua/cmake/init.lua index adfa84e..be5308b 100644 --- a/lua/cmake/init.lua +++ b/lua/cmake/init.lua @@ -4,7 +4,7 @@ local autocmds = require("cmake.autocmds") local utils = require("cmake.utils") local constants = require("cmake.constants") -local uv = vim.uv or vim.loop +local uv = vim.uv local M = {} @@ -19,7 +19,7 @@ function M.setup(opts) autocmds.set_on_variants() commands.register_commands() end) - require("cmake.project").setup({ first_time_only = true }) + require("cmake.project").setup() else end end) diff --git a/lua/cmake/project.lua b/lua/cmake/project.lua index 8f7d97f..a5d6cdd 100644 --- a/lua/cmake/project.lua +++ b/lua/cmake/project.lua @@ -3,7 +3,7 @@ local VariantConfig = require("cmake.variants") local FileApi = require("cmake.fileapi") local utils = require("cmake.utils") local constants = require("cmake.constants") -local uv = vim.uv or vim.loop +local uv = vim.uv local Project = {} @@ -23,7 +23,6 @@ end local append_after_success_actions = function() local read_reply = function(v, not_presented) if (not_presented and not fileapis[v.directory]) or not not_presented then - --TODO: replace to vim.fs.joinpath after nvim 0.10 release utils.symlink(v.directory .. "/compile_commands.json", uv.cwd()) fileapis[v.directory] = { targets = {} } FileApi.read_reply(v.directory, function(target) @@ -60,9 +59,11 @@ local init_fileapis = function() end -- TODO: validate yaml and fallback to config's variants if not valid +-- TODO: make variants order more stable. at least when reading from file function Project.from_variants(variants) + local variants_copy = vim.deepcopy(variants) local list_variants = {} - for k, v in pairs(variants) do + for k, v in pairs(variants_copy) do table.insert(list_variants, v) list_variants[#list_variants]._name = k end @@ -81,6 +82,17 @@ function Project.from_variants(variants) init_fileapis() end +--NOTE: Neovim craches on this code +function Project.clear_cache(callback) + uv.fs_unlink(vim.fs.joinpath(Project.current_directory(), "CMakeCache.txt"), function(f_err) + assert(f_err, f_err) + uv.fs_unlink(vim.fs.joinpath(Project.current_directory(), "CMakeFiles"), function(d_err) + assert(d_err, d_err) + callback() + end) + end) +end + function Project.generate_options(opts) opts = opts or {} return configs @@ -192,6 +204,7 @@ function Project.create_fileapi_query(opts, callback) end local do_setup = function(opts) + reset_internals() local variants_path = vim.fs.joinpath(uv.cwd(), constants.variants_yaml_filename) utils.file_exists(variants_path, function(variants_exists) if variants_exists then @@ -210,7 +223,6 @@ function Project.setup(opts) if opts.first_time_only and initialised then return end - reset_internals() if not initialised then require("cmake.capabilities").setup(function() do_setup(opts) diff --git a/lua/cmake/terminal.lua b/lua/cmake/terminal.lua index 358d731..d1d8a75 100644 --- a/lua/cmake/terminal.lua +++ b/lua/cmake/terminal.lua @@ -1,32 +1,39 @@ -local Terminal = require("toggleterm.terminal").Terminal -local ui = require("toggleterm.ui") local config = require("cmake.config") +local api = vim.api + local M = {} -local cmake +local cmake = { + bufnr = nil, + window = nil, + jobid = nil, +} + local runnable ---TODO: cmake must be an id, not terminal +local prepare_cmake_buf = function() + if cmake.bufnr and api.nvim_buf_is_valid(cmake.bufnr) then + api.nvim_buf_delete(cmake.bufnr, { force = true }) + end + cmake.bufnr = api.nvim_create_buf(false, false) +end -M.cmake_execute = function(command, opts) - opts = opts or {} - if cmake then - cmake:shutdown() - cmake = nil +local termopen = function(command, opts) + -- For some reason termopen() doesn't like an empty env table + if command.env and vim.tbl_isempty(command.env) then + command.env = nil end - local term_opts = { - direction = config.terminal.direction, - display_name = config.terminal.display_name, - hidden = config.terminal.hidden, - clear_env = config.terminal.clear_env, - cmd = command.cmd .. " " .. command.args, - -- env = command.env, - on_exit = function(t, pid, code, name) + vim.fn.termopen(command.cmd .. " " .. command.args, { + -- detach = 1, + cwd = command.cwd, + env = command.env, + clear_env = config.clear_env, + on_exit = function(pid, code, event) if code == 0 then command.after_success() - if config.terminal.close_on_exit == "success" then - t:close() + if config.cmake_terminal.close_on_exit == "success" or config.cmake_terminal.close_on_exit == true then + api.nvim_win_close(cmake.window, true) end if config.notification.after == "success" or config.notification.after == true then vim.notify( @@ -34,56 +41,68 @@ M.cmake_execute = function(command, opts) vim.log.levels.INFO ) end - elseif config.notification.after == "failure" or config.notification.after == true then - local msg = "CMake failed. Code " .. tostring(code) - local opt_msg = vim.tbl_get(opts, "notify", "err_message") - if type(opt_msg) == "string" then - msg = opt_msg - elseif type(opt_msg) == "function" then - msg = opt_msg(code) + else + if config.notification.after == "failure" or config.notification.after == true then + local msg = "CMake failed. Code " .. tostring(code) + local opt_msg = vim.tbl_get(opts, "notify", "err_message") + if type(opt_msg) == "string" then + msg = opt_msg + elseif type(opt_msg) == "function" then + msg = opt_msg(code) + end + vim.notify(msg, vim.log.levels.ERROR) end - vim.notify(msg, vim.log.levels.ERROR) end end, - on_open = function(t) - t:set_mode("n") - end, - } - term_opts.close_on_exit = type(config.terminal.close_on_exit) == "boolean" and config.terminal.close_on_exit - or false - cmake = Terminal:new(term_opts) - cmake:open() - if not config.terminal.focus and cmake:is_focused() then - ui.goto_previous() - ui.stopinsert() + }) +end + +local open_window = function() + if not cmake.bufnr then + vim.notify("No CMake buffer created yet", vim.log.levels.INFO) + return end + cmake.window = api.nvim_open_win(cmake.bufnr, config.cmake_terminal.enter, { + win = 0, + split = config.cmake_terminal.split, + height = config.cmake_terminal.size, + width = config.cmake_terminal.size, + }) +end + +M.cmake_execute = function(command, opts) + opts = opts or {} + + prepare_cmake_buf() + if config.cmake_terminal.open_on_start and not (cmake.window and api.nvim_win_is_valid(cmake.window)) then + open_window() + end + vim.api.nvim_buf_call(cmake.bufnr, function() + termopen(command, opts) + end) end M.cmake_toggle = function() - if cmake then - cmake:toggle() + if cmake.window and api.nvim_win_is_valid(cmake.window) then + api.nvim_win_close(cmake.window, true) else - vim.notify("No CMake terminal") + open_window() end end M.target_execute = function(command, opts) opts = opts or {} - local term_opts = { - direction = config.runner_terminal.direction, - close_on_exit = config.runner_terminal.close_on_exit, - hidden = config.runner_terminal.hidden, - clear_env = config.clear_env, - } - if not runnable then - runnable = Terminal:new(term_opts) - end - if not runnable:is_open() then - runnable:open() - end - if command.cmd then - runnable:send(command.cmd, not config.runner_terminal.focus) - end + local bufnr = api.nvim_create_buf(true, false) + api.nvim_open_win(bufnr, config.target_terminal.enter, { + win = 0, + split = config.target_terminal.split, + height = config.target_terminal.size, + width = config.target_terminal.size, + }) + api.nvim_buf_call(bufnr, function() + vim.cmd.terminal() + api.nvim_chan_send(vim.bo.channel, command.cwd .. "/" .. command.cmd) + end) end return M diff --git a/lua/cmake/utils.lua b/lua/cmake/utils.lua index b9bcdf8..0c16658 100644 --- a/lua/cmake/utils.lua +++ b/lua/cmake/utils.lua @@ -1,8 +1,5 @@ local config = require("cmake.config") -local capabilities = require("cmake.capabilities") -local scan = require("plenary.scandir") -local Path = require("plenary.path") -local uv = vim.uv or vim.loop +local uv = vim.uv local utils = {} @@ -75,22 +72,26 @@ function utils.write_file(path, txt, callback) end) end ---TODO: async mkdir -p - function utils.symlink(src_path, dst_path, callback) - --TODO: replace to vim.fs.joinpath after nvim 0.10 release - local src = Path:new(src_path, "compile_commands.json") - if src:exists() then - vim.cmd( - 'silent exec "!' - .. config.cmake_path - .. " -E create_symlink " - .. src:normalize() - .. " " - .. Path:new(dst_path, "compile_commands.json"):normalize() - .. '"' - ) - end + local src = vim.fs.joinpath(src_path, "compile_commands.json") + utils.file_exists(src, function() + uv.spawn(config.cmake.cmake_path, { + args = { + "-E", + "create_symlink", + src, + vim.fs.joinpath(dst_path, "compile_commands.json"), + }, + }, function(code, signal) + if code ~= 0 then + vim.notify("CMake: error while creating symlink. Code " .. tostring(code), vim.log.levels.ERROR) + return + end + if type(callback) == "function" then + callback() + end + end) + end) end return utils diff --git a/lua/cmake/variants.lua b/lua/cmake/variants.lua index b68ff05..6a4c859 100644 --- a/lua/cmake/variants.lua +++ b/lua/cmake/variants.lua @@ -1,7 +1,7 @@ local config = require("cmake.config") local utils = require("cmake.utils") -local uv = vim.uv or vim.loop +local uv = vim.uv local VariantConfig = {} -- cgit v1.2.3