From 8d05fcaaeb1442c709f148ff934d92900743f051 Mon Sep 17 00:00:00 2001 From: Daniil Rozanov Date: Fri, 26 Apr 2024 00:50:04 +0300 Subject: feat: command to edit variants If there is no cmake-variants.yaml file in current directory, command creates it with default variants --- lua/cmake/actions.lua | 19 + lua/cmake/commands.lua | 6 +- lua/cmake/config.lua | 16 +- lua/cmake/constants.lua | 3 + lua/cmake/fileapi.lua | 2 + lua/cmake/lazy.lua | 21 + lua/cmake/lyaml.lua | 1038 +++++++++++++++++++++++------------------------ lua/cmake/project.lua | 44 +- lua/cmake/terminal.lua | 2 - lua/cmake/test.lua | 11 + lua/cmake/variants.lua | 3 - 11 files changed, 601 insertions(+), 564 deletions(-) create mode 100644 lua/cmake/constants.lua create mode 100644 lua/cmake/lazy.lua create mode 100644 lua/cmake/test.lua diff --git a/lua/cmake/actions.lua b/lua/cmake/actions.lua index ff64f95..3c09d93 100644 --- a/lua/cmake/actions.lua +++ b/lua/cmake/actions.lua @@ -1,6 +1,8 @@ local pr = require("cmake.project") local config = require("cmake.config") local t = require("cmake.terminal") +local utils = require("cmake.utils") +local constants = require("cmake.constants") local Path = require("plenary.path") local M = {} @@ -121,4 +123,21 @@ M.toggle = function() t.cmake_toggle() end +M.edit_variants = function() + utils.file_exists(constants.variants_yaml_filename, function(variants_exists) + if variants_exists then + vim.schedule(function() + vim.cmd(string.format("e %s", constants.variants_yaml_filename)) + end) + else + local default_yaml = require("cmake.lyaml").dump(config.cmake.variants) + utils.write_file(constants.variants_yaml_filename, default_yaml, function() + vim.schedule(function() + vim.cmd(string.format("e %s", constants.variants_yaml_filename)) + end) + end) + end + end) +end + return M diff --git a/lua/cmake/commands.lua b/lua/cmake/commands.lua index f754b1c..c2f6e75 100644 --- a/lua/cmake/commands.lua +++ b/lua/cmake/commands.lua @@ -29,7 +29,11 @@ M.register_commands = function() cmd("CMakeToggle", function() require("cmake.actions").toggle() - end, { desc = "Toggle CMake terminal" }) + end, { desc = "Toggle terminal with cmake command" }) + + cmd("CMakeEditVariants", function() + require("cmake.actions").edit_variants() + end, { desc = "Edit variants" }) end return M diff --git a/lua/cmake/config.lua b/lua/cmake/config.lua index 0e3a641..5a4cb75 100644 --- a/lua/cmake/config.lua +++ b/lua/cmake/config.lua @@ -9,12 +9,14 @@ local default_config = { build_tool_args = {}, generator = nil, variants = { - { + buildType = { default = "debug", description = "Build type", choices = { - debug = { short = "Debug", long = "Long debug", buildType = "Debug" }, - release = { short = "Release", long = "Long release", buildType = "Release" }, + debug = { short = "Debug", buildType = "Debug" }, + release = { short = "Release", buildType = "Release" }, + relWithDebInfo = { short = "Release with debug info", buildType = "RelWithDebInfo" }, + minSizeRel = { short = "Minimal size releaze", buildType = "MinSizeRel" }, }, }, }, @@ -23,7 +25,7 @@ local default_config = { source_directory = "${workspaceFolder}", }, terminal = { - direction = "horizontal", + direction = "vertical", display_name = "CMake", close_on_exit = "success", hidden = false, @@ -31,7 +33,7 @@ local default_config = { focus = false, }, runner_terminal = { - direction = "horizontal", + direction = "vertical", close_on_exit = false, hidden = false, clear_env = false, @@ -41,8 +43,8 @@ local default_config = { after = "success", }, variants_display = { - short = { sep = " × " }, - long = { sep = " ❄ " }, + short = { sep = " × ", show = true }, + long = { sep = " ❄ ", show = false }, }, } diff --git a/lua/cmake/constants.lua b/lua/cmake/constants.lua new file mode 100644 index 0000000..fcbfea3 --- /dev/null +++ b/lua/cmake/constants.lua @@ -0,0 +1,3 @@ +return { + variants_yaml_filename = "cmake-variants.yaml", +} diff --git a/lua/cmake/fileapi.lua b/lua/cmake/fileapi.lua index 2f61677..47a7459 100644 --- a/lua/cmake/fileapi.lua +++ b/lua/cmake/fileapi.lua @@ -47,6 +47,8 @@ function FileApi.read_reply(path, callback) if object.kind == "codemodel" then utils.read_file(Path:new(reply_dir, object.jsonFile):absolute(), function(codemodel_data) local codemodel = vim.json.decode(codemodel_data) + --FIX: this loop does not read all files if codemodel contains many targets. This is because libuv (or some external settings) forbids to open files + -- in async mode more than some limit number. Seems like the solution is to queue these calls and limit max number for opened files per time for _, target in ipairs(codemodel.configurations[1].targets) do utils.read_file( Path:new(reply_dir, target.jsonFile):absolute(), diff --git a/lua/cmake/lazy.lua b/lua/cmake/lazy.lua new file mode 100644 index 0000000..7669dcd --- /dev/null +++ b/lua/cmake/lazy.lua @@ -0,0 +1,21 @@ +local lazy = {} + +--- Require on index. +--- +--- Will only require the module after the first index of a module. +--- Only works for modules that export a table. +---@param require_path string +---@return table +lazy.require = function(require_path) + return setmetatable({}, { + __index = function(_, key) + return require(require_path)[key] + end, + + __newindex = function(_, key, value) + require(require_path)[key] = value + end, + }) +end + +return lazy diff --git a/lua/cmake/lyaml.lua b/lua/cmake/lyaml.lua index 9555a75..3a17cc1 100644 --- a/lua/cmake/lyaml.lua +++ b/lua/cmake/lyaml.lua @@ -1,519 +1,495 @@ ---[[ -(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. ---]] +-- (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 + indent = indent or 0 + done = done or {} + if type(value) == "table" then + -- done[value] = true + + local rep = "\n" + for key, val in pairs(value) do + local keyRep + if type(key) == "number" then + keyRep = "-" + else + keyRep = string.format("%s:", tostring(key)) + end + local child_indent = indent + 2 + rep = rep + .. string + .format("%s%s %s\n", string.rep(" ", indent), keyRep, table_print_value(val, child_indent, done)) + :gsub("\n\n", "\n") + end + + -- rep = rep .. string.rep(" ", indent) -- indent it + + -- 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)) + return vim.trim(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 + 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") + what = what or " " + return s:gsub("^[" .. what .. "]*(.-)[" .. what .. "]*$", "%1") end local push = function(stack, item) - stack[#stack + 1] = item + stack[#stack + 1] = item end local pop = function(stack) - local item = stack[#stack] - stack[#stack] = nil - return item + local item = stack[#stack] + stack[#stack] = nil + return item end local context = function(str) - if type(str) ~= "string" then - return "" - end + if type(str) ~= "string" then + return "" + end - str = str:sub(0, 25):gsub("\n", "\\n"):gsub('"', '\\"') - return ', near "' .. str .. '"' + 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 + 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])" + 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 ]+" }, + { "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 + 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] + 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] + self.current = self.current + 1 + return self.tokens[self.current] end Parser.advanceValue = function(self) - return self:advance()[2][1] + return self:advance()[2][1] end Parser.accept = function(self, type) - if self:peekType(type) then - return self:advance() - end + 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)) + 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)) + 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 + 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 + 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" }) + self:ignore({ "space" }) end Parser.ignoreWhitespace = function(self) - self:ignore({ "space", "indent", "dedent" }) + 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 + 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() + self:accept("doc") + return self:parse() end Parser.inline = function(self) - local current = self:peek(0) - if not current then - return {}, 0 - end + local current = self:peek(0) + if not current then + return {}, 0 + end - local inline = {} - local i = 0 + 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 + 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 + 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] + 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 + return self:parent(level) and self:parent(level).token[1] == type end Parser.parseString = function(self) - if self:isInline() then - local result = self:advanceValue() + 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 - - --[[ + 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 - --[[ + 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 @@ -521,149 +497,149 @@ Parser.parseString = function(self) example c: 3 --]] - return self:parseTextBlock("\n") - end + 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 + 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 + 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 + 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 + 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() + local list = {} + while self:accept("-") do + self:ignoreSpace() + list[#list + 1] = self:parse() - self:ignoreSpace() - end - return list + 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 + 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 + self:ignoreSpace() + list[#list + 1] = self:parse() + self:ignoreSpace() + i = i + 1 + end - return list + return list end Parser.parseTimestamp = function(self) - local capture = self:advance()[2] + 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 }) + 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() + return Parser:new(exports.tokenize(str)):parse() end exports.dump = table_print diff --git a/lua/cmake/project.lua b/lua/cmake/project.lua index f98b82a..ad713e3 100644 --- a/lua/cmake/project.lua +++ b/lua/cmake/project.lua @@ -1,9 +1,9 @@ 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 constants = require("cmake.constants") +local uv = vim.uv or vim.loop local Project = {} @@ -52,7 +52,16 @@ 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 + local list_variants = {} + for k, v in pairs(variants) do + table.insert(list_variants, v) + list_variants[#list_variants]._name = k + end + table.sort(list_variants, function(a, b) + vim.notify(a._name .. " " .. b._name) + return a._name < b._name + end) + for var, is_default in VariantConfig.cartesian_product(list_variants) do var.current_build = 1 table.insert(configs, var) current_config = not current_config and is_default and #configs or current_config @@ -157,7 +166,7 @@ function Project.create_fileapi_query(opts, callback) 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) + --TODO: compare getmetatable(opts.config) with VariantConfig (and PresetsConfig in future) elseif type(opts.config) == "table" then path = opts.config.directory else @@ -176,22 +185,17 @@ 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, - }) + local variants_path = vim.fs.joinpath(uv.cwd(), constants.variants_yaml_filename) + utils.file_exists(variants_path, function(variants_exists) + if variants_exists then + utils.read_file(variants_path, function(variants_data) + local yaml = require("cmake.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/terminal.lua b/lua/cmake/terminal.lua index 9ba8cb1..358d731 100644 --- a/lua/cmake/terminal.lua +++ b/lua/cmake/terminal.lua @@ -35,7 +35,6 @@ M.cmake_execute = function(command, opts) ) 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 @@ -82,7 +81,6 @@ M.target_execute = function(command, opts) if not runnable:is_open() then runnable:open() end - vim.notify(vim.inspect(command), vim.log.levels.INFO) if command.cmd then runnable:send(command.cmd, not config.runner_terminal.focus) end diff --git a/lua/cmake/test.lua b/lua/cmake/test.lua new file mode 100644 index 0000000..d5fbab9 --- /dev/null +++ b/lua/cmake/test.lua @@ -0,0 +1,11 @@ +local lyaml = require("cmake.lyaml") +lyaml.dump({ { foo = "bar" } }) +--> --- +--> foo: bar +--> ... + +lyaml.dump({ "one", "two" }) +--> --- one +--> ... +--> --- two +--> ... diff --git a/lua/cmake/variants.lua b/lua/cmake/variants.lua index 50254c8..289d0e4 100644 --- a/lua/cmake/variants.lua +++ b/lua/cmake/variants.lua @@ -97,9 +97,7 @@ function VariantConfig:new(source) 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 = {}, @@ -130,7 +128,6 @@ function VariantConfig.cartesian_product(sets) ret.env[ename] = eres end end - -- vim.notify(vim.inspect(ret), vim.log.levels.INFO) return VariantConfig:new(ret), is_default end local result = {} -- cgit v1.2.3