From f9b36bf730fda5488be87b91fd03a6f7cbf64e73 Mon Sep 17 00:00:00 2001 From: Daniil Rozanov Date: Wed, 24 Apr 2024 03:34:52 +0300 Subject: refactor: full rewrite --- lua/cmake-explorer/cache.lua | 47 -- lua/cmake-explorer/capabilities.lua | 46 -- lua/cmake-explorer/config.lua | 46 -- lua/cmake-explorer/file_api.lua | 92 ---- lua/cmake-explorer/init.lua | 67 --- lua/cmake-explorer/notification.lua | 14 - lua/cmake-explorer/project.lua | 283 ------------ lua/cmake-explorer/project_uauaua.lua | 113 ----- lua/cmake-explorer/runner.lua | 84 ---- 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 | 92 ---- lua/cmake/actions.lua | 111 +++++ lua/cmake/capabilities.lua | 63 +++ lua/cmake/commands.lua | 31 ++ lua/cmake/config.lua | 66 +++ lua/cmake/fileapi.lua | 98 ++++ lua/cmake/init.lua | 19 + lua/cmake/lyaml.lua | 671 ++++++++++++++++++++++++++++ lua/cmake/project.lua | 197 ++++++++ lua/cmake/telescope/make_entry.lua | 57 +++ lua/cmake/telescope/pickers.lua | 102 +++++ lua/cmake/telescope/previewers.lua | 40 ++ lua/cmake/terminal.lua | 90 ++++ lua/cmake/utils.lua | 96 ++++ lua/cmake/variants.lua | 157 +++++++ plugin/cmake-explorer.lua | 33 -- plugin/cmake.lua | 1 + 30 files changed, 1799 insertions(+), 1163 deletions(-) delete mode 100644 lua/cmake-explorer/cache.lua delete mode 100644 lua/cmake-explorer/capabilities.lua delete mode 100644 lua/cmake-explorer/config.lua delete mode 100644 lua/cmake-explorer/file_api.lua delete mode 100644 lua/cmake-explorer/init.lua delete mode 100644 lua/cmake-explorer/notification.lua delete mode 100644 lua/cmake-explorer/project.lua delete mode 100644 lua/cmake-explorer/project_uauaua.lua delete mode 100644 lua/cmake-explorer/runner.lua delete mode 100644 lua/cmake-explorer/telescope/make_entry.lua delete mode 100644 lua/cmake-explorer/telescope/pickers.lua delete mode 100644 lua/cmake-explorer/telescope/previewers.lua delete mode 100644 lua/cmake-explorer/telescope/test.lua delete mode 100644 lua/cmake-explorer/utils.lua create mode 100644 lua/cmake/actions.lua create mode 100644 lua/cmake/capabilities.lua create mode 100644 lua/cmake/commands.lua create mode 100644 lua/cmake/config.lua create mode 100644 lua/cmake/fileapi.lua create mode 100644 lua/cmake/init.lua create mode 100644 lua/cmake/lyaml.lua create mode 100644 lua/cmake/project.lua create mode 100644 lua/cmake/telescope/make_entry.lua create mode 100644 lua/cmake/telescope/pickers.lua create mode 100644 lua/cmake/telescope/previewers.lua create mode 100644 lua/cmake/terminal.lua create mode 100644 lua/cmake/utils.lua create mode 100644 lua/cmake/variants.lua delete mode 100644 plugin/cmake-explorer.lua create mode 100644 plugin/cmake.lua diff --git a/lua/cmake-explorer/cache.lua b/lua/cmake-explorer/cache.lua deleted file mode 100644 index 046a9f9..0000000 --- a/lua/cmake-explorer/cache.lua +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 052f484..0000000 --- a/lua/cmake-explorer/capabilities.lua +++ /dev/null @@ -1,46 +0,0 @@ -local config = require("cmake-explorer.config") - -local multiconfig_generators = { - "Ninja Multi-Config", - "Xcode", - "Visual Studio 12 2013", - "Visual Studio 14 2015", - "Visual Studio 15 2017", - "Visual Studio 16 2019", - "Visual Studio 17 2022", - "Green Hills MULTI", -} - -local Capabilities = { - json = nil, -} - -function Capabilities.generators() - local ret = {} - if not Capabilities then - return ret - end - for k, v in pairs(Capabilities.json.generators) do - table.insert(ret, v.name) - end - return vim.fn.reverse(ret) -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 - -function Capabilities.has_fileapi() - return vim.tbl_get(Capabilities.json, "fileApi") ~= nil -end - -Capabilities.setup = function() - local output = vim.fn.system({ config.cmake_path, "-E", "capabilities" }) - Capabilities.json = vim.json.decode(output) -end - -return Capabilities diff --git a/lua/cmake-explorer/config.lua b/lua/cmake-explorer/config.lua deleted file mode 100644 index 611f526..0000000 --- a/lua/cmake-explorer/config.lua +++ /dev/null @@ -1,46 +0,0 @@ -local default_config = { - 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) - -M.setup = function(opts) - local newconf = vim.tbl_deep_extend("force", default_config, opts or {}) - - for k, v in pairs(newconf) do - M[k] = v - end -end - -return M diff --git a/lua/cmake-explorer/file_api.lua b/lua/cmake-explorer/file_api.lua deleted file mode 100644 index 37eda3a..0000000 --- a/lua/cmake-explorer/file_api.lua +++ /dev/null @@ -1,92 +0,0 @@ -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" } - -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, - 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 -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()) - 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() -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 -end - -function FileApi:exists() - return self:query_exists() and self:reply_exists() -end - -function FileApi.is_fileapi(other) - return getmetatable(other) == FileApi -end - -return FileApi diff --git a/lua/cmake-explorer/init.lua b/lua/cmake-explorer/init.lua deleted file mode 100644 index 622eebd..0000000 --- a/lua/cmake-explorer/init.lua +++ /dev/null @@ -1,67 +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 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 deleted file mode 100644 index 0029241..0000000 --- a/lua/cmake-explorer/notification.lua +++ /dev/null @@ -1,14 +0,0 @@ -local has_notify, notify = pcall(require, "notify") - -local Notification = {} - -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 - -return Notification diff --git a/lua/cmake-explorer/project.lua b/lua/cmake-explorer/project.lua deleted file mode 100644 index ca77728..0000000 --- a/lua/cmake-explorer/project.lua +++ /dev/null @@ -1,283 +0,0 @@ -local config = require("cmake-explorer.config") -local Path = require("plenary.path") -local utils = require("cmake-explorer.utils") -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: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 deleted file mode 100644 index b54abd6..0000000 --- a/lua/cmake-explorer/project_uauaua.lua +++ /dev/null @@ -1,113 +0,0 @@ -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 deleted file mode 100644 index f2765fe..0000000 --- a/lua/cmake-explorer/runner.lua +++ /dev/null @@ -1,84 +0,0 @@ -local Job = require("plenary.job") -local notif = require("cmake-explorer.notification") - -local M = {} - -local running_jobs = {} -local last_job = nil - -function M.start(command) - if not command then - print("runner start. command is nil") - return - end - local env = vim.tbl_extend("force", vim.loop.os_environ(), command.env and command.env or {}) - - 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 then - if command.after_success then - command.after_success() - end - else - notif.notify( - "Code " - .. code - .. " Signal " - .. signal - .. ": " - .. command.cmd - .. " " - .. table.concat(command.args, " "), - vim.log.levels.ERROR - ) - end - end), - }) - job:start() - table.insert(running_jobs, job) - last_job = job -end - -function M.cancel_job() - if not last_job then - return false - end - - -- Check if this job was run through debugger. - if last_job.session then - if not last_job.session() then - return false - end - last_job.terminate() - return true - end - - if last_job.is_shutdown then - return false - end - - last_job:shutdown(1, 9) - - if vim.fn.has("win32") == 1 or vim.fn.has("mac") == 1 then - -- Kill all children. - for _, pid in ipairs(vim.api.nvim_get_proc_children(last_job.pid)) do - vim.loop.kill(pid, 9) - end - else - vim.loop.kill(last_job.pid, 9) - end - return true -end - -return M diff --git a/lua/cmake-explorer/telescope/make_entry.lua b/lua/cmake-explorer/telescope/make_entry.lua deleted file mode 100644 index cc919bd..0000000 --- a/lua/cmake-explorer/telescope/make_entry.lua +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index b2c7f56..0000000 --- a/lua/cmake-explorer/telescope/pickers.lua +++ /dev/null @@ -1,103 +0,0 @@ -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 deleted file mode 100644 index 39fea4a..0000000 --- a/lua/cmake-explorer/telescope/previewers.lua +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index 7b8bb00..0000000 --- a/lua/cmake-explorer/telescope/test.lua +++ /dev/null @@ -1,46 +0,0 @@ -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 deleted file mode 100644 index 61b5c98..0000000 --- a/lua/cmake-explorer/utils.lua +++ /dev/null @@ -1,92 +0,0 @@ -local config = require("cmake-explorer.config") -local capabilities = require("cmake-explorer.capabilities") -local Path = require("plenary.path") - -local utils = {} - -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 / build_dir):absolute() - else - return Path:new(build_path, build_dir):normalize() - end -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 - return if_eq - else - return val - end - else - if if_not_eq then - return if_not_eq - else - return nil - end - end -end - -utils.is_neq = function(val, cmp, if_eq, if_not_eq) - if val ~= cmp then - if if_eq then - return if_eq - else - return val - end - else - if if_not_eq then - return if_not_eq - else - return nil - end - 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/cmake/actions.lua b/lua/cmake/actions.lua new file mode 100644 index 0000000..d330a0a --- /dev/null +++ b/lua/cmake/actions.lua @@ -0,0 +1,111 @@ +local pr = require("cmake.project") +local config = require("cmake.config") +local t = require("cmake.terminal") + +local M = {} + +local default_generate_exe_opts = { + notify = { + ok_message = "CMake build finished", + err_message = function(code) + return "CMake generate failed with code " .. tostring(code) + end, + }, +} + +local default_build_exe_opts = { + notify = { + ok_message = "CMake build finished", + err_message = function(code) + return "CMake build failed with code " .. tostring(code) + end, + }, +} + +M.generate = function(opts) + pr.create_fileapi_query({ idx = pr.current_generate_option_idx() }, function() + vim.schedule(function() + t.cmake_execute(pr.current_generate_option().generate_command, default_generate_exe_opts) + end) + end) +end + +M.generate_select = function(opts) + 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) + 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) + 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) + end +end + +M.build_select = function(opts) + local items = pr.current_generate_option(opts).build_options + vim.ui.select(items, { + prompt = "Select build option to generate:", + format_item = function(item) + return table.concat(item.name, config.variants_display.short.sep) + end, + }, function(choice, idx) + if not idx then + 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) + return +end + +M.run_tagret_select = function(opts) + opts = opts or {} + opts.type = "EXECUTABLE" + local items = pr.current_targets(opts) + vim.ui.select(items, { + prompt = "Select tagret to run:", + format_item = function(item) + return item.name + end, + }, function(choice, idx) + if not idx then + return + end + pr.set_current_executable_target(idx) + local command = { + cwd = pr.current_directory(), + cmd = choice.path, + } + t.target_execute(command) + end) +end + +return M diff --git a/lua/cmake/capabilities.lua b/lua/cmake/capabilities.lua new file mode 100644 index 0000000..6a70be2 --- /dev/null +++ b/lua/cmake/capabilities.lua @@ -0,0 +1,63 @@ +local config = require("cmake.config") + +local multiconfig_generators = { + "Ninja Multi-Config", + "Xcode", + "Visual Studio 12 2013", + "Visual Studio 14 2015", + "Visual Studio 15 2017", + "Visual Studio 16 2019", + "Visual Studio 17 2022", + "Green Hills MULTI", +} + +local Capabilities = { + json = nil, +} + +function Capabilities.generators() + local ret = {} + if not Capabilities then + return ret + end + for k, v in pairs(Capabilities.json.generators) do + table.insert(ret, v.name) + end + return vim.fn.reverse(ret) +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 + +function Capabilities.has_fileapi() + return vim.tbl_get(Capabilities.json, "fileApi") ~= nil +end + +-- TODO: make this async +Capabilities.setup = function(callback) + local lines = {} + vim.fn.jobstart({ config.cmake.cmake_path, "-E", "capabilities" }, { + on_stdout = function(_, data) + if data then + vim.list_extend(lines, data) + 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, + }) +end + +return Capabilities diff --git a/lua/cmake/commands.lua b/lua/cmake/commands.lua new file mode 100644 index 0000000..78e5bb8 --- /dev/null +++ b/lua/cmake/commands.lua @@ -0,0 +1,31 @@ +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" }) + + cmd("CMakeBuildSelect", function() + require("cmake.actions").build_select() + end, { desc = "Select build option and build" }) + + 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" }) +end + +return M diff --git a/lua/cmake/config.lua b/lua/cmake/config.lua new file mode 100644 index 0000000..9a0e5ba --- /dev/null +++ b/lua/cmake/config.lua @@ -0,0 +1,66 @@ +local default_config = { + cmake = { + cmake_path = "cmake", + environment = {}, + configure_environment = {}, + build_directory = "${workspaceFolder}/build-${buildType}", + build_environment = {}, + build_args = {}, + build_tool_args = {}, + generator = nil, + 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" }, + }, + }, + }, + parallel_jobs = 0, + save_before_build = true, + source_directory = "${workspaceFolder}", + }, + terminal = { + direction = "horizontal", + display_name = "CMake", + close_on_exit = "success", + hidden = false, + clear_env = false, + focus = false, + }, + runner_terminal = { + direction = "horizontal", + close_on_exit = false, + hidden = false, + clear_env = false, + focus = true, + }, + notification = { + after = "success", + }, + variants_display = { + short = { sep = " × " }, + long = { sep = " ❄ " }, + }, +} + +local M = vim.deepcopy(default_config) + +M.setup = function(opts) + local newconf = vim.tbl_deep_extend("force", default_config, opts or {}) + + for k, v in pairs(newconf) do + M[k] = v + end +end + +return M diff --git a/lua/cmake/fileapi.lua b/lua/cmake/fileapi.lua new file mode 100644 index 0000000..2f61677 --- /dev/null +++ b/lua/cmake/fileapi.lua @@ -0,0 +1,98 @@ +local capabilities = require("cmake.capabilities") +local Path = require("plenary.path") +local scan = require("plenary.scandir") +local utils = require("cmake.utils") +local uv = vim.loop + +local query_path_suffix = { ".cmake", "api", "v1", "query", "client-cmake", "query.json" } +local reply_dir_suffix = { ".cmake", "api", "v1", "reply" } + +local FileApi = {} + +function FileApi.create(path, callback) + local query = Path:new(path, unpack(query_path_suffix)):normalize() + utils.file_exists(query, function(exists) + 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) + else + vim.notify("Bad fileApi ", vim.log.levels.ERROR) + end + else + callback() + end + end) +end + +function FileApi.read_reply(path, callback) + local reply_dir = Path:new(path, unpack(reply_dir_suffix)):absolute() + utils.file_exists(reply_dir, function(exists) + if not exists then + return + end + local ret = { targets = {} } + scan.scan_dir_async(reply_dir, { + search_pattern = "index*", + on_exit = function(results) + if #results == 0 then + return + end + utils.read_file(results[1], function(index_data) + local index = vim.json.decode(index_data) + for _, object in ipairs(index.objects) do + 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) + for _, target in ipairs(codemodel.configurations[1].targets) do + utils.read_file( + Path:new(reply_dir, target.jsonFile):absolute(), + function(target_data) + local target_json = vim.json.decode(target_data) + local _target = { + id = target_json.id, + name = target_json.name, + type = target_json.type, + } + if target_json.artifacts then + --NOTE: add_library( OBJECT ...) could contain more than ohe object in artifacts + -- so maybe in future it will be useful to handle not only first one. Current behaviour + -- aims to get path for only EXECUTABLE targets + _target.path = target_json.artifacts[1].path + end + callback(_target) + end + ) + end + end) + end + end + end) + end, + }) + return ret + end) +end + +function FileApi.query_exists(path, callback) + utils.file_exists(Path:new(path, unpack(query_path_suffix)):normalize(), function(query_exists) + callback(query_exists) + end) +end + +function FileApi.exists(path, callback) + FileApi.query_exists(path, function(query_exists) + if not query_exists then + callback(false) + else + utils.file_exists(Path:new(path, unpack(reply_dir_suffix)):normalize(), function(reply_exists) + callback(reply_exists) + end) + end + end) +end + +return FileApi diff --git a/lua/cmake/init.lua b/lua/cmake/init.lua new file mode 100644 index 0000000..d53e074 --- /dev/null +++ b/lua/cmake/init.lua @@ -0,0 +1,19 @@ +local config = require("cmake.config") +local commands = require("cmake.commands") + +local M = {} + +function M.setup(opts) + opts = opts or {} + config.setup(opts) + if vim.fn.executable(config.cmake.cmake_path) then + commands.register_commands() + require("cmake.capabilities").setup(function() + require("cmake.project").setup(opts) + end) + else + vim.notify("CMake: " .. config.cmake.cmake_path .. " is not executable", vim.log.levels.WARN) + end +end + +return M diff --git a/lua/cmake/lyaml.lua b/lua/cmake/lyaml.lua new file mode 100644 index 0000000..9555a75 --- /dev/null +++ b/lua/cmake/lyaml.lua @@ -0,0 +1,671 @@ +--[[ +(The MIT License) + +Copyright (c) 2017 Dominic Letz dominicletz@exosite.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--]] + +local table_print_value +table_print_value = function(value, indent, done) + indent = indent or 0 + done = done or {} + if type(value) == "table" and not done[value] then + done[value] = true + + local list = {} + for key in pairs(value) do + list[#list + 1] = key + end + table.sort(list, function(a, b) + return tostring(a) < tostring(b) + end) + local last = list[#list] + + local rep = "{\n" + local comma + for _, key in ipairs(list) do + if key == last then + comma = "" + else + comma = "," + end + local keyRep + if type(key) == "number" then + keyRep = key + else + keyRep = string.format("%q", tostring(key)) + end + rep = rep + .. string.format( + "%s[%s] = %s%s\n", + string.rep(" ", indent + 2), + keyRep, + table_print_value(value[key], indent + 2, done), + comma + ) + end + + rep = rep .. string.rep(" ", indent) -- indent it + rep = rep .. "}" + + done[value] = false + return rep + elseif type(value) == "string" then + return string.format("%q", value) + else + return tostring(value) + end +end + +local table_print = function(tt) + print("return " .. table_print_value(tt)) +end + +local table_clone = function(t) + local clone = {} + for k, v in pairs(t) do + clone[k] = v + end + return clone +end + +local string_trim = function(s, what) + what = what or " " + return s:gsub("^[" .. what .. "]*(.-)[" .. what .. "]*$", "%1") +end + +local push = function(stack, item) + stack[#stack + 1] = item +end + +local pop = function(stack) + local item = stack[#stack] + stack[#stack] = nil + return item +end + +local context = function(str) + if type(str) ~= "string" then + return "" + end + + str = str:sub(0, 25):gsub("\n", "\\n"):gsub('"', '\\"') + return ', near "' .. str .. '"' +end + +local Parser = {} +function Parser.new(self, tokens) + self.tokens = tokens + self.parse_stack = {} + self.refs = {} + self.current = 0 + return self +end + +local exports = { version = "1.2" } + +local word = function(w) + return "^(" .. w .. ")([%s$%c])" +end + +local tokens = { + { "comment", "^#[^\n]*" }, + { "indent", "^\n( *)" }, + { "space", "^ +" }, + { + "true", + word("enabled"), + const = true, + value = true, + }, + { + "true", + word("true"), + const = true, + value = true, + }, + { + "true", + word("yes"), + const = true, + value = true, + }, + { + "true", + word("on"), + const = true, + value = true, + }, + { + "false", + word("disabled"), + const = true, + value = false, + }, + { + "false", + word("false"), + const = true, + value = false, + }, + { + "false", + word("no"), + const = true, + value = false, + }, + { + "false", + word("off"), + const = true, + value = false, + }, + { + "null", + word("null"), + const = true, + value = nil, + }, + { + "null", + word("Null"), + const = true, + value = nil, + }, + { + "null", + word("NULL"), + const = true, + value = nil, + }, + { + "null", + word("~"), + const = true, + value = nil, + }, + { "id", '^"([^"]-)" *(:[%s%c])' }, + { "id", "^'([^']-)' *(:[%s%c])" }, + { "string", '^"([^"]-)"', force_text = true }, + { "string", "^'([^']-)'", force_text = true }, + { "timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)" }, + { "timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)" }, + { "timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)" }, + { "timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)" }, + { "timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)" }, + { "timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)" }, + { "doc", "^%-%-%-[^%c]*" }, + { ",", "^," }, + { "string", "^%b{} *[^,%c]+", noinline = true }, + { "{", "^{" }, + { "}", "^}" }, + { "string", "^%b[] *[^,%c]+", noinline = true }, + { "[", "^%[" }, + { "]", "^%]" }, + { "-", "^%-", noinline = true }, + { ":", "^:" }, + { "pipe", "^(|)(%d*[+%-]?)", sep = "\n" }, + { "pipe", "^(>)(%d*[+%-]?)", sep = " " }, + { "id", "^([%w][%w %-_]*)(:[%s%c])" }, + { "string", "^[^%c]+", noinline = true }, + { "string", "^[^,%]}%c ]+" }, +} +exports.tokenize = function(str) + local token + local row = 0 + local ignore + local indents = 0 + local lastIndents + local stack = {} + local indentAmount = 0 + local inline = false + str = str:gsub("\r\n", "\010") + + while #str > 0 do + for i in ipairs(tokens) do + local captures = {} + if not inline or tokens[i].noinline == nil then + captures = { str:match(tokens[i][2]) } + end + + if #captures > 0 then + captures.input = str:sub(0, 25) + token = table_clone(tokens[i]) + token[2] = captures + local str2 = str:gsub(tokens[i][2], "", 1) + token.raw = str:sub(1, #str - #str2) + str = str2 + + if token[1] == "{" or token[1] == "[" then + inline = true + elseif token.const then + -- Since word pattern contains last char we're re-adding it + str = token[2][2] .. str + token.raw = token.raw:sub(1, #token.raw - #token[2][2]) + elseif token[1] == "id" then + -- Since id pattern contains last semi-colon we're re-adding it + str = token[2][2] .. str + token.raw = token.raw:sub(1, #token.raw - #token[2][2]) + -- Trim + token[2][1] = string_trim(token[2][1]) + elseif token[1] == "string" then + -- Finding numbers + local snip = token[2][1] + if not token.force_text then + if snip:match("^(-?%d+%.%d+)$") or snip:match("^(-?%d+)$") then + token[1] = "number" + end + end + elseif token[1] == "comment" then + ignore = true + elseif token[1] == "indent" then + row = row + 1 + inline = false + lastIndents = indents + if indentAmount == 0 then + indentAmount = #token[2][1] + end + + if indentAmount ~= 0 then + indents = (#token[2][1] / indentAmount) + else + indents = 0 + end + + if indents == lastIndents then + ignore = true + elseif indents > lastIndents + 2 then + error( + "SyntaxError: invalid indentation, got " + .. tostring(indents) + .. " instead of " + .. tostring(lastIndents) + .. context(token[2].input) + ) + elseif indents > lastIndents + 1 then + push(stack, token) + elseif indents < lastIndents then + local input = token[2].input + token = { "dedent", { "", input = "" } } + token.input = input + while lastIndents > indents + 1 do + lastIndents = lastIndents - 1 + push(stack, token) + end + end + end -- if token[1] == XXX + token.row = row + break + end -- if #captures > 0 + end + + if not ignore then + if token then + push(stack, token) + token = nil + else + error("SyntaxError " .. context(str)) + end + end + + ignore = false + end + + return stack +end + +Parser.peek = function(self, offset) + offset = offset or 1 + return self.tokens[offset + self.current] +end + +Parser.advance = function(self) + self.current = self.current + 1 + return self.tokens[self.current] +end + +Parser.advanceValue = function(self) + return self:advance()[2][1] +end + +Parser.accept = function(self, type) + if self:peekType(type) then + return self:advance() + end +end + +Parser.expect = function(self, type, msg) + return self:accept(type) or error(msg .. context(self:peek()[1].input)) +end + +Parser.expectDedent = function(self, msg) + return self:accept("dedent") or (self:peek() == nil) or error(msg .. context(self:peek()[2].input)) +end + +Parser.peekType = function(self, val, offset) + return self:peek(offset) and self:peek(offset)[1] == val +end + +Parser.ignore = function(self, items) + local advanced + repeat + advanced = false + for _, v in pairs(items) do + if self:peekType(v) then + self:advance() + advanced = true + end + end + until advanced == false +end + +Parser.ignoreSpace = function(self) + self:ignore({ "space" }) +end + +Parser.ignoreWhitespace = function(self) + self:ignore({ "space", "indent", "dedent" }) +end + +Parser.parse = function(self) + local ref = nil + if self:peekType("string") and not self:peek().force_text then + local char = self:peek()[2][1]:sub(1, 1) + if char == "&" then + ref = self:peek()[2][1]:sub(2) + self:advanceValue() + self:ignoreSpace() + elseif char == "*" then + ref = self:peek()[2][1]:sub(2) + return self.refs[ref] + end + end + + local result + local c = { + indent = self:accept("indent") and 1 or 0, + token = self:peek(), + } + push(self.parse_stack, c) + + if c.token[1] == "doc" then + result = self:parseDoc() + elseif c.token[1] == "-" then + result = self:parseList() + elseif c.token[1] == "{" then + result = self:parseInlineHash() + elseif c.token[1] == "[" then + result = self:parseInlineList() + elseif c.token[1] == "id" then + result = self:parseHash() + elseif c.token[1] == "string" then + result = self:parseString("\n") + elseif c.token[1] == "timestamp" then + result = self:parseTimestamp() + elseif c.token[1] == "number" then + result = tonumber(self:advanceValue()) + elseif c.token[1] == "pipe" then + result = self:parsePipe() + elseif c.token.const == true then + self:advanceValue() + result = c.token.value + else + error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input)) + end + + pop(self.parse_stack) + while c.indent > 0 do + c.indent = c.indent - 1 + local term = "term " .. c.token[1] .. ": '" .. c.token[2][1] .. "'" + self:expectDedent("last " .. term .. " is not properly dedented") + end + + if ref then + self.refs[ref] = result + end + return result +end + +Parser.parseDoc = function(self) + self:accept("doc") + return self:parse() +end + +Parser.inline = function(self) + local current = self:peek(0) + if not current then + return {}, 0 + end + + local inline = {} + local i = 0 + + while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do + inline[self:peek(i)[1]] = true + i = i - 1 + end + return inline, -i +end + +Parser.isInline = function(self) + local _, i = self:inline() + return i > 0 +end + +Parser.parent = function(self, level) + level = level or 1 + return self.parse_stack[#self.parse_stack - level] +end + +Parser.parentType = function(self, type, level) + return self:parent(level) and self:parent(level).token[1] == type +end + +Parser.parseString = function(self) + if self:isInline() then + local result = self:advanceValue() + + --[[ + - a: this looks + flowing: but is + no: string + --]] + local types = self:inline() + if types["id"] and types["-"] then + if not self:peekType("indent") or not self:peekType("indent", 2) then + return result + end + end + + --[[ + a: 1 + b: this is + a flowing string + example + c: 3 + --]] + if self:peekType("indent") then + self:expect("indent", "text block needs to start with indent") + local addtl = self:accept("indent") + + result = result .. "\n" .. self:parseTextBlock("\n") + + self:expectDedent("text block ending dedent missing") + if addtl then + self:expectDedent("text block ending dedent missing") + end + end + return result + else + --[[ + a: 1 + b: + this is also + a flowing string + example + c: 3 + --]] + return self:parseTextBlock("\n") + end +end + +Parser.parsePipe = function(self) + local pipe = self:expect("pipe") + self:expect("indent", "text block needs to start with indent") + local result = self:parseTextBlock(pipe.sep) + self:expectDedent("text block ending dedent missing") + return result +end + +Parser.parseTextBlock = function(self, sep) + local token = self:advance() + local result = string_trim(token.raw, "\n") + local indents = 0 + while self:peek() ~= nil and (indents > 0 or not self:peekType("dedent")) do + local newtoken = self:advance() + while token.row < newtoken.row do + result = result .. sep + token.row = token.row + 1 + end + if newtoken[1] == "indent" then + indents = indents + 1 + elseif newtoken[1] == "dedent" then + indents = indents - 1 + else + result = result .. string_trim(newtoken.raw, "\n") + end + end + return result +end + +Parser.parseHash = function(self, hash) + hash = hash or {} + local indents = 0 + + if self:isInline() then + local id = self:advanceValue() + self:expect(":", "expected semi-colon after id") + self:ignoreSpace() + if self:accept("indent") then + indents = indents + 1 + hash[id] = self:parse() + else + hash[id] = self:parse() + if self:accept("indent") then + indents = indents + 1 + end + end + self:ignoreSpace() + end + + while self:peekType("id") do + local id = self:advanceValue() + self:expect(":", "expected semi-colon after id") + self:ignoreSpace() + hash[id] = self:parse() + self:ignoreSpace() + end + + while indents > 0 do + self:expectDedent("expected dedent") + indents = indents - 1 + end + + return hash +end + +Parser.parseInlineHash = function(self) + local id + local hash = {} + local i = 0 + + self:accept("{") + while not self:accept("}") do + self:ignoreSpace() + if i > 0 then + self:expect(",", "expected comma") + end + + self:ignoreWhitespace() + if self:peekType("id") then + id = self:advanceValue() + if id then + self:expect(":", "expected semi-colon after id") + self:ignoreSpace() + hash[id] = self:parse() + self:ignoreWhitespace() + end + end + + i = i + 1 + end + return hash +end + +Parser.parseList = function(self) + local list = {} + while self:accept("-") do + self:ignoreSpace() + list[#list + 1] = self:parse() + + self:ignoreSpace() + end + return list +end + +Parser.parseInlineList = function(self) + local list = {} + local i = 0 + self:accept("[") + while not self:accept("]") do + self:ignoreSpace() + if i > 0 then + self:expect(",", "expected comma") + end + + self:ignoreSpace() + list[#list + 1] = self:parse() + self:ignoreSpace() + i = i + 1 + end + + return list +end + +Parser.parseTimestamp = function(self) + local capture = self:advance()[2] + + return os.time({ + year = capture[1], + month = capture[2], + day = capture[3], + hour = capture[4] or 0, + min = capture[5] or 0, + sec = capture[6] or 0, + isdst = false, + }) - os.time({ year = 1970, month = 1, day = 1, hour = 8 }) +end + +exports.eval = function(str) + return Parser:new(exports.tokenize(str)):parse() +end + +exports.dump = table_print + +return exports diff --git a/lua/cmake/project.lua b/lua/cmake/project.lua new file mode 100644 index 0000000..f98b82a --- /dev/null +++ b/lua/cmake/project.lua @@ -0,0 +1,197 @@ +local config = require("cmake.config") +local VariantConfig = require("cmake.variants") +local FileApi = require("cmake.fileapi") +local lyaml = require("cmake.lyaml") +local utils = require("cmake.utils") +local scan = require("plenary.scandir") + +local Project = {} + +local configs = {} +local current_config = nil +local fileapis = {} + +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", vim.loop.cwd()) + fileapis[v.directory] = { targets = {} } + FileApi.read_reply(v.directory, function(target) + table.insert(fileapis[v.directory].targets, target) + end) + end + end + for _, v in ipairs(configs) do + v.generate_command.after_success = function() + read_reply(v, false) + end + for _, bv in ipairs(v.build_options) do + bv.command.after_success = function() + read_reply(v, true) + end + end + end +end + +local init_fileapis = function() + fileapis = {} + for _, v in ipairs(configs) do + if not fileapis[v.directory] then + fileapis[v.directory] = { targets = {} } + FileApi.exists(v.directory, function(fileapi_exists) + if fileapi_exists then + FileApi.read_reply(v.directory, function(target) + table.insert(fileapis[v.directory].targets, target) + end) + end + end) + end + end +end + +-- TODO: validate yaml and fallback to config's variants if not valid +function Project.from_variants(variants) + for var, is_default in VariantConfig.cartesian_product(variants) do + var.current_build = 1 + table.insert(configs, var) + current_config = not current_config and is_default and #configs or current_config + end + if not current_config and #configs ~= 0 then + current_config = 1 + end + append_after_success_actions() + init_fileapis() +end + +function Project.generate_options(opts) + opts = opts or {} + return configs +end + +--TODO: remove opts where it is useless +function Project.current_generate_option(opts) + opts = opts or {} + assert(current_config, "No current project config") + return configs[current_config] +end + +function Project.current_generate_option_idx() + return current_config +end + +function Project.set_current_generate_option(idx) + current_config = idx +end + +--TODO: check on out of range +function Project.current_build_option_idx() + return configs[current_config].current_build +end + +function Project.current_build_option() + if not Project.current_build_option_idx() then + return nil + end + return configs[current_config].build_options[Project.current_build_option_idx()] +end + +function Project.set_current_build_option(idx) + configs[current_config].current_build = idx +end + +function Project.current_directory() + return current_config and configs[current_config].directory or nil +end + +local current_fileapi = function() + if not Project.current_directory() or not fileapis[Project.current_directory()] then + return nil + end + return fileapis[Project.current_directory()] +end + +function Project.set_current_executable_target(idx) + current_fileapi().current_executable_target = idx +end + +function Project.current_executable_target_idx() + local _curr_fileapi = current_fileapi() + if not _curr_fileapi then + return nil + end + return _curr_fileapi.current_executable_target +end + +function Project.current_executable_target() + local _curr_fileapi = current_fileapi() + if not _curr_fileapi then + return nil + end + local _curr_exe_target_idx = Project.current_executable_target_idx() + if not _curr_exe_target_idx then + return nil + end + return _curr_fileapi.targets[_curr_exe_target_idx] +end + +function Project.current_targets(opts) + opts = opts or {} + local _curr_fileapi = current_fileapi() + if not _curr_fileapi then + return nil + end + if opts.type then + return vim.tbl_filter(function(t) + return t.type == opts.type + end, _curr_fileapi.targets) + end + return _curr_fileapi.targets +end + +function Project.create_fileapi_query(opts, callback) + opts = opts or {} + local path + + if type(opts.idx) == "number" then + path = configs[opts.idx].directory + elseif type(opts.path) == "string" then + path = opts.path + --TODO: compare getmetatable(opts.config) with VariantConfig (and PresetsConfig in future) + elseif type(opts.config) == "table" then + path = opts.config.directory + else + path = configs[current_config].directory + end + FileApi.query_exists(path, function(query_exists) + if not query_exists then + FileApi.create(path, function() + callback() + end) + else + callback() + end + end) +end + +function Project.setup(opts) + opts = opts or {} + scan.scan_dir_async(".", { + depth = 0, + hidden = true, + silent = true, + search_pattern = ".variants.yaml", + on_exit = function(variants_results) + if #variants_results ~= 0 then + utils.read_file(variants_results[1], function(variants_data) + local yaml = lyaml.eval(variants_data) + Project.from_variants(yaml) + end) + else + Project.from_variants(config.cmake.variants) + end + end, + }) +end + +return Project diff --git a/lua/cmake/telescope/make_entry.lua b/lua/cmake/telescope/make_entry.lua new file mode 100644 index 0000000..d0b04bf --- /dev/null +++ b/lua/cmake/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.config") + +local M = {} + +M.gen_from_configure = function(opts) + local project = require("cmake").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").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/telescope/pickers.lua b/lua/cmake/telescope/pickers.lua new file mode 100644 index 0000000..c543df8 --- /dev/null +++ b/lua/cmake/telescope/pickers.lua @@ -0,0 +1,102 @@ +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.telescope.make_entry") +local previewers = require("cmake.telescope.previewers") + +local M = {} + +M.build_dirs = function(opts) + local cmake = require("cmake") + 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") + local runner = require("cmake.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.generate_command) + end) + return true + end, + }) + :find() +end + +M.build = function(opts) + local cmake = require("cmake") + local runner = require("cmake.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/telescope/previewers.lua b/lua/cmake/telescope/previewers.lua new file mode 100644 index 0000000..fee3d96 --- /dev/null +++ b/lua/cmake/telescope/previewers.lua @@ -0,0 +1,40 @@ +local previewers = require("telescope.previewers") +local config = require("cmake.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/terminal.lua b/lua/cmake/terminal.lua new file mode 100644 index 0000000..9581297 --- /dev/null +++ b/lua/cmake/terminal.lua @@ -0,0 +1,90 @@ +local Terminal = require("toggleterm.terminal").Terminal +local ui = require("toggleterm.ui") +local config = require("cmake.config") + +local M = {} + +local cmake +local runnable + +--TODO: cmake must be an id, not terminal + +M.cmake_execute = function(command, opts) + opts = opts or {} + if cmake then + cmake:shutdown() + cmake = 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) + if code == 0 then + command.after_success() + if config.terminal.close_on_exit == "success" then + t:close() + end + if config.notification.after == "success" or config.notification.after == true then + vim.notify( + vim.tbl_get(opts, "notify", "ok_message") or "CMake successfully completed", + vim.log.levels.INFO + ) + end + elseif config.notification.after == "failure" or config.notification.after == true then + vim.notify(vim.inspect("failure ")) + 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 + 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 +end + +M.cmake_toggle = function() + cmake:toggle() +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 + local cd = "cd " .. command.cwd + local cmd = "./" .. command.cmd + vim.notify(cd) + vim.notify(cmd) + + if not runnable:is_open() then + runnable:open() + end + runnable:send(cd) + runnable:send(cmd) +end + +return M diff --git a/lua/cmake/utils.lua b/lua/cmake/utils.lua new file mode 100644 index 0000000..1c68fae --- /dev/null +++ b/lua/cmake/utils.lua @@ -0,0 +1,96 @@ +local config = require("cmake.config") +local capabilities = require("cmake.capabilities") +local scan = require("plenary.scandir") +local Path = require("plenary.path") +local uv = vim.loop + +local utils = {} + +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.touch_file(path, txt, flag, callback) + uv.fs_open(path, flag, 438, function(err, fd) + assert(not err, err) + assert(fd) + uv.fs_close(fd, function(c_err) + assert(not c_err, c_err) + if type(callback) == "function" then + callback() + end + end) + end) +end + +function utils.file_exists(path, callback) + uv.fs_stat(path, function(err, _) + local exists + if err then + exists = false + else + exists = true + end + if type(callback) == "function" then + callback(exists) + end + end) +end + +function utils.read_file(path, callback) + uv.fs_open(path, "r", 438, function(err, fd) + assert(not err, err) + assert(fd, fd) + uv.fs_fstat(fd, function(s_err, stat) + assert(not s_err, s_err) + assert(stat, stat) + uv.fs_read(fd, stat.size, 0, function(r_err, data) + assert(not r_err, r_err) + uv.fs_close(fd, function(c_err) + assert(not c_err, c_err) + callback(data) + end) + end) + end) + end) +end + +function utils.write_file(path, txt, callback) + uv.fs_open(path, "w", 438, function(err, fd) + assert(not err, err) + assert(fd) + uv.fs_write(fd, txt, nil, function(w_err, _) + assert(not w_err, w_err) + uv.fs_close(fd, function(c_err) + assert(not c_err, c_err) + if type(callback) == "function" then + callback() + end + end) + end) + 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 +end + +return utils diff --git a/lua/cmake/variants.lua b/lua/cmake/variants.lua new file mode 100644 index 0000000..50254c8 --- /dev/null +++ b/lua/cmake/variants.lua @@ -0,0 +1,157 @@ +local config = require("cmake.config") +local utils = require("cmake.utils") + +local VariantConfig = {} + +VariantConfig.__index = VariantConfig + +local global_variant_subs = { + ["${workspaceFolder}"] = vim.loop.cwd(), + ["${userHome}"] = vim.loop.os_homedir(), +} + +local _configure_args = function(obj, build_directory) + local args = {} + if obj.generator then + table.insert(args, "-G " .. '"' .. obj.generator .. '"') + end + table.insert(args, "-B" .. build_directory) + if obj.buildType then + table.insert(args, "-DCMAKE_BUILD_TYPE=" .. obj.buildType) + end + if obj.linkage and string.lower(obj.linkage) == "static" then + table.insert(args, "-DCMAKE_BUILD_SHARED_LIBS=OFF") + elseif obj.linkage and string.lower(obj.linkage) == "shared" then + table.insert(args, "-DCMAKE_BUILD_SHARED_LIBS=ON") + end + for k, v in pairs(obj.settings or {}) do + table.insert(args, "-D" .. k .. "=" .. v) + end + table.insert(args, "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON") + return args +end + +local _configure_command = function(obj, configure_args) + local ret = {} + ret.cmd = config.cmake.cmake_path + ret.args = table.concat(configure_args, " ") + ret.env = vim.tbl_deep_extend("keep", obj.env, config.cmake.configure_environment, config.cmake.environment) + return ret +end + +local _build_args = function(obj, build_directory) + local args = { "--build" } + table.insert(args, build_directory) + if #obj.buildArgs ~= 0 then + for _, v in ipairs(obj.buildArgs) do + table.insert(args, v) + end + elseif #config.cmake.build_args ~= 0 then + for _, v in ipairs(config.cmake.build_args) do + table.insert(args, v) + end + end + if #obj.buildToolArgs ~= 0 or #config.cmake.build_tool_args ~= 0 then + table.insert(args, "--") + if #obj.buildToolArgs ~= 0 then + for _, v in ipairs(obj.buildToolArgs) do + table.insert(args, v) + end + elseif #config.cmake.build_tool_args ~= 0 then + for _, v in ipairs(config.cmake.build_tool_args) do + table.insert(args, v) + end + end + end + return args +end + +local _build_command = function(obj, build_args) + local ret = {} + ret.cmd = config.cmake.cmake_path + ret.args = table.concat(build_args, " ") + ret.env = vim.tbl_deep_extend("keep", obj.env, config.cmake.configure_environment, config.cmake.environment) + return ret +end + +function VariantConfig:new(source) + local obj = {} + local subs = vim.tbl_deep_extend("keep", global_variant_subs, { ["${buildType}"] = source.buildType }) + + obj.name = source.short + obj.long_name = source.long + obj.directory = utils.substitude(config.cmake.build_directory, subs) + local configure_args = _configure_args(source, obj.directory) + obj.generate_command = _configure_command(source, configure_args) + local build_args = _build_args(source, obj.directory) + obj.build_options = { + { + name = source.short, + long_name = source.long, + command = _build_command(source, build_args), + }, + } + + setmetatable(obj, VariantConfig) + return obj +end + +function VariantConfig.cartesian_product(sets) + -- vim.notify("cartesian_product", vim.log.levels.INFO) + local function collapse_result(res) + -- vim.notify("collapse_result", vim.log.levels.INFO) + 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 + -- vim.notify(vim.inspect(ret), vim.log.levels.INFO) + return VariantConfig:new(ret), is_default + 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 + +return VariantConfig diff --git a/plugin/cmake-explorer.lua b/plugin/cmake-explorer.lua deleted file mode 100644 index 8413923..0000000 --- a/plugin/cmake-explorer.lua +++ /dev/null @@ -1,33 +0,0 @@ -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" }) diff --git a/plugin/cmake.lua b/plugin/cmake.lua new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/plugin/cmake.lua @@ -0,0 +1 @@ + -- cgit v1.2.3