From 672f0d32e322b79661b5d7959887adaa9e41ad98 Mon Sep 17 00:00:00 2001 From: Daniil Rozanov Date: Fri, 29 Mar 2024 04:37:56 +0300 Subject: feat: build from current variant --- lua/cmake-explorer.lua | 112 -------- lua/cmake-explorer/build_list.lua | 57 ---- lua/cmake-explorer/cache.lua | 47 ++++ lua/cmake-explorer/capabilities.lua | 8 +- lua/cmake-explorer/config.lua | 37 ++- lua/cmake-explorer/file_api.lua | 117 +++++---- lua/cmake-explorer/init.lua | 67 +++++ lua/cmake-explorer/notification.lua | 1 + lua/cmake-explorer/project.lua | 375 +++++++++++++++++++-------- lua/cmake-explorer/project_uauaua.lua | 113 ++++++++ lua/cmake-explorer/runner.lua | 27 +- lua/cmake-explorer/telescope/make_entry.lua | 57 ++++ lua/cmake-explorer/telescope/pickers.lua | 103 ++++++++ lua/cmake-explorer/telescope/previewers.lua | 40 +++ lua/cmake-explorer/telescope/test.lua | 46 ++++ lua/cmake-explorer/utils.lua | 102 ++++---- lua/telescope/_extensions/cmake-explorer.lua | 0 plugin/cmake-explorer.lua | 33 +++ 18 files changed, 941 insertions(+), 401 deletions(-) delete mode 100644 lua/cmake-explorer.lua delete mode 100644 lua/cmake-explorer/build_list.lua create mode 100644 lua/cmake-explorer/cache.lua create mode 100644 lua/cmake-explorer/init.lua create mode 100644 lua/cmake-explorer/project_uauaua.lua create mode 100644 lua/cmake-explorer/telescope/make_entry.lua create mode 100644 lua/cmake-explorer/telescope/pickers.lua create mode 100644 lua/cmake-explorer/telescope/previewers.lua create mode 100644 lua/cmake-explorer/telescope/test.lua create mode 100644 lua/telescope/_extensions/cmake-explorer.lua create mode 100644 plugin/cmake-explorer.lua diff --git a/lua/cmake-explorer.lua b/lua/cmake-explorer.lua deleted file mode 100644 index eec4f5d..0000000 --- a/lua/cmake-explorer.lua +++ /dev/null @@ -1,112 +0,0 @@ -local config = require("cmake-explorer.config") -local runner = require("cmake-explorer.runner") -local Project = require("cmake-explorer.project") -local capabilities = require("cmake-explorer.capabilities") -local utils = require("cmake-explorer.utils") -local Path = require("plenary.path") - -local M = {} - -local project = nil - -local format_build_dir = function() - if Path:new(config.build_dir):is_absolute() then - return function(v) - return Path:new(v.path):make_relative(vim.env.HOME) - end - else - return function(v) - return Path:new(v.path):make_relative(project.path) - end - end -end - -function M.list_build_dirs() - if project then - vim.print(project:list_build_dirs()) - end -end - -function M.configure() - assert(project) - local generators = capabilities.generators() - table.insert(generators, 1, "Default") - vim.ui.select(generators, { prompt = "Select generator" }, function(generator) - if not generator then - return - end - -- TODO: handle default generator from env (or from anywhere else) - generator = utils.is_neq(generator, "Default") - vim.ui.select(config.build_types, { prompt = "Select build type" }, function(build_type) - if not build_type then - return - end - vim.ui.input({ prompt = "Input additional args" }, function(args) - if not build_type then - return - end - local task = project:configure({ generator = generator, build_type = build_type, args = args }) - runner.start(task) - end) - end) - end) -end - -function M.configure_dir() - assert(project) - - vim.ui.select( - project:list_build_dirs(), - { prompt = "Select directory to build", format_item = format_build_dir() }, - function(dir) - if not dir then - return - end - local task = project:configure(dir.path) - runner.start(task) - end - ) -end - -function M.configure_last() - local task = project:configure_last() - runner.start(task) -end - -M.setup = function(cfg) - cfg = cfg or {} - - config.setup(cfg) - capabilities.setup() - - project = Project:new(vim.loop.cwd()) - if not project then - print("cmake-explorer: no CMakeLists.txt file found. Aborting setup") - return - end - project:scan_build_dirs() - - local cmd = vim.api.nvim_create_user_command - - cmd("CMakeConfigure", M.configure, { -- opts - nargs = 0, - bang = true, - desc = "CMake configure with parameters", - }) - - cmd( - "CMakeConfigureLast", - M.configure_last, - { nargs = 0, bang = true, desc = "CMake configure last if exists. Otherwise default" } - ) - - cmd( - "CMakeConfigureDir", - M.configure_dir, - { nargs = 0, bang = true, desc = "CMake configure last if exists. Otherwise default" } - ) - - cmd("CMakeListBuilds", M.list_build_dirs, { nargs = 0 }) -end - -return M diff --git a/lua/cmake-explorer/build_list.lua b/lua/cmake-explorer/build_list.lua deleted file mode 100644 index 7abaddc..0000000 --- a/lua/cmake-explorer/build_list.lua +++ /dev/null @@ -1,57 +0,0 @@ -local Build = require("cmake-explorer.build") - -local BuildFilter = {} - -BuildFilter.__call = function(self, build) - for k, v in pairs(self) do - if type(k) == "string" then - if v ~= build[k] then - return false - end - end - end - return true -end - -local BuildList = { - __newindex = function(t, k, v) - for _, value in ipairs(t) do - if value == v then - return - end - end - rawset(t, k, v) - end, -} - -function BuildList:new() - local obj = { - current = nil, - } - setmetatable(obj, BuildList) - return obj -end - -function BuildList:insert(o) - local build = Build:new(o) - table.insert(self, build) - self.current = build -end - -function BuildList:filter(pred) - pred = pred or {} - local i, n = 0, #self - if type(pred) == "table" then - setmetatable(pred, BuildFilter) - end - return function() - repeat - i = i + 1 - if pred(self[i]) then - return self[i] - end - until i ~= n - end -end - -return BuildList diff --git a/lua/cmake-explorer/cache.lua b/lua/cmake-explorer/cache.lua new file mode 100644 index 0000000..046a9f9 --- /dev/null +++ b/lua/cmake-explorer/cache.lua @@ -0,0 +1,47 @@ +local Cache = {} + +local os = { + iswin32 = vim.fn.has("win32") == 1, + ismac = vim.fn.has("mac") == 1, + iswsl = vim.fn.has("wsl") == 1, + islinux = vim.fn.has("linux") == 1, +} + +local dir = { + unix = vim.fn.expand("~") .. "/.cache/cmake_explorer_nvim/", + mac = vim.fn.expand("~") .. "/.cache/cmake_explorer_nvim/", + win = vim.fn.expand("~") .. "/AppData/Local/cmake_explorer_nvim/", +} + +local function get_cache_path() + if os.islinux then + return dir.unix + elseif os.ismac then + return dir.mac + elseif os.iswsl then + return dir.unix + elseif os.iswin32 then + return dir.win + end +end + +local function get_clean_path(path) + local current_path = path + local clean_path = current_path:gsub("/", "") + clean_path = clean_path:gsub("\\", "") + clean_path = clean_path:gsub(":", "") + return clean_path +end + +function Cache.load(path) + return +end + +function Cache.save_global(tbl) + local to_save = vim.tbl_deep_extend("keep", tbl) + setmetatable(to_save, nil) + local path = get_project_path() + local file = io.open(path, "w") +end + +return Cache diff --git a/lua/cmake-explorer/capabilities.lua b/lua/cmake-explorer/capabilities.lua index f3a966c..052f484 100644 --- a/lua/cmake-explorer/capabilities.lua +++ b/lua/cmake-explorer/capabilities.lua @@ -8,7 +8,7 @@ local multiconfig_generators = { "Visual Studio 15 2017", "Visual Studio 16 2019", "Visual Studio 17 2022", - -- "Green Hills MULTI" + "Green Hills MULTI", } local Capabilities = { @@ -27,6 +27,10 @@ function Capabilities.generators() end function Capabilities.is_multiconfig_generator(generator) + -- if generator is nil, assume is is not multiconifg + if not generator then + return + end return vim.tbl_contains(multiconfig_generators, generator) end @@ -35,7 +39,7 @@ function Capabilities.has_fileapi() end Capabilities.setup = function() - local output = vim.fn.system({ config.cmake_cmd, "-E", "capabilities" }) + local output = vim.fn.system({ config.cmake_path, "-E", "capabilities" }) Capabilities.json = vim.json.decode(output) end diff --git a/lua/cmake-explorer/config.lua b/lua/cmake-explorer/config.lua index 21788ce..611f526 100644 --- a/lua/cmake-explorer/config.lua +++ b/lua/cmake-explorer/config.lua @@ -1,9 +1,36 @@ local default_config = { - cmake_cmd = "cmake", - build_dir_template = { "build", "${buildType}", sep = "-", case = nil }, - build_dir = "", - build_types = { "Debug", "Release" }, - options = {}, + cmake_path = "cmake", + environment = {}, + configure_environment = {}, + build_directory = "${workspaceFolder}/build-${buildType}", + build_environment = {}, + build_args = {}, + build_tool_args = {}, + generator = nil, + default_variants = { + { + default = "debug", + description = "Build type", + choices = { + debug = { short = "Debug", long = "Long debug", buildType = "Debug" }, + release = { short = "Release", long = "Long release", buildType = "Release" }, + }, + }, + { + default = "static", + choices = { + static = { short = "Static", long = "Long static", linkage = "static" }, + shared = { short = "Shared", long = "Long shared", linkage = "shared" }, + }, + }, + }, + variants_display = { + short_sep = " × ", + long_sep = " ❄ ", + }, + parallel_jobs = nil, + save_before_build = true, + source_directory = "${workspaceFolder}", } local M = vim.deepcopy(default_config) diff --git a/lua/cmake-explorer/file_api.lua b/lua/cmake-explorer/file_api.lua index cd83be4..37eda3a 100644 --- a/lua/cmake-explorer/file_api.lua +++ b/lua/cmake-explorer/file_api.lua @@ -2,6 +2,7 @@ local capabilities = require("cmake-explorer.capabilities") local Path = require("plenary.path") local Scandir = require("plenary.scandir") local notif = require("cmake-explorer.notification") +local utils = require("cmake-explorer.utils") local query_path_suffix = { ".cmake", "api", "v1", "query", "client-cmake-explorer", "query.json" } local reply_dir_suffix = { ".cmake", "api", "v1", "reply" } @@ -11,83 +12,81 @@ local FileApi = {} FileApi.__index = FileApi function FileApi:new(opts) - if not capabilities.has_fileapi() then - notif.notify("No fileapi files", vim.log.levels.ERROR) - return - end - local path - if type(opts) == "string" then - path = opts - end - local obj = { - path = Path:new(path):absolute(), - index = nil, - cmakefiles = nil, - codemodel = nil, - targets = nil, - } - Path:new(path):mkdir({ parents = true }) - setmetatable(obj, FileApi) - return obj + if not capabilities.has_fileapi() then + notif.notify("No fileapi files", vim.log.levels.ERROR) + return + end + local path + if type(opts) == "string" then + path = opts + end + local obj = { + path = path, + index = nil, + cmakefiles = nil, + codemodel = nil, + targets = {}, + } + setmetatable(obj, FileApi) + return obj end function FileApi:create() - local query = Path:new(self.path, unpack(query_path_suffix)) - if not query:exists() then - if not query:touch({ parents = true }) then - notif.notify("Cannot create query file", vim.log.levels.ERROR) - return - end - query:write(vim.json.encode(capabilities.json.fileApi), "w") - end - Path:new(self.path, unpack(reply_dir_suffix)):mkdir({ parents = true }) - return true + local query = Path:new(self.path, unpack(query_path_suffix)) + if not query:exists() then + if not query:touch({ parents = true }) then + notif.notify("Cannot create query file", vim.log.levels.ERROR) + return + end + query:write(vim.json.encode(capabilities.json.fileApi), "w") + end + Path:new(self.path, unpack(reply_dir_suffix)):mkdir({ parents = true }) + return true end function FileApi:read_reply() - if not self:reply_exists() then - notif.notify("No reply directory", vim.log.levels.ERROR) - return - end - local reply_dir = Path:new(self.path, unpack(reply_dir_suffix)) - local index = Scandir.scan_dir(tostring(reply_dir), { search_pattern = "index*" }) - if #index == 0 then - notif.notify("No files in reply", vim.log.levels.ERROR) - return - end - self.index = vim.json.decode(Path:new(index[1]):read()) - for _, object in ipairs(self.index.objects) do - if object.kind == "codemodel" then - self.codemodel = vim.json.decode((reply_dir / object.jsonFile):read()) - self.targets = {} - for _, target in ipairs(self.codemodel.configurations[1].targets) do - table.insert(self.targets, vim.json.decode((reply_dir / target.jsonFile):read())) - end - elseif object.kind == "cmakeFiles" then - self.cmakefiles = vim.json.decode(Path:new(reply_dir / object.jsonFile):read()) - end - end - return true + if not self:reply_exists() then + notif.notify("No reply directory", vim.log.levels.ERROR) + return + end + local reply_dir = Path:new(self.path, unpack(reply_dir_suffix)) + local index = Scandir.scan_dir(tostring(reply_dir), { search_pattern = "index*" }) + if #index == 0 then + notif.notify("No files in reply", vim.log.levels.ERROR) + return + end + self.index = vim.json.decode(Path:new(index[1]):read()) + for _, object in ipairs(self.index.objects) do + if object.kind == "codemodel" then + self.codemodel = vim.json.decode((reply_dir / object.jsonFile):read()) + for _, target in ipairs(self.codemodel.configurations[1].targets) do + self.targets[target.name] = vim.json.decode((reply_dir / target.jsonFile):read()) + end + elseif object.kind == "cmakeFiles" then + self.cmakefiles = vim.json.decode(Path:new(reply_dir / object.jsonFile):read()) + end + end + return true end function FileApi:query_exists() - return Path:new(self.path, unpack(query_path_suffix)):exists() + return Path:new(self.path, unpack(query_path_suffix)):exists() end function FileApi:reply_exists() - local reply_dir = Path:new(self.path, unpack(reply_dir_suffix)) - if not reply_dir:exists() then - return - end - return true + local reply_dir = Path:new(self.path, unpack(reply_dir_suffix)) + if not reply_dir:exists() then + return + end + return true end function FileApi:exists() - return self:query_exists() and self:reply_exists() + return self:query_exists() and self:reply_exists() end function FileApi.is_fileapi(other) - return getmetatable(other) == FileApi + return getmetatable(other) == FileApi end return FileApi diff --git a/lua/cmake-explorer/init.lua b/lua/cmake-explorer/init.lua new file mode 100644 index 0000000..622eebd --- /dev/null +++ b/lua/cmake-explorer/init.lua @@ -0,0 +1,67 @@ +local config = require("cmake-explorer.config") +local runner = require("cmake-explorer.runner") +local Project = require("cmake-explorer.project") +local capabilities = require("cmake-explorer.capabilities") +local utils = require("cmake-explorer.utils") +local Path = require("plenary.path") +local pickers = require("cmake-explorer.telescope.pickers") +local notif = require("cmake-explorer.notification") + +local M = {} + +M.project = nil + +local format_build_dir = function() + if Path:new(config.build_dir):is_absolute() then + return function(v) + return Path:new(v.path):make_relative(vim.env.HOME) + end + else + return function(v) + return Path:new(v.path):make_relative(M.project.path) + end + end +end + +function M.configure(opts) + assert(M.project) + opts = opts or {} + pickers.configure(opts) +end + +function M.configure_last(opts) + if not M.project.current_config then + notif.notify("No current configuration") + return + end + runner.start(M.project:configure_command()) +end + +function M.build(opts) + opts = opts or {} + pickers.build(opts) +end + +function M.build_last(opts) + if not M.project.current_config then + notif.notify("No current configuration") + return + end + runner.start(M.project:build_command()) +end + +function M.setup(opts) + opts = opts or {} + + config.setup(opts) + capabilities.setup() + + M.project = Project:from_variants(config.default_variants) + + if not M.project then + print("fuuuuuuuuuuuu") + return + end +end + +return M diff --git a/lua/cmake-explorer/notification.lua b/lua/cmake-explorer/notification.lua index fa8ff6e..0029241 100644 --- a/lua/cmake-explorer/notification.lua +++ b/lua/cmake-explorer/notification.lua @@ -6,6 +6,7 @@ function Notification.notify(msg, lvl, opts) opts = opts or {} if has_notify then opts.hide_from_history = true + opts.title = "CMake Explorer" return notify(msg, lvl, opts) end end diff --git a/lua/cmake-explorer/project.lua b/lua/cmake-explorer/project.lua index 7006656..ca77728 100644 --- a/lua/cmake-explorer/project.lua +++ b/lua/cmake-explorer/project.lua @@ -1,114 +1,283 @@ local config = require("cmake-explorer.config") -local capabilities = require("cmake-explorer.capabilities") -local FileApi = require("cmake-explorer.file_api") local Path = require("plenary.path") -local Scandir = require("plenary.scandir") local utils = require("cmake-explorer.utils") -local notif = require("cmake-explorer.notification") +local FileApi = require("cmake-explorer.file_api") + +local VariantConfig = {} + +VariantConfig.__index = VariantConfig + +local variant_subs = { + ["${workspaceFolder}"] = vim.loop.cwd(), + ["${userHome}"] = vim.loop.os_homedir(), +} + +function VariantConfig:new(obj) + setmetatable(obj, VariantConfig) + obj.subs = obj:_subs() + obj.build_directory = obj:_build_directory() + obj.configure_args = obj:_configure_args() + obj.configure_command = obj:_configure_command() + obj.build_args = obj:_build_args() + obj.build_command = obj:_build_command() + if not obj.fileapis[obj.build_directory] then + local fa = FileApi:new(obj.build_directory) + if fa and fa:exists() then + fa:read_reply() + obj.fileapis[obj.build_directory] = fa + end + end + + return obj +end + +function VariantConfig:_subs() + return vim.tbl_deep_extend("keep", variant_subs, { ["${buildType}"] = self.buildType }) +end + +function VariantConfig:_build_directory() + return utils.substitude(config.build_directory, self.subs) +end + +function VariantConfig:_configure_args() + local args = {} + if self.generator then + table.insert(args, "-G " .. '"' .. self.generator .. '"') + end + if self.buildType then + table.insert(args, "-DCMAKE_BUILD_TYPE=" .. self.buildType) + end + if self.linkage and string.lower(self.linkage) == "static" then + table.insert(args, "-DCMAKE_BUILD_SHARED_LIBS=OFF") + elseif self.linkage and string.lower(self.linkage) == "shared" then + table.insert(args, "-DCMAKE_BUILD_SHARED_LIBS=ON") + end + for k, v in pairs(self.settings or {}) do + table.insert(args, "-D" .. k .. "=" .. v) + end + table.insert(args, "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON") + table.insert( + args, + "-B" .. Path:new(self.build_directory):make_relative(utils.substitude(config.source_directory, self.subs)) + ) + return args +end + +function VariantConfig:_configure_command() + local ret = {} + ret.cmd = config.cmake_path + ret.args = self.configure_args + ret.cwd = variant_subs["${workspaceFolder}"] + ret.env = vim.tbl_deep_extend("keep", self.env, config.configure_environment, config.environment) + ret.after_success = function() + utils.symlink_compile_commands(self.build_directory, variant_subs["${workspaceFolder}"]) + self.fileapis[self.build_directory]:read_reply() + end + ret.before_run = function() + self.current_config_ref = self + local fa = FileApi:new(self.build_directory) + if not fa then + return + end + if not fa:create() then + return + end + self.fileapis[self.build_directory] = fa + return true + end + return ret +end + +function VariantConfig:_build_args() + local args = { "--build" } + table.insert( + args, + Path:new(self.build_directory):make_relative(utils.substitude(config.source_directory, self.subs)) + ) + if #self.buildArgs ~= 0 then + for _, v in ipairs(self.buildArgs) do + table.insert(args, v) + end + elseif #config.build_args ~= 0 then + for _, v in ipairs(config.build_args) do + table.insert(args, v) + end + end + if #self.buildToolArgs ~= 0 or #config.build_tool_args ~= 0 then + table.insert(args, "--") + if #self.buildToolArgs ~= 0 then + for _, v in ipairs(self.buildToolArgs) do + table.insert(args, v) + end + elseif #config.build_tool_args ~= 0 then + for _, v in ipairs(config.build_tool_args) do + table.insert(args, v) + end + end + end + return args +end + +function VariantConfig:_build_command() + local ret = {} + ret.cmd = config.cmake_path + ret.args = self.build_args + ret.cwd = variant_subs["${workspaceFolder}"] + ret.env = vim.tbl_deep_extend("keep", self.env, config.configure_environment, config.environment) + return ret +end + +local function cartesian_product(sets) + local function collapse_result(res) + local ret = { + short = {}, + long = {}, + buildType = nil, + linkage = nil, + generator = nil, + buildArgs = {}, + buildToolArgs = {}, + settings = {}, + env = {}, + } + local is_default = true + for _, v in ipairs(res) do + if not v.default then + is_default = false + end + ret.short[#ret.short + 1] = v.short + ret.long[#ret.long + 1] = v.long + ret.buildType = v.buildType or ret.buildType + ret.linkage = v.linkage or ret.linkage + ret.generator = v.generator or ret.generator + ret.buildArgs = v.buildArgs or ret.buildArgs + ret.buildToolArgs = v.buildToolArgs or ret.buildToolArgs + for sname, sval in pairs(v.settings or {}) do + ret.settings[sname] = sval + end + for ename, eres in pairs(v.env or {}) do + ret.env[ename] = eres + end + end + ret.display = {} + ret.display.short = table.concat(ret.short, config.variants_display.short_sep) + ret.display.long = table.concat(ret.long, config.variants_display.short_sep) + ret.default = is_default or nil + return ret + end + local result = {} + local set_count = #sets + local function descend(depth) + for k, v in pairs(sets[depth].choices) do + if sets[depth].default ~= k then + result.default = false + end + result[depth] = v + result[depth].default = (k == sets[depth].default) + if depth == set_count then + coroutine.yield(collapse_result(result)) + else + descend(depth + 1) + end + end + end + return coroutine.wrap(function() + descend(1) + end) +end local Project = {} Project.__index = Project -function Project:new(o) - o = o or {} - - local path - if type(o) == "string" then - path = o - elseif type(o) == "table" and o.path then - path = o.path - else - return - end - - if not Path:new(path, "CMakeLists.txt"):exists() then - return - end - - local obj = { - path = Path:new(path):absolute(), - fileapis = {}, - last_generate = {}, - } - setmetatable(obj, Project) - return obj -end - -function Project:scan_build_dirs() - local builds_root = utils.is_eq( - Path:new(config.build_dir):absolute(), - true, - config.build_dir, - Path:new(self.path, config.build_dir):absolute() - ) - local candidates = Scandir.scan_dir(builds_root, { hidden = false, only_dirs = true, depth = 0, silent = true }) - for _, v in ipairs(candidates) do - local fa = FileApi:new(v) - if fa and fa:exists() and fa:read_reply() then - self.fileapis[v] = fa - end - end -end - -function Project:symlink_compile_commands(path) - local src = Path:new(path, "compile_commands.json") - if src:exists() then - vim.cmd( - 'silent exec "!' - .. config.cmake_cmd - .. " -E create_symlink " - .. src:absolute() - .. " " - .. Path:new(self.path, "compile_commands.json"):absolute() - .. '"' - ) - end -end - -function Project:configure(params) - params = params or {} - local args = utils.generate_args(params, self.path) - local build_dir = utils.build_path(params, self.path) - if not args then - return - end - if not self.fileapis[build_dir] then - local fa = FileApi:new(build_dir) - if not fa then - notif.notify("Cannot fileapi object", vim.log.levels.ERROR) - return - end - if not fa:create() then - return - end - self.fileapis[build_dir] = fa - end - - return { - cmd = config.cmake_cmd, - args = args, - cwd = Path:new(self.path):absolute(), - after_success = function() - self.last_generate = build_dir - self.fileapis[build_dir]:read_reply() - self:symlink_compile_commands(build_dir) - end, - } -end - -function Project:configure_last() - return self:configure(self.last_generate) -end - -function Project:list_build_dirs() - local ret = {} - for k, _ in pairs(self.fileapis) do - local build = {} - build.path = k - table.insert(ret, build) - end - return ret +function Project:from_variants(variants) + local obj = + { headers = {}, display = { short_len = 10, long_len = 30 }, configs = {}, current_config = nil, fileapis = {} } + for _, v in pairs(variants) do + table.insert(obj.headers, v.description or "") + end + for v in cartesian_product(variants) do + v.fileapis = obj.fileapis + v.current_config_ref = obj.current_config + v = VariantConfig:new(v) + obj.display.short_len = math.max(obj.display.short_len, string.len(v.display.short)) + table.insert(obj.configs, v) + if v.default then + obj.current_config = v + end + if not obj.fileapis[v.build_directory] then + local fa = FileApi:new(v.build_directory) + if fa and fa:exists() then + fa:read_reply() + obj.fileapis[v.build_directory] = fa + end + end + end + setmetatable(obj, Project) + return obj +end + +function Project:from_presets(presets) + local obj = { { 1, 3, ddf = "" } } + return setmetatable(obj, self) +end + +function Project:set_current_config(idx) + self.current_config = self.configs[idx] +end + +function Project:set_current_build() end + +function Project:configure_command() + return self.current_config.configure_command +end + +function Project:current_configure_index() + for k, v in ipairs(self.configs) do + if v == self.current_config then + return k + end + end + return 1 +end + +function Project:current_build_index() + if not self.current_config then + return 1 + end + if getmetatable(self.current_config) == VariantConfig then + return 1 + end +end + +function Project:configure_display_options() + return self.display_options +end + +function Project:build_command() + if not self.current_config then + return + end + if getmetatable(self.current_config) == VariantConfig then + return self.current_config.build_command + end +end + +function Project:build_directory() + if not self.current_config then + return + end + return self.current_config.build_directory +end + +function Project:list_configs() + return self.configs +end + +function Project:list_builds(opts) + if getmetatable(self.current_config) == VariantConfig then + return { self.current_config } + end end return Project diff --git a/lua/cmake-explorer/project_uauaua.lua b/lua/cmake-explorer/project_uauaua.lua new file mode 100644 index 0000000..b54abd6 --- /dev/null +++ b/lua/cmake-explorer/project_uauaua.lua @@ -0,0 +1,113 @@ +local config = require("cmake-explorer.config") +local capabilities = require("cmake-explorer.capabilities") +local FileApi = require("cmake-explorer.file_api") +local Path = require("plenary.path") +local Scandir = require("plenary.scandir") +local utils = require("cmake-explorer.utils") +local notif = require("cmake-explorer.notification") + +local Project = {} + +Project.__index = Project + +function Project:new(o) + o = o or {} + + local path + if type(o) == "string" then + path = o + elseif type(o) == "table" and o.path then + path = o.path + else + return + end + + if not Path:new(path, "CMakeLists.txt"):exists() then + return + end + + local obj = { + path = path, + fileapis = {}, + last_generate = nil, + } + notif.notify("PATH " .. obj.path) + setmetatable( + obj.fileapis, + utils.make_maplike_list(function(v) + return v.path + end) + ) + setmetatable(obj, Project) + return obj +end + +function Project:scan_build_dirs() + local builds_root = utils.is_eq( + Path:new(config.build_dir):is_absolute(), + true, + Path:new(config.build_dir), + Path:new(self.path, config.build_dir) + ) + local candidates = + Scandir.scan_dir(builds_root:absolute(), { hidden = false, only_dirs = true, depth = 0, silent = true }) + for _, v in ipairs(candidates) do + local fa = FileApi:new(v) + if fa and fa:exists() and fa:read_reply() then + self.fileapis[fa.path] = fa + end + end +end + +function Project:symlink_compile_commands(path) + local src = Path:new(path, "compile_commands.json") + if src:exists() then + vim.cmd( + 'silent exec "!' + .. config.cmake_cmd + .. " -E create_symlink " + .. src:normalize() + .. " " + .. Path:new(self.path, "compile_commands.json"):normalize() + .. '"' + ) + end +end + +function Project:configure(params) + params = params or {} + local args = utils.generate_args(params, self.path) + local build_dir = utils.build_path(params, self.path) + if not args then + return + end + if not self.fileapis[build_dir] then + local fa = FileApi:new(build_dir) + if not fa then + notif.notify("Cannot fileapi object", vim.log.levels.ERROR) + return + end + if not fa:create() then + return + end + self.fileapis[build_dir] = fa + end + + local job_args = { + cmd = config.cmake_cmd, + args = args, + cwd = self.path, + after_success = function() + self.last_generate = job_args + self.fileapis[build_dir]:read_reply() + self:symlink_compile_commands(build_dir) + end, + } + return job_args +end + +function Project:configure_last() + return self.last_generate +end + +return Project diff --git a/lua/cmake-explorer/runner.lua b/lua/cmake-explorer/runner.lua index ca596ac..f2765fe 100644 --- a/lua/cmake-explorer/runner.lua +++ b/lua/cmake-explorer/runner.lua @@ -1,4 +1,5 @@ local Job = require("plenary.job") +local notif = require("cmake-explorer.notification") local M = {} @@ -12,17 +13,33 @@ function M.start(command) end local env = vim.tbl_extend("force", vim.loop.os_environ(), command.env and command.env or {}) - vim.notify(command.cmd .. " " .. table.concat(command.args, " ")) + if command.before_run then + if not command.before_run() then + notif.notify("Before run command failed", vim.log.levels.ERROR) + return + end + end + notif.notify(command.cmd .. " " .. table.concat(command.args, " ")) local job = Job:new({ command = command.cmd, args = command.args, env = env, + on_stdout = vim.schedule_wrap(function(err, data) end), on_exit = vim.schedule_wrap(function(_, code, signal) - if code == 0 and signal == 0 and command.after_success then - command.after_success() + if code == 0 and signal == 0 then + if command.after_success then + command.after_success() + end else - vim.notify( - "Code " .. tostring(code) .. ": " .. command.cmd .. " " .. table.concat(command.args, " "), + notif.notify( + "Code " + .. code + .. " Signal " + .. signal + .. ": " + .. command.cmd + .. " " + .. table.concat(command.args, " "), vim.log.levels.ERROR ) end diff --git a/lua/cmake-explorer/telescope/make_entry.lua b/lua/cmake-explorer/telescope/make_entry.lua new file mode 100644 index 0000000..cc919bd --- /dev/null +++ b/lua/cmake-explorer/telescope/make_entry.lua @@ -0,0 +1,57 @@ +local make_entry = require("telescope.make_entry") +local entry_display = require("telescope.pickers.entry_display") +local config = require("cmake-explorer.config") + +local M = {} + +M.gen_from_configure = function(opts) + local project = require("cmake-explorer").project + local displayer = entry_display.create({ + separator = " ", + items = { + { width = project.display.short_len + 5 }, + { remaining = true }, + }, + }) + local make_display = function(entry) + vim.print(entry) + return displayer({ + { entry.value.display.short, "TelescopeResultsIdentifier" }, + { entry.value.display.long, "TelescopeResultsComment" }, + }) + end + return function(entry) + return make_entry.set_default_entry_mt({ + value = entry, + ordinal = table.concat(entry.short, config.variants_display.short_sep), + display = make_display, + }, opts) + end +end + +M.gen_from_build = function(opts) + local project = require("cmake-explorer").project + local displayer = entry_display.create({ + separator = " ", + items = { + { width = project.display.short_len + 5 }, + { remaining = true }, + }, + }) + local make_display = function(entry) + vim.print(entry) + return displayer({ + { entry.value.display.short, "TelescopeResultsIdentifier" }, + { entry.value.display.long, "TelescopeResultsComment" }, + }) + end + return function(entry) + return make_entry.set_default_entry_mt({ + value = entry, + ordinal = table.concat(entry.short, config.variants_display.short_sep), + display = make_display, + }, opts) + end +end + +return M diff --git a/lua/cmake-explorer/telescope/pickers.lua b/lua/cmake-explorer/telescope/pickers.lua new file mode 100644 index 0000000..b2c7f56 --- /dev/null +++ b/lua/cmake-explorer/telescope/pickers.lua @@ -0,0 +1,103 @@ +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local conf = require("telescope.config").values +local actions = require("telescope.actions") +local action_state = require("telescope.actions.state") +local cmake_make_entry = require("cmake-explorer.telescope.make_entry") +local notif = require("cmake-explorer.notification") +local previewers = require("cmake-explorer.telescope.previewers") + +local M = {} + +M.build_dirs = function(opts) + local cmake = require("cmake-explorer") + pickers + .new(opts, { + prompt_title = "CMake Builds", + finder = finders.new_table({ + results = cmake.project.fileapis, + -- entry_maker = cmake_make_entry.gen_from_fileapi(opts), + entry_maker = function(entry) + return { + value = entry, + display = entry.path, + ordinal = entry.path, + } + end, + sorter = conf.generic_sorter(opts), + -- attach_mappings = function(prompt_bufnr, map) + -- actions.select_default:replace(function() end) + -- return true + -- end, + }), + }) + :find() +end + +M.configure = function(opts) + local cmake = require("cmake-explorer") + local runner = require("cmake-explorer.runner") + opts.layout_strategy = "vertical" + opts.layout_config = { + prompt_position = "top", + preview_cutoff = 0, + preview_height = 5, + mirror = true, + } + pickers + .new(opts, { + default_selection_index = cmake.project:current_configure_index(), + prompt_title = "CMake Configure Options", + finder = finders.new_table({ + results = cmake.project:list_configs(), + entry_maker = cmake_make_entry.gen_from_configure(opts), + }), + sorter = conf.generic_sorter(opts), + previewer = previewers.configure_previewer(), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + cmake.project.current_config = selection.value + runner.start(selection.value.configure_command) + end) + return true + end, + }) + :find() +end + +M.build = function(opts) + local cmake = require("cmake-explorer") + local runner = require("cmake-explorer.runner") + opts.layout_strategy = "vertical" + opts.layout_config = { + prompt_position = "top", + preview_cutoff = 0, + preview_height = 5, + mirror = true, + } + pickers + .new(opts, { + default_selection_index = cmake.project:current_build_index(), + prompt_title = "CMake Build Options", + finder = finders.new_table({ + results = cmake.project:list_builds(), + entry_maker = cmake_make_entry.gen_from_configure(opts), + }), + sorter = conf.generic_sorter(opts), + previewer = previewers.build_previewer(), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + cmake.project.current_config = selection.value + runner.start(selection.value.build_command) + end) + return true + end, + }) + :find() +end + +return M diff --git a/lua/cmake-explorer/telescope/previewers.lua b/lua/cmake-explorer/telescope/previewers.lua new file mode 100644 index 0000000..39fea4a --- /dev/null +++ b/lua/cmake-explorer/telescope/previewers.lua @@ -0,0 +1,40 @@ +local previewers = require("telescope.previewers") +local config = require("cmake-explorer.config") + +local M = {} + +M.configure_previewer = function(opts) + return previewers.new_buffer_previewer({ + title = "Configure Details", + + define_preview = function(self, entry) + if self.state.bufname then + return + end + local entries = { + "Command:", + config.cmake_path .. " " .. table.concat(entry.value.configure_args, " "), + } + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, entries) + end, + }) +end + +M.build_previewer = function(opts) + return previewers.new_buffer_previewer({ + title = "Build Details", + + define_preview = function(self, entry) + if self.state.bufname then + return + end + local entries = { + "Command:", + config.cmake_path .. " " .. table.concat(entry.value.build_args, " "), + } + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, entries) + end, + }) +end + +return M diff --git a/lua/cmake-explorer/telescope/test.lua b/lua/cmake-explorer/telescope/test.lua new file mode 100644 index 0000000..7b8bb00 --- /dev/null +++ b/lua/cmake-explorer/telescope/test.lua @@ -0,0 +1,46 @@ +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local conf = require("telescope.config").values +local actions = require("telescope.actions") +local action_state = require("telescope.actions.state") + +-- our picker function: colors +local colors = function(opts) + opts = opts or {} + pickers + .new(opts, { + prompt_title = "colors", + finder = finders.new_table({ + results = { + { "red", "#ff0000" }, + { "green", "#00ff00" }, + { "blue", "#0000ff" }, + }, + entry_maker = function(entry) + return { + value = entry, + display = entry[1], + ordinal = entry[1], + } + end, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr, map) + map({ "i", "n" }, "", function(_prompt_bufnr) + print("You typed ") + end) + + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + -- print(vim.inspect(selection)) + vim.api.nvim_put({ selection[1] }, "", false, true) + end) + return true + end, + }) + :find() +end + +-- to execute the function +colors() diff --git a/lua/cmake-explorer/utils.lua b/lua/cmake-explorer/utils.lua index e944642..61b5c98 100644 --- a/lua/cmake-explorer/utils.lua +++ b/lua/cmake-explorer/utils.lua @@ -2,75 +2,40 @@ local config = require("cmake-explorer.config") local capabilities = require("cmake-explorer.capabilities") local Path = require("plenary.path") -local utils = { - plugin_prefix = "CM", -} +local utils = {} -utils.build_dir_name = function(params) - if capabilities.is_multiconfig_generator(params.generator) then - return config.build_dir_template[1] - else - local paths = {} - for k, v in ipairs(config.build_dir_template) do - local path = v:gsub("${buildType}", params.build_type) - if k ~= 1 and config.build_dir_template.case then - if config.build_dir_template.case == "lower" then - path = string.lower(path) - elseif config.build_dir_template.case == "upper" then - path = string.upper(path) - end - end - table.insert(paths, path) - end - return table.concat(paths, config.build_dir_template.sep) - end -end - -utils.build_path = function(params, source_dir) - if type(params) == "string" then - return params - end +utils.build_path = function(build_dir, source_dir) local build_path = Path:new(config.build_dir) if build_path:is_absolute() then - return (build_path / utils.build_dir_name(params)):absolute() + return (build_path / build_dir):absolute() else - return Path:new(source_dir, build_path, utils.build_dir_name(params)):absolute() + return Path:new(build_path, build_dir):normalize() end end -utils.generate_args = function(params, source_dir) - local ret = {} - - if type(params) == "string" then - table.insert(ret, "-B" .. Path:new(params):make_relative(source_dir)) - else - if params.preset then - table.insert(ret, "--preset " .. params.preset) - else - if params.generator and vim.tbl_contains(capabilities.generators(), params.generator) then - table.insert(ret, "-G" .. params.generator) - end - - params.build_type = params.build_type or config.build_types[1] - if params.build_type then - table.insert(ret, "-DCMAKE_BUILD_TYPE=" .. params.build_type) - end - - table.insert(ret, "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON") - - if type(params.args) == "table" then - for k, v in pairs(params.args) do - table.insert(ret, "-D" .. k .. "=" .. v) - end - elseif type(params.args) == "string" then - table.insert(ret, params.args) - end - table.insert(ret, "-B" .. Path:new(utils.build_path(params, source_dir)):make_relative(source_dir)) - end +utils.substitude = function(str, subs) + local ret = str + for k, v in pairs(subs) do + ret = ret:gsub(k, v) end return ret end +function utils.symlink_compile_commands(src_path, dst_path) + 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 +end + utils.is_eq = function(val, cmp, if_eq, if_not_eq) if val == cmp then if if_eq then @@ -103,4 +68,25 @@ utils.is_neq = function(val, cmp, if_eq, if_not_eq) end end +utils.make_maplike_list = function(proj) + local mt = {} + mt.__index = function(t, k) + for _, value in ipairs(t) do + if proj(value) == k then + return value + end + end + end + mt.__newindex = function(t, k, v) + for key, value in ipairs(t) do + if proj(value) == k then + rawset(t, key, v) + return + end + end + rawset(t, #t + 1, v) + end + return mt +end + return utils diff --git a/lua/telescope/_extensions/cmake-explorer.lua b/lua/telescope/_extensions/cmake-explorer.lua new file mode 100644 index 0000000..e69de29 diff --git a/plugin/cmake-explorer.lua b/plugin/cmake-explorer.lua new file mode 100644 index 0000000..8413923 --- /dev/null +++ b/plugin/cmake-explorer.lua @@ -0,0 +1,33 @@ +local cmd = vim.api.nvim_create_user_command + +cmd("CMakeSelectBehaviour", function() + require("cmake-explorer").change_current_behaviour() +end, { desc = "Configure one of existings directories" }) + +cmd("CMakeSelectConfig", function() + require("cmake-explorer").change_current_config() +end, { desc = "Configure with parameters" }) + +cmd("CMakeConfigure", function() + require("cmake-explorer").configure() +end, { desc = "Configure with parameters" }) + +cmd("CMakeConfigureLast", function() + require("cmake-explorer").configure_last() +end, { desc = "Configure last if exists. Otherwise default" }) + +cmd("CMakeBuild", function() + require("cmake-explorer").build() +end, { desc = "Configure one of existings directories" }) + +cmd("CMakeBuildLast", function() + require("cmake-explorer").build_last() +end, { desc = "Configure one of existings directories" }) + +cmd("CMakeConfigureProject", function() + require("cmake-explorer").configure_project() +end, { desc = "Configure one of existings directories" }) + +cmd("CMakeInitProject", function() + require("cmake-explorer").init_project() +end, { desc = "Configure one of existings directories" }) -- cgit v1.2.3