diff --git a/doc/en/scripting/builtins/libutf8.md b/doc/en/scripting/builtins/libutf8.md index bd8dd22b..c8837c65 100644 --- a/doc/en/scripting/builtins/libutf8.md +++ b/doc/en/scripting/builtins/libutf8.md @@ -30,4 +30,7 @@ utf8.lower(text: str) -> str -- Escapes a string utf8.escape(text: str) -> str + +-- Escapes special XML characters +utf8.escape_xml(text: str) -> str ``` diff --git a/doc/ru/scripting/builtins/libutf8.md b/doc/ru/scripting/builtins/libutf8.md index ce933417..e4d79f4f 100644 --- a/doc/ru/scripting/builtins/libutf8.md +++ b/doc/ru/scripting/builtins/libutf8.md @@ -30,4 +30,7 @@ utf8.lower(text: str) -> str -- Экранирует строку utf8.escape(text: str) -> str + +-- Экранирует спец-символы XML +utf8.escape_xml(text: str) -> str ``` diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index 2d7f0a1d..9a89b2b5 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -187,6 +187,12 @@ string.escape(str: string) -> string Экранирует строку. Является псевдонимом `utf8.escape`. +```lua +string.escape_xml(text: str) -> str +``` + +Экранирует спец-символы XML. Является псевдонимом `utf8.escape_xml`. + ```lua string.pad(str: string, size: number, [опционально] char: string) -> string ``` diff --git a/res/devtools/default_syntax_scheme.toml b/res/devtools/default_syntax_scheme.toml new file mode 100644 index 00000000..885defd6 --- /dev/null +++ b/res/devtools/default_syntax_scheme.toml @@ -0,0 +1,19 @@ +[default] +bold = false +italic = false +strikethrough = false +underline = false +color = [0.8, 0.8, 0.8, 1] + +[keyword] +bold = true +color = [0.9, 0.6, 0.4, 1] + +[literal] +color = [0.4, 0.8, 0.5, 1] + +[comment] +color = [0.5, 0.7, 1.0, 1] + +[error] +color = [1.0, 0.2, 0.1, 1] diff --git a/res/modules/internal/extensions/string.lua b/res/modules/internal/extensions/string.lua index f76470df..313ae57b 100644 --- a/res/modules/internal/extensions/string.lua +++ b/res/modules/internal/extensions/string.lua @@ -105,6 +105,7 @@ end string.lower = utf8.lower string.upper = utf8.upper string.escape = utf8.escape +string.escape_xml = utf8.escape_xml local meta = getmetatable("") diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 15360521..4766cbfd 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -3,8 +3,8 @@ local gui_util = { } --- Parse `pagename?arg1=value1&arg2=value2` queries ---- @param query page query string ---- @return page_name, args_table +--- @param query string page query string +--- @return string, string -> page_name, args_table function gui_util.parse_query(query) local args = {} local name @@ -23,8 +23,7 @@ function gui_util.parse_query(query) return name, args end ---- @param query page query string ---- @return document_id +--- @param query string page query string function gui_util.load_page(query) local name, args = gui_util.parse_query(query) for i = #gui_util.local_dispatchers, 1, -1 do @@ -116,4 +115,52 @@ gui_util.Document = Document gui_util.Element = Element gui_util.RadioGroup = RadioGroup +function gui.show_message(text, actual_callback) + local id = "dialog_"..random.uuid() + + local callback = function() + gui.root[id]:destruct() + if actual_callback then + actual_callback() + end + end + gui.root.root:add(string.format([[ + + + + + + + ]], id, string.escape_xml(text)), {callback=callback}) + input.add_callback("key:escape", callback, gui.root[id]) +end + +function gui.ask(text, on_yes, on_no) + on_yes = on_yes or function() end + on_no = on_no or function() end + + local id = "dialog_"..random.uuid() + + local yes_callback = function() + gui.root[id]:destruct() + on_yes() + end + local no_callback = function() + gui.root[id]:destruct() + on_no() + end + gui.root.root:add(string.format([[ + + + + + + + + ]], id, string.escape_xml(text)), {on_yes=yes_callback, on_no=no_callback}) + input.add_callback("key:escape", no_callback, gui.root[id]) +end + return gui_util diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 571bf32f..dcca0f28 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -150,6 +150,18 @@ _MENU = _GUI_ROOT.menu menu = _MENU gui.root = _GUI_ROOT +do + local status, err = pcall(function() + local default_styles = toml.parse(file.read( + "res:devtools/default_syntax_scheme.toml" + )) + gui.set_syntax_styles(default_styles) + end) + if not status then + debug.error("could not to load default syntax scheme: "..err) + end +end + --- Console library extension --- console.cheats = {} diff --git a/src/devtools/SyntaxProcessor.cpp b/src/devtools/SyntaxProcessor.cpp index a4a1c7cf..c1c3f179 100644 --- a/src/devtools/SyntaxProcessor.cpp +++ b/src/devtools/SyntaxProcessor.cpp @@ -7,19 +7,15 @@ using namespace devtools; static std::unique_ptr build_styles( + const FontStylesScheme& colorScheme, const std::vector& tokens ) { using devtools::TokenTag; - FontStylesScheme styles { - { - {false, false, false, false, glm::vec4(0.8f, 0.8f, 0.8f, 1)}, // default - {true, false, false, false, glm::vec4(0.9, 0.6f, 0.4f, 1)}, // keyword - {false, false, false, false, glm::vec4(0.4, 0.8f, 0.5f, 1)}, // string - {false, false, false, false, glm::vec4(0.3, 0.3f, 0.3f, 1)}, // comment - {true, false, false, false, glm::vec4(1.0f, 0.2f, 0.1f, 1)}, // unexpected - }, - {} - }; + FontStylesScheme styles {colorScheme.palette, {}}; + if (styles.palette.empty()) { + styles.palette.push_back(FontStyle { + false, false, false, false, glm::vec4(0.8f, 0.8f, 0.8f, 1)}); + } size_t offset = 0; for (int i = 0; i < tokens.size(); i++) { const auto& token = tokens.at(i); @@ -47,6 +43,9 @@ static std::unique_ptr build_styles( styleIndex = 0; break; } + if (styleIndex >= styles.palette.size()) { + styleIndex = 0; + } styles.map.insert( styles.map.end(), token.end.pos - token.start.pos, styleIndex ); @@ -67,7 +66,9 @@ void SyntaxProcessor::addSyntax( } std::unique_ptr SyntaxProcessor::highlight( - const std::string& ext, std::wstring_view source + const FontStylesScheme& colorScheme, + const std::string& ext, + std::wstring_view source ) const { const auto& found = langsExtensions.find(ext); if (found == langsExtensions.end()) { @@ -76,7 +77,7 @@ std::unique_ptr SyntaxProcessor::highlight( const auto& syntax = *found->second; try { auto tokens = tokenize(syntax, "", source); - return build_styles(tokens); + return build_styles(colorScheme, tokens); } catch (const parsing_error& err) { return nullptr; } diff --git a/src/devtools/SyntaxProcessor.hpp b/src/devtools/SyntaxProcessor.hpp index afb975a8..d0ae41e7 100644 --- a/src/devtools/SyntaxProcessor.hpp +++ b/src/devtools/SyntaxProcessor.hpp @@ -18,7 +18,9 @@ namespace devtools { class SyntaxProcessor { public: std::unique_ptr highlight( - const std::string& ext, std::wstring_view source + const FontStylesScheme& colorScheme, + const std::string& ext, + std::wstring_view source ) const; void addSyntax(std::unique_ptr syntax); diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 3e9a3a27..da4fec51 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -23,11 +23,12 @@ void UiDocument::rebuildIndices() { map["root"] = root; } -const UINodesMap& UiDocument::getMap() const { - return map; +void UiDocument::pushIndices(const std::shared_ptr& node) { + gui::UINode::getIndices(node, map); + map["root"] = root; } -UINodesMap& UiDocument::getMapWriteable() { +const UINodesMap& UiDocument::getMap() const { return map; } diff --git a/src/frontend/UiDocument.hpp b/src/frontend/UiDocument.hpp index 9d6e6eca..2ad00da8 100644 --- a/src/frontend/UiDocument.hpp +++ b/src/frontend/UiDocument.hpp @@ -36,10 +36,10 @@ public: ); void rebuildIndices(); + void pushIndices(const std::shared_ptr& node); const std::string& getId() const; const UINodesMap& getMap() const; - UINodesMap& getMapWriteable(); std::shared_ptr getRoot() const; std::shared_ptr get(const std::string& id) const; const uidocscript& getScript() const; diff --git a/src/graphics/commons/FontStyle.cpp b/src/graphics/commons/FontStyle.cpp new file mode 100644 index 00000000..53cc0657 --- /dev/null +++ b/src/graphics/commons/FontStyle.cpp @@ -0,0 +1,37 @@ +#include "FontStyle.hpp" + +#include "data/dv.hpp" +#include "data/dv_util.hpp" +#include "devtools/SyntaxProcessor.hpp" + +FontStyle FontStyle::parse(const dv::value& src) { + FontStyle style {}; + src.at("bold").get(style.bold); + src.at("italic").get(style.italic); + src.at("strikethrough").get(style.strikethrough); + src.at("underline").get(style.underline); + dv::get_vec(src, "color", style.color); + return style; +} + +static void parse_style( + const dv::value& src, + FontStylesScheme& scheme, + const std::string& name, + devtools::SyntaxStyles tag +) { + if (src.has(name)) { + scheme.palette[static_cast(tag)] = FontStyle::parse(src[name]); + } +} + +FontStylesScheme FontStylesScheme::parse(const dv::value& src) { + FontStylesScheme scheme {}; + scheme.palette.resize(8); + parse_style(src, scheme, "default", devtools::SyntaxStyles::DEFAULT); + parse_style(src, scheme, "keyword", devtools::SyntaxStyles::KEYWORD); + parse_style(src, scheme, "literal", devtools::SyntaxStyles::LITERAL); + parse_style(src, scheme, "comment", devtools::SyntaxStyles::COMMENT); + parse_style(src, scheme, "error", devtools::SyntaxStyles::ERROR); + return scheme; +} diff --git a/src/graphics/commons/FontStyle.hpp b/src/graphics/commons/FontStyle.hpp new file mode 100644 index 00000000..5f0129f9 --- /dev/null +++ b/src/graphics/commons/FontStyle.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "data/dv_fwd.hpp" + +#include +#include + +struct FontStyle { + bool bold = false; + bool italic = false; + bool strikethrough = false; + bool underline = false; + glm::vec4 color {1, 1, 1, 1}; + + FontStyle() = default; + + FontStyle( + bool bold, + bool italic, + bool strikethrough, + bool underline, + glm::vec4 color + ) + : bold(bold), + italic(italic), + strikethrough(strikethrough), + underline(underline), + color(std::move(color)) { + } + + static FontStyle parse(const dv::value& src); +}; + +struct FontStylesScheme { + std::vector palette; + std::vector map; + + static FontStylesScheme parse(const dv::value& src); +}; diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index 12371595..f30335be 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -7,41 +7,14 @@ #include "typedefs.hpp" #include "FontMetics.hpp" +#include "data/dv_fwd.hpp" +#include "../commons/FontStyle.hpp" class Texture; class Batch2D; class Batch3D; class Camera; -struct FontStyle { - bool bold = false; - bool italic = false; - bool strikethrough = false; - bool underline = false; - glm::vec4 color {1, 1, 1, 1}; - - FontStyle() = default; - - FontStyle( - bool bold, - bool italic, - bool strikethrough, - bool underline, - glm::vec4 color - ) - : bold(bold), - italic(italic), - strikethrough(strikethrough), - underline(underline), - color(std::move(color)) { - } -}; - -struct FontStylesScheme { - std::vector palette; - std::vector map; -}; - class Font { int lineHeight; int yoffset; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 3ab438b1..22ae5b90 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -80,6 +80,14 @@ std::shared_ptr GUI::getMenu() { return menu; } +void GUI::setSyntaxColorScheme(std::unique_ptr scheme) { + syntaxColorScheme = std::move(scheme); +} + +FontStylesScheme* GUI::getSyntaxColorScheme() const { + return syntaxColorScheme.get(); +} + void GUI::onAssetsLoad(Assets* assets) { rootDocument->rebuildIndices(); assets->store(rootDocument, "core:root"); @@ -302,7 +310,7 @@ bool GUI::isFocusCaught() const { } void GUI::add(std::shared_ptr node) { - UINode::getIndices(node, rootDocument->getMapWriteable()); + rootDocument->pushIndices(node); container->add(std::move(node)); } diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index f2255fb5..16d8e358 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -17,6 +17,7 @@ struct CursorState; class Engine; class Input; class Window; +struct FontStylesScheme; namespace devtools { class Editor; @@ -73,6 +74,7 @@ namespace gui { std::shared_ptr focus; std::shared_ptr tooltip; std::shared_ptr rootDocument; + std::unique_ptr syntaxColorScheme; std::unordered_map> storage; std::unique_ptr uicamera; @@ -157,6 +159,9 @@ namespace gui { /// @deprecated std::shared_ptr getContainer() const; + void setSyntaxColorScheme(std::unique_ptr scheme); + FontStylesScheme* getSyntaxColorScheme() const; + void onAssetsLoad(Assets* assets); void postRunnable(const runnable& callback); diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 83c15544..26160e12 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -918,7 +918,9 @@ void TextBox::onTab(bool shiftPressed) { void TextBox::refreshSyntax() { if (!syntax.empty()) { const auto& processor = gui.getEditor().getSyntaxProcessor(); - if (auto styles = processor.highlight(syntax, input)) { + auto scheme = gui.getSyntaxColorScheme(); + if (auto styles = + processor.highlight(scheme ? *scheme : FontStylesScheme {}, syntax, input)) { label->setStyles(std::move(styles)); } } diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 01bfd0d6..b08d5445 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -105,7 +105,7 @@ static int l_container_add(lua::State* L) { auto subnode = guiutil::create( engine->getGUI(), xmlsrc, std::move(env) ); - UINode::getIndices(subnode, docnode.document->getMapWriteable()); + docnode.document->pushIndices(subnode); node->add(std::move(subnode)); } catch (const std::exception& err) { throw std::runtime_error("container:add(...): " + std::string(err.what())); @@ -410,9 +410,7 @@ static const std::string& request_node_id(const DocumentNode& docnode) { reinterpret_cast(docnode.node.get())); } docnode.node->setId(std::move(id)); - UINode::getIndices( - docnode.node, docnode.document->getMapWriteable() - ); + docnode.document->pushIndices(docnode.node); return docnode.node->getId(); } @@ -1089,6 +1087,16 @@ static int l_gui_load_document(lua::State* L) { return 0; } +static int l_set_syntax_styles(lua::State* L) { + if (engine->isHeadless()) { + return 0; + } + engine->getGUI().setSyntaxColorScheme(std::make_unique( + FontStylesScheme::parse(lua::tovalue(L, 1)) + )); + return 0; +} + const luaL_Reg guilib[] = { {"get_viewport", lua::wrap}, {"getattr", lua::wrap}, @@ -1101,6 +1109,7 @@ const luaL_Reg guilib[] = { {"confirm", lua::wrap}, {"alert", lua::wrap}, {"load_document", lua::wrap}, + {"set_syntax_styles", lua::wrap}, {"__reindex", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libutf8.cpp b/src/logic/scripting/lua/libs/libutf8.cpp index e6538c2e..15fc54e9 100644 --- a/src/logic/scripting/lua/libs/libutf8.cpp +++ b/src/logic/scripting/lua/libs/libutf8.cpp @@ -88,6 +88,11 @@ static int l_escape(lua::State* L) { return lua::pushstring(L, util::escape(string)); } +static int l_escape_xml(lua::State* L) { + auto string = lua::require_wstring(L, 1); + return lua::pushwstring(L, util::escape_xml(string)); +} + const luaL_Reg utf8lib[] = { {"tobytes", lua::wrap}, {"tostring", lua::wrap}, @@ -98,5 +103,6 @@ const luaL_Reg utf8lib[] = { {"lower", lua::wrap}, {"encode", lua::wrap}, {"escape", lua::wrap}, + {"escape_xml", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 63271a13..e1a99f9e 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -60,6 +60,33 @@ std::string util::escape(std::string_view s, bool escapeUnicode) { return ss.str(); } +std::wstring util::escape_xml(std::wstring_view s) { + std::wstringstream ss; + for (wchar_t c : s) { + switch (c) { + case L'&': + ss << L"&"; + break; + case L'<': + ss << L"<"; + break; + case L'>': + ss << L">"; + break; + case '"': + ss << L"""; + break; + case '\'': + ss << L"'"; + break; + default: + ss << c; + break; + } + } + return ss.str(); +} + std::string util::quote(const std::string& s) { return escape(s, false); } diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index 3d8bf16e..c26f0274 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -10,6 +10,9 @@ namespace util { /// @brief Function used for string serialization in text formats std::string escape(std::string_view s, bool escapeUnicode=true); + /// @brief Escape all special XML characters + std::wstring escape_xml(std::wstring_view s); + /// @brief Function used for error messages std::string quote(const std::string& s);