aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lua/cmake-explorer/cache.lua47
-rw-r--r--lua/cmake-explorer/config.lua46
-rw-r--r--lua/cmake-explorer/file_api.lua92
-rw-r--r--lua/cmake-explorer/init.lua67
-rw-r--r--lua/cmake-explorer/notification.lua14
-rw-r--r--lua/cmake-explorer/project.lua283
-rw-r--r--lua/cmake-explorer/project_uauaua.lua113
-rw-r--r--lua/cmake-explorer/runner.lua84
-rw-r--r--lua/cmake-explorer/telescope/pickers.lua103
-rw-r--r--lua/cmake-explorer/telescope/test.lua46
-rw-r--r--lua/cmake-explorer/utils.lua92
-rw-r--r--lua/cmake/actions.lua111
-rw-r--r--lua/cmake/capabilities.lua (renamed from lua/cmake-explorer/capabilities.lua)25
-rw-r--r--lua/cmake/commands.lua31
-rw-r--r--lua/cmake/config.lua66
-rw-r--r--lua/cmake/fileapi.lua98
-rw-r--r--lua/cmake/init.lua19
-rw-r--r--lua/cmake/lyaml.lua671
-rw-r--r--lua/cmake/project.lua197
-rw-r--r--lua/cmake/telescope/make_entry.lua (renamed from lua/cmake-explorer/telescope/make_entry.lua)6
-rw-r--r--lua/cmake/telescope/pickers.lua102
-rw-r--r--lua/cmake/telescope/previewers.lua (renamed from lua/cmake-explorer/telescope/previewers.lua)2
-rw-r--r--lua/cmake/terminal.lua90
-rw-r--r--lua/cmake/utils.lua96
-rw-r--r--lua/cmake/variants.lua157
-rw-r--r--plugin/cmake-explorer.lua33
-rw-r--r--plugin/cmake.lua1
27 files changed, 1664 insertions, 1028 deletions
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/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/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/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" }, "<C-r>", function(_prompt_bufnr)
- print("You typed <C-r>")
- 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-explorer/capabilities.lua b/lua/cmake/capabilities.lua
index 052f484..6a70be2 100644
--- a/lua/cmake-explorer/capabilities.lua
+++ b/lua/cmake/capabilities.lua
@@ -1,4 +1,4 @@
-local config = require("cmake-explorer.config")
+local config = require("cmake.config")
local multiconfig_generators = {
"Ninja Multi-Config",
@@ -38,9 +38,26 @@ 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)
+-- 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(<name> 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-explorer/telescope/make_entry.lua b/lua/cmake/telescope/make_entry.lua
index cc919bd..d0b04bf 100644
--- a/lua/cmake-explorer/telescope/make_entry.lua
+++ b/lua/cmake/telescope/make_entry.lua
@@ -1,11 +1,11 @@
local make_entry = require("telescope.make_entry")
local entry_display = require("telescope.pickers.entry_display")
-local config = require("cmake-explorer.config")
+local config = require("cmake.config")
local M = {}
M.gen_from_configure = function(opts)
- local project = require("cmake-explorer").project
+ local project = require("cmake").project
local displayer = entry_display.create({
separator = " ",
items = {
@@ -30,7 +30,7 @@ M.gen_from_configure = function(opts)
end
M.gen_from_build = function(opts)
- local project = require("cmake-explorer").project
+ local project = require("cmake").project
local displayer = entry_display.create({
separator = " ",
items = {
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-explorer/telescope/previewers.lua b/lua/cmake/telescope/previewers.lua
index 39fea4a..fee3d96 100644
--- a/lua/cmake-explorer/telescope/previewers.lua
+++ b/lua/cmake/telescope/previewers.lua
@@ -1,5 +1,5 @@
local previewers = require("telescope.previewers")
-local config = require("cmake-explorer.config")
+local config = require("cmake.config")
local 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 @@
+