From 210bbf67a44b7795f3822ea944ac1961cf1e4b62 Mon Sep 17 00:00:00 2001 From: S41L0R <60116462+S41L0R@users.noreply.github.com> Date: Sun, 4 Jun 2023 14:28:44 -0400 Subject: [PATCH 1/3] Only ever colorize viewport --- LanguageDefinitions.cpp | 1538 +++++------ TextEditor.cpp | 5381 ++++++++++++++++++++------------------- TextEditor.h | 881 +++---- 3 files changed, 3926 insertions(+), 3874 deletions(-) diff --git a/LanguageDefinitions.cpp b/LanguageDefinitions.cpp index 052bfb81..62eec067 100644 --- a/LanguageDefinitions.cpp +++ b/LanguageDefinitions.cpp @@ -1,947 +1,979 @@ #include "TextEditor.h" -static bool TokenizeCStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; +namespace ImGuiColorTextEdit { - if (*p == '"') + static bool TokenizeCStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) { - p++; + const char* p = in_begin; - while (p < in_end) + if (*p == '"') { - // handle end of string - if (*p == '"') + p++; + + while (p < in_end) { - out_begin = in_begin; - out_end = p + 1; - return true; - } + // handle end of string + if (*p == '"') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } - // handle escape character for " - if (*p == '\\' && p + 1 < in_end && p[1] == '"') - p++; + // handle escape character for " + if (*p == '\\' && p + 1 < in_end && p[1] == '"') + p++; - p++; + p++; + } } - } - - return false; -} -static bool TokenizeCStyleCharacterLiteral(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; + return false; + } - if (*p == '\'') + static bool TokenizeCStyleCharacterLiteral(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) { - p++; + const char* p = in_begin; - // handle escape characters - if (p < in_end && *p == '\\') + if (*p == '\'') + { p++; - if (p < in_end) - p++; + // handle escape characters + if (p < in_end && *p == '\\') + p++; - // handle end of character literal - if (p < in_end && *p == '\'') - { - out_begin = in_begin; - out_end = p + 1; - return true; - } - } + if (p < in_end) + p++; - return false; -} + // handle end of character literal + if (p < in_end && *p == '\'') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } + } -static bool TokenizeCStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; + return false; + } - if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + static bool TokenizeCStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) { - p++; + const char* p = in_begin; - while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + { p++; - out_begin = in_begin; - out_end = p; - return true; - } - - return false; -} - -static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; + while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + p++; - const bool startsWithNumber = *p >= '0' && *p <= '9'; + out_begin = in_begin; + out_end = p; + return true; + } - if (*p != '+' && *p != '-' && !startsWithNumber) return false; + } - p++; + static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) + { + const char* p = in_begin; - bool hasNumber = startsWithNumber; + const bool startsWithNumber = *p >= '0' && *p <= '9'; - while (p < in_end && (*p >= '0' && *p <= '9')) - { - hasNumber = true; + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; p++; - } - - if (hasNumber == false) - return false; - bool isFloat = false; - bool isHex = false; - bool isBinary = false; + bool hasNumber = startsWithNumber; - if (p < in_end) - { - if (*p == '.') + while (p < in_end && (*p >= '0' && *p <= '9')) { - isFloat = true; + hasNumber = true; p++; - - while (p < in_end && (*p >= '0' && *p <= '9')) - p++; } - else if (*p == 'x' || *p == 'X') - { - // hex formatted integer of the type 0xef80 - isHex = true; + if (hasNumber == false) + return false; - p++; + bool isFloat = false; + bool isHex = false; + bool isBinary = false; - while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) - p++; - } - else if (*p == 'b' || *p == 'B') + if (p < in_end) { - // binary formatted integer of the type 0b01011101 + if (*p == '.') + { + isFloat = true; - isBinary = true; + p++; - p++; + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; + } + else if (*p == 'x' || *p == 'X') + { + // hex formatted integer of the type 0xef80 + + isHex = true; - while (p < in_end && (*p >= '0' && *p <= '1')) p++; - } - } - if (isHex == false && isBinary == false) - { - // floating point exponent - if (p < in_end && (*p == 'e' || *p == 'E')) - { - isFloat = true; + while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) + p++; + } + else if (*p == 'b' || *p == 'B') + { + // binary formatted integer of the type 0b01011101 - p++; + isBinary = true; - if (p < in_end && (*p == '+' || *p == '-')) p++; - bool hasDigits = false; + while (p < in_end && (*p >= '0' && *p <= '1')) + p++; + } + } - while (p < in_end && (*p >= '0' && *p <= '9')) + if (isHex == false && isBinary == false) + { + // floating point exponent + if (p < in_end && (*p == 'e' || *p == 'E')) { - hasDigits = true; + isFloat = true; p++; - } - if (hasDigits == false) - return false; - } + if (p < in_end && (*p == '+' || *p == '-')) + p++; - // single precision floating point type - if (p < in_end && *p == 'f') - p++; - } + bool hasDigits = false; - if (isFloat == false) - { - // integer size type - while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) - p++; - } + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasDigits = true; - out_begin = in_begin; - out_end = p; - return true; -} + p++; + } -static bool TokenizeCStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - (void)in_end; + if (hasDigits == false) + return false; + } + + // single precision floating point type + if (p < in_end && *p == 'f') + p++; + } + + if (isFloat == false) + { + // integer size type + while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) + p++; + } - switch (*in_begin) - { - case '[': - case ']': - case '{': - case '}': - case '!': - case '%': - case '^': - case '&': - case '*': - case '(': - case ')': - case '-': - case '+': - case '=': - case '~': - case '|': - case '<': - case '>': - case '?': - case ':': - case '/': - case ';': - case ',': - case '.': out_begin = in_begin; - out_end = in_begin + 1; + out_end = p; return true; } - return false; -} - -static bool TokenizeLuaStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; + static bool TokenizeCStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) + { + (void)in_end; - bool is_single_quote = false; - bool is_double_quotes = false; - bool is_double_square_brackets = false; + switch (*in_begin) + { + case '[': + case ']': + case '{': + case '}': + case '!': + case '%': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '+': + case '=': + case '~': + case '|': + case '<': + case '>': + case '?': + case ':': + case '/': + case ';': + case ',': + case '.': + out_begin = in_begin; + out_end = in_begin + 1; + return true; + } - switch (*p) - { - case '\'': - is_single_quote = true; - break; - case '"': - is_double_quotes = true; - break; - case '[': - p++; - if (p < in_end && *(p) == '[') - is_double_square_brackets = true; - break; + return false; } - if (is_single_quote || is_double_quotes || is_double_square_brackets) + static bool TokenizeLuaStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) { - p++; + const char* p = in_begin; - while (p < in_end) + bool is_single_quote = false; + bool is_double_quotes = false; + bool is_double_square_brackets = false; + + switch (*p) + { + case '\'': + is_single_quote = true; + break; + case '"': + is_double_quotes = true; + break; + case '[': + p++; + if (p < in_end && *(p) == '[') + is_double_square_brackets = true; + break; + } + + if (is_single_quote || is_double_quotes || is_double_square_brackets) { - // handle end of string - if ((is_single_quote && *p == '\'') || (is_double_quotes && *p == '"') || (is_double_square_brackets && *p == ']' && p + 1 < in_end && *(p + 1) == ']')) + p++; + + while (p < in_end) { - out_begin = in_begin; + // handle end of string + if ((is_single_quote && *p == '\'') || (is_double_quotes && *p == '"') || (is_double_square_brackets && *p == ']' && p + 1 < in_end && *(p + 1) == ']')) + { + out_begin = in_begin; - if (is_double_square_brackets) - out_end = p + 2; - else - out_end = p + 1; + if (is_double_square_brackets) + out_end = p + 2; + else + out_end = p + 1; - return true; - } + return true; + } - // handle escape character for " - if (*p == '\\' && p + 1 < in_end && (is_single_quote || is_double_quotes)) - p++; + // handle escape character for " + if (*p == '\\' && p + 1 < in_end && (is_single_quote || is_double_quotes)) + p++; - p++; + p++; + } } - } - - return false; -} -static bool TokenizeLuaStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; + return false; + } - if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + static bool TokenizeLuaStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) { - p++; + const char* p = in_begin; - while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + { p++; - out_begin = in_begin; - out_end = p; - return true; - } - - return false; -} - -static bool TokenizeLuaStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - const char* p = in_begin; + while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + p++; - const bool startsWithNumber = *p >= '0' && *p <= '9'; + out_begin = in_begin; + out_end = p; + return true; + } - if (*p != '+' && *p != '-' && !startsWithNumber) return false; + } - p++; + static bool TokenizeLuaStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) + { + const char* p = in_begin; - bool hasNumber = startsWithNumber; + const bool startsWithNumber = *p >= '0' && *p <= '9'; - while (p < in_end && (*p >= '0' && *p <= '9')) - { - hasNumber = true; + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; p++; - } - if (hasNumber == false) - return false; + bool hasNumber = startsWithNumber; - if (p < in_end) - { - if (*p == '.') + while (p < in_end && (*p >= '0' && *p <= '9')) { - p++; + hasNumber = true; - while (p < in_end && (*p >= '0' && *p <= '9')) - p++; + p++; } - // floating point exponent - if (p < in_end && (*p == 'e' || *p == 'E')) - { - p++; + if (hasNumber == false) + return false; - if (p < in_end && (*p == '+' || *p == '-')) + if (p < in_end) + { + if (*p == '.') + { p++; - bool hasDigits = false; + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; + } - while (p < in_end && (*p >= '0' && *p <= '9')) + // floating point exponent + if (p < in_end && (*p == 'e' || *p == 'E')) { - hasDigits = true; - p++; - } - if (hasDigits == false) - return false; - } - } + if (p < in_end && (*p == '+' || *p == '-')) + p++; - out_begin = in_begin; - out_end = p; - return true; -} + bool hasDigits = false; -static bool TokenizeLuaStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) -{ - (void)in_end; + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasDigits = true; + + p++; + } + + if (hasDigits == false) + return false; + } + } - switch (*in_begin) - { - case '[': - case ']': - case '{': - case '}': - case '!': - case '%': - case '#': - case '^': - case '&': - case '*': - case '(': - case ')': - case '-': - case '+': - case '=': - case '~': - case '|': - case '<': - case '>': - case '?': - case ':': - case '/': - case ';': - case ',': - case '.': out_begin = in_begin; - out_end = in_begin + 1; + out_end = p; return true; } - return false; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + static bool TokenizeLuaStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) { - static const char* const cppKeywords[] = { - "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", - "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", - "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", - "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", - "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" - }; - for (auto& k : cppKeywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "printf", "sprintf", "snprintf", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper", - "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + (void)in_end; - langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + switch (*in_begin) { - paletteIndex = PaletteIndex::Max; + case '[': + case ']': + case '{': + case '}': + case '!': + case '%': + case '#': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '+': + case '=': + case '~': + case '|': + case '<': + case '>': + case '?': + case ':': + case '/': + case ';': + case ',': + case '.': + out_begin = in_begin; + out_end = in_begin + 1; + return true; + } - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; + return false; + } - if (in_begin == in_end) + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::CPlusPlus() + { + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const cppKeywords[] = { + "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", + "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", + "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", + "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", + "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" + }; + for (auto& k : cppKeywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "printf", "sprintf", "snprintf", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper", + "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max" + }; + for (auto& k : identifiers) { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); } - else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::CharLiteral; - else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - - return paletteIndex != PaletteIndex::Max; - }; - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "C++"; - - inited = true; + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "C++"; + + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::HLSL() { - static const char* const keywords[] = { - "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", - "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", - "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", - "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", - "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", - "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", - "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", - "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", - "VertexShader", "void", "volatile", "while", - "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", - "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", - "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", - "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", - "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", - "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", - "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", - "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", - "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", - "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", - "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", - "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", - "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", - "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", - "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", - "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", - "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", - "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + static const char* const keywords[] = { + "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", + "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", + "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", + "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", + "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", + "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", + "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", + "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", + "VertexShader", "void", "volatile", "while", + "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", + "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", + "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", + "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", + "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", + "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", + "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", + "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", + "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", + "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", + "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", + "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", + "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", + "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", + "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", + "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", + "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", + "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "HLSL"; + langDef.mName = "HLSL"; - inited = true; + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::GLSL() { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", + "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", + "_Noreturn", "_Static_assert", "_Thread_local" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "GLSL"; + langDef.mName = "GLSL"; - inited = true; + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Python() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::Python() { - static const char* const keywords[] = { - "False", "await", "else", "import", "pass", "None", "break", "except", "in", "raise", "True", "class", "finally", "is", "return", "and", "continue", "for", "lambda", "try", "as", "def", "from", "nonlocal", "while", "assert", "del", "global", "not", "with", "async", "elif", "if", "or", "yield" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abs", "aiter", "all", "any", "anext", "ascii", "bin", "bool", "breakpoint", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip", "__import__" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + static const char* const keywords[] = { + "False", "await", "else", "import", "pass", "None", "break", "except", "in", "raise", "True", "class", "finally", "is", "return", "and", "continue", "for", "lambda", "try", "as", "def", "from", "nonlocal", "while", "assert", "del", "global", "not", "with", "async", "elif", "if", "or", "yield" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abs", "aiter", "all", "any", "anext", "ascii", "bin", "bool", "breakpoint", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip", "__import__" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } - langDef.mTokenRegexStrings.push_back(std::make_pair("(b|u|f|r)?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("(b|u|f|r)?\\\'(\\\\.|[^\\\'])*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("(b|u|f|r)?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("(b|u|f|r)?\\\'(\\\\.|[^\\\'])*\\\'", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - langDef.mCommentStart = "\"\"\""; - langDef.mCommentEnd = "\"\"\""; - langDef.mSingleLineComment = "#"; + langDef.mCommentStart = "\"\"\""; + langDef.mCommentEnd = "\"\"\""; + langDef.mSingleLineComment = "#"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "Python"; + langDef.mName = "Python"; - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + inited = true; } + return langDef; + } - langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::C() + { + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - paletteIndex = PaletteIndex::Max; - - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; - - if (in_begin == in_end) + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", + "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", + "_Noreturn", "_Static_assert", "_Thread_local" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" + }; + for (auto& k : identifiers) { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); } - else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::CharLiteral; - else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - - return paletteIndex != PaletteIndex::Max; - }; - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "C"; - - inited = true; + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "C"; + + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::SQL() { - static const char* const keywords[] = { - "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", - "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", - "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", - "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", - "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", - "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", - "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", - "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", - "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", - "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", - "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", - "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", - "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", - "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", - "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", - "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", - "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", - "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", - "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + static const char* const keywords[] = { + "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", + "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", + "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", + "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", + "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", + "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", + "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", + "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", + "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", + "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" + }; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", + "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", + "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", + "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", + "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", + "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", + "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", + "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", + "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", + "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "--"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "--"; - langDef.mCaseSensitive = false; - langDef.mAutoIndentation = false; + langDef.mCaseSensitive = false; + langDef.mAutoIndentation = false; - langDef.mName = "SQL"; + langDef.mName = "SQL"; - inited = true; + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::AngelScript() { - static const char* const keywords[] = { - "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", - "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", - "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", - "uint64", "void", "while", "xor" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", - "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + static const char* const keywords[] = { + "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", + "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", + "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", + "uint64", "void", "while", "xor" + }; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", + "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "AngelScript"; + langDef.mName = "AngelScript"; - inited = true; + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::Lua() { - static const char* const keywords[] = { - "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", - "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", - "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", - "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", - "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", - "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", - "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", - "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", - "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", - "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + static const char* const keywords[] = { + "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" + }; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", + "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", + "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", + "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", + "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", + "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", + "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", + "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", + "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", + "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" + }; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeLuaStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeLuaStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeLuaStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeLuaStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "--[["; + langDef.mCommentEnd = "]]"; + langDef.mSingleLineComment = "--"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = false; + + langDef.mName = "Lua"; + + inited = true; } + return langDef; + } - langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::CSharp() + { + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - paletteIndex = PaletteIndex::Max; - - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; - - if (in_begin == in_end) + static const char* const keywords[] = { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "in (generic modifier)", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "out (generic modifier)", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "using static", "void", "volatile", "while" + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "add", "alias", "ascending", "async", "await", "descending", "dynamic", "from", "get", "global", "group", "into", "join", "let", "orderby", "partial", "remove", "select", "set", "value", "var", "when", "where", "yield" + }; + for (auto& k : identifiers) { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); } - else if (TokenizeLuaStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeLuaStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeLuaStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeLuaStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - return paletteIndex != PaletteIndex::Max; - }; + langDef.mTokenRegexStrings.push_back(std::make_pair("($|@)?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - langDef.mCommentStart = "--[["; - langDef.mCommentEnd = "]]"; - langDef.mSingleLineComment = "--"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = false; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "Lua"; + langDef.mName = "C#"; - inited = true; + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CSharp() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::Json() { - static const char* const keywords[] = { - "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "in (generic modifier)", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "out (generic modifier)", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "using static", "void", "volatile", "while" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "add", "alias", "ascending", "async", "await", "descending", "dynamic", "from", "get", "global", "group", "into", "join", "let", "orderby", "partial", "remove", "select", "set", "value", "var", "when", "where", "yield" - }; - for (auto& k : identifiers) + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } + langDef.mKeywords.clear(); + langDef.mIdentifiers.clear(); - langDef.mTokenRegexStrings.push_back(std::make_pair("($|@)?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.\\:]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair("false|true", PaletteIndex::Keyword)); - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "C#"; + langDef.mName = "Json"; - inited = true; + inited = true; + } + return langDef; } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Json() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) + + const ImGuiColorTextEdit::TextEditor::LanguageDefinition& ImGuiColorTextEdit::TextEditor::LanguageDefinition::Yaml() { - langDef.mKeywords.clear(); - langDef.mIdentifiers.clear(); + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + langDef.mKeywords.clear(); + langDef.mIdentifiers.clear(); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.\\:]", PaletteIndex::Punctuation)); - langDef.mTokenRegexStrings.push_back(std::make_pair("false|true", PaletteIndex::Keyword)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.\\:]", PaletteIndex::Punctuation)); + langDef.mTokenRegexStrings.push_back(std::make_pair(".*:", PaletteIndex::KnownIdentifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("false|true", PaletteIndex::Keyword)); - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; - langDef.mName = "Json"; + langDef.mName = "Json"; - inited = true; + inited = true; + } + return langDef; } - return langDef; } \ No newline at end of file diff --git a/TextEditor.cpp b/TextEditor.cpp index 85236cfa..46c765d6 100644 --- a/TextEditor.cpp +++ b/TextEditor.cpp @@ -11,1711 +11,1689 @@ // TODO // - multiline comments vs single-line: latter is blocking start of a ML -template -bool equals(InputIt1 first1, InputIt1 last1, - InputIt2 first2, InputIt2 last2, BinaryPredicate p) -{ - for (; first1 != last1 && first2 != last2; ++first1, ++first2) - { - if (!p(*first1, *first2)) - return false; - } - return first1 == last1 && first2 == last2; -} - -TextEditor::TextEditor() - : mLineSpacing(1.0f) - , mUndoIndex(0) - , mTabSize(4) - , mOverwrite(false) - , mReadOnly(false) - , mWithinRender(false) - , mScrollToCursor(false) - , mScrollToTop(false) - , mTextChanged(false) - , mColorizerEnabled(true) - , mTextStart(20.0f) - , mLeftMargin(10) - , mColorRangeMin(0) - , mColorRangeMax(0) - , mSelectionMode(SelectionMode::Normal) - , mCheckComments(true) - , mHandleKeyboardInputs(true) - , mHandleMouseInputs(true) - , mIgnoreImGuiChild(false) - , mShowWhitespaces(true) - , mShowShortTabGlyphs(false) - , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - , mLastClick(-1.0f) -{ - SetPalette(GetMarianaPalette()); - mLines.push_back(Line()); -} - -TextEditor::~TextEditor() -{ -} - -void TextEditor::SetLanguageDefinition(const LanguageDefinition& aLanguageDef) -{ - mLanguageDefinition = &aLanguageDef; - mRegexList.clear(); - - for (const auto& r : mLanguageDefinition->mTokenRegexStrings) - mRegexList.push_back(std::make_pair(boost::regex(r.first, boost::regex_constants::optimize), r.second)); - - Colorize(); -} - -const char* TextEditor::GetLanguageDefinitionName() const -{ - return mLanguageDefinition != nullptr ? mLanguageDefinition->mName.c_str() : "unknown"; -} - -void TextEditor::SetPalette(const Palette& aValue) -{ - mPaletteBase = aValue; -} - -std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const -{ - std::string result; - - auto lstart = aStart.mLine; - auto lend = aEnd.mLine; - auto istart = GetCharacterIndexR(aStart); - auto iend = GetCharacterIndexR(aEnd); - size_t s = 0; - - for (size_t i = lstart; i < lend; i++) - s += mLines[i].size(); - - result.reserve(s + s / 8); - - while (istart < iend || lstart < lend) - { - if (lstart >= (int)mLines.size()) - break; - - auto& line = mLines[lstart]; - if (istart < (int)line.size()) - { - result += line[istart].mChar; - istart++; - } - else - { - istart = 0; - ++lstart; - result += '\n'; - } - } +namespace ImGuiColorTextEdit { - return result; -} - -TextEditor::Coordinates TextEditor::GetActualCursorCoordinates(int aCursor) const -{ - if (aCursor == -1) - return SanitizeCoordinates(mState.mCursors[mState.mCurrentCursor].mCursorPosition); - else - return SanitizeCoordinates(mState.mCursors[aCursor].mCursorPosition); -} - -TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const -{ - auto line = aValue.mLine; - auto column = aValue.mColumn; - if (line >= (int)mLines.size()) + template + bool equals(InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2, BinaryPredicate p) { - if (mLines.empty()) + for (; first1 != last1 && first2 != last2; ++first1, ++first2) { - line = 0; - column = 0; + if (!p(*first1, *first2)) + return false; } - else - { - line = (int)mLines.size() - 1; - column = GetLineMaxColumn(line); - } - return Coordinates(line, column); - } - else - { - column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); - return Coordinates(line, column); - } -} - -// https://en.wikipedia.org/wiki/UTF-8 -// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code) -static int UTF8CharLength(TextEditor::Char c) -{ - if ((c & 0xFE) == 0xFC) - return 6; - if ((c & 0xFC) == 0xF8) - return 5; - if ((c & 0xF8) == 0xF0) - return 4; - else if ((c & 0xF0) == 0xE0) - return 3; - else if ((c & 0xE0) == 0xC0) - return 2; - return 1; -} - -// "Borrowed" from ImGui source -static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) -{ - if (c < 0x80) - { - buf[0] = (char)c; - return 1; - } - if (c < 0x800) - { - if (buf_size < 2) return 0; - buf[0] = (char)(0xc0 + (c >> 6)); - buf[1] = (char)(0x80 + (c & 0x3f)); - return 2; + return first1 == last1 && first2 == last2; } - if (c >= 0xdc00 && c < 0xe000) + + TextEditor::TextEditor() + : mLineSpacing(1.0f) + , mUndoIndex(0) + , mTabSize(4) + , mOverwrite(false) + , mReadOnly(false) + , mWithinRender(false) + , mScrollToCursor(false) + , mScrollToTop(false) + , mTextChanged(false) + , mColorizerEnabled(true) + , mTextStart(20.0f) + , mLeftMargin(10) + , mColorRangeMin(0) + , mColorRangeMax(0) + , mSelectionMode(SelectionMode::Normal) + , mCheckComments(true) + , mHandleKeyboardInputs(true) + , mHandleMouseInputs(true) + , mIgnoreImGuiChild(false) + , mShowWhitespaces(true) + , mShowShortTabGlyphs(false) + , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + , mLastClick(-1.0f) { - return 0; + SetPalette(GetMarianaPalette()); + mLines.push_back(Line()); } - if (c >= 0xd800 && c < 0xdc00) + + TextEditor::~TextEditor() { - if (buf_size < 4) return 0; - buf[0] = (char)(0xf0 + (c >> 18)); - buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); - buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); - buf[3] = (char)(0x80 + ((c) & 0x3f)); - return 4; } - //else if (c < 0x10000) + + void TextEditor::SetLanguageDefinition(const LanguageDefinition& aLanguageDef) { - if (buf_size < 3) return 0; - buf[0] = (char)(0xe0 + (c >> 12)); - buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); - buf[2] = (char)(0x80 + ((c) & 0x3f)); - return 3; - } -} + mLanguageDefinition = &aLanguageDef; + mRegexList.clear(); -void TextEditor::Advance(Coordinates& aCoordinates) const -{ - if (aCoordinates.mLine >= (int)mLines.size()) - return; + for (const auto& r : mLanguageDefinition->mTokenRegexStrings) + mRegexList.push_back(std::make_pair(boost::regex(r.first, boost::regex_constants::optimize), r.second)); - auto& line = mLines[aCoordinates.mLine]; - auto cindex = GetCharacterIndexL(aCoordinates); + Colorize(); + } - if (cindex < (int)line.size()) + const char* TextEditor::GetLanguageDefinitionName() const { - auto delta = UTF8CharLength(line[cindex].mChar); - cindex = std::min(cindex + delta, (int)line.size()); + return mLanguageDefinition != nullptr ? mLanguageDefinition->mName.c_str() : "unknown"; } - else if (mLines.size() > aCoordinates.mLine + 1) + + void TextEditor::SetPalette(const Palette& aValue) { - ++aCoordinates.mLine; - cindex = 0; + mPaletteBase = aValue; } - aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); -} -void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd) -{ - assert(aEnd >= aStart); - assert(!mReadOnly); + std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const + { + std::string result; - //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); + auto lstart = aStart.mLine; + auto lend = aEnd.mLine; + auto istart = GetCharacterIndexR(aStart); + auto iend = GetCharacterIndexR(aEnd); + size_t s = 0; - if (aEnd == aStart) - return; + for (size_t i = lstart; i < lend; i++) + s += mLines[i].size(); - auto start = GetCharacterIndexL(aStart); - auto end = GetCharacterIndexR(aEnd); + result.reserve(s + s / 8); - if (aStart.mLine == aEnd.mLine) - { - auto n = GetLineMaxColumn(aStart.mLine); - if (aEnd.mColumn >= n) - RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line - else - RemoveGlyphsFromLine(aStart.mLine, start, end); - } - else - { - RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line - RemoveGlyphsFromLine(aEnd.mLine, 0, end); - auto& firstLine = mLines[aStart.mLine]; - auto& lastLine = mLines[aEnd.mLine]; + while (istart < iend || lstart < lend) + { + if (lstart >= (int)mLines.size()) + break; - if (aStart.mLine < aEnd.mLine) - AddGlyphsToLine(aStart.mLine, firstLine.size(), lastLine.begin(), lastLine.end()); + auto& line = mLines[lstart]; + if (istart < (int)line.size()) + { + result += line[istart].mChar; + istart++; + } + else + { + istart = 0; + ++lstart; + result += '\n'; + } + } - if (aStart.mLine < aEnd.mLine) - RemoveLines(aStart.mLine + 1, aEnd.mLine + 1); + return result; } - mTextChanged = true; -} - -int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue) -{ - assert(!mReadOnly); - - int cindex = GetCharacterIndexR(aWhere); - int totalLines = 0; - while (*aValue != '\0') + TextEditor::Coordinates TextEditor::GetActualCursorCoordinates(int aCursor) const { - assert(!mLines.empty()); + if (aCursor == -1) + return SanitizeCoordinates(mState.mCursors[mState.mCurrentCursor].mCursorPosition); + else + return SanitizeCoordinates(mState.mCursors[aCursor].mCursorPosition); + } - if (*aValue == '\r') - { - // skip - ++aValue; - } - else if (*aValue == '\n') + TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const + { + auto line = aValue.mLine; + auto column = aValue.mColumn; + if (line >= (int)mLines.size()) { - if (cindex < (int)mLines[aWhere.mLine].size()) + if (mLines.empty()) { - auto& newLine = InsertLine(aWhere.mLine + 1); - auto& line = mLines[aWhere.mLine]; - AddGlyphsToLine(aWhere.mLine + 1, 0, line.begin() + cindex, line.end()); - RemoveGlyphsFromLine(aWhere.mLine, cindex); + line = 0; + column = 0; } else { - InsertLine(aWhere.mLine + 1); + line = (int)mLines.size() - 1; + column = GetLineMaxColumn(line); } - ++aWhere.mLine; - aWhere.mColumn = 0; - cindex = 0; - ++totalLines; - ++aValue; + return Coordinates(line, column); } else { - auto& line = mLines[aWhere.mLine]; - auto d = UTF8CharLength(*aValue); - while (d-- > 0 && *aValue != '\0') - AddGlyphToLine(aWhere.mLine, cindex++, Glyph(*aValue++, PaletteIndex::Default)); - aWhere.mColumn = GetCharacterColumn(aWhere.mLine, cindex); + column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); + return Coordinates(line, column); } + } - mTextChanged = true; + // https://en.wikipedia.org/wiki/UTF-8 + // We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code) + static int UTF8CharLength(TextEditor::Char c) + { + if ((c & 0xFE) == 0xFC) + return 6; + if ((c & 0xFC) == 0xF8) + return 5; + if ((c & 0xF8) == 0xF0) + return 4; + else if ((c & 0xF0) == 0xE0) + return 3; + else if ((c & 0xE0) == 0xC0) + return 2; + return 1; + } + + // "Borrowed" from ImGui source + static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) + { + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c) & 0x3f)); + return 3; + } } - return totalLines; -} + void TextEditor::Advance(Coordinates& aCoordinates) const + { + if (aCoordinates.mLine >= (int)mLines.size()) + return; -void TextEditor::AddUndo(UndoRecord& aValue) -{ - assert(!mReadOnly); - //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", - // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, - // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, - // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn, - // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn - // ); + auto& line = mLines[aCoordinates.mLine]; + auto cindex = GetCharacterIndexL(aCoordinates); - mUndoBuffer.resize((size_t)(mUndoIndex + 1)); - mUndoBuffer.back() = aValue; - ++mUndoIndex; -} + if (cindex < (int)line.size()) + { + auto delta = UTF8CharLength(line[cindex].mChar); + cindex = std::min(cindex + delta, (int)line.size()); + } + else if (mLines.size() > aCoordinates.mLine + 1) + { + ++aCoordinates.mLine; + cindex = 0; + } + aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); + } -TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition, bool aInsertionMode, bool* isOverLineNumber) const -{ - ImVec2 origin = ImGui::GetCursorScreenPos(); - ImVec2 local(aPosition.x - origin.x + 3.0f, aPosition.y - origin.y); + void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd) + { + assert(aEnd >= aStart); + assert(!mReadOnly); - if (isOverLineNumber != nullptr) - *isOverLineNumber = local.x < mTextStart; + //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); - float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; + if (aEnd == aStart) + return; - int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); + auto start = GetCharacterIndexL(aStart); + auto end = GetCharacterIndexR(aEnd); - int columnCoord = 0; + if (aStart.mLine == aEnd.mLine) + { + auto n = GetLineMaxColumn(aStart.mLine); + if (aEnd.mColumn >= n) + RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line + else + RemoveGlyphsFromLine(aStart.mLine, start, end); + } + else + { + RemoveGlyphsFromLine(aStart.mLine, start); // from start to end of line + RemoveGlyphsFromLine(aEnd.mLine, 0, end); + auto& firstLine = mLines[aStart.mLine]; + auto& lastLine = mLines[aEnd.mLine]; - if (lineNo >= 0 && lineNo < (int)mLines.size()) - { - auto& line = mLines.at(lineNo); + if (aStart.mLine < aEnd.mLine) + AddGlyphsToLine(aStart.mLine, firstLine.size(), lastLine.begin(), lastLine.end()); - int columnIndex = 0; - std::string cumulatedString = ""; - float columnWidth = 0.0f; - float columnX = 0.0f; - int delta = 0; + if (aStart.mLine < aEnd.mLine) + RemoveLines(aStart.mLine + 1, aEnd.mLine + 1); + } + + mTextChanged = true; + } - // First we find the hovered column coord. - for (size_t columnIndex = 0; columnIndex < line.size(); ++columnIndex) + int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue) + { + assert(!mReadOnly); + + int cindex = GetCharacterIndexR(aWhere); + int totalLines = 0; + while (*aValue != '\0') { - float columnWidth = 0.0f; - int delta = 0; + assert(!mLines.empty()); - if (line[columnIndex].mChar == '\t') + if (*aValue == '\r') { - float oldX = columnX; - columnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - columnWidth = columnX - oldX; - delta = mTabSize - (columnCoord % mTabSize); + // skip + ++aValue; + } + else if (*aValue == '\n') + { + if (cindex < (int)mLines[aWhere.mLine].size()) + { + auto& newLine = InsertLine(aWhere.mLine + 1); + auto& line = mLines[aWhere.mLine]; + AddGlyphsToLine(aWhere.mLine + 1, 0, line.begin() + cindex, line.end()); + RemoveGlyphsFromLine(aWhere.mLine, cindex); + } + else + { + InsertLine(aWhere.mLine + 1); + } + ++aWhere.mLine; + aWhere.mColumn = 0; + cindex = 0; + ++totalLines; + ++aValue; } else { - char buf[7]; - auto d = UTF8CharLength(line[columnIndex].mChar); - int i = 0; - while (i < 6 && d-- > 0) - buf[i++] = line[columnIndex].mChar; - buf[i] = '\0'; - columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; - columnX += columnWidth; - delta = 1; + auto& line = mLines[aWhere.mLine]; + auto d = UTF8CharLength(*aValue); + while (d-- > 0 && *aValue != '\0') + AddGlyphToLine(aWhere.mLine, cindex++, Glyph(*aValue++, PaletteIndex::Default)); + aWhere.mColumn = GetCharacterColumn(aWhere.mLine, cindex); } - if (mTextStart + columnX - (aInsertionMode ? 0.5f : 0.0f) * columnWidth < local.x) - columnCoord += delta; - else - break; + mTextChanged = true; } - // Then we reduce by 1 column coord if cursor is on the left side of the hovered column. - //if (aInsertionMode && mTextStart + columnX - columnWidth * 2.0f < local.x) - // columnIndex = std::min((int)line.size() - 1, columnIndex + 1); + return totalLines; } - return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); -} + void TextEditor::AddUndo(UndoRecord& aValue) + { + assert(!mReadOnly); + //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", + // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, + // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, + // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn, + // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn + // ); -TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const -{ - Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) - return at; + mUndoBuffer.resize((size_t)(mUndoIndex + 1)); + mUndoBuffer.back() = aValue; + ++mUndoIndex; + } - auto& line = mLines[at.mLine]; - auto cindex = GetCharacterIndexL(at); + TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition, bool aInsertionMode, bool* isOverLineNumber) const + { + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImVec2 local(aPosition.x - origin.x + 3.0f, aPosition.y - origin.y); - if (cindex >= (int)line.size()) - return at; + if (isOverLineNumber != nullptr) + *isOverLineNumber = local.x < mTextStart; - bool initialIsWordChar = IsGlyphWordChar(line[cindex]); - bool initialIsSpace = isspace(line[cindex].mChar); - uint8_t initialChar = line[cindex].mChar; - bool needToAdvance = false; - while (true) - { - --cindex; - if (cindex < 0) - { - cindex = 0; - break; - } + float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; + + int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); - auto c = line[cindex].mChar; - if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx + int columnCoord = 0; + + if (lineNo >= 0 && lineNo < (int)mLines.size()) { - bool isWordChar = IsGlyphWordChar(line[cindex]); - bool isSpace = isspace(line[cindex].mChar); - if (initialIsSpace && !isSpace || initialIsWordChar && !isWordChar || !initialIsWordChar && !initialIsSpace && initialChar != line[cindex].mChar) + auto& line = mLines.at(lineNo); + + int columnIndex = 0; + std::string cumulatedString = ""; + float columnWidth = 0.0f; + float columnX = 0.0f; + int delta = 0; + + // First we find the hovered column coord. + for (size_t columnIndex = 0; columnIndex < line.size(); ++columnIndex) { - needToAdvance = true; - break; + float columnWidth = 0.0f; + int delta = 0; + + if (line[columnIndex].mChar == '\t') + { + float oldX = columnX; + columnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + columnWidth = columnX - oldX; + delta = mTabSize - (columnCoord % mTabSize); + } + else + { + char buf[7]; + auto d = UTF8CharLength(line[columnIndex].mChar); + int i = 0; + while (i < 6 && d-- > 0) + buf[i++] = line[columnIndex].mChar; + buf[i] = '\0'; + columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; + columnX += columnWidth; + delta = 1; + } + + if (mTextStart + columnX - (aInsertionMode ? 0.5f : 0.0f) * columnWidth < local.x) + columnCoord += delta; + else + break; } + + // Then we reduce by 1 column coord if cursor is on the left side of the hovered column. + //if (aInsertionMode && mTextStart + columnX - columnWidth * 2.0f < local.x) + // columnIndex = std::min((int)line.size() - 1, columnIndex + 1); } - } - at.mColumn = GetCharacterColumn(at.mLine, cindex); - if (needToAdvance) - Advance(at); - return at; -} -TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const -{ - Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) - return at; + return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); + } - auto& line = mLines[at.mLine]; - auto cindex = GetCharacterIndexL(at); + TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const + { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; - if (cindex >= (int)line.size()) - return at; + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndexL(at); - bool initialIsWordChar = IsGlyphWordChar(line[cindex]); - bool initialIsSpace = isspace(line[cindex].mChar); - uint8_t initialChar = line[cindex].mChar; - while (true) - { - auto d = UTF8CharLength(line[cindex].mChar); - cindex += d; if (cindex >= (int)line.size()) - break; + return at; - bool isWordChar = IsGlyphWordChar(line[cindex]); - bool isSpace = isspace(line[cindex].mChar); - if (initialIsSpace && !isSpace || initialIsWordChar && !isWordChar || !initialIsWordChar && !initialIsSpace && initialChar != line[cindex].mChar) - break; - } - at.mColumn = GetCharacterColumn(at.mLine, cindex); - return at; -} + bool initialIsWordChar = IsGlyphWordChar(line[cindex]); + bool initialIsSpace = isspace(line[cindex].mChar); + uint8_t initialChar = line[cindex].mChar; + bool needToAdvance = false; + while (true) + { + --cindex; + if (cindex < 0) + { + cindex = 0; + break; + } -TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates& aFrom) const -{ - Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) + auto c = line[cindex].mChar; + if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx + { + bool isWordChar = IsGlyphWordChar(line[cindex]); + bool isSpace = isspace(line[cindex].mChar); + if (initialIsSpace && !isSpace || initialIsWordChar && !isWordChar || !initialIsWordChar && !initialIsSpace && initialChar != line[cindex].mChar) + { + needToAdvance = true; + break; + } + } + } + at.mColumn = GetCharacterColumn(at.mLine, cindex); + if (needToAdvance) + Advance(at); return at; + } - // skip to the next non-word character - auto cindex = GetCharacterIndexR(aFrom); - bool isword = false; - bool skip = false; - if (cindex < (int)mLines[at.mLine].size()) + TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + auto& line = mLines[at.mLine]; - isword = !!isalnum(line[cindex].mChar); - skip = isword; + auto cindex = GetCharacterIndexL(at); + + if (cindex >= (int)line.size()) + return at; + + bool initialIsWordChar = IsGlyphWordChar(line[cindex]); + bool initialIsSpace = isspace(line[cindex].mChar); + uint8_t initialChar = line[cindex].mChar; + while (true) + { + auto d = UTF8CharLength(line[cindex].mChar); + cindex += d; + if (cindex >= (int)line.size()) + break; + + bool isWordChar = IsGlyphWordChar(line[cindex]); + bool isSpace = isspace(line[cindex].mChar); + if (initialIsSpace && !isSpace || initialIsWordChar && !isWordChar || !initialIsWordChar && !initialIsSpace && initialChar != line[cindex].mChar) + break; + } + at.mColumn = GetCharacterColumn(at.mLine, cindex); + return at; } - while (!isword || skip) + TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates& aFrom) const { - if (at.mLine >= mLines.size()) + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + // skip to the next non-word character + auto cindex = GetCharacterIndexR(aFrom); + bool isword = false; + bool skip = false; + if (cindex < (int)mLines[at.mLine].size()) { - auto l = std::max(0, (int)mLines.size() - 1); - return Coordinates(l, GetLineMaxColumn(l)); + auto& line = mLines[at.mLine]; + isword = !!isalnum(line[cindex].mChar); + skip = isword; } - auto& line = mLines[at.mLine]; - if (cindex < (int)line.size()) + while (!isword || skip) { - isword = isalnum(line[cindex].mChar); + if (at.mLine >= mLines.size()) + { + auto l = std::max(0, (int)mLines.size() - 1); + return Coordinates(l, GetLineMaxColumn(l)); + } - if (isword && !skip) - return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); + auto& line = mLines[at.mLine]; + if (cindex < (int)line.size()) + { + isword = isalnum(line[cindex].mChar); - if (!isword) - skip = false; + if (isword && !skip) + return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); - cindex++; - } - else - { - cindex = 0; - ++at.mLine; - skip = false; - isword = false; + if (!isword) + skip = false; + + cindex++; + } + else + { + cindex = 0; + ++at.mLine; + skip = false; + isword = false; + } } - } - return at; -} + return at; + } -int TextEditor::GetCharacterIndexL(const Coordinates& aCoordinates) const -{ - if (aCoordinates.mLine >= mLines.size()) - return -1; + int TextEditor::GetCharacterIndexL(const Coordinates& aCoordinates) const + { + if (aCoordinates.mLine >= mLines.size()) + return -1; - auto& line = mLines[aCoordinates.mLine]; - int c = 0; - int i = 0; - int tabCoordsLeft = 0; + auto& line = mLines[aCoordinates.mLine]; + int c = 0; + int i = 0; + int tabCoordsLeft = 0; - for (; i < line.size() && c < aCoordinates.mColumn;) - { - if (line[i].mChar == '\t') + for (; i < line.size() && c < aCoordinates.mColumn;) { + if (line[i].mChar == '\t') + { + if (tabCoordsLeft == 0) + tabCoordsLeft = mTabSize - (c % mTabSize); + if (tabCoordsLeft > 0) + tabCoordsLeft--; + c++; + } + else + ++c; if (tabCoordsLeft == 0) - tabCoordsLeft = mTabSize - (c % mTabSize); - if (tabCoordsLeft > 0) - tabCoordsLeft--; - c++; + i += UTF8CharLength(line[i].mChar); } - else - ++c; - if (tabCoordsLeft == 0) - i += UTF8CharLength(line[i].mChar); + return i; } - return i; -} -int TextEditor::GetCharacterIndexR(const Coordinates& aCoordinates) const -{ - if (aCoordinates.mLine >= mLines.size()) - return -1; - auto& line = mLines[aCoordinates.mLine]; - int c = 0; - int i = 0; - for (; i < line.size() && c < aCoordinates.mColumn;) + int TextEditor::GetCharacterIndexR(const Coordinates& aCoordinates) const { - if (line[i].mChar == '\t') - c = (c / mTabSize) * mTabSize + mTabSize; - else - ++c; - i += UTF8CharLength(line[i].mChar); - } - return i; -} - -int TextEditor::GetCharacterColumn(int aLine, int aIndex) const -{ - if (aLine >= mLines.size()) - return 0; - auto& line = mLines[aLine]; - int col = 0; - int i = 0; - while (i < aIndex && i < (int)line.size()) - { - auto c = line[i].mChar; - i += UTF8CharLength(c); - if (c == '\t') - col = (col / mTabSize) * mTabSize + mTabSize; - else - col++; - } - return col; -} - -int TextEditor::GetLineCharacterCount(int aLine) const -{ - if (aLine >= mLines.size()) - return 0; - auto& line = mLines[aLine]; - int c = 0; - for (unsigned i = 0; i < line.size(); c++) - i += UTF8CharLength(line[i].mChar); - return c; -} - -int TextEditor::GetLineMaxColumn(int aLine) const -{ - if (aLine >= mLines.size()) - return 0; - auto& line = mLines[aLine]; - int col = 0; - for (unsigned i = 0; i < line.size(); ) - { - auto c = line[i].mChar; - if (c == '\t') - col = (col / mTabSize) * mTabSize + mTabSize; - else - col++; - i += UTF8CharLength(c); + if (aCoordinates.mLine >= mLines.size()) + return -1; + auto& line = mLines[aCoordinates.mLine]; + int c = 0; + int i = 0; + for (; i < line.size() && c < aCoordinates.mColumn;) + { + if (line[i].mChar == '\t') + c = (c / mTabSize) * mTabSize + mTabSize; + else + ++c; + i += UTF8CharLength(line[i].mChar); + } + return i; } - return col; -} - -bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const -{ - if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) - return true; - - auto& line = mLines[aAt.mLine]; - auto cindex = GetCharacterIndexR(aAt); - if (cindex >= (int)line.size()) - return true; - - if (mColorizerEnabled) - return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; - return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); -} - -void TextEditor::RemoveLines(int aStart, int aEnd) -{ - assert(!mReadOnly); - assert(aEnd >= aStart); - assert(mLines.size() > (size_t)(aEnd - aStart)); - - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) + int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { - ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); - if (e.first >= aStart && e.first <= aEnd) - continue; - etmp.insert(e); + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + int i = 0; + while (i < aIndex && i < (int)line.size()) + { + auto c = line[i].mChar; + i += UTF8CharLength(c); + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + } + return col; } - mErrorMarkers = std::move(etmp); - Breakpoints btmp; - for (auto i : mBreakpoints) + int TextEditor::GetLineCharacterCount(int aLine) const { - if (i >= aStart && i <= aEnd) - continue; - btmp.insert(i >= aStart ? i - 1 : i); + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int c = 0; + for (unsigned i = 0; i < line.size(); c++) + i += UTF8CharLength(line[i].mChar); + return c; } - mBreakpoints = std::move(btmp); - mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); - assert(!mLines.empty()); + int TextEditor::GetLineMaxColumn(int aLine) const + { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + for (unsigned i = 0; i < line.size(); ) + { + auto c = line[i].mChar; + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + i += UTF8CharLength(c); + } + return col; + } - mTextChanged = true; + bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const + { + if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) + return true; - OnLinesDeleted(aStart, aEnd); -} + auto& line = mLines[aAt.mLine]; + auto cindex = GetCharacterIndexR(aAt); + if (cindex >= (int)line.size()) + return true; -void TextEditor::RemoveLine(int aIndex, const std::unordered_set* aHandledCursors) -{ - assert(!mReadOnly); - assert(mLines.size() > 1); + if (mColorizerEnabled) + return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) - { - ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); - if (e.first - 1 == aIndex) - continue; - etmp.insert(e); + return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); } - mErrorMarkers = std::move(etmp); - Breakpoints btmp; - for (auto i : mBreakpoints) + void TextEditor::RemoveLines(int aStart, int aEnd) { - if (i == aIndex) - continue; - btmp.insert(i >= aIndex ? i - 1 : i); - } - mBreakpoints = std::move(btmp); + assert(!mReadOnly); + assert(aEnd >= aStart); + assert(mLines.size() > (size_t)(aEnd - aStart)); - mLines.erase(mLines.begin() + aIndex); - assert(!mLines.empty()); + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + { + ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); + if (e.first >= aStart && e.first <= aEnd) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); - mTextChanged = true; + Breakpoints btmp; + for (auto i : mBreakpoints) + { + if (i >= aStart && i <= aEnd) + continue; + btmp.insert(i >= aStart ? i - 1 : i); + } + mBreakpoints = std::move(btmp); - OnLineDeleted(aIndex, aHandledCursors); -} + mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); + assert(!mLines.empty()); -void TextEditor::RemoveCurrentLines() -{ - UndoRecord u; - u.mBefore = mState; + mTextChanged = true; - if (HasSelection()) - { - for (int c = mState.mCurrentCursor; c > -1; c--) - { - u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); - DeleteSelection(c); - } + OnLinesDeleted(aStart, aEnd); } - for (int c = mState.mCurrentCursor; c > -1; c--) + void TextEditor::RemoveLine(int aIndex, const std::unordered_set* aHandledCursors) { - int currentLine = mState.mCursors[c].mCursorPosition.mLine; - int nextLine = currentLine + 1; - int prevLine = currentLine - 1; + assert(!mReadOnly); + assert(mLines.size() > 1); - Coordinates toDeleteStart, toDeleteEnd; - if (mLines.size() > nextLine) // next line exists - { - toDeleteStart = Coordinates(currentLine, 0); - toDeleteEnd = Coordinates(nextLine, 0); - SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine, 0 }, c); - } - else if (prevLine > -1) // previous line exists + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) { - toDeleteStart = Coordinates(prevLine, GetLineMaxColumn(prevLine)); - toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine)); - SetCursorPosition({ prevLine, 0 }, c); + ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); + if (e.first - 1 == aIndex) + continue; + etmp.insert(e); } - else + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) { - toDeleteStart = Coordinates(currentLine, 0); - toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine)); - SetCursorPosition({ currentLine, 0 }, c); + if (i == aIndex) + continue; + btmp.insert(i >= aIndex ? i - 1 : i); } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aIndex); + assert(!mLines.empty()); - u.mOperations.push_back({ GetText(toDeleteStart, toDeleteEnd), toDeleteStart, toDeleteEnd, UndoOperationType::Delete }); + mTextChanged = true; - std::unordered_set handledCursors = { c }; - if (toDeleteStart.mLine != toDeleteEnd.mLine) - RemoveLine(currentLine, &handledCursors); - else - DeleteRange(toDeleteStart, toDeleteEnd); + OnLineDeleted(aIndex, aHandledCursors); } - u.mAfter = mState; - AddUndo(u); -} - -void TextEditor::OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted) -{ - static std::unordered_map cursorCharIndices; - if (aBeforeChange) + void TextEditor::RemoveCurrentLines() { - cursorCharIndices.clear(); - for (int c = 0; c <= mState.mCurrentCursor; c++) + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { - if (mState.mCursors[c].mCursorPosition.mLine == aLine) + for (int c = mState.mCurrentCursor; c > -1; c--) { - if (mState.mCursors[c].mCursorPosition.mColumn > aColumn) - { - cursorCharIndices[c] = GetCharacterIndexR({ aLine, mState.mCursors[c].mCursorPosition.mColumn }); - cursorCharIndices[c] += aDeleted ? -aCharCount : aCharCount; - } + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); + DeleteSelection(c); } } - } - else - { - for (auto& item : cursorCharIndices) - SetCursorPosition({ aLine, GetCharacterColumn(aLine, item.second) }, item.first); - } -} - -void TextEditor::RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar) -{ - int column = GetCharacterColumn(aLine, aStartChar); - int deltaX = GetCharacterColumn(aLine, aEndChar) - column; - auto& line = mLines[aLine]; - OnLineChanged(true, aLine, column, aEndChar - aStartChar, true); - line.erase(line.begin() + aStartChar, aEndChar == -1 ? line.end() : line.begin() + aEndChar); - OnLineChanged(false, aLine, column, aEndChar - aStartChar, true); -} - -void TextEditor::AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd) -{ - int targetColumn = GetCharacterColumn(aLine, aTargetIndex); - int charsInserted = std::distance(aSourceStart, aSourceEnd); - auto& line = mLines[aLine]; - OnLineChanged(true, aLine, targetColumn, charsInserted, false); - line.insert(line.begin() + aTargetIndex, aSourceStart, aSourceEnd); - OnLineChanged(false, aLine, targetColumn, charsInserted, false); -} - -void TextEditor::AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph) -{ - int targetColumn = GetCharacterColumn(aLine, aTargetIndex); - auto& line = mLines[aLine]; - OnLineChanged(true, aLine, targetColumn, 1, false); - line.insert(line.begin() + aTargetIndex, aGlyph); - OnLineChanged(false, aLine, targetColumn, 1, false); -} - -TextEditor::Line& TextEditor::InsertLine(int aIndex) -{ - assert(!mReadOnly); - - auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); - - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) - etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); - mErrorMarkers = std::move(etmp); - - Breakpoints btmp; - for (auto i : mBreakpoints) - btmp.insert(i >= aIndex ? i + 1 : i); - mBreakpoints = std::move(btmp); - - OnLineAdded(aIndex); - - return result; -} - -std::string TextEditor::GetWordUnderCursor() const -{ - auto c = GetCursorPosition(); - return GetWordAt(c); -} - -std::string TextEditor::GetWordAt(const Coordinates& aCoords) const -{ - auto start = FindWordStart(aCoords); - auto end = FindWordEnd(aCoords); - - std::string r; - - auto istart = GetCharacterIndexR(start); - auto iend = GetCharacterIndexR(end); - - for (auto it = istart; it < iend; ++it) - r.push_back(mLines[aCoords.mLine][it].mChar); - - return r; -} - -ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const -{ - if (!mColorizerEnabled) - return mPalette[(int)PaletteIndex::Default]; - if (aGlyph.mComment) - return mPalette[(int)PaletteIndex::Comment]; - if (aGlyph.mMultiLineComment) - return mPalette[(int)PaletteIndex::MultiLineComment]; - auto const color = mPalette[(int)aGlyph.mColorIndex]; - if (aGlyph.mPreprocessor) - { - const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor]; - const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2; - const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2; - const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2; - const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2; - return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)); - } - return color; -} - -bool TextEditor::IsGlyphWordChar(const Glyph& aGlyph) -{ - int sizeInBytes = UTF8CharLength(aGlyph.mChar); - return sizeInBytes > 1 || - aGlyph.mChar >= 'a' && aGlyph.mChar <= 'z' || - aGlyph.mChar >= 'A' && aGlyph.mChar <= 'Z' || - aGlyph.mChar >= '0' && aGlyph.mChar <= '9' || - aGlyph.mChar == '_'; -} - -void TextEditor::HandleKeyboardInputs(bool aParentIsFocused) -{ - if (ImGui::IsWindowFocused() || aParentIsFocused) - { - if (ImGui::IsWindowHovered()) - ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); - //ImGui::CaptureKeyboardFromApp(true); - ImGuiIO& io = ImGui::GetIO(); - auto isOSX = io.ConfigMacOSXBehaviors; - auto alt = io.KeyAlt; - auto ctrl = io.KeyCtrl; - auto shift = io.KeyShift; - auto super = io.KeySuper; - - auto isShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && !alt && !shift; - auto isShiftShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && shift && !alt; - auto isWordmoveKey = isOSX ? alt : ctrl; - auto isAltOnly = alt && !ctrl && !shift && !super; - auto isCtrlOnly = ctrl && !alt && !shift && !super; - auto isShiftOnly = shift && !alt && !ctrl && !super; - - io.WantCaptureKeyboard = true; - io.WantTextInput = true; - - if (!IsReadOnly() && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) - Undo(); - else if (!IsReadOnly() && isAltOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) - Undo(); - else if (!IsReadOnly() && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y))) - Redo(); - else if (!IsReadOnly() && isShiftShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) - Redo(); - else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) - MoveUp(1, shift); - else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) - MoveDown(1, shift); - else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) - MoveLeft(1, shift, isWordmoveKey); - else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) - MoveRight(1, shift, isWordmoveKey); - else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) - MoveUp(GetPageSize() - 4, shift); - else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) - MoveDown(GetPageSize() - 4, shift); - else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) - MoveTop(shift); - else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) - MoveBottom(shift); - else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) - MoveHome(shift); - else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) - MoveEnd(shift); - else if (!IsReadOnly() && !alt && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) - Delete(ctrl); - else if (!IsReadOnly() && !alt && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) - Backspace(ctrl); - else if (!IsReadOnly() && !alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_K))) - RemoveCurrentLines(); - else if (!IsReadOnly() && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftBracket))) - ChangeCurrentLinesIndentation(false); - else if (!IsReadOnly() && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightBracket))) - ChangeCurrentLinesIndentation(true); - else if (!IsReadOnly() && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Slash))) - ToggleLineComment(); - else if (!alt && !ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) - mOverwrite ^= true; - else if (isCtrlOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) - Copy(); - else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) - Copy(); - else if (!IsReadOnly() && isShiftOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) - Paste(); - else if (!IsReadOnly() && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) - Paste(); - else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) - Cut(); - else if (isShiftOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) - Cut(); - else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) - SelectAll(); - else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_D))) - AddCursorForNextOccurrence(); - else if (!IsReadOnly() && !alt && !ctrl && !shift && !super && (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_KeypadEnter)))) - EnterCharacter('\n', false); - else if (!IsReadOnly() && !alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) - EnterCharacter('\t', shift); - if (!IsReadOnly() && !io.InputQueueCharacters.empty() && !ctrl && !super) - { - for (int i = 0; i < io.InputQueueCharacters.Size; i++) - { - auto c = io.InputQueueCharacters[i]; - if (c != 0 && (c == '\n' || c >= 32)) - EnterCharacter(c, shift); - } - io.InputQueueCharacters.resize(0); - } - } -} - -void TextEditor::HandleMouseInputs() -{ - ImGuiIO& io = ImGui::GetIO(); - auto shift = io.KeyShift; - auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; - auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; - - /* - Pan with middle mouse button - */ - if (ImGui::IsMouseReleased(2)) - mState.mPanning = false; - if (mState.mPanning) - { - ImVec2 scroll = { ImGui::GetScrollX(), ImGui::GetScrollY() }; - ImVec2 currentMousePos = ImGui::GetMouseDragDelta(2); - ImVec2 mouseDelta = { - currentMousePos.x - mState.mLastMousePos.x, - currentMousePos.y - mState.mLastMousePos.y - }; - ImGui::SetScrollY(scroll.y - mouseDelta.y); - ImGui::SetScrollX(scroll.x - mouseDelta.x); - mState.mLastMousePos = currentMousePos; - } - - if (ImGui::IsWindowHovered()) - { - auto click = ImGui::IsMouseClicked(0); - if (!shift && !alt) - { - auto doubleClick = ImGui::IsMouseDoubleClicked(0); - auto t = ImGui::GetTime(); - auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); - - /* - Pan with middle mouse button - */ - - if (!mState.mPanning && ImGui::IsMouseDown(2)) - { - mState.mPanning = true; - mState.mLastMousePos = ImGui::GetMouseDragDelta(2); - } - - /* - Left mouse button triple click - */ - - if (tripleClick) - { - if (ctrl) - mState.AddCursor(); - else - mState.mCurrentCursor = 0; - - Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos()); - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = { cursorCoords.mLine, 0 }; - mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = - cursorCoords.mLine < mLines.size() - 1 ? - Coordinates{ cursorCoords.mLine + 1, 0 } : - Coordinates{ cursorCoords.mLine, GetCharacterColumn(cursorCoords.mLine, mLines[cursorCoords.mLine].size()) }; - mSelectionMode = SelectionMode::Normal; - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + int currentLine = mState.mCursors[c].mCursorPosition.mLine; + int nextLine = currentLine + 1; + int prevLine = currentLine - 1; - mLastClick = -1.0f; + Coordinates toDeleteStart, toDeleteEnd; + if (mLines.size() > nextLine) // next line exists + { + toDeleteStart = Coordinates(currentLine, 0); + toDeleteEnd = Coordinates(nextLine, 0); + SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine, 0 }, c); + } + else if (prevLine > -1) // previous line exists + { + toDeleteStart = Coordinates(prevLine, GetLineMaxColumn(prevLine)); + toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine)); + SetCursorPosition({ prevLine, 0 }, c); } + else + { + toDeleteStart = Coordinates(currentLine, 0); + toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine)); + SetCursorPosition({ currentLine, 0 }, c); + } + + u.mOperations.push_back({ GetText(toDeleteStart, toDeleteEnd), toDeleteStart, toDeleteEnd, UndoOperationType::Delete }); - /* - Left mouse button double click - */ + std::unordered_set handledCursors = { c }; + if (toDeleteStart.mLine != toDeleteEnd.mLine) + RemoveLine(currentLine, &handledCursors); + else + DeleteRange(toDeleteStart, toDeleteEnd); + } - else if (doubleClick) + u.mAfter = mState; + AddUndo(u); + } + + void TextEditor::OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted) + { + static std::unordered_map cursorCharIndices; + if (aBeforeChange) + { + cursorCharIndices.clear(); + for (int c = 0; c <= mState.mCurrentCursor; c++) { - if (ctrl) - mState.AddCursor(); - else - mState.mCurrentCursor = 0; + if (mState.mCursors[c].mCursorPosition.mLine == aLine) + { + if (mState.mCursors[c].mCursorPosition.mColumn > aColumn) + { + cursorCharIndices[c] = GetCharacterIndexR({ aLine, mState.mCursors[c].mCursorPosition.mColumn }); + cursorCharIndices[c] += aDeleted ? -aCharCount : aCharCount; + } + } + } + } + else + { + for (auto& item : cursorCharIndices) + SetCursorPosition({ aLine, GetCharacterColumn(aLine, item.second) }, item.first); + } + } - mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = FindWordStart(mState.mCursors[mState.mCurrentCursor].mCursorPosition); - mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = FindWordEnd(mState.mCursors[mState.mCurrentCursor].mCursorPosition); - if (mSelectionMode == SelectionMode::Line) - mSelectionMode = SelectionMode::Normal; - else - mSelectionMode = SelectionMode::Word; - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode); + void TextEditor::RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar) + { + int column = GetCharacterColumn(aLine, aStartChar); + int deltaX = GetCharacterColumn(aLine, aEndChar) - column; + auto& line = mLines[aLine]; + OnLineChanged(true, aLine, column, aEndChar - aStartChar, true); + line.erase(line.begin() + aStartChar, aEndChar == -1 ? line.end() : line.begin() + aEndChar); + OnLineChanged(false, aLine, column, aEndChar - aStartChar, true); + } + + void TextEditor::AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd) + { + int targetColumn = GetCharacterColumn(aLine, aTargetIndex); + int charsInserted = std::distance(aSourceStart, aSourceEnd); + auto& line = mLines[aLine]; + OnLineChanged(true, aLine, targetColumn, charsInserted, false); + line.insert(line.begin() + aTargetIndex, aSourceStart, aSourceEnd); + OnLineChanged(false, aLine, targetColumn, charsInserted, false); + } + + void TextEditor::AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph) + { + int targetColumn = GetCharacterColumn(aLine, aTargetIndex); + auto& line = mLines[aLine]; + OnLineChanged(true, aLine, targetColumn, 1, false); + line.insert(line.begin() + aTargetIndex, aGlyph); + OnLineChanged(false, aLine, targetColumn, 1, false); + } + + TextEditor::Line& TextEditor::InsertLine(int aIndex) + { + assert(!mReadOnly); + + auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) + btmp.insert(i >= aIndex ? i + 1 : i); + mBreakpoints = std::move(btmp); - mLastClick = (float)ImGui::GetTime(); + OnLineAdded(aIndex); + + return result; + } + + std::string TextEditor::GetWordUnderCursor() const + { + auto c = GetCursorPosition(); + return GetWordAt(c); + } + + std::string TextEditor::GetWordAt(const Coordinates& aCoords) const + { + auto start = FindWordStart(aCoords); + auto end = FindWordEnd(aCoords); + + std::string r; + + auto istart = GetCharacterIndexR(start); + auto iend = GetCharacterIndexR(end); + + for (auto it = istart; it < iend; ++it) + r.push_back(mLines[aCoords.mLine][it].mChar); + + return r; + } + + ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const + { + if (!mColorizerEnabled) + return mPalette[(int)PaletteIndex::Default]; + if (aGlyph.mComment) + return mPalette[(int)PaletteIndex::Comment]; + if (aGlyph.mMultiLineComment) + return mPalette[(int)PaletteIndex::MultiLineComment]; + auto const color = mPalette[(int)aGlyph.mColorIndex]; + if (aGlyph.mPreprocessor) + { + const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor]; + const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2; + const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2; + const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2; + const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2; + return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)); + } + return color; + } + + bool TextEditor::IsGlyphWordChar(const Glyph& aGlyph) + { + int sizeInBytes = UTF8CharLength(aGlyph.mChar); + return sizeInBytes > 1 || + aGlyph.mChar >= 'a' && aGlyph.mChar <= 'z' || + aGlyph.mChar >= 'A' && aGlyph.mChar <= 'Z' || + aGlyph.mChar >= '0' && aGlyph.mChar <= '9' || + aGlyph.mChar == '_'; + } + + void TextEditor::HandleKeyboardInputs(bool aParentIsFocused) + { + if (ImGui::IsWindowFocused() || aParentIsFocused) + { + if (ImGui::IsWindowHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); + //ImGui::CaptureKeyboardFromApp(true); + + ImGuiIO& io = ImGui::GetIO(); + auto isOSX = io.ConfigMacOSXBehaviors; + auto alt = io.KeyAlt; + auto ctrl = io.KeyCtrl; + auto shift = io.KeyShift; + auto super = io.KeySuper; + + auto isShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && !alt && !shift; + auto isShiftShortcut = (isOSX ? (super && !ctrl) : (ctrl && !super)) && shift && !alt; + auto isWordmoveKey = isOSX ? alt : ctrl; + auto isAltOnly = alt && !ctrl && !shift && !super; + auto isCtrlOnly = ctrl && !alt && !shift && !super; + auto isShiftOnly = shift && !alt && !ctrl && !super; + + io.WantCaptureKeyboard = true; + io.WantTextInput = true; + + if (!IsReadOnly() && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + Undo(); + else if (!IsReadOnly() && isAltOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Undo(); + else if (!IsReadOnly() && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y))) + Redo(); + else if (!IsReadOnly() && isShiftShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + Redo(); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + MoveUp(1, shift); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) + MoveDown(1, shift); + else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) + MoveLeft(1, shift, isWordmoveKey); + else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) + MoveRight(1, shift, isWordmoveKey); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) + MoveUp(GetPageSize() - 4, shift); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) + MoveDown(GetPageSize() - 4, shift); + else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + MoveTop(shift); + else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + MoveBottom(shift); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + MoveHome(shift); + else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + MoveEnd(shift); + else if (!IsReadOnly() && !alt && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + Delete(ctrl); + else if (!IsReadOnly() && !alt && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Backspace(ctrl); + else if (!IsReadOnly() && !alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_K))) + RemoveCurrentLines(); + else if (!IsReadOnly() && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftBracket))) + ChangeCurrentLinesIndentation(false); + else if (!IsReadOnly() && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightBracket))) + ChangeCurrentLinesIndentation(true); + else if (!IsReadOnly() && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Slash))) + ToggleLineComment(); + else if (!alt && !ctrl && !shift && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + mOverwrite ^= true; + else if (isCtrlOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + Copy(); + else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + Copy(); + else if (!IsReadOnly() && isShiftOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + Paste(); + else if (!IsReadOnly() && isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) + Paste(); + else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) + Cut(); + else if (isShiftOnly && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + Cut(); + else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) + SelectAll(); + else if (isShortcut && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_D))) + AddCursorForNextOccurrence(); + else if (!IsReadOnly() && !alt && !ctrl && !shift && !super && (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_KeypadEnter)))) + EnterCharacter('\n', false); + else if (!IsReadOnly() && !alt && !ctrl && !super && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) + EnterCharacter('\t', shift); + if (!IsReadOnly() && !io.InputQueueCharacters.empty() && !ctrl && !super) + { + for (int i = 0; i < io.InputQueueCharacters.Size; i++) + { + auto c = io.InputQueueCharacters[i]; + if (c != 0 && (c == '\n' || c >= 32)) + EnterCharacter(c, shift); + } + io.InputQueueCharacters.resize(0); } + } + } + + void TextEditor::HandleMouseInputs() + { + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + /* + Pan with middle mouse button + */ + if (ImGui::IsMouseReleased(2)) + mState.mPanning = false; + if (mState.mPanning) + { + ImVec2 scroll = { ImGui::GetScrollX(), ImGui::GetScrollY() }; + ImVec2 currentMousePos = ImGui::GetMouseDragDelta(2); + ImVec2 mouseDelta = { + currentMousePos.x - mState.mLastMousePos.x, + currentMousePos.y - mState.mLastMousePos.y + }; + ImGui::SetScrollY(scroll.y - mouseDelta.y); + ImGui::SetScrollX(scroll.x - mouseDelta.x); + mState.mLastMousePos = currentMousePos; + } - /* - Left mouse button click - */ - else if (click) + if (ImGui::IsWindowHovered()) + { + auto click = ImGui::IsMouseClicked(0); + if (!shift && !alt) { - if (ctrl) - mState.AddCursor(); - else - mState.mCurrentCursor = 0; + auto doubleClick = ImGui::IsMouseDoubleClicked(0); + auto t = ImGui::GetTime(); + auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); - bool isOverLineNumber; - Coordinates cursorCoords = mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite, &isOverLineNumber); - if (isOverLineNumber) + /* + Pan with middle mouse button + */ + + if (!mState.mPanning && ImGui::IsMouseDown(2)) { + mState.mPanning = true; + mState.mLastMousePos = ImGui::GetMouseDragDelta(2); + } + + /* + Left mouse button triple click + */ + + if (tripleClick) + { + if (ctrl) + mState.AddCursor(); + else + mState.mCurrentCursor = 0; + + Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos()); mState.mCursors[mState.mCurrentCursor].mInteractiveStart = { cursorCoords.mLine, 0 }; mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = cursorCoords.mLine < mLines.size() - 1 ? - Coordinates{ cursorCoords.mLine + 1, 0 } : - Coordinates{ cursorCoords.mLine, GetCharacterColumn(cursorCoords.mLine, mLines[cursorCoords.mLine].size()) }; + Coordinates{ cursorCoords.mLine + 1, 0 } : + Coordinates{ cursorCoords.mLine, GetCharacterColumn(cursorCoords.mLine, mLines[cursorCoords.mLine].size()) }; mSelectionMode = SelectionMode::Normal; + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode); + + mLastClick = -1.0f; } - else if (ctrl) - mSelectionMode = SelectionMode::Word; - else - mSelectionMode = SelectionMode::Normal; - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode, -1, ctrl); - mLastClick = (float)ImGui::GetTime(); - } - // Mouse left button dragging (=> update selection) - else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) - { - mDraggingSelection = true; - io.WantCaptureMouse = true; - mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite); - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode); - } - else if (ImGui::IsMouseReleased(0)) - { - mDraggingSelection = false; - mState.SortCursorsFromTopToBottom(); - MergeCursorsIfPossible(); + /* + Left mouse button double click + */ + + else if (doubleClick) + { + if (ctrl) + mState.AddCursor(); + else + mState.mCurrentCursor = 0; + + mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = FindWordStart(mState.mCursors[mState.mCurrentCursor].mCursorPosition); + mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = FindWordEnd(mState.mCursors[mState.mCurrentCursor].mCursorPosition); + if (mSelectionMode == SelectionMode::Line) + mSelectionMode = SelectionMode::Normal; + else + mSelectionMode = SelectionMode::Word; + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode); + + mLastClick = (float)ImGui::GetTime(); + } + + /* + Left mouse button click + */ + else if (click) + { + if (ctrl) + mState.AddCursor(); + else + mState.mCurrentCursor = 0; + + bool isOverLineNumber; + Coordinates cursorCoords = mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite, &isOverLineNumber); + if (isOverLineNumber) + { + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = { cursorCoords.mLine, 0 }; + mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = + cursorCoords.mLine < mLines.size() - 1 ? + Coordinates{ cursorCoords.mLine + 1, 0 } : + Coordinates{ cursorCoords.mLine, GetCharacterColumn(cursorCoords.mLine, mLines[cursorCoords.mLine].size()) }; + mSelectionMode = SelectionMode::Normal; + } + else if (ctrl) + mSelectionMode = SelectionMode::Word; + else + mSelectionMode = SelectionMode::Normal; + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode, -1, ctrl); + + mLastClick = (float)ImGui::GetTime(); + } + // Mouse left button dragging (=> update selection) + else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) + { + mDraggingSelection = true; + io.WantCaptureMouse = true; + mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite); + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode); + } + else if (ImGui::IsMouseReleased(0)) + { + mDraggingSelection = false; + mState.SortCursorsFromTopToBottom(); + MergeCursorsIfPossible(); + } } - } - else if (shift) - { - if (click) + else if (shift) { - Coordinates oldCursorPosition = mState.mCursors[mState.mCurrentCursor].mCursorPosition; - Coordinates newSelection = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite); - if (newSelection > mState.mCursors[mState.mCurrentCursor].mCursorPosition) - SetSelectionEnd(newSelection); - else - SetSelectionStart(newSelection); - mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = mState.mCursors[mState.mCurrentCursor].mSelectionEnd; - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mSelectionStart; - mState.mCursors[mState.mCurrentCursor].mCursorPosition = newSelection; - mState.mCursors[mState.mCurrentCursor].mCursorPositionChanged = oldCursorPosition != newSelection; + if (click) + { + Coordinates oldCursorPosition = mState.mCursors[mState.mCurrentCursor].mCursorPosition; + Coordinates newSelection = ScreenPosToCoordinates(ImGui::GetMousePos(), !mOverwrite); + if (newSelection > mState.mCursors[mState.mCurrentCursor].mCursorPosition) + SetSelectionEnd(newSelection); + else + SetSelectionStart(newSelection); + mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = mState.mCursors[mState.mCurrentCursor].mSelectionEnd; + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mSelectionStart; + mState.mCursors[mState.mCurrentCursor].mCursorPosition = newSelection; + mState.mCursors[mState.mCurrentCursor].mCursorPositionChanged = oldCursorPosition != newSelection; + } } } } -} -void TextEditor::UpdatePalette() -{ - /* Update palette with the current alpha from style */ - for (int i = 0; i < (int)PaletteIndex::Max; ++i) + void TextEditor::UpdatePalette() { - auto color = U32ColorToVec4(mPaletteBase[i]); - color.w *= ImGui::GetStyle().Alpha; - mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); + /* Update palette with the current alpha from style */ + for (int i = 0; i < (int)PaletteIndex::Max; ++i) + { + auto color = U32ColorToVec4(mPaletteBase[i]); + color.w *= ImGui::GetStyle().Alpha; + mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); + } } -} -void TextEditor::Render(bool aParentIsFocused) -{ - /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ - const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; - mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); + float _lastScrollY; + ImVec2 _lastContentSize; + void TextEditor::Render(bool aParentIsFocused) + { + /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ + const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; + mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); - assert(mLineBuffer.empty()); + assert(mLineBuffer.empty()); - auto contentSize = ImGui::GetWindowContentRegionMax(); - auto drawList = ImGui::GetWindowDrawList(); - float longest(mTextStart); + auto contentSize = ImGui::GetWindowContentRegionMax(); + auto drawList = ImGui::GetWindowDrawList(); + float longest(mTextStart); - if (mScrollToTop) - { - mScrollToTop = false; - ImGui::SetScrollY(0.f); - } + if (mScrollToTop) + { + mScrollToTop = false; + ImGui::SetScrollY(0.f); + } - ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); - auto scrollX = ImGui::GetScrollX(); - auto scrollY = ImGui::GetScrollY(); + ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); + auto scrollX = ImGui::GetScrollX(); + auto scrollY = ImGui::GetScrollY(); - auto lineNo = (int)floor(scrollY / mCharAdvance.y); - auto globalLineMax = (int)mLines.size(); - auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); + if (scrollY != _lastScrollY || contentSize.y != _lastContentSize.y) + mNeedsWindowColorization = true; - // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width - char buf[16]; - snprintf(buf, 16, " %d ", globalLineMax); - mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin; + int lineMin = mState.mLineMin = (int)floor(scrollY / mCharAdvance.y); + auto globalLineMax = (int)mLines.size(); + int lineMax = mState.mLineMax = std::max(0, std::min((int)mLines.size() - 1, mState.mLineMin + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); - if (!mLines.empty()) - { - float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; + // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width + char buf[16]; + snprintf(buf, 16, " %d ", globalLineMax); + mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin; - while (lineNo <= lineMax) + if (!mLines.empty()) { - ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y); - ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); + float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; - auto& line = mLines[lineNo]; - longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest); - auto columnNo = 0; - Coordinates lineStartCoord(lineNo, 0); - Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo)); - - // Draw selection for the current line - for (int c = 0; c <= mState.mCurrentCursor; c++) + while (lineMin <= lineMax) { - float sstart = -1.0f; - float ssend = -1.0f; + ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineMin * mCharAdvance.y); + ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); - assert(mState.mCursors[c].mSelectionStart <= mState.mCursors[c].mSelectionEnd); - if (mState.mCursors[c].mSelectionStart <= lineEndCoord) - sstart = mState.mCursors[c].mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mCursors[c].mSelectionStart) : 0.0f; - if (mState.mCursors[c].mSelectionEnd > lineStartCoord) - ssend = TextDistanceToLineStart(mState.mCursors[c].mSelectionEnd < lineEndCoord ? mState.mCursors[c].mSelectionEnd : lineEndCoord); + auto& line = mLines[lineMin]; + longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineMin, GetLineMaxColumn(lineMin))), longest); + auto columnNo = 0; + Coordinates lineStartCoord(lineMin, 0); + Coordinates lineEndCoord(lineMin, GetLineMaxColumn(lineMin)); - if (mState.mCursors[c].mSelectionEnd.mLine > lineNo) - ssend += mCharAdvance.x; - - if (sstart != -1 && ssend != -1 && sstart < ssend) + // Draw selection for the current line + for (int c = 0; c <= mState.mCurrentCursor; c++) { - ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); - ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); - } - } + float sstart = -1.0f; + float ssend = -1.0f; - // Draw breakpoints - auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); + assert(mState.mCursors[c].mSelectionStart <= mState.mCursors[c].mSelectionEnd); + if (mState.mCursors[c].mSelectionStart <= lineEndCoord) + sstart = mState.mCursors[c].mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mCursors[c].mSelectionStart) : 0.0f; + if (mState.mCursors[c].mSelectionEnd > lineStartCoord) + ssend = TextDistanceToLineStart(mState.mCursors[c].mSelectionEnd < lineEndCoord ? mState.mCursors[c].mSelectionEnd : lineEndCoord); - if (mBreakpoints.count(lineNo + 1) != 0) - { - auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); - } + if (mState.mCursors[c].mSelectionEnd.mLine > lineMin) + ssend += mCharAdvance.x; - // Draw error markers - auto errorIt = mErrorMarkers.find(lineNo + 1); - if (errorIt != mErrorMarkers.end()) - { - auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]); + if (sstart != -1 && ssend != -1 && sstart < ssend) + { + ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); + ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); + } + } + + // Draw breakpoints + auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); - if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) + if (mBreakpoints.count(lineMin + 1) != 0) { - ImGui::BeginTooltip(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); - ImGui::Text("Error at line %d:", errorIt->first); - ImGui::PopStyleColor(); - ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); - ImGui::Text("%s", errorIt->second.c_str()); - ImGui::PopStyleColor(); - ImGui::EndTooltip(); + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); } - } - // Draw line number (right aligned) - snprintf(buf, 16, "%d ", lineNo + 1); + // Draw error markers + auto errorIt = mErrorMarkers.find(lineMin + 1); + if (errorIt != mErrorMarkers.end()) + { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]); - auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x; - drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); + if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) + { + ImGui::BeginTooltip(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); + ImGui::Text("Error at line %d:", errorIt->first); + ImGui::PopStyleColor(); + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); + ImGui::Text("%s", errorIt->second.c_str()); + ImGui::PopStyleColor(); + ImGui::EndTooltip(); + } + } - std::vector cursorCoordsInThisLine; - for (int c = 0; c <= mState.mCurrentCursor; c++) - { - if (mState.mCursors[c].mCursorPosition.mLine == lineNo) - cursorCoordsInThisLine.push_back(mState.mCursors[c].mCursorPosition); - } - if (cursorCoordsInThisLine.size() > 0) - { - auto focused = ImGui::IsWindowFocused() || aParentIsFocused; + // Draw line number (right aligned) + snprintf(buf, 16, "%d ", lineMin + 1); + + auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x; + drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); - // Render the cursors - if (focused) + std::vector cursorCoordsInThisLine; + for (int c = 0; c <= mState.mCurrentCursor; c++) { - for (const auto& cursorCoords : cursorCoordsInThisLine) - { - float width = 1.0f; - auto cindex = GetCharacterIndexR(cursorCoords); - float cx = TextDistanceToLineStart(cursorCoords); + if (mState.mCursors[c].mCursorPosition.mLine == lineMin) + cursorCoordsInThisLine.push_back(mState.mCursors[c].mCursorPosition); + } + if (cursorCoordsInThisLine.size() > 0) + { + auto focused = ImGui::IsWindowFocused() || aParentIsFocused; - if (mOverwrite && cindex < (int)line.size()) + // Render the cursors + if (focused) + { + for (const auto& cursorCoords : cursorCoordsInThisLine) { - auto c = line[cindex].mChar; - if (c == '\t') - { - auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - width = x - cx; - } - else + float width = 1.0f; + auto cindex = GetCharacterIndexR(cursorCoords); + float cx = TextDistanceToLineStart(cursorCoords); + + if (mOverwrite && cindex < (int)line.size()) { - char buf2[2]; - buf2[0] = line[cindex].mChar; - buf2[1] = '\0'; - width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x; + auto c = line[cindex].mChar; + if (c == '\t') + { + auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + width = x - cx; + } + else + { + char buf2[2]; + buf2[0] = line[cindex].mChar; + buf2[1] = '\0'; + width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x; + } } + ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); + ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); } - ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); - ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); } } - } - // Render colorized text - auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); - ImVec2 bufferOffset; - - for (int i = 0; i < line.size();) - { - auto& glyph = line[i]; - auto color = GetGlyphColor(glyph); + // Render colorized text + auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); + ImVec2 bufferOffset; - if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) + for (int i = 0; i < line.size();) { - const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); - drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); - auto textSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, mLineBuffer.c_str(), nullptr, nullptr); - bufferOffset.x += textSize.x; - mLineBuffer.clear(); - } - prevColor = color; + auto& glyph = line[i]; + auto color = GetGlyphColor(glyph); - if (glyph.mChar == '\t') - { - auto oldX = bufferOffset.x; - bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - ++i; + if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) + { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + auto textSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, mLineBuffer.c_str(), nullptr, nullptr); + bufferOffset.x += textSize.x; + mLineBuffer.clear(); + } + prevColor = color; - if (mShowWhitespaces) + if (glyph.mChar == '\t') { - ImVec2 p1, p2, p3, p4; + auto oldX = bufferOffset.x; + bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + ++i; - if (mShowShortTabGlyphs) + if (mShowWhitespaces) { - const auto s = ImGui::GetFontSize(); - const auto x1 = textScreenPos.x + oldX + 1.0f; - const auto x2 = textScreenPos.x + oldX + mCharAdvance.x - 1.0f; - const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + ImVec2 p1, p2, p3, p4; - p1 = ImVec2(x1, y); - p2 = ImVec2(x2, y); - p3 = ImVec2(x2 - s * 0.16f, y - s * 0.16f); - p4 = ImVec2(x2 - s * 0.16f, y + s * 0.16f); + if (mShowShortTabGlyphs) + { + const auto s = ImGui::GetFontSize(); + const auto x1 = textScreenPos.x + oldX + 1.0f; + const auto x2 = textScreenPos.x + oldX + mCharAdvance.x - 1.0f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + + p1 = ImVec2(x1, y); + p2 = ImVec2(x2, y); + p3 = ImVec2(x2 - s * 0.16f, y - s * 0.16f); + p4 = ImVec2(x2 - s * 0.16f, y + s * 0.16f); + } + else + { + const auto s = ImGui::GetFontSize(); + const auto x1 = textScreenPos.x + oldX + 1.0f; + const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + + p1 = ImVec2(x1, y); + p2 = ImVec2(x2, y); + p3 = ImVec2(x2 - s * 0.2f, y - s * 0.2f); + p4 = ImVec2(x2 - s * 0.2f, y + s * 0.2f); + } + + drawList->AddLine(p1, p2, mPalette[(int)PaletteIndex::ControlCharacter]); + drawList->AddLine(p2, p3, mPalette[(int)PaletteIndex::ControlCharacter]); + drawList->AddLine(p2, p4, mPalette[(int)PaletteIndex::ControlCharacter]); } - else + } + else if (glyph.mChar == ' ') + { + if (mShowWhitespaces) { const auto s = ImGui::GetFontSize(); - const auto x1 = textScreenPos.x + oldX + 1.0f; - const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; + const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; - - p1 = ImVec2(x1, y); - p2 = ImVec2(x2, y); - p3 = ImVec2(x2 - s * 0.2f, y - s * 0.2f); - p4 = ImVec2(x2 - s * 0.2f, y + s * 0.2f); + drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); } - - drawList->AddLine(p1, p2, mPalette[(int)PaletteIndex::ControlCharacter]); - drawList->AddLine(p2, p3, mPalette[(int)PaletteIndex::ControlCharacter]); - drawList->AddLine(p2, p4, mPalette[(int)PaletteIndex::ControlCharacter]); + bufferOffset.x += spaceSize; + i++; } - } - else if (glyph.mChar == ' ') - { - if (mShowWhitespaces) + else { - const auto s = ImGui::GetFontSize(); - const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; - const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; - drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); + auto l = UTF8CharLength(glyph.mChar); + while (l-- > 0) + mLineBuffer.push_back(line[i++].mChar); } - bufferOffset.x += spaceSize; - i++; + ++columnNo; } - else + + if (!mLineBuffer.empty()) { - auto l = UTF8CharLength(glyph.mChar); - while (l-- > 0) - mLineBuffer.push_back(line[i++].mChar); + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + mLineBuffer.clear(); } - ++columnNo; - } - if (!mLineBuffer.empty()) - { - const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); - drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); - mLineBuffer.clear(); + ++lineMin; } - ++lineNo; - } - - // Draw a tooltip on known identifiers/preprocessor symbols - if (ImGui::IsMousePosValid() && ImGui::IsWindowHovered() && mLanguageDefinition != nullptr) - { - auto mpos = ImGui::GetMousePos(); - ImVec2 origin = ImGui::GetCursorScreenPos(); - ImVec2 local(mpos.x - origin.x, mpos.y - origin.y); - //printf("Mouse: pos(%g, %g), origin(%g, %g), local(%g, %g)\n", mpos.x, mpos.y, origin.x, origin.y, local.x, local.y); - if (local.x >= mTextStart) + // Draw a tooltip on known identifiers/preprocessor symbols + if (ImGui::IsMousePosValid() && ImGui::IsWindowHovered() && mLanguageDefinition != nullptr) { - auto pos = ScreenPosToCoordinates(mpos); - //printf("Coord(%d, %d)\n", pos.mLine, pos.mColumn); - auto id = GetWordAt(pos); - if (!id.empty()) + auto mpos = ImGui::GetMousePos(); + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImVec2 local(mpos.x - origin.x, mpos.y - origin.y); + //printf("Mouse: pos(%g, %g), origin(%g, %g), local(%g, %g)\n", mpos.x, mpos.y, origin.x, origin.y, local.x, local.y); + if (local.x >= mTextStart) { - auto it = mLanguageDefinition->mIdentifiers.find(id); - if (it != mLanguageDefinition->mIdentifiers.end()) - { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(it->second.mDeclaration.c_str()); - ImGui::EndTooltip(); - } - else + auto pos = ScreenPosToCoordinates(mpos); + //printf("Coord(%d, %d)\n", pos.mLine, pos.mColumn); + auto id = GetWordAt(pos); + if (!id.empty()) { - auto pi = mLanguageDefinition->mPreprocIdentifiers.find(id); - if (pi != mLanguageDefinition->mPreprocIdentifiers.end()) + auto it = mLanguageDefinition->mIdentifiers.find(id); + if (it != mLanguageDefinition->mIdentifiers.end()) { ImGui::BeginTooltip(); - ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::TextUnformatted(it->second.mDeclaration.c_str()); ImGui::EndTooltip(); } + else + { + auto pi = mLanguageDefinition->mPreprocIdentifiers.find(id); + if (pi != mLanguageDefinition->mPreprocIdentifiers.end()) + { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } + } } } } } - } - ImGui::SetCursorPos(ImVec2(0, 0)); - ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); + ImGui::SetCursorPos(ImVec2(0, 0)); + ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); - if (mScrollToCursor) - { - EnsureCursorVisible(); - mScrollToCursor = false; + if (mScrollToCursor) + { + EnsureCursorVisible(); + mScrollToCursor = false; + } + + _lastScrollY = scrollY; + _lastContentSize = contentSize; } -} -bool TextEditor::FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd) -{ - assert(aTextSize > 0); - for (int i = 0; i < mLines.size(); i++) + bool TextEditor::FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd) { - int currentLine = (aFrom.mLine + i) % mLines.size(); - int lineStartIndex = i == 0 ? GetCharacterIndexR(aFrom) : 0; - int aTextIndex = 0; - int j = lineStartIndex; - for (; j < mLines[currentLine].size(); j++) + assert(aTextSize > 0); + for (int i = 0; i < mLines.size(); i++) { + int currentLine = (aFrom.mLine + i) % mLines.size(); + int lineStartIndex = i == 0 ? GetCharacterIndexR(aFrom) : 0; + int aTextIndex = 0; + int j = lineStartIndex; + for (; j < mLines[currentLine].size(); j++) + { + if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') + break; + if (aText[aTextIndex] == mLines[currentLine][j].mChar) + aTextIndex++; + else + aTextIndex = 0; + } if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') - break; - if (aText[aTextIndex] == mLines[currentLine][j].mChar) - aTextIndex++; - else - aTextIndex = 0; + { + if (aText[aTextIndex] == '\0') + aTextSize = aTextIndex; + outStart = { currentLine, GetCharacterColumn(currentLine, j - aTextSize) }; + outEnd = { currentLine, GetCharacterColumn(currentLine, j) }; + return true; + } } - if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') + // in line where we started again but from char index 0 to aFrom.mColumn { - if (aText[aTextIndex] == '\0') - aTextSize = aTextIndex; - outStart = { currentLine, GetCharacterColumn(currentLine, j - aTextSize) }; - outEnd = { currentLine, GetCharacterColumn(currentLine, j) }; - return true; + int aTextIndex = 0; + int j = 0; + for (; j < GetCharacterIndexR(aFrom); j++) + { + if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') + break; + if (aText[aTextIndex] == mLines[aFrom.mLine][j].mChar) + aTextIndex++; + else + aTextIndex = 0; + } + if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') + { + if (aText[aTextIndex] == '\0') + aTextSize = aTextIndex; + outStart = { aFrom.mLine, GetCharacterColumn(aFrom.mLine, j - aTextSize) }; + outEnd = { aFrom.mLine, GetCharacterColumn(aFrom.mLine, j) }; + return true; + } } + return false; } - // in line where we started again but from char index 0 to aFrom.mColumn + + bool TextEditor::Render(const char* aTitle, bool aParentIsFocused, const ImVec2& aSize, bool aBorder) { - int aTextIndex = 0; - int j = 0; - for (; j < GetCharacterIndexR(aFrom); j++) - { - if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') - break; - if (aText[aTextIndex] == mLines[aFrom.mLine][j].mChar) - aTextIndex++; - else - aTextIndex = 0; - } - if (aTextIndex == aTextSize || aText[aTextIndex] == '\0') + for (int c = 0; c <= mState.mCurrentCursor; c++) { - if (aText[aTextIndex] == '\0') - aTextSize = aTextIndex; - outStart = { aFrom.mLine, GetCharacterColumn(aFrom.mLine, j - aTextSize) }; - outEnd = { aFrom.mLine, GetCharacterColumn(aFrom.mLine, j) }; - return true; + if (mState.mCursors[c].mCursorPositionChanged) + OnCursorPositionChanged(c); + if (c <= mState.mCurrentCursor) + mState.mCursors[c].mCursorPositionChanged = false; } - } - return false; -} -bool TextEditor::Render(const char* aTitle, bool aParentIsFocused, const ImVec2& aSize, bool aBorder) -{ - for (int c = 0; c <= mState.mCurrentCursor; c++) - { - if (mState.mCursors[c].mCursorPositionChanged) - OnCursorPositionChanged(c); - if (c <= mState.mCurrentCursor) - mState.mCursors[c].mCursorPositionChanged = false; - } + mWithinRender = true; + mTextChanged = false; - mWithinRender = true; - mTextChanged = false; + UpdatePalette(); - UpdatePalette(); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + if (!mIgnoreImGuiChild) + ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - if (!mIgnoreImGuiChild) - ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs); + bool isFocused = ImGui::IsWindowFocused(); + if (mHandleKeyboardInputs) + { + HandleKeyboardInputs(aParentIsFocused); + ImGui::PushAllowKeyboardFocus(true); + } - bool isFocused = ImGui::IsWindowFocused(); - if (mHandleKeyboardInputs) - { - HandleKeyboardInputs(aParentIsFocused); - ImGui::PushAllowKeyboardFocus(true); - } + if (mHandleMouseInputs) + HandleMouseInputs(); - if (mHandleMouseInputs) - HandleMouseInputs(); + ColorizeInternal(); + Render(aParentIsFocused); - ColorizeInternal(); - Render(aParentIsFocused); + if (mNeedsWindowColorization) { + Colorize(); + mNeedsWindowColorization = false; + } - if (mHandleKeyboardInputs) - ImGui::PopAllowKeyboardFocus(); + if (mHandleKeyboardInputs) + ImGui::PopAllowKeyboardFocus(); - if (!mIgnoreImGuiChild) - ImGui::EndChild(); + if (!mIgnoreImGuiChild) + ImGui::EndChild(); - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); - mWithinRender = false; - return isFocused; -} + mWithinRender = false; + return isFocused; + } -void TextEditor::SetText(const std::string& aText) -{ - mLines.clear(); - mLines.emplace_back(Line()); - for (auto chr : aText) + void TextEditor::SetText(const std::string& aText) { - if (chr == '\r') - { - // ignore the carriage return character - } - else if (chr == '\n') - mLines.emplace_back(Line()); - else + mLines.clear(); + mLines.emplace_back(Line()); + for (auto chr : aText) { - mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); + if (chr == '\r') + { + // ignore the carriage return character + } + else if (chr == '\n') + mLines.emplace_back(Line()); + else + { + mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); + } } - } - - mTextChanged = true; - mScrollToTop = true; - - mUndoBuffer.clear(); - mUndoIndex = 0; - Colorize(); -} - -void TextEditor::SetTextLines(const std::vector& aLines) -{ - mLines.clear(); + mTextChanged = true; + mScrollToTop = true; - if (aLines.empty()) - { - mLines.emplace_back(Line()); + mUndoBuffer.clear(); + mUndoIndex = 0; } - else + + void TextEditor::SetTextLines(const std::vector& aLines) { - mLines.resize(aLines.size()); + mLines.clear(); - for (size_t i = 0; i < aLines.size(); ++i) + if (aLines.empty()) { - const std::string& aLine = aLines[i]; - - mLines[i].reserve(aLine.size()); - for (size_t j = 0; j < aLine.size(); ++j) - mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); + mLines.emplace_back(Line()); } - } + else + { + mLines.resize(aLines.size()); - mTextChanged = true; - mScrollToTop = true; + for (size_t i = 0; i < aLines.size(); ++i) + { + const std::string& aLine = aLines[i]; - mUndoBuffer.clear(); - mUndoIndex = 0; + mLines[i].reserve(aLine.size()); + for (size_t j = 0; j < aLine.size(); ++j) + mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); + } + } - Colorize(); -} + mTextChanged = true; + mScrollToTop = true; + mUndoBuffer.clear(); + mUndoIndex = 0; -void TextEditor::ChangeCurrentLinesIndentation(bool aIncrease) -{ - assert(!mReadOnly); + Colorize(); + } - UndoRecord u; - u.mBefore = mState; - for (int c = mState.mCurrentCursor; c > -1; c--) + void TextEditor::ChangeCurrentLinesIndentation(bool aIncrease) { - auto start = mState.mCursors[c].mSelectionStart; - auto end = mState.mCursors[c].mSelectionEnd; - auto originalEnd = end; + assert(!mReadOnly); - if (start > end) - std::swap(start, end); - start.mColumn = 0; - // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; - if (end.mColumn == 0 && end.mLine > 0) - --end.mLine; - if (end.mLine >= (int)mLines.size()) - end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; - end.mColumn = GetLineMaxColumn(end.mLine); + UndoRecord u; + u.mBefore = mState; - //if (end.mColumn >= GetLineMaxColumn(end.mLine)) - // end.mColumn = GetLineMaxColumn(end.mLine) - 1; + for (int c = mState.mCurrentCursor; c > -1; c--) + { + auto start = mState.mCursors[c].mSelectionStart; + auto end = mState.mCursors[c].mSelectionEnd; + auto originalEnd = end; - UndoOperation removeOperation = { GetText(start, end) , start, end, UndoOperationType::Delete }; + if (start > end) + std::swap(start, end); + start.mColumn = 0; + // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; + if (end.mColumn == 0 && end.mLine > 0) + --end.mLine; + if (end.mLine >= (int)mLines.size()) + end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; + end.mColumn = GetLineMaxColumn(end.mLine); - bool modified = false; + //if (end.mColumn >= GetLineMaxColumn(end.mLine)) + // end.mColumn = GetLineMaxColumn(end.mLine) - 1; - for (int i = start.mLine; i <= end.mLine; i++) - { - auto& line = mLines[i]; - if (!aIncrease) - { - if (!line.empty()) + UndoOperation removeOperation = { GetText(start, end) , start, end, UndoOperationType::Delete }; + + bool modified = false; + for (int i = start.mLine; i <= end.mLine; i++) + { + auto& line = mLines[i]; + if (!aIncrease) { - if (line.front().mChar == '\t') - { - RemoveGlyphsFromLine(i, 0, 1); - modified = true; - } - else + if (!line.empty()) + { - for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) + if (line.front().mChar == '\t') { RemoveGlyphsFromLine(i, 0, 1); modified = true; } + else + { + for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) + { + RemoveGlyphsFromLine(i, 0, 1); + modified = true; + } + } } } + else if (mLines[i].size() > 0) + { + AddGlyphToLine(i, 0, Glyph('\t', TextEditor::PaletteIndex::Background)); + modified = true; + } } - else if (mLines[i].size() > 0) - { - AddGlyphToLine(i, 0, Glyph('\t', TextEditor::PaletteIndex::Background)); - modified = true; - } - } - if (modified) - { - start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); - Coordinates rangeEnd; - std::string addedText; - if (originalEnd.mColumn != 0) - { - end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); - rangeEnd = end; - addedText = GetText(start, end); - } - else + if (modified) { - end = Coordinates(originalEnd.mLine, 0); - rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); - addedText = GetText(start, rangeEnd); - } + start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); + Coordinates rangeEnd; + std::string addedText; + if (originalEnd.mColumn != 0) + { + end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); + rangeEnd = end; + addedText = GetText(start, end); + } + else + { + end = Coordinates(originalEnd.mLine, 0); + rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); + addedText = GetText(start, rangeEnd); + } - u.mOperations.push_back(removeOperation); - u.mOperations.push_back({ addedText, start, rangeEnd, UndoOperationType::Add }); - u.mAfter = mState; + u.mOperations.push_back(removeOperation); + u.mOperations.push_back({ addedText, start, rangeEnd, UndoOperationType::Add }); + u.mAfter = mState; - mState.mCursors[c].mSelectionStart = start; - mState.mCursors[c].mSelectionEnd = end; + mState.mCursors[c].mSelectionStart = start; + mState.mCursors[c].mSelectionEnd = end; - mTextChanged = true; + mTextChanged = true; + } } - } - - EnsureCursorVisible(); - if (u.mOperations.size() > 0) - AddUndo(u); -} -void TextEditor::ToggleLineComment() -{ - assert(!mReadOnly); - if (mLanguageDefinition == nullptr) - return; - const std::string& commentString = mLanguageDefinition->mSingleLineComment; - - UndoRecord u; - u.mBefore = mState; - - bool shouldAddComment = false; - for (int c = mState.mCurrentCursor; c > -1 && !shouldAddComment; c--) - { - for (int currentLine = mState.mCursors[c].mSelectionEnd.mLine; currentLine >= mState.mCursors[c].mSelectionStart.mLine && !shouldAddComment; currentLine--) - { - if (Coordinates{ currentLine, 0 } == mState.mCursors[c].mSelectionEnd && mState.mCursors[c].mSelectionEnd != mState.mCursors[c].mSelectionStart) // when selection ends at line start - continue; - int currentIndex = 0; - while (currentIndex < mLines[currentLine].size() && (mLines[currentLine][currentIndex].mChar == ' ' || mLines[currentLine][currentIndex].mChar == '\t')) currentIndex++; - if (currentIndex == mLines[currentLine].size()) - continue; - int i = 0; - while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++; - bool matched = i == commentString.length(); - shouldAddComment |= !matched; - } + EnsureCursorVisible(); + if (u.mOperations.size() > 0) + AddUndo(u); } - if (shouldAddComment) - { - for (int c = mState.mCurrentCursor; c > -1; c--) - { - for (int currentLine = mState.mCursors[c].mSelectionEnd.mLine; currentLine >= mState.mCursors[c].mSelectionStart.mLine; currentLine--) - { - if (Coordinates{ currentLine, 0 } == mState.mCursors[c].mSelectionEnd && mState.mCursors[c].mSelectionEnd != mState.mCursors[c].mSelectionStart) // when selection ends at line start - continue; - Coordinates lineStart = { currentLine, 0 }; - Coordinates insertionEnd = lineStart; - InsertTextAt(insertionEnd, (commentString + ' ').c_str()); // sets insertion end - u.mOperations.push_back({ (commentString + ' ') , lineStart, insertionEnd, UndoOperationType::Add }); - Colorize(lineStart.mLine, 1); - } - } - } - else + void TextEditor::ToggleLineComment() { - for (int c = mState.mCurrentCursor; c > -1; c--) + assert(!mReadOnly); + if (mLanguageDefinition == nullptr) + return; + const std::string& commentString = mLanguageDefinition->mSingleLineComment; + + UndoRecord u; + u.mBefore = mState; + + bool shouldAddComment = false; + for (int c = mState.mCurrentCursor; c > -1 && !shouldAddComment; c--) { - for (int currentLine = mState.mCursors[c].mSelectionEnd.mLine; currentLine >= mState.mCursors[c].mSelectionStart.mLine; currentLine--) + for (int currentLine = mState.mCursors[c].mSelectionEnd.mLine; currentLine >= mState.mCursors[c].mSelectionStart.mLine && !shouldAddComment; currentLine--) { if (Coordinates{ currentLine, 0 } == mState.mCursors[c].mSelectionEnd && mState.mCursors[c].mSelectionEnd != mState.mCursors[c].mSelectionStart) // when selection ends at line start continue; @@ -1726,313 +1704,477 @@ void TextEditor::ToggleLineComment() int i = 0; while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++; bool matched = i == commentString.length(); - assert(matched); - if (currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == ' ') - i++; - - Coordinates start = { currentLine, GetCharacterColumn(currentLine, currentIndex) }; - Coordinates end = { currentLine, GetCharacterColumn(currentLine, currentIndex + i) }; - u.mOperations.push_back({ GetText(start, end) , start, end, UndoOperationType::Delete}); - DeleteRange(start, end); - Colorize(currentLine, 1); + shouldAddComment |= !matched; } } - } - - u.mAfter = mState; - AddUndo(u); -} - -void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) -{ - assert(!mReadOnly); - bool hasSelection = HasSelection(); - bool anyCursorHasMultilineSelection = false; - for (int c = mState.mCurrentCursor; c > -1; c--) - if (mState.mCursors[c].mSelectionStart.mLine != mState.mCursors[c].mSelectionEnd.mLine) + if (shouldAddComment) { - anyCursorHasMultilineSelection = true; - break; + for (int c = mState.mCurrentCursor; c > -1; c--) + { + for (int currentLine = mState.mCursors[c].mSelectionEnd.mLine; currentLine >= mState.mCursors[c].mSelectionStart.mLine; currentLine--) + { + if (Coordinates{ currentLine, 0 } == mState.mCursors[c].mSelectionEnd && mState.mCursors[c].mSelectionEnd != mState.mCursors[c].mSelectionStart) // when selection ends at line start + continue; + Coordinates lineStart = { currentLine, 0 }; + Coordinates insertionEnd = lineStart; + InsertTextAt(insertionEnd, (commentString + ' ').c_str()); // sets insertion end + u.mOperations.push_back({ (commentString + ' ') , lineStart, insertionEnd, UndoOperationType::Add }); + Colorize(lineStart.mLine, 1); + } + } + } + else + { + for (int c = mState.mCurrentCursor; c > -1; c--) + { + for (int currentLine = mState.mCursors[c].mSelectionEnd.mLine; currentLine >= mState.mCursors[c].mSelectionStart.mLine; currentLine--) + { + if (Coordinates{ currentLine, 0 } == mState.mCursors[c].mSelectionEnd && mState.mCursors[c].mSelectionEnd != mState.mCursors[c].mSelectionStart) // when selection ends at line start + continue; + int currentIndex = 0; + while (currentIndex < mLines[currentLine].size() && (mLines[currentLine][currentIndex].mChar == ' ' || mLines[currentLine][currentIndex].mChar == '\t')) currentIndex++; + if (currentIndex == mLines[currentLine].size()) + continue; + int i = 0; + while (i < commentString.length() && currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == commentString[i]) i++; + bool matched = i == commentString.length(); + assert(matched); + if (currentIndex + i < mLines[currentLine].size() && mLines[currentLine][currentIndex + i].mChar == ' ') + i++; + + Coordinates start = { currentLine, GetCharacterColumn(currentLine, currentIndex) }; + Coordinates end = { currentLine, GetCharacterColumn(currentLine, currentIndex + i) }; + u.mOperations.push_back({ GetText(start, end) , start, end, UndoOperationType::Delete}); + DeleteRange(start, end); + Colorize(currentLine, 1); + } + } } - bool isIndentOperation = hasSelection && anyCursorHasMultilineSelection && aChar == '\t'; - if (isIndentOperation) - { - ChangeCurrentLinesIndentation(!aShift); - return; - } - UndoRecord u; - u.mBefore = mState; + u.mAfter = mState; + AddUndo(u); + } - if (hasSelection) + void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { + assert(!mReadOnly); + + bool hasSelection = HasSelection(); + bool anyCursorHasMultilineSelection = false; for (int c = mState.mCurrentCursor; c > -1; c--) + if (mState.mCursors[c].mSelectionStart.mLine != mState.mCursors[c].mSelectionEnd.mLine) + { + anyCursorHasMultilineSelection = true; + break; + } + bool isIndentOperation = hasSelection && anyCursorHasMultilineSelection && aChar == '\t'; + if (isIndentOperation) { - u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); - DeleteSelection(c); + ChangeCurrentLinesIndentation(!aShift); + return; } - } // HasSelection - std::vector coords; - for (int c = mState.mCurrentCursor; c > -1; c--) // order important here for typing \n in the same line at the same time - { - auto coord = GetActualCursorCoordinates(c); - coords.push_back(coord); - UndoOperation added; - added.mType = UndoOperationType::Add; - added.mStart = coord; + UndoRecord u; + u.mBefore = mState; - assert(!mLines.empty()); + if (hasSelection) + { + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); + DeleteSelection(c); + } + } // HasSelection - if (aChar == '\n') + std::vector coords; + for (int c = mState.mCurrentCursor; c > -1; c--) // order important here for typing \n in the same line at the same time { - InsertLine(coord.mLine + 1); - auto& line = mLines[coord.mLine]; - auto& newLine = mLines[coord.mLine + 1]; + auto coord = GetActualCursorCoordinates(c); + coords.push_back(coord); + UndoOperation added; + added.mType = UndoOperationType::Add; + added.mStart = coord; - added.mText = ""; - added.mText += (char)aChar; - if (mLanguageDefinition != nullptr && mLanguageDefinition->mAutoIndentation) - for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) - { - newLine.push_back(line[it]); - added.mText += line[it].mChar; - } + assert(!mLines.empty()); - const size_t whitespaceSize = newLine.size(); - auto cindex = GetCharacterIndexR(coord); - AddGlyphsToLine(coord.mLine + 1, newLine.size(), line.begin() + cindex, line.end()); - RemoveGlyphsFromLine(coord.mLine, cindex); - SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)), c); - } - else - { - char buf[7]; - int e = ImTextCharToUtf8(buf, 7, aChar); - if (e > 0) + if (aChar == '\n') { - buf[e] = '\0'; + InsertLine(coord.mLine + 1); auto& line = mLines[coord.mLine]; - auto cindex = GetCharacterIndexR(coord); + auto& newLine = mLines[coord.mLine + 1]; - if (mOverwrite && cindex < (int)line.size()) - { - auto d = UTF8CharLength(line[cindex].mChar); + added.mText = ""; + added.mText += (char)aChar; + if (mLanguageDefinition != nullptr && mLanguageDefinition->mAutoIndentation) + for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) + { + newLine.push_back(line[it]); + added.mText += line[it].mChar; + } - UndoOperation removed; - removed.mType = UndoOperationType::Delete; - removed.mStart = mState.mCursors[c].mCursorPosition; - removed.mEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); + const size_t whitespaceSize = newLine.size(); + auto cindex = GetCharacterIndexR(coord); + AddGlyphsToLine(coord.mLine + 1, newLine.size(), line.begin() + cindex, line.end()); + RemoveGlyphsFromLine(coord.mLine, cindex); + SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)), c); + } + else + { + char buf[7]; + int e = ImTextCharToUtf8(buf, 7, aChar); + if (e > 0) + { + buf[e] = '\0'; + auto& line = mLines[coord.mLine]; + auto cindex = GetCharacterIndexR(coord); - while (d-- > 0 && cindex < (int)line.size()) + if (mOverwrite && cindex < (int)line.size()) { - removed.mText += line[cindex].mChar; - RemoveGlyphsFromLine(coord.mLine, cindex, cindex + 1); + auto d = UTF8CharLength(line[cindex].mChar); + + UndoOperation removed; + removed.mType = UndoOperationType::Delete; + removed.mStart = mState.mCursors[c].mCursorPosition; + removed.mEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); + + while (d-- > 0 && cindex < (int)line.size()) + { + removed.mText += line[cindex].mChar; + RemoveGlyphsFromLine(coord.mLine, cindex, cindex + 1); + } + u.mOperations.push_back(removed); } - u.mOperations.push_back(removed); - } - for (auto p = buf; *p != '\0'; p++, ++cindex) - AddGlyphToLine(coord.mLine, cindex, Glyph(*p, PaletteIndex::Default)); - added.mText = buf; + for (auto p = buf; *p != '\0'; p++, ++cindex) + AddGlyphToLine(coord.mLine, cindex, Glyph(*p, PaletteIndex::Default)); + added.mText = buf; - SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)), c); + SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)), c); + } + else + continue; } - else - continue; + + mTextChanged = true; + + added.mEnd = GetActualCursorCoordinates(c); + u.mOperations.push_back(added); } - mTextChanged = true; + u.mAfter = mState; + AddUndo(u); - added.mEnd = GetActualCursorCoordinates(c); - u.mOperations.push_back(added); + for (const auto& coord : coords) + Colorize(coord.mLine - 1, 3); + EnsureCursorVisible(); } - u.mAfter = mState; - AddUndo(u); + void TextEditor::SetReadOnly(bool aValue) + { + mReadOnly = aValue; + } - for (const auto& coord : coords) - Colorize(coord.mLine - 1, 3); - EnsureCursorVisible(); -} + void TextEditor::OnCursorPositionChanged(int aCursor) + { + if (mDraggingSelection) + return; -void TextEditor::SetReadOnly(bool aValue) -{ - mReadOnly = aValue; -} + //std::cout << "Cursor position changed\n"; + mState.SortCursorsFromTopToBottom(); + MergeCursorsIfPossible(); + } -void TextEditor::OnCursorPositionChanged(int aCursor) -{ - if (mDraggingSelection) - return; + void TextEditor::SetColorizerEnable(bool aValue) + { + mColorizerEnabled = aValue; + } - //std::cout << "Cursor position changed\n"; - mState.SortCursorsFromTopToBottom(); - MergeCursorsIfPossible(); -} + void TextEditor::SetCursorPosition(const Coordinates& aPosition, int aCursor) + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; -void TextEditor::SetColorizerEnable(bool aValue) -{ - mColorizerEnabled = aValue; -} + //std::string log = "Moved cursor " + std::to_string(aCursor) + " from " + + // std::to_string(mState.mCursors[aCursor].mCursorPosition.mLine) + "," + std::to_string(mState.mCursors[aCursor].mCursorPosition.mColumn) + " to "; -void TextEditor::SetCursorPosition(const Coordinates& aPosition, int aCursor) -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; + if (mState.mCursors[aCursor].mCursorPosition != aPosition) + { + mState.mCursors[aCursor].mCursorPosition = aPosition; + mState.mCursors[aCursor].mSelectionEnd = mState.mCursors[aCursor].mSelectionStart = aPosition; + mState.mCursors[aCursor].mInteractiveEnd = mState.mCursors[aCursor].mInteractiveStart = aPosition; + mState.mCursors[aCursor].mCursorPositionChanged = true; + EnsureCursorVisible(); + } - //std::string log = "Moved cursor " + std::to_string(aCursor) + " from " + - // std::to_string(mState.mCursors[aCursor].mCursorPosition.mLine) + "," + std::to_string(mState.mCursors[aCursor].mCursorPosition.mColumn) + " to "; + //log += std::to_string(mState.mCursors[aCursor].mCursorPosition.mLine) + "," + std::to_string(mState.mCursors[aCursor].mCursorPosition.mColumn); + //std::cout << log << std::endl; + } - if (mState.mCursors[aCursor].mCursorPosition != aPosition) + void TextEditor::SetCursorPosition(int aLine, int aCharIndex, int aCursor) { - mState.mCursors[aCursor].mCursorPosition = aPosition; - mState.mCursors[aCursor].mSelectionEnd = mState.mCursors[aCursor].mSelectionStart = aPosition; - mState.mCursors[aCursor].mInteractiveEnd = mState.mCursors[aCursor].mInteractiveStart = aPosition; - mState.mCursors[aCursor].mCursorPositionChanged = true; - EnsureCursorVisible(); + SetCursorPosition({ aLine, GetCharacterColumn(aLine, aCharIndex) }, aCursor); } - //log += std::to_string(mState.mCursors[aCursor].mCursorPosition.mLine) + "," + std::to_string(mState.mCursors[aCursor].mCursorPosition.mColumn); - //std::cout << log << std::endl; -} + void TextEditor::SetSelectionStart(const Coordinates& aPosition, int aCursor) + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; -void TextEditor::SetCursorPosition(int aLine, int aCharIndex, int aCursor) -{ - SetCursorPosition({ aLine, GetCharacterColumn(aLine, aCharIndex) }, aCursor); -} + mState.mCursors[aCursor].mSelectionStart = SanitizeCoordinates(aPosition); + if (mState.mCursors[aCursor].mSelectionStart > mState.mCursors[aCursor].mSelectionEnd) + std::swap(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); + } -void TextEditor::SetSelectionStart(const Coordinates& aPosition, int aCursor) -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; + void TextEditor::SetSelectionEnd(const Coordinates& aPosition, int aCursor) + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; - mState.mCursors[aCursor].mSelectionStart = SanitizeCoordinates(aPosition); - if (mState.mCursors[aCursor].mSelectionStart > mState.mCursors[aCursor].mSelectionEnd) - std::swap(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); -} + mState.mCursors[aCursor].mSelectionEnd = SanitizeCoordinates(aPosition); + if (mState.mCursors[aCursor].mSelectionStart > mState.mCursors[aCursor].mSelectionEnd) + std::swap(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); + } -void TextEditor::SetSelectionEnd(const Coordinates& aPosition, int aCursor) -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; + void TextEditor::SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode, int aCursor, bool isSpawningNewCursor) + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; - mState.mCursors[aCursor].mSelectionEnd = SanitizeCoordinates(aPosition); - if (mState.mCursors[aCursor].mSelectionStart > mState.mCursors[aCursor].mSelectionEnd) - std::swap(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); -} + auto oldSelStart = mState.mCursors[aCursor].mSelectionStart; + auto oldSelEnd = mState.mCursors[aCursor].mSelectionEnd; -void TextEditor::SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode, int aCursor, bool isSpawningNewCursor) -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; + mState.mCursors[aCursor].mSelectionStart = SanitizeCoordinates(aStart); + mState.mCursors[aCursor].mSelectionEnd = SanitizeCoordinates(aEnd); + if (mState.mCursors[aCursor].mSelectionStart > mState.mCursors[aCursor].mSelectionEnd) + std::swap(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); - auto oldSelStart = mState.mCursors[aCursor].mSelectionStart; - auto oldSelEnd = mState.mCursors[aCursor].mSelectionEnd; + switch (aMode) + { + case TextEditor::SelectionMode::Normal: + case TextEditor::SelectionMode::Word: + break; + case TextEditor::SelectionMode::Line: + { + const auto lineNo = mState.mCursors[aCursor].mSelectionEnd.mLine; + const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0; + mState.mCursors[aCursor].mSelectionStart = Coordinates(mState.mCursors[aCursor].mSelectionStart.mLine, 0); + mState.mCursors[aCursor].mSelectionEnd = mLines.size() > lineNo + 1 ? Coordinates(lineNo + 1, 0) : Coordinates(lineNo, GetLineMaxColumn(lineNo)); + mState.mCursors[aCursor].mCursorPosition = mState.mCursors[aCursor].mSelectionEnd; + break; + } + default: + break; + } - mState.mCursors[aCursor].mSelectionStart = SanitizeCoordinates(aStart); - mState.mCursors[aCursor].mSelectionEnd = SanitizeCoordinates(aEnd); - if (mState.mCursors[aCursor].mSelectionStart > mState.mCursors[aCursor].mSelectionEnd) - std::swap(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); + if (mState.mCursors[aCursor].mSelectionStart != oldSelStart || + mState.mCursors[aCursor].mSelectionEnd != oldSelEnd) + if (!isSpawningNewCursor) + mState.mCursors[aCursor].mCursorPositionChanged = true; + } - switch (aMode) + void TextEditor::SetSelection(int aStartLine, int aStartCharIndex, int aEndLine, int aEndCharIndex, SelectionMode aMode, int aCursor, bool isSpawningNewCursor) { - case TextEditor::SelectionMode::Normal: - case TextEditor::SelectionMode::Word: - break; - case TextEditor::SelectionMode::Line: + SetSelection( + { aStartLine, GetCharacterColumn(aStartLine, aStartCharIndex) }, + { aEndLine, GetCharacterColumn(aEndLine, aEndCharIndex) }, + aMode, aCursor, isSpawningNewCursor); + } + + void TextEditor::SetTabSize(int aValue) { - const auto lineNo = mState.mCursors[aCursor].mSelectionEnd.mLine; - const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0; - mState.mCursors[aCursor].mSelectionStart = Coordinates(mState.mCursors[aCursor].mSelectionStart.mLine, 0); - mState.mCursors[aCursor].mSelectionEnd = mLines.size() > lineNo + 1 ? Coordinates(lineNo + 1, 0) : Coordinates(lineNo, GetLineMaxColumn(lineNo)); - mState.mCursors[aCursor].mCursorPosition = mState.mCursors[aCursor].mSelectionEnd; - break; + mTabSize = std::max(0, std::min(32, aValue)); } - default: - break; + + void TextEditor::InsertText(const std::string& aValue, int aCursor) + { + InsertText(aValue.c_str(), aCursor); + } + + void TextEditor::InsertText(const char* aValue, int aCursor) + { + if (aValue == nullptr) + return; + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + auto pos = GetActualCursorCoordinates(aCursor); + auto start = std::min(pos, mState.mCursors[aCursor].mSelectionStart); + int totalLines = pos.mLine - start.mLine; + + totalLines += InsertTextAt(pos, aValue); + + SetSelection(pos, pos, SelectionMode::Normal, aCursor); + SetCursorPosition(pos, aCursor); + Colorize(start.mLine - 1, totalLines + 2); + } + + void TextEditor::DeleteSelection(int aCursor) + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + assert(mState.mCursors[aCursor].mSelectionEnd >= mState.mCursors[aCursor].mSelectionStart); + + if (mState.mCursors[aCursor].mSelectionEnd == mState.mCursors[aCursor].mSelectionStart) + return; + + DeleteRange(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); + + SetSelection(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionStart, SelectionMode::Normal, aCursor); + SetCursorPosition(mState.mCursors[aCursor].mSelectionStart, aCursor); + mState.mCursors[aCursor].mInteractiveStart = mState.mCursors[aCursor].mSelectionStart; + mState.mCursors[aCursor].mInteractiveEnd = mState.mCursors[aCursor].mSelectionEnd; + Colorize(mState.mCursors[aCursor].mSelectionStart.mLine, 1); + } + + void TextEditor::MoveUp(int aAmount, bool aSelect) + { + if (HasSelection() && !aSelect) + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + SetSelection(mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionStart, SelectionMode::Normal, c); + SetCursorPosition(mState.mCursors[c].mSelectionStart, c); + } + } + else + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + auto oldPos = mState.mCursors[c].mCursorPosition; + mState.mCursors[c].mCursorPosition.mLine = std::max(0, mState.mCursors[c].mCursorPosition.mLine - aAmount); + if (oldPos != mState.mCursors[c].mCursorPosition) + { + if (aSelect) + { + if (oldPos == mState.mCursors[c].mInteractiveStart) + mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; + else if (oldPos == mState.mCursors[c].mInteractiveEnd) + mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; + else + { + mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; + mState.mCursors[c].mInteractiveEnd = oldPos; + } + } + else + mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; + SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); + } + } + } + EnsureCursorVisible(); + } + + void TextEditor::MoveDown(int aAmount, bool aSelect) + { + if (HasSelection() && !aSelect) + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + SetSelection(mState.mCursors[c].mSelectionEnd, mState.mCursors[c].mSelectionEnd, SelectionMode::Normal, c); + SetCursorPosition(mState.mCursors[c].mSelectionEnd, c); + } + } + else + { + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + assert(mState.mCursors[c].mCursorPosition.mColumn >= 0); + auto oldPos = mState.mCursors[c].mCursorPosition; + mState.mCursors[c].mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursors[c].mCursorPosition.mLine + aAmount)); + + if (mState.mCursors[c].mCursorPosition != oldPos) + { + if (aSelect) + { + if (oldPos == mState.mCursors[c].mInteractiveEnd) + mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; + else if (oldPos == mState.mCursors[c].mInteractiveStart) + mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; + else + { + mState.mCursors[c].mInteractiveStart = oldPos; + mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; + } + } + else + mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; + SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); + + } + } + } + EnsureCursorVisible(); + } + + static bool IsUTFSequence(char c) + { + return (c & 0xC0) == 0x80; } - if (mState.mCursors[aCursor].mSelectionStart != oldSelStart || - mState.mCursors[aCursor].mSelectionEnd != oldSelEnd) - if (!isSpawningNewCursor) - mState.mCursors[aCursor].mCursorPositionChanged = true; -} - -void TextEditor::SetSelection(int aStartLine, int aStartCharIndex, int aEndLine, int aEndCharIndex, SelectionMode aMode, int aCursor, bool isSpawningNewCursor) -{ - SetSelection( - { aStartLine, GetCharacterColumn(aStartLine, aStartCharIndex) }, - { aEndLine, GetCharacterColumn(aEndLine, aEndCharIndex) }, - aMode, aCursor, isSpawningNewCursor); -} - -void TextEditor::SetTabSize(int aValue) -{ - mTabSize = std::max(0, std::min(32, aValue)); -} - -void TextEditor::InsertText(const std::string& aValue, int aCursor) -{ - InsertText(aValue.c_str(), aCursor); -} - -void TextEditor::InsertText(const char* aValue, int aCursor) -{ - if (aValue == nullptr) - return; - if (aCursor == -1) - aCursor = mState.mCurrentCursor; - - auto pos = GetActualCursorCoordinates(aCursor); - auto start = std::min(pos, mState.mCursors[aCursor].mSelectionStart); - int totalLines = pos.mLine - start.mLine; - - totalLines += InsertTextAt(pos, aValue); - - SetSelection(pos, pos, SelectionMode::Normal, aCursor); - SetCursorPosition(pos, aCursor); - Colorize(start.mLine - 1, totalLines + 2); -} - -void TextEditor::DeleteSelection(int aCursor) -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; - - assert(mState.mCursors[aCursor].mSelectionEnd >= mState.mCursors[aCursor].mSelectionStart); - - if (mState.mCursors[aCursor].mSelectionEnd == mState.mCursors[aCursor].mSelectionStart) - return; - - DeleteRange(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); - - SetSelection(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionStart, SelectionMode::Normal, aCursor); - SetCursorPosition(mState.mCursors[aCursor].mSelectionStart, aCursor); - mState.mCursors[aCursor].mInteractiveStart = mState.mCursors[aCursor].mSelectionStart; - mState.mCursors[aCursor].mInteractiveEnd = mState.mCursors[aCursor].mSelectionEnd; - Colorize(mState.mCursors[aCursor].mSelectionStart.mLine, 1); -} - -void TextEditor::MoveUp(int aAmount, bool aSelect) -{ - if (HasSelection() && !aSelect) + void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) { - for (int c = 0; c <= mState.mCurrentCursor; c++) + if (mLines.empty()) + return; + + if (HasSelection() && !aSelect && !aWordMode) { - SetSelection(mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionStart, SelectionMode::Normal, c); - SetCursorPosition(mState.mCursors[c].mSelectionStart, c); + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + SetSelection(mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionStart, SelectionMode::Normal, c); + SetCursorPosition(mState.mCursors[c].mSelectionStart, c); + } } - } - else - { - for (int c = 0; c <= mState.mCurrentCursor; c++) + else { - auto oldPos = mState.mCursors[c].mCursorPosition; - mState.mCursors[c].mCursorPosition.mLine = std::max(0, mState.mCursors[c].mCursorPosition.mLine - aAmount); - if (oldPos != mState.mCursors[c].mCursorPosition) + for (int c = 0; c <= mState.mCurrentCursor; c++) { + int amount = aAmount; + auto oldPos = mState.mCursors[c].mCursorPosition; + mState.mCursors[c].mCursorPosition = GetActualCursorCoordinates(c); + auto line = mState.mCursors[c].mCursorPosition.mLine; + auto cindex = GetCharacterIndexR(mState.mCursors[c].mCursorPosition); + + while (amount-- > 0) + { + if (cindex == 0) + { + if (line > 0) + { + --line; + if ((int)mLines.size() > line) + cindex = (int)mLines[line].size(); + else + cindex = 0; + } + } + else + { + --cindex; + if (cindex > 0) + { + if ((int)mLines.size() > line) + { + while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar)) + --cindex; + } + } + } + + mState.mCursors[c].mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + if (aWordMode) + { + mState.mCursors[c].mCursorPosition = FindWordStart(mState.mCursors[c].mCursorPosition); + cindex = GetCharacterIndexR(mState.mCursors[c].mCursorPosition); + } + } + + mState.mCursors[c].mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + std::cout << "changed from " << oldPos.mColumn << " to " << mState.mCursors[c].mCursorPosition.mColumn << std::endl; + + assert(mState.mCursors[c].mCursorPosition.mColumn >= 0); if (aSelect) { if (oldPos == mState.mCursors[c].mInteractiveStart) @@ -2046,38 +2188,69 @@ void TextEditor::MoveUp(int aAmount, bool aSelect) } } else + { + if (HasSelection() && !aWordMode) + mState.mCursors[c].mCursorPosition = mState.mCursors[c].mInteractiveStart; mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); + } + std::cout << "Setting selection for " << c << std::endl; + SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal, c); } } + EnsureCursorVisible(); } - EnsureCursorVisible(); -} -void TextEditor::MoveDown(int aAmount, bool aSelect) -{ - if (HasSelection() && !aSelect) + void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) { - for (int c = 0; c <= mState.mCurrentCursor; c++) + if (mLines.empty()) + return; + + if (HasSelection() && !aSelect && !aWordMode) { - SetSelection(mState.mCursors[c].mSelectionEnd, mState.mCursors[c].mSelectionEnd, SelectionMode::Normal, c); - SetCursorPosition(mState.mCursors[c].mSelectionEnd, c); + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + SetSelection(mState.mCursors[c].mSelectionEnd, mState.mCursors[c].mSelectionEnd, SelectionMode::Normal, c); + SetCursorPosition(mState.mCursors[c].mSelectionEnd, c); + } } - } - else - { - for (int c = 0; c <= mState.mCurrentCursor; c++) + else { - assert(mState.mCursors[c].mCursorPosition.mColumn >= 0); - auto oldPos = mState.mCursors[c].mCursorPosition; - mState.mCursors[c].mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursors[c].mCursorPosition.mLine + aAmount)); - - if (mState.mCursors[c].mCursorPosition != oldPos) + for (int c = 0; c <= mState.mCurrentCursor; c++) { + auto oldPos = mState.mCursors[c].mCursorPosition; + if (oldPos.mLine >= mLines.size()) + continue; + + int amount = aAmount; + auto cindex = GetCharacterIndexR(mState.mCursors[c].mCursorPosition); + while (amount-- > 0) + { + auto lindex = mState.mCursors[c].mCursorPosition.mLine; + auto& line = mLines[lindex]; + + if (cindex >= line.size()) + { + if (mState.mCursors[c].mCursorPosition.mLine < mLines.size() - 1) + { + mState.mCursors[c].mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursors[c].mCursorPosition.mLine + 1)); + mState.mCursors[c].mCursorPosition.mColumn = 0; + } + else + continue; + } + else + { + cindex += UTF8CharLength(line[cindex].mChar); + mState.mCursors[c].mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); + if (aWordMode) + mState.mCursors[c].mCursorPosition = FindWordEnd(mState.mCursors[c].mCursorPosition); + } + } + if (aSelect) { if (oldPos == mState.mCursors[c].mInteractiveEnd) - mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; + mState.mCursors[c].mInteractiveEnd = SanitizeCoordinates(mState.mCursors[c].mCursorPosition); else if (oldPos == mState.mCursors[c].mInteractiveStart) mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; else @@ -2087,81 +2260,59 @@ void TextEditor::MoveDown(int aAmount, bool aSelect) } } else + { + if (HasSelection() && !aWordMode) + mState.mCursors[c].mCursorPosition = mState.mCursors[c].mInteractiveEnd; mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); - + } + SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal, c); } } + EnsureCursorVisible(); } - EnsureCursorVisible(); -} -static bool IsUTFSequence(char c) -{ - return (c & 0xC0) == 0x80; -} + void TextEditor::MoveTop(bool aSelect) + { + mState.mCurrentCursor = 0; + auto oldPos = mState.mCursors[mState.mCurrentCursor].mCursorPosition; + SetCursorPosition(Coordinates(0, 0)); -void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) -{ - if (mLines.empty()) - return; + if (mState.mCursors[mState.mCurrentCursor].mCursorPosition != oldPos) + { + if (aSelect) + { + mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = oldPos; + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mCursorPosition; + } + else + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = mState.mCursors[mState.mCurrentCursor].mCursorPosition; + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd); + } + } - if (HasSelection() && !aSelect && !aWordMode) + void TextEditor::TextEditor::MoveBottom(bool aSelect) { - for (int c = 0; c <= mState.mCurrentCursor; c++) + mState.mCurrentCursor = 0; + auto oldPos = GetCursorPosition(); + auto newPos = Coordinates((int)mLines.size() - 1, 0); + SetCursorPosition(newPos); + if (aSelect) { - SetSelection(mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionStart, SelectionMode::Normal, c); - SetCursorPosition(mState.mCursors[c].mSelectionStart, c); + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = oldPos; + mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = newPos; } + else + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = newPos; + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd); } - else + + void TextEditor::MoveHome(bool aSelect) { for (int c = 0; c <= mState.mCurrentCursor; c++) { - int amount = aAmount; auto oldPos = mState.mCursors[c].mCursorPosition; - mState.mCursors[c].mCursorPosition = GetActualCursorCoordinates(c); - auto line = mState.mCursors[c].mCursorPosition.mLine; - auto cindex = GetCharacterIndexR(mState.mCursors[c].mCursorPosition); - - while (amount-- > 0) - { - if (cindex == 0) - { - if (line > 0) - { - --line; - if ((int)mLines.size() > line) - cindex = (int)mLines[line].size(); - else - cindex = 0; - } - } - else - { - --cindex; - if (cindex > 0) - { - if ((int)mLines.size() > line) - { - while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar)) - --cindex; - } - } - } - - mState.mCursors[c].mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); - if (aWordMode) - { - mState.mCursors[c].mCursorPosition = FindWordStart(mState.mCursors[c].mCursorPosition); - cindex = GetCharacterIndexR(mState.mCursors[c].mCursorPosition); - } - } - - mState.mCursors[c].mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); - std::cout << "changed from " << oldPos.mColumn << " to " << mState.mCursors[c].mCursorPosition.mColumn << std::endl; + SetCursorPosition(Coordinates(mState.mCursors[c].mCursorPosition.mLine, 0), c); - assert(mState.mCursors[c].mCursorPosition.mColumn >= 0); if (aSelect) { if (oldPos == mState.mCursors[c].mInteractiveStart) @@ -2175,69 +2326,22 @@ void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) } } else - { - if (HasSelection() && !aWordMode) - mState.mCursors[c].mCursorPosition = mState.mCursors[c].mInteractiveStart; mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - } - std::cout << "Setting selection for " << c << std::endl; - SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal, c); + SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); } } - EnsureCursorVisible(); -} -void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) -{ - if (mLines.empty()) - return; - - if (HasSelection() && !aSelect && !aWordMode) - { - for (int c = 0; c <= mState.mCurrentCursor; c++) - { - SetSelection(mState.mCursors[c].mSelectionEnd, mState.mCursors[c].mSelectionEnd, SelectionMode::Normal, c); - SetCursorPosition(mState.mCursors[c].mSelectionEnd, c); - } - } - else + void TextEditor::MoveEnd(bool aSelect) { for (int c = 0; c <= mState.mCurrentCursor; c++) { auto oldPos = mState.mCursors[c].mCursorPosition; - if (oldPos.mLine >= mLines.size()) - continue; - - int amount = aAmount; - auto cindex = GetCharacterIndexR(mState.mCursors[c].mCursorPosition); - while (amount-- > 0) - { - auto lindex = mState.mCursors[c].mCursorPosition.mLine; - auto& line = mLines[lindex]; - - if (cindex >= line.size()) - { - if (mState.mCursors[c].mCursorPosition.mLine < mLines.size() - 1) - { - mState.mCursors[c].mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursors[c].mCursorPosition.mLine + 1)); - mState.mCursors[c].mCursorPosition.mColumn = 0; - } - else - continue; - } - else - { - cindex += UTF8CharLength(line[cindex].mChar); - mState.mCursors[c].mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); - if (aWordMode) - mState.mCursors[c].mCursorPosition = FindWordEnd(mState.mCursors[c].mCursorPosition); - } - } + SetCursorPosition(Coordinates(mState.mCursors[c].mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine)), c); if (aSelect) { if (oldPos == mState.mCursors[c].mInteractiveEnd) - mState.mCursors[c].mInteractiveEnd = SanitizeCoordinates(mState.mCursors[c].mCursorPosition); + mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; else if (oldPos == mState.mCursors[c].mInteractiveStart) mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; else @@ -2247,1129 +2351,1038 @@ void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) } } else - { - if (HasSelection() && !aWordMode) - mState.mCursors[c].mCursorPosition = mState.mCursors[c].mInteractiveEnd; mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - } - SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal, c); + SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); } } - EnsureCursorVisible(); -} -void TextEditor::MoveTop(bool aSelect) -{ - mState.mCurrentCursor = 0; - auto oldPos = mState.mCursors[mState.mCurrentCursor].mCursorPosition; - SetCursorPosition(Coordinates(0, 0)); - - if (mState.mCursors[mState.mCurrentCursor].mCursorPosition != oldPos) + void TextEditor::Delete(bool aWordMode) { - if (aSelect) - { - mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = oldPos; - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mCursorPosition; - } - else - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = mState.mCursors[mState.mCurrentCursor].mCursorPosition; - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd); - } -} + assert(!mReadOnly); -void TextEditor::TextEditor::MoveBottom(bool aSelect) -{ - mState.mCurrentCursor = 0; - auto oldPos = GetCursorPosition(); - auto newPos = Coordinates((int)mLines.size() - 1, 0); - SetCursorPosition(newPos); - if (aSelect) - { - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = oldPos; - mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = newPos; - } - else - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = newPos; - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd); -} + if (mLines.empty()) + return; -void TextEditor::MoveHome(bool aSelect) -{ - for (int c = 0; c <= mState.mCurrentCursor; c++) - { - auto oldPos = mState.mCursors[c].mCursorPosition; - SetCursorPosition(Coordinates(mState.mCursors[c].mCursorPosition.mLine, 0), c); + UndoRecord u; + u.mBefore = mState; - if (aSelect) + if (HasSelection()) { - if (oldPos == mState.mCursors[c].mInteractiveStart) - mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; - else if (oldPos == mState.mCursors[c].mInteractiveEnd) - mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - else + for (int c = mState.mCurrentCursor; c > -1; c--) { - mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; - mState.mCursors[c].mInteractiveEnd = oldPos; + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); + DeleteSelection(c); } } else - mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); - } -} - -void TextEditor::MoveEnd(bool aSelect) -{ - for (int c = 0; c <= mState.mCurrentCursor; c++) - { - auto oldPos = mState.mCursors[c].mCursorPosition; - SetCursorPosition(Coordinates(mState.mCursors[c].mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine)), c); - - if (aSelect) { - if (oldPos == mState.mCursors[c].mInteractiveEnd) - mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - else if (oldPos == mState.mCursors[c].mInteractiveStart) - mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mCursorPosition; - else + std::vector positions; + for (int c = 0; c <= mState.mCurrentCursor; c++) { - mState.mCursors[c].mInteractiveStart = oldPos; - mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - } - } - else - mState.mCursors[c].mInteractiveStart = mState.mCursors[c].mInteractiveEnd = mState.mCursors[c].mCursorPosition; - SetSelection(mState.mCursors[c].mInteractiveStart, mState.mCursors[c].mInteractiveEnd, SelectionMode::Normal, c); - } -} + auto pos = GetActualCursorCoordinates(c); + positions.push_back(pos); + SetCursorPosition(pos, c); + auto& line = mLines[pos.mLine]; -void TextEditor::Delete(bool aWordMode) -{ - assert(!mReadOnly); - - if (mLines.empty()) - return; - - UndoRecord u; - u.mBefore = mState; - - if (HasSelection()) - { - for (int c = mState.mCurrentCursor; c > -1; c--) - { - u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); - DeleteSelection(c); - } - } - else - { - std::vector positions; - for (int c = 0; c <= mState.mCurrentCursor; c++) - { - auto pos = GetActualCursorCoordinates(c); - positions.push_back(pos); - SetCursorPosition(pos, c); - auto& line = mLines[pos.mLine]; - - if (pos.mColumn == GetLineMaxColumn(pos.mLine)) - { - if (pos.mLine == (int)mLines.size() - 1) - continue; + if (pos.mColumn == GetLineMaxColumn(pos.mLine)) + { + if (pos.mLine == (int)mLines.size() - 1) + continue; - Coordinates startCoords = GetActualCursorCoordinates(c); - Coordinates endCoords = startCoords; - Advance(endCoords); - u.mOperations.push_back({ "\n", startCoords, endCoords, UndoOperationType::Delete }); + Coordinates startCoords = GetActualCursorCoordinates(c); + Coordinates endCoords = startCoords; + Advance(endCoords); + u.mOperations.push_back({ "\n", startCoords, endCoords, UndoOperationType::Delete }); - auto& nextLine = mLines[pos.mLine + 1]; - AddGlyphsToLine(pos.mLine, line.size(), nextLine.begin(), nextLine.end()); - for (int otherCursor = c + 1; - otherCursor <= mState.mCurrentCursor && mState.mCursors[otherCursor].mCursorPosition.mLine == pos.mLine + 1; - otherCursor++) // move up cursors in next line - { - int otherCursorCharIndex = GetCharacterIndexR(mState.mCursors[otherCursor].mCursorPosition); - int otherCursorNewCharIndex = GetCharacterIndexR(pos) + otherCursorCharIndex; - auto targetCoords = Coordinates(pos.mLine, GetCharacterColumn(pos.mLine, otherCursorNewCharIndex)); - SetCursorPosition(targetCoords, otherCursor); - } - RemoveLine(pos.mLine + 1); - } - else - { - if (aWordMode) - { - Coordinates end = FindWordEnd(mState.mCursors[c].mCursorPosition); - u.mOperations.push_back({ GetText(mState.mCursors[c].mCursorPosition, end), mState.mCursors[c].mCursorPosition , end, UndoOperationType::Delete }); - DeleteRange(mState.mCursors[c].mCursorPosition, end); - int charactersDeleted = end.mColumn - mState.mCursors[c].mCursorPosition.mColumn; + auto& nextLine = mLines[pos.mLine + 1]; + AddGlyphsToLine(pos.mLine, line.size(), nextLine.begin(), nextLine.end()); + for (int otherCursor = c + 1; + otherCursor <= mState.mCurrentCursor && mState.mCursors[otherCursor].mCursorPosition.mLine == pos.mLine + 1; + otherCursor++) // move up cursors in next line + { + int otherCursorCharIndex = GetCharacterIndexR(mState.mCursors[otherCursor].mCursorPosition); + int otherCursorNewCharIndex = GetCharacterIndexR(pos) + otherCursorCharIndex; + auto targetCoords = Coordinates(pos.mLine, GetCharacterColumn(pos.mLine, otherCursorNewCharIndex)); + SetCursorPosition(targetCoords, otherCursor); + } + RemoveLine(pos.mLine + 1); } else { - auto cindex = GetCharacterIndexR(pos); + if (aWordMode) + { + Coordinates end = FindWordEnd(mState.mCursors[c].mCursorPosition); + u.mOperations.push_back({ GetText(mState.mCursors[c].mCursorPosition, end), mState.mCursors[c].mCursorPosition , end, UndoOperationType::Delete }); + DeleteRange(mState.mCursors[c].mCursorPosition, end); + int charactersDeleted = end.mColumn - mState.mCursors[c].mCursorPosition.mColumn; + } + else + { + auto cindex = GetCharacterIndexR(pos); - Coordinates start = GetActualCursorCoordinates(c); - Coordinates end = start; - end.mColumn++; - u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete }); + Coordinates start = GetActualCursorCoordinates(c); + Coordinates end = start; + end.mColumn++; + u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete }); - auto d = UTF8CharLength(line[cindex].mChar); - while (d-- > 0 && cindex < (int)line.size()) - RemoveGlyphsFromLine(pos.mLine, cindex, cindex + 1); + auto d = UTF8CharLength(line[cindex].mChar); + while (d-- > 0 && cindex < (int)line.size()) + RemoveGlyphsFromLine(pos.mLine, cindex, cindex + 1); + } } } - } - mTextChanged = true; + mTextChanged = true; - for (const auto& pos : positions) - Colorize(pos.mLine, 1); - } + for (const auto& pos : positions) + Colorize(pos.mLine, 1); + } - u.mAfter = mState; - AddUndo(u); -} + u.mAfter = mState; + AddUndo(u); + } -void TextEditor::Backspace(bool aWordMode) -{ - assert(!mReadOnly); + void TextEditor::Backspace(bool aWordMode) + { + assert(!mReadOnly); - if (mLines.empty()) - return; + if (mLines.empty()) + return; - UndoRecord u; - u.mBefore = mState; + UndoRecord u; + u.mBefore = mState; - if (HasSelection()) - { - for (int c = mState.mCurrentCursor; c > -1; c--) + if (HasSelection()) { - u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); - DeleteSelection(c); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); + DeleteSelection(c); + } } - } - else - { - for (int c = 0; c <= mState.mCurrentCursor; c++) + else { - auto pos = GetActualCursorCoordinates(c); - SetCursorPosition(pos, c); - - if (mState.mCursors[c].mCursorPosition.mColumn == 0) + for (int c = 0; c <= mState.mCurrentCursor; c++) { - if (mState.mCursors[c].mCursorPosition.mLine == 0) - continue; + auto pos = GetActualCursorCoordinates(c); + SetCursorPosition(pos, c); - Coordinates startCoords = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); - Coordinates endCoords = startCoords; - Advance(endCoords); - u.mOperations.push_back({ "\n", startCoords, endCoords, UndoOperationType::Delete }); - - auto& line = mLines[mState.mCursors[c].mCursorPosition.mLine]; - int prevLineIndex = mState.mCursors[c].mCursorPosition.mLine - 1; - auto& prevLine = mLines[prevLineIndex]; - auto prevSize = GetLineMaxColumn(prevLineIndex); - AddGlyphsToLine(prevLineIndex, prevLine.size(), line.begin(), line.end()); - std::unordered_set cursorsHandled = { c }; - for (int otherCursor = c + 1; - otherCursor <= mState.mCurrentCursor && mState.mCursors[otherCursor].mCursorPosition.mLine == mState.mCursors[c].mCursorPosition.mLine; - otherCursor++) // move up cursors in same line + if (mState.mCursors[c].mCursorPosition.mColumn == 0) { - int otherCursorCharIndex = GetCharacterIndexR(mState.mCursors[otherCursor].mCursorPosition); - int otherCursorNewCharIndex = GetCharacterIndexR({ prevLineIndex, prevSize }) + otherCursorCharIndex; - auto targetCoords = Coordinates(prevLineIndex, GetCharacterColumn(prevLineIndex, otherCursorNewCharIndex)); - SetCursorPosition(targetCoords, otherCursor); - cursorsHandled.insert(otherCursor); - } + if (mState.mCursors[c].mCursorPosition.mLine == 0) + continue; - ErrorMarkers etmp; - for (auto& i : mErrorMarkers) - etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursors[c].mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); - mErrorMarkers = std::move(etmp); + Coordinates startCoords = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); + Coordinates endCoords = startCoords; + Advance(endCoords); + u.mOperations.push_back({ "\n", startCoords, endCoords, UndoOperationType::Delete }); + + auto& line = mLines[mState.mCursors[c].mCursorPosition.mLine]; + int prevLineIndex = mState.mCursors[c].mCursorPosition.mLine - 1; + auto& prevLine = mLines[prevLineIndex]; + auto prevSize = GetLineMaxColumn(prevLineIndex); + AddGlyphsToLine(prevLineIndex, prevLine.size(), line.begin(), line.end()); + std::unordered_set cursorsHandled = { c }; + for (int otherCursor = c + 1; + otherCursor <= mState.mCurrentCursor && mState.mCursors[otherCursor].mCursorPosition.mLine == mState.mCursors[c].mCursorPosition.mLine; + otherCursor++) // move up cursors in same line + { + int otherCursorCharIndex = GetCharacterIndexR(mState.mCursors[otherCursor].mCursorPosition); + int otherCursorNewCharIndex = GetCharacterIndexR({ prevLineIndex, prevSize }) + otherCursorCharIndex; + auto targetCoords = Coordinates(prevLineIndex, GetCharacterColumn(prevLineIndex, otherCursorNewCharIndex)); + SetCursorPosition(targetCoords, otherCursor); + cursorsHandled.insert(otherCursor); + } - RemoveLine(mState.mCursors[c].mCursorPosition.mLine, &cursorsHandled); - SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine - 1, prevSize }, c); - } - else - { - auto& line = mLines[mState.mCursors[c].mCursorPosition.mLine]; + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursors[c].mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); - if (aWordMode) - { - Coordinates start = FindWordStart(mState.mCursors[c].mCursorPosition - Coordinates(0, 1)); - u.mOperations.push_back({ GetText(start, mState.mCursors[c].mCursorPosition) , start, mState.mCursors[c].mCursorPosition, UndoOperationType::Delete }); - DeleteRange(start, mState.mCursors[c].mCursorPosition); - int charactersDeleted = mState.mCursors[c].mCursorPosition.mColumn - start.mColumn; - mState.mCursors[c].mCursorPosition.mColumn -= charactersDeleted; + RemoveLine(mState.mCursors[c].mCursorPosition.mLine, &cursorsHandled); + SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine - 1, prevSize }, c); } else { - auto cindex = GetCharacterIndexR(pos) - 1; - auto cend = cindex + 1; - while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) - --cindex; - - //if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) - // --cindex; - - UndoOperation removed; - removed.mType = UndoOperationType::Delete; - removed.mStart = removed.mEnd = GetActualCursorCoordinates(c); + auto& line = mLines[mState.mCursors[c].mCursorPosition.mLine]; - if (line[cindex].mChar == '\t') + if (aWordMode) { - int tabStartColumn = GetCharacterColumn(removed.mStart.mLine, cindex); - int tabLength = removed.mStart.mColumn - tabStartColumn; - mState.mCursors[c].mCursorPosition.mColumn -= tabLength; - removed.mStart.mColumn -= tabLength; + Coordinates start = FindWordStart(mState.mCursors[c].mCursorPosition - Coordinates(0, 1)); + u.mOperations.push_back({ GetText(start, mState.mCursors[c].mCursorPosition) , start, mState.mCursors[c].mCursorPosition, UndoOperationType::Delete }); + DeleteRange(start, mState.mCursors[c].mCursorPosition); + int charactersDeleted = mState.mCursors[c].mCursorPosition.mColumn - start.mColumn; + mState.mCursors[c].mCursorPosition.mColumn -= charactersDeleted; } else { - --mState.mCursors[c].mCursorPosition.mColumn; - --removed.mStart.mColumn; - } + auto cindex = GetCharacterIndexR(pos) - 1; + auto cend = cindex + 1; + while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) + --cindex; - while (cindex < line.size() && cend-- > cindex) - { - removed.mText += line[cindex].mChar; - RemoveGlyphsFromLine(mState.mCursors[c].mCursorPosition.mLine, cindex, cindex + 1); - } - u.mOperations.push_back(removed); - } - mState.mCursors[c].mCursorPositionChanged = true; - } - } + //if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) + // --cindex; - mTextChanged = true; + UndoOperation removed; + removed.mType = UndoOperationType::Delete; + removed.mStart = removed.mEnd = GetActualCursorCoordinates(c); - EnsureCursorVisible(); - for (int c = 0; c <= mState.mCurrentCursor; c++) - Colorize(mState.mCursors[c].mCursorPosition.mLine, 1); - } + if (line[cindex].mChar == '\t') + { + int tabStartColumn = GetCharacterColumn(removed.mStart.mLine, cindex); + int tabLength = removed.mStart.mColumn - tabStartColumn; + mState.mCursors[c].mCursorPosition.mColumn -= tabLength; + removed.mStart.mColumn -= tabLength; + } + else + { + --mState.mCursors[c].mCursorPosition.mColumn; + --removed.mStart.mColumn; + } - u.mAfter = mState; - AddUndo(u); -} + while (cindex < line.size() && cend-- > cindex) + { + removed.mText += line[cindex].mChar; + RemoveGlyphsFromLine(mState.mCursors[c].mCursorPosition.mLine, cindex, cindex + 1); + } + u.mOperations.push_back(removed); + } + mState.mCursors[c].mCursorPositionChanged = true; + } + } -void TextEditor::SelectWordUnderCursor() -{ - auto c = GetCursorPosition(); - SetSelection(FindWordStart(c), FindWordEnd(c)); -} + mTextChanged = true; -void TextEditor::SelectAll() -{ - SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0), SelectionMode::Line); -} + EnsureCursorVisible(); + for (int c = 0; c <= mState.mCurrentCursor; c++) + Colorize(mState.mCursors[c].mCursorPosition.mLine, 1); + } -bool TextEditor::HasSelection() const -{ - for (int c = 0; c <= mState.mCurrentCursor; c++) - if (mState.mCursors[c].mSelectionEnd > mState.mCursors[c].mSelectionStart) - return true; - return false; -} + u.mAfter = mState; + AddUndo(u); + } -void TextEditor::Copy() -{ - if (HasSelection()) + void TextEditor::SelectWordUnderCursor() { - std::string clipboardText = GetClipboardText(); - ImGui::SetClipboardText(clipboardText.c_str()); + auto c = GetCursorPosition(); + SetSelection(FindWordStart(c), FindWordEnd(c)); } - else + + void TextEditor::SelectAll() { - if (!mLines.empty()) - { - std::string str; - auto& line = mLines[GetActualCursorCoordinates().mLine]; - for (auto& g : line) - str.push_back(g.mChar); - ImGui::SetClipboardText(str.c_str()); - } + SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0), SelectionMode::Line); } -} -void TextEditor::Cut() -{ - if (IsReadOnly()) + bool TextEditor::HasSelection() const { - Copy(); + for (int c = 0; c <= mState.mCurrentCursor; c++) + if (mState.mCursors[c].mSelectionEnd > mState.mCursors[c].mSelectionStart) + return true; + return false; } - else + + void TextEditor::Copy() { if (HasSelection()) { - UndoRecord u; - u.mBefore = mState; - - Copy(); - for (int c = mState.mCurrentCursor; c > -1; c--) + std::string clipboardText = GetClipboardText(); + ImGui::SetClipboardText(clipboardText.c_str()); + } + else + { + if (!mLines.empty()) { - u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); - DeleteSelection(c); + std::string str; + auto& line = mLines[GetActualCursorCoordinates().mLine]; + for (auto& g : line) + str.push_back(g.mChar); + ImGui::SetClipboardText(str.c_str()); } - - u.mAfter = mState; - AddUndo(u); } } -} - -void TextEditor::Paste() -{ - if (IsReadOnly()) - return; - // check if we should do multicursor paste - std::string clipText = ImGui::GetClipboardText(); - bool canPasteToMultipleCursors = false; - std::vector> clipTextLines; - if (mState.mCurrentCursor > 0) + void TextEditor::Cut() { - clipTextLines.push_back({ 0,0 }); - for (int i = 0; i < clipText.length(); i++) + if (IsReadOnly()) + { + Copy(); + } + else { - if (clipText[i] == '\n') + if (HasSelection()) { - clipTextLines.back().second = i; - clipTextLines.push_back({ i + 1, 0 }); + UndoRecord u; + u.mBefore = mState; + + Copy(); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); + DeleteSelection(c); + } + + u.mAfter = mState; + AddUndo(u); } } - clipTextLines.back().second = clipText.length(); - canPasteToMultipleCursors = clipTextLines.size() == mState.mCurrentCursor + 1; } - if (clipText.length() > 0) + void TextEditor::Paste() { - UndoRecord u; - u.mBefore = mState; + if (IsReadOnly()) + return; - if (HasSelection()) + // check if we should do multicursor paste + std::string clipText = ImGui::GetClipboardText(); + bool canPasteToMultipleCursors = false; + std::vector> clipTextLines; + if (mState.mCurrentCursor > 0) { - for (int c = mState.mCurrentCursor; c > -1; c--) + clipTextLines.push_back({ 0,0 }); + for (int i = 0; i < clipText.length(); i++) { - u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); - DeleteSelection(c); + if (clipText[i] == '\n') + { + clipTextLines.back().second = i; + clipTextLines.push_back({ i + 1, 0 }); + } } + clipTextLines.back().second = clipText.length(); + canPasteToMultipleCursors = clipTextLines.size() == mState.mCurrentCursor + 1; } - for (int c = mState.mCurrentCursor; c > -1; c--) + if (clipText.length() > 0) { - Coordinates start = GetActualCursorCoordinates(c); - if (canPasteToMultipleCursors) + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { - std::string clipSubText = clipText.substr(clipTextLines[c].first, clipTextLines[c].second - clipTextLines[c].first); - InsertText(clipSubText, c); - u.mOperations.push_back({ clipSubText, start, GetActualCursorCoordinates(c), UndoOperationType::Add }); + for (int c = mState.mCurrentCursor; c > -1; c--) + { + u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd, UndoOperationType::Delete }); + DeleteSelection(c); + } } - else + + for (int c = mState.mCurrentCursor; c > -1; c--) { - InsertText(clipText, c); - u.mOperations.push_back({ clipText, start, GetActualCursorCoordinates(c), UndoOperationType::Add }); + Coordinates start = GetActualCursorCoordinates(c); + if (canPasteToMultipleCursors) + { + std::string clipSubText = clipText.substr(clipTextLines[c].first, clipTextLines[c].second - clipTextLines[c].first); + InsertText(clipSubText, c); + u.mOperations.push_back({ clipSubText, start, GetActualCursorCoordinates(c), UndoOperationType::Add }); + } + else + { + InsertText(clipText, c); + u.mOperations.push_back({ clipText, start, GetActualCursorCoordinates(c), UndoOperationType::Add }); + } } + + u.mAfter = mState; + AddUndo(u); } + } - u.mAfter = mState; - AddUndo(u); + int TextEditor::GetUndoIndex() const + { + return mUndoIndex; } -} - -int TextEditor::GetUndoIndex() const -{ - return mUndoIndex; -} - -bool TextEditor::CanUndo() const -{ - return !mReadOnly && mUndoIndex > 0; -} - -bool TextEditor::CanRedo() const -{ - return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); -} - -void TextEditor::Undo(int aSteps) -{ - while (CanUndo() && aSteps-- > 0) - mUndoBuffer[--mUndoIndex].Undo(this); -} - -void TextEditor::Redo(int aSteps) -{ - while (CanRedo() && aSteps-- > 0) - mUndoBuffer[mUndoIndex++].Redo(this); -} - -void TextEditor::ClearExtraCursors() -{ - mState.mCurrentCursor = 0; -} - -void TextEditor::ClearSelections() -{ - for (int c = mState.mCurrentCursor; c > -1; c--) - mState.mCursors[c].mInteractiveEnd = - mState.mCursors[c].mInteractiveStart = - mState.mCursors[c].mSelectionEnd = - mState.mCursors[c].mSelectionStart = mState.mCursors[c].mCursorPosition; -} - -void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor) -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; - Coordinates nextStart, nextEnd; - FindNextOccurrence(aText, aTextSize, mState.mCursors[aCursor].mCursorPosition, nextStart, nextEnd); - mState.mCursors[aCursor].mInteractiveStart = nextStart; - mState.mCursors[aCursor].mCursorPosition = mState.mCursors[aCursor].mInteractiveEnd = nextEnd; - SetSelection(mState.mCursors[aCursor].mInteractiveStart, mState.mCursors[aCursor].mInteractiveEnd, mSelectionMode, aCursor); - EnsureCursorVisible(aCursor); -} - -void TextEditor::AddCursorForNextOccurrence() -{ - const Cursor& currentCursor = mState.mCursors[mState.GetLastAddedCursorIndex()]; - if (currentCursor.mSelectionStart == currentCursor.mSelectionEnd) - return; - - std::string selectionText = GetText(currentCursor.mSelectionStart, currentCursor.mSelectionEnd); - Coordinates nextStart, nextEnd; - if (!FindNextOccurrence(selectionText.c_str(), selectionText.length(), currentCursor.mSelectionEnd, nextStart, nextEnd)) - return; - - mState.AddCursor(); - mState.mCursors[mState.mCurrentCursor].mInteractiveStart = nextStart; - mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = nextEnd; - SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode, -1, true); - mState.SortCursorsFromTopToBottom(); - MergeCursorsIfPossible(); - EnsureCursorVisible(); -} - -const TextEditor::Palette& TextEditor::GetDarkPalette() -{ - const static Palette p = { { - 0xb0b0b0ff, // Default - 0x569cd6ff, // Keyword - 0x00ff00ff, // Number - 0xe07070ff, // String - 0xe0a070ff, // Char literal - 0xffffffff, // Punctuation - 0x808040ff, // Preprocessor - 0xaaaaaaff, // Identifier - 0x4dc69bff, // Known identifier - 0xa040c0ff, // Preproc identifier - 0x206020ff, // Comment (single line) - 0x206040ff, // Comment (multi line) - 0x101010ff, // Background - 0xe0e0e0ff, // Cursor - 0x2060a080, // Selection - 0xff200080, // ErrorMarker - 0x90909090, // ControlCharacter - 0x0080f040, // Breakpoint - 0x007070ff, // Line number - 0x00000040, // Current line fill - 0x80808040, // Current line fill (inactive) - 0xa0a0a040, // Current line edge - } }; - return p; -} - -const TextEditor::Palette& TextEditor::GetMarianaPalette() -{ - const static Palette p = { { - 0xffffffff, // Default - 0xc695c6ff, // Keyword - 0xf9ae58ff, // Number - 0x99c794ff, // String - 0xe0a070ff, // Char literal - 0x5fb4b4ff, // Punctuation - 0x808040ff, // Preprocessor - 0xffffffff, // Identifier - 0x4dc69bff, // Known identifier - 0xe0a0ffff, // Preproc identifier - 0xa6acb9ff, // Comment (single line) - 0xa6acb9ff, // Comment (multi line) - 0x303841ff, // Background - 0xe0e0e0ff, // Cursor - 0x4e5a6580, // Selection - 0xec5f6680, // ErrorMarker - 0xffffff30, // ControlCharacter - 0x0080f040, // Breakpoint - 0xffffffb0, // Line number - 0x4e5a6580, // Current line fill - 0x4e5a6530, // Current line fill (inactive) - 0x4e5a65b0, // Current line edge - } }; - return p; -} - -const TextEditor::Palette& TextEditor::GetLightPalette() -{ - const static Palette p = { { - 0x404040ff, // None - 0x060cffff, // Keyword - 0x008000ff, // Number - 0xa02020ff, // String - 0x704030ff, // Char literal - 0x000000ff, // Punctuation - 0x606040ff, // Preprocessor - 0x404040ff, // Identifier - 0x106060ff, // Known identifier - 0xa040c0ff, // Preproc identifier - 0x205020ff, // Comment (single line) - 0x205040ff, // Comment (multi line) - 0xffffffff, // Background - 0x000000ff, // Cursor - 0x00006040, // Selection - 0xff1000a0, // ErrorMarker - 0x90909090, // ControlCharacter - 0x0080f080, // Breakpoint - 0x005050ff, // Line number - 0x00000040, // Current line fill - 0x80808040, // Current line fill (inactive) - 0x00000040, // Current line edge - } }; - return p; -} - -const TextEditor::Palette& TextEditor::GetRetroBluePalette() -{ - const static Palette p = { { - 0xffff00ff, // None - 0x00ffffff, // Keyword - 0x00ff00ff, // Number - 0x008080ff, // String - 0x008080ff, // Char literal - 0xffffffff, // Punctuation - 0x008000ff, // Preprocessor - 0xffff00ff, // Identifier - 0xffffffff, // Known identifier - 0xff00ffff, // Preproc identifier - 0x808080ff, // Comment (single line) - 0x404040ff, // Comment (multi line) - 0x000080ff, // Background - 0xff8000ff, // Cursor - 0x00ffff80, // Selection - 0xff0000a0, // ErrorMarker - 0x0080ff80, // Breakpoint - 0x008080ff, // Line number - 0x00000040, // Current line fill - 0x80808040, // Current line fill (inactive) - 0x00000040, // Current line edge - } }; - return p; -} - -void TextEditor::MergeCursorsIfPossible() -{ - // requires the cursors to be sorted from top to bottom - std::unordered_set cursorsToDelete; - if (HasSelection()) - { - // merge cursors if they overlap - for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs - { - int pc = c - 1; // pc for previous cursor - - bool pcContainsC = mState.mCursors[pc].mSelectionEnd >= mState.mCursors[c].mSelectionEnd; - bool pcContainsStartOfC = mState.mCursors[pc].mSelectionEnd > mState.mCursors[c].mSelectionStart; - - if (pcContainsC) - { - cursorsToDelete.insert(c); - } - else if (pcContainsStartOfC) - { - mState.mCursors[pc].mSelectionEnd = mState.mCursors[c].mSelectionEnd; - mState.mCursors[pc].mInteractiveEnd = mState.mCursors[c].mSelectionEnd; - mState.mCursors[pc].mInteractiveStart = mState.mCursors[pc].mSelectionStart; - mState.mCursors[pc].mCursorPosition = mState.mCursors[c].mSelectionEnd; - cursorsToDelete.insert(c); - } - } - } - else - { - // merge cursors if they are at the same position - for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs - { - int pc = c - 1; - if (mState.mCursors[pc].mCursorPosition == mState.mCursors[c].mCursorPosition) - cursorsToDelete.insert(c); - } - } - for (int c = mState.mCurrentCursor; c > -1; c--)// iterate backwards through each of them - { - if (cursorsToDelete.find(c) != cursorsToDelete.end()) - mState.mCursors.erase(mState.mCursors.begin() + c); - } - mState.mCurrentCursor -= cursorsToDelete.size(); -} - - -std::string TextEditor::GetText() const -{ - auto lastLine = (int)mLines.size() - 1; - auto lastLineLength = GetLineMaxColumn(lastLine); - return GetText(Coordinates(), Coordinates(lastLine, lastLineLength)); -} -std::vector TextEditor::GetTextLines() const -{ - std::vector result; + bool TextEditor::CanUndo() const + { + return !mReadOnly && mUndoIndex > 0; + } - result.reserve(mLines.size()); + bool TextEditor::CanRedo() const + { + return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); + } - for (auto& line : mLines) + void TextEditor::Undo(int aSteps) { - std::string text; + while (CanUndo() && aSteps-- > 0) + mUndoBuffer[--mUndoIndex].Undo(this); + } - text.resize(line.size()); + void TextEditor::Redo(int aSteps) + { + while (CanRedo() && aSteps-- > 0) + mUndoBuffer[mUndoIndex++].Redo(this); + } - for (size_t i = 0; i < line.size(); ++i) - text[i] = line[i].mChar; + void TextEditor::ClearExtraCursors() + { + mState.mCurrentCursor = 0; + } - result.emplace_back(std::move(text)); + void TextEditor::ClearSelections() + { + for (int c = mState.mCurrentCursor; c > -1; c--) + mState.mCursors[c].mInteractiveEnd = + mState.mCursors[c].mInteractiveStart = + mState.mCursors[c].mSelectionEnd = + mState.mCursors[c].mSelectionStart = mState.mCursors[c].mCursorPosition; } - return result; -} + void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor) + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + Coordinates nextStart, nextEnd; + FindNextOccurrence(aText, aTextSize, mState.mCursors[aCursor].mCursorPosition, nextStart, nextEnd); + mState.mCursors[aCursor].mInteractiveStart = nextStart; + mState.mCursors[aCursor].mCursorPosition = mState.mCursors[aCursor].mInteractiveEnd = nextEnd; + SetSelection(mState.mCursors[aCursor].mInteractiveStart, mState.mCursors[aCursor].mInteractiveEnd, mSelectionMode, aCursor); + EnsureCursorVisible(aCursor); + } -std::string TextEditor::GetClipboardText() const -{ - std::string result; - for (int c = 0; c <= mState.mCurrentCursor; c++) + void TextEditor::AddCursorForNextOccurrence() { - if (mState.mCursors[c].mSelectionStart < mState.mCursors[c].mSelectionEnd) + const Cursor& currentCursor = mState.mCursors[mState.GetLastAddedCursorIndex()]; + if (currentCursor.mSelectionStart == currentCursor.mSelectionEnd) + return; + + std::string selectionText = GetText(currentCursor.mSelectionStart, currentCursor.mSelectionEnd); + Coordinates nextStart, nextEnd; + if (!FindNextOccurrence(selectionText.c_str(), selectionText.length(), currentCursor.mSelectionEnd, nextStart, nextEnd)) + return; + + mState.AddCursor(); + mState.mCursors[mState.mCurrentCursor].mInteractiveStart = nextStart; + mState.mCursors[mState.mCurrentCursor].mCursorPosition = mState.mCursors[mState.mCurrentCursor].mInteractiveEnd = nextEnd; + SetSelection(mState.mCursors[mState.mCurrentCursor].mInteractiveStart, mState.mCursors[mState.mCurrentCursor].mInteractiveEnd, mSelectionMode, -1, true); + mState.SortCursorsFromTopToBottom(); + MergeCursorsIfPossible(); + EnsureCursorVisible(); + } + + const TextEditor::Palette& TextEditor::GetDarkPalette() + { + const static Palette p = { { + 0xb0b0b0ff, // Default + 0x569cd6ff, // Keyword + 0x00ff00ff, // Number + 0xe07070ff, // String + 0xe0a070ff, // Char literal + 0xffffffff, // Punctuation + 0x808040ff, // Preprocessor + 0xaaaaaaff, // Identifier + 0x4dc69bff, // Known identifier + 0xa040c0ff, // Preproc identifier + 0x206020ff, // Comment (single line) + 0x206040ff, // Comment (multi line) + 0x101010ff, // Background + 0xe0e0e0ff, // Cursor + 0x2060a080, // Selection + 0xff200080, // ErrorMarker + 0x90909090, // ControlCharacter + 0x0080f040, // Breakpoint + 0x007070ff, // Line number + 0x00000040, // Current line fill + 0x80808040, // Current line fill (inactive) + 0xa0a0a040, // Current line edge + } }; + return p; + } + + const TextEditor::Palette& TextEditor::GetMarianaPalette() + { + const static Palette p = { { + 0xffffffff, // Default + 0xc695c6ff, // Keyword + 0xf9ae58ff, // Number + 0x99c794ff, // String + 0xe0a070ff, // Char literal + 0x5fb4b4ff, // Punctuation + 0x808040ff, // Preprocessor + 0xffffffff, // Identifier + 0x4dc69bff, // Known identifier + 0xe0a0ffff, // Preproc identifier + 0xa6acb9ff, // Comment (single line) + 0xa6acb9ff, // Comment (multi line) + 0x303841ff, // Background + 0xe0e0e0ff, // Cursor + 0x4e5a6580, // Selection + 0xec5f6680, // ErrorMarker + 0xffffff30, // ControlCharacter + 0x0080f040, // Breakpoint + 0xffffffb0, // Line number + 0x4e5a6580, // Current line fill + 0x4e5a6530, // Current line fill (inactive) + 0x4e5a65b0, // Current line edge + } }; + return p; + } + + const TextEditor::Palette& TextEditor::GetLightPalette() + { + const static Palette p = { { + 0x404040ff, // None + 0x060cffff, // Keyword + 0x008000ff, // Number + 0xa02020ff, // String + 0x704030ff, // Char literal + 0x000000ff, // Punctuation + 0x606040ff, // Preprocessor + 0x404040ff, // Identifier + 0x106060ff, // Known identifier + 0xa040c0ff, // Preproc identifier + 0x205020ff, // Comment (single line) + 0x205040ff, // Comment (multi line) + 0xffffffff, // Background + 0x000000ff, // Cursor + 0x00006040, // Selection + 0xff1000a0, // ErrorMarker + 0x90909090, // ControlCharacter + 0x0080f080, // Breakpoint + 0x005050ff, // Line number + 0x00000040, // Current line fill + 0x80808040, // Current line fill (inactive) + 0x00000040, // Current line edge + } }; + return p; + } + + const TextEditor::Palette& TextEditor::GetRetroBluePalette() + { + const static Palette p = { { + 0xffff00ff, // None + 0x00ffffff, // Keyword + 0x00ff00ff, // Number + 0x008080ff, // String + 0x008080ff, // Char literal + 0xffffffff, // Punctuation + 0x008000ff, // Preprocessor + 0xffff00ff, // Identifier + 0xffffffff, // Known identifier + 0xff00ffff, // Preproc identifier + 0x808080ff, // Comment (single line) + 0x404040ff, // Comment (multi line) + 0x000080ff, // Background + 0xff8000ff, // Cursor + 0x00ffff80, // Selection + 0xff0000a0, // ErrorMarker + 0x0080ff80, // Breakpoint + 0x008080ff, // Line number + 0x00000040, // Current line fill + 0x80808040, // Current line fill (inactive) + 0x00000040, // Current line edge + } }; + return p; + } + + void TextEditor::MergeCursorsIfPossible() + { + // requires the cursors to be sorted from top to bottom + std::unordered_set cursorsToDelete; + if (HasSelection()) { - if (result.length() != 0) - result += '\n'; - result += GetText(mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd); + // merge cursors if they overlap + for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs + { + int pc = c - 1; // pc for previous cursor + + bool pcContainsC = mState.mCursors[pc].mSelectionEnd >= mState.mCursors[c].mSelectionEnd; + bool pcContainsStartOfC = mState.mCursors[pc].mSelectionEnd > mState.mCursors[c].mSelectionStart; + + if (pcContainsC) + { + cursorsToDelete.insert(c); + } + else if (pcContainsStartOfC) + { + mState.mCursors[pc].mSelectionEnd = mState.mCursors[c].mSelectionEnd; + mState.mCursors[pc].mInteractiveEnd = mState.mCursors[c].mSelectionEnd; + mState.mCursors[pc].mInteractiveStart = mState.mCursors[pc].mSelectionStart; + mState.mCursors[pc].mCursorPosition = mState.mCursors[c].mSelectionEnd; + cursorsToDelete.insert(c); + } + } + } + else + { + // merge cursors if they are at the same position + for (int c = mState.mCurrentCursor; c > 0; c--)// iterate backwards through pairs + { + int pc = c - 1; + if (mState.mCursors[pc].mCursorPosition == mState.mCursors[c].mCursorPosition) + cursorsToDelete.insert(c); + } + } + for (int c = mState.mCurrentCursor; c > -1; c--)// iterate backwards through each of them + { + if (cursorsToDelete.find(c) != cursorsToDelete.end()) + mState.mCursors.erase(mState.mCursors.begin() + c); } + mState.mCurrentCursor -= cursorsToDelete.size(); } - return result; -} -std::string TextEditor::GetSelectedText(int aCursor) const -{ - if (aCursor == -1) - aCursor = mState.mCurrentCursor; - return GetText(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); -} + std::string TextEditor::GetText() const + { + auto lastLine = (int)mLines.size() - 1; + auto lastLineLength = GetLineMaxColumn(lastLine); + return GetText(Coordinates(), Coordinates(lastLine, lastLineLength)); + } -std::string TextEditor::GetCurrentLineText()const -{ - auto lineLength = GetLineMaxColumn(mState.mCursors[mState.mCurrentCursor].mCursorPosition.mLine); - return GetText( - Coordinates(mState.mCursors[mState.mCurrentCursor].mCursorPosition.mLine, 0), - Coordinates(mState.mCursors[mState.mCurrentCursor].mCursorPosition.mLine, lineLength)); -} + std::vector TextEditor::GetTextLines() const + { + std::vector result; -void TextEditor::ProcessInputs() -{ -} + result.reserve(mLines.size()); -void TextEditor::Colorize(int aFromLine, int aLines) -{ - int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines); - mColorRangeMin = std::min(mColorRangeMin, aFromLine); - mColorRangeMax = std::max(mColorRangeMax, toLine); - mColorRangeMin = std::max(0, mColorRangeMin); - mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax); - mCheckComments = true; -} + for (auto& line : mLines) + { + std::string text; -void TextEditor::ColorizeRange(int aFromLine, int aToLine) -{ - if (mLines.empty() || aFromLine >= aToLine || mLanguageDefinition == nullptr) - return; + text.resize(line.size()); - std::string buffer; - boost::cmatch results; - std::string id; + for (size_t i = 0; i < line.size(); ++i) + text[i] = line[i].mChar; - int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); - for (int i = aFromLine; i < endLine; ++i) - { - auto& line = mLines[i]; + result.emplace_back(std::move(text)); + } - if (line.empty()) - continue; + return result; + } - buffer.resize(line.size()); - for (size_t j = 0; j < line.size(); ++j) + std::string TextEditor::GetClipboardText() const + { + std::string result; + for (int c = 0; c <= mState.mCurrentCursor; c++) { - auto& col = line[j]; - buffer[j] = col.mChar; - col.mColorIndex = PaletteIndex::Default; + if (mState.mCursors[c].mSelectionStart < mState.mCursors[c].mSelectionEnd) + { + if (result.length() != 0) + result += '\n'; + result += GetText(mState.mCursors[c].mSelectionStart, mState.mCursors[c].mSelectionEnd); + } } + return result; + } + + std::string TextEditor::GetSelectedText(int aCursor) const + { + if (aCursor == -1) + aCursor = mState.mCurrentCursor; + + return GetText(mState.mCursors[aCursor].mSelectionStart, mState.mCursors[aCursor].mSelectionEnd); + } + + std::string TextEditor::GetCurrentLineText()const + { + auto lineLength = GetLineMaxColumn(mState.mCursors[mState.mCurrentCursor].mCursorPosition.mLine); + return GetText( + Coordinates(mState.mCursors[mState.mCurrentCursor].mCursorPosition.mLine, 0), + Coordinates(mState.mCursors[mState.mCurrentCursor].mCursorPosition.mLine, lineLength)); + } + + void TextEditor::ProcessInputs() + { + } + + void TextEditor::Colorize(int aFromLine, int aLines) + { + int fromLine = aFromLine == -1 ? mState.mLineMin : aFromLine; + int toLine = aLines == -1 ? std::min((int)mLines.size(), mState.mLineMax) : std::min((int)mLines.size(), fromLine + aLines); + mColorRangeMin = std::max(0, fromLine); + mColorRangeMax = std::max(mColorRangeMin, toLine); + mCheckComments = true; + } - const char* bufferBegin = &buffer.front(); - const char* bufferEnd = bufferBegin + buffer.size(); + void TextEditor::ColorizeRange(int aFromLine, int aToLine) + { + if (mLines.empty() || aFromLine >= aToLine || mLanguageDefinition == nullptr) + return; - auto last = bufferEnd; + std::string buffer; + boost::cmatch results; + std::string id; - for (auto first = bufferBegin; first != last; ) + int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); + for (int i = aFromLine; i < endLine; ++i) { - const char* token_begin = nullptr; - const char* token_end = nullptr; - PaletteIndex token_color = PaletteIndex::Default; + auto& line = mLines[i]; - bool hasTokenizeResult = false; + if (line.empty()) + continue; - if (mLanguageDefinition->mTokenize != nullptr) + buffer.resize(line.size()); + for (size_t j = 0; j < line.size(); ++j) { - if (mLanguageDefinition->mTokenize(first, last, token_begin, token_end, token_color)) - hasTokenizeResult = true; + auto& col = line[j]; + buffer[j] = col.mChar; + col.mColorIndex = PaletteIndex::Default; } - if (hasTokenizeResult == false) + const char* bufferBegin = &buffer.front(); + const char* bufferEnd = bufferBegin + buffer.size(); + + auto last = bufferEnd; + + for (auto first = bufferBegin; first != last; ) { - // todo : remove - //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); + const char* token_begin = nullptr; + const char* token_end = nullptr; + PaletteIndex token_color = PaletteIndex::Default; - for (const auto& p : mRegexList) + bool hasTokenizeResult = false; + + if (mLanguageDefinition->mTokenize != nullptr) { - if (boost::regex_search(first, last, results, p.first, boost::regex_constants::match_continuous)) - { + if (mLanguageDefinition->mTokenize(first, last, token_begin, token_end, token_color)) hasTokenizeResult = true; - - auto& v = *results.begin(); - token_begin = v.first; - token_end = v.second; - token_color = p.second; - break; - } } - } - - if (hasTokenizeResult == false) - { - first++; - } - else - { - const size_t token_length = token_end - token_begin; - if (token_color == PaletteIndex::Identifier) + if (hasTokenizeResult == false) { - id.assign(token_begin, token_end); + // todo : remove + //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); - // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ? - if (!mLanguageDefinition->mCaseSensitive) - std::transform(id.begin(), id.end(), id.begin(), ::toupper); - - if (!line[first - bufferBegin].mPreprocessor) + for (const auto& p : mRegexList) { - if (mLanguageDefinition->mKeywords.count(id) != 0) - token_color = PaletteIndex::Keyword; - else if (mLanguageDefinition->mIdentifiers.count(id) != 0) - token_color = PaletteIndex::KnownIdentifier; - else if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0) - token_color = PaletteIndex::PreprocIdentifier; + if (boost::regex_search(first, last, results, p.first, boost::regex_constants::match_continuous)) + { + hasTokenizeResult = true; + + auto& v = *results.begin(); + token_begin = v.first; + token_end = v.second; + token_color = p.second; + break; + } } - else + } + + if (hasTokenizeResult == false) + { + first++; + } + else + { + const size_t token_length = token_end - token_begin; + + if (token_color == PaletteIndex::Identifier) { - if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0) - token_color = PaletteIndex::PreprocIdentifier; + id.assign(token_begin, token_end); + + // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ? + if (!mLanguageDefinition->mCaseSensitive) + std::transform(id.begin(), id.end(), id.begin(), ::toupper); + + if (!line[first - bufferBegin].mPreprocessor) + { + if (mLanguageDefinition->mKeywords.count(id) != 0) + token_color = PaletteIndex::Keyword; + else if (mLanguageDefinition->mIdentifiers.count(id) != 0) + token_color = PaletteIndex::KnownIdentifier; + else if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } + else + { + if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } } - } - for (size_t j = 0; j < token_length; ++j) - line[(token_begin - bufferBegin) + j].mColorIndex = token_color; + for (size_t j = 0; j < token_length; ++j) + line[(token_begin - bufferBegin) + j].mColorIndex = token_color; - first = token_end; + first = token_end; + } } } } -} - -void TextEditor::ColorizeInternal() -{ - if (mLines.empty() || !mColorizerEnabled || mLanguageDefinition == nullptr) - return; - if (mCheckComments) + void TextEditor::ColorizeInternal() { - auto endLine = mLines.size(); - auto endIndex = 0; - auto commentStartLine = endLine; - auto commentStartIndex = endIndex; - auto withinString = false; - auto withinSingleLineComment = false; - auto withinPreproc = false; - auto firstChar = true; // there is no other non-whitespace characters in the line before - auto concatenate = false; // '\' on the very end of the line - auto currentLine = 0; - auto currentIndex = 0; - while (currentLine < endLine || currentIndex < endIndex) - { - auto& line = mLines[currentLine]; + if (mLines.empty() || !mColorizerEnabled || mLanguageDefinition == nullptr) + return; - if (currentIndex == 0 && !concatenate) + if (mCheckComments) + { + auto endLine = mColorRangeMax; + auto endIndex = 0; + auto commentStartLine = endLine; + auto commentStartIndex = endIndex; + auto withinString = false; + auto withinSingleLineComment = false; + auto withinPreproc = false; + auto firstChar = true; // there are no other non-whitespace characters in the line before + auto concatenate = false; // '\' on the very end of the line + auto currentLine = mColorRangeMin; + auto currentIndex = 0; + while (currentLine < endLine || currentIndex < endIndex) { - withinSingleLineComment = false; - withinPreproc = false; - firstChar = true; - } + auto& line = mLines[currentLine]; - concatenate = false; + if (currentIndex == 0 && !concatenate) + { + withinSingleLineComment = false; + withinPreproc = false; + firstChar = true; + } - if (!line.empty()) - { - auto& g = line[currentIndex]; - auto c = g.mChar; + concatenate = false; - if (c != mLanguageDefinition->mPreprocChar && !isspace(c)) - firstChar = false; + if (!line.empty()) + { + auto& g = line[currentIndex]; + auto c = g.mChar; - if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') - concatenate = true; + if (c != mLanguageDefinition->mPreprocChar && !isspace(c)) + firstChar = false; - bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') + concatenate = true; - if (withinString) - { - line[currentIndex].mMultiLineComment = inComment; + bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); - if (c == '\"') + if (withinString) { - if (currentIndex + 1 < (int)line.size() && line[currentIndex + 1].mChar == '\"') + line[currentIndex].mMultiLineComment = inComment; + + if (c == '\"') + { + if (currentIndex + 1 < (int)line.size() && line[currentIndex + 1].mChar == '\"') + { + currentIndex += 1; + if (currentIndex < (int)line.size()) + line[currentIndex].mMultiLineComment = inComment; + } + else + withinString = false; + } + else if (c == '\\') { currentIndex += 1; if (currentIndex < (int)line.size()) line[currentIndex].mMultiLineComment = inComment; } - else - withinString = false; - } - else if (c == '\\') - { - currentIndex += 1; - if (currentIndex < (int)line.size()) - line[currentIndex].mMultiLineComment = inComment; - } - } - else - { - if (firstChar && c == mLanguageDefinition->mPreprocChar) - withinPreproc = true; - - if (c == '\"') - { - withinString = true; - line[currentIndex].mMultiLineComment = inComment; } else { - auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; - auto from = line.begin() + currentIndex; - auto& startStr = mLanguageDefinition->mCommentStart; - auto& singleStartStr = mLanguageDefinition->mSingleLineComment; + if (firstChar && c == mLanguageDefinition->mPreprocChar) + withinPreproc = true; - if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() && - equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred)) + if (c == '\"') { - commentStartLine = currentLine; - commentStartIndex = currentIndex; + withinString = true; + line[currentIndex].mMultiLineComment = inComment; } - else if (singleStartStr.size() > 0 && - currentIndex + singleStartStr.size() <= line.size() && - equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) + else { - withinSingleLineComment = true; - } + auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; + auto from = line.begin() + currentIndex; + auto& startStr = mLanguageDefinition->mCommentStart; + auto& singleStartStr = mLanguageDefinition->mSingleLineComment; - inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() && + equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred)) + { + commentStartLine = currentLine; + commentStartIndex = currentIndex; + } + else if (singleStartStr.size() > 0 && + currentIndex + singleStartStr.size() <= line.size() && + equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) + { + withinSingleLineComment = true; + } - line[currentIndex].mMultiLineComment = inComment; - line[currentIndex].mComment = withinSingleLineComment; + inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); - auto& endStr = mLanguageDefinition->mCommentEnd; - if (currentIndex + 1 >= (int)endStr.size() && - equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) - { - commentStartIndex = endIndex; - commentStartLine = endLine; + line[currentIndex].mMultiLineComment = inComment; + line[currentIndex].mComment = withinSingleLineComment; + + auto& endStr = mLanguageDefinition->mCommentEnd; + if (currentIndex + 1 >= (int)endStr.size() && + equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) + { + commentStartIndex = endIndex; + commentStartLine = endLine; + } } } + if (currentIndex < (int)line.size()) + line[currentIndex].mPreprocessor = withinPreproc; + currentIndex += UTF8CharLength(c); + if (currentIndex >= (int)line.size()) + { + currentIndex = 0; + ++currentLine; + } } - if (currentIndex < (int)line.size()) - line[currentIndex].mPreprocessor = withinPreproc; - currentIndex += UTF8CharLength(c); - if (currentIndex >= (int)line.size()) + else { currentIndex = 0; ++currentLine; } } - else - { - currentIndex = 0; - ++currentLine; - } + mCheckComments = false; } - mCheckComments = false; - } - - if (mColorRangeMin < mColorRangeMax) - { - const int increment = (mLanguageDefinition->mTokenize == nullptr) ? 10 : 10000; - const int to = std::min(mColorRangeMin + increment, mColorRangeMax); - ColorizeRange(mColorRangeMin, to); - mColorRangeMin = to; - if (mColorRangeMax == mColorRangeMin) + if (mColorRangeMin < mColorRangeMax) { - mColorRangeMin = std::numeric_limits::max(); - mColorRangeMax = 0; + const int increment = (mLanguageDefinition->mTokenize == nullptr) ? 10 : 10000; + const int to = std::min(mColorRangeMin + increment, mColorRangeMax); + ColorizeRange(mColorRangeMin, to); + mColorRangeMin = to; + + if (mColorRangeMax == mColorRangeMin) + { + mColorRangeMin = std::numeric_limits::max(); + mColorRangeMax = 0; + } + return; } - return; } -} -float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const -{ - auto& line = mLines[aFrom.mLine]; - float distance = 0.0f; - float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; - int colIndex = GetCharacterIndexR(aFrom); - for (size_t it = 0u; it < line.size() && it < colIndex; ) + float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const { - if (line[it].mChar == '\t') - { - distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); - ++it; - } - else + auto& line = mLines[aFrom.mLine]; + float distance = 0.0f; + float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; + int colIndex = GetCharacterIndexR(aFrom); + for (size_t it = 0u; it < line.size() && it < colIndex; ) { - auto d = UTF8CharLength(line[it].mChar); - char tempCString[7]; - int i = 0; - for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++) - tempCString[i] = line[it].mChar; + if (line[it].mChar == '\t') + { + distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + ++it; + } + else + { + auto d = UTF8CharLength(line[it].mChar); + char tempCString[7]; + int i = 0; + for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++) + tempCString[i] = line[it].mChar; - tempCString[i] = '\0'; - distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x; + tempCString[i] = '\0'; + distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x; + } } - } - - return distance; -} -void TextEditor::EnsureCursorVisible(int aCursor) -{ - if (aCursor == -1) - aCursor = mState.GetLastAddedCursorIndex(); + return distance; + } - if (!mWithinRender) + void TextEditor::EnsureCursorVisible(int aCursor) { - mScrollToCursor = true; - return; - } + if (aCursor == -1) + aCursor = mState.GetLastAddedCursorIndex(); - float scrollX = ImGui::GetScrollX(); - float scrollY = ImGui::GetScrollY(); + if (!mWithinRender) + { + mScrollToCursor = true; + return; + } - auto height = ImGui::GetWindowHeight(); - auto width = ImGui::GetWindowWidth(); + float scrollX = ImGui::GetScrollX(); + float scrollY = ImGui::GetScrollY(); - auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); - auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y); + auto height = ImGui::GetWindowHeight(); + auto width = ImGui::GetWindowWidth(); - auto left = (int)ceil(scrollX / mCharAdvance.x); - auto right = (int)ceil((scrollX + width) / mCharAdvance.x); + auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); + auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y); - auto pos = GetActualCursorCoordinates(aCursor); - auto len = TextDistanceToLineStart(pos); + auto left = (int)ceil(scrollX / mCharAdvance.x); + auto right = (int)ceil((scrollX + width) / mCharAdvance.x); - if (pos.mLine < top) - ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y)); - if (pos.mLine > bottom - 4) - ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height)); - if (len + mTextStart < left + 4) - ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); - if (len + mTextStart > right - 4) - ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); -} + auto pos = GetActualCursorCoordinates(aCursor); + auto len = TextDistanceToLineStart(pos); + + if (pos.mLine < top) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y)); + if (pos.mLine > bottom - 4) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height)); + if (len + mTextStart < left + 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); + if (len + mTextStart > right - 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); + } -int TextEditor::GetPageSize() const -{ - auto height = ImGui::GetWindowHeight() - 20.0f; - return (int)floor(height / mCharAdvance.y); -} + int TextEditor::GetPageSize() const + { + auto height = ImGui::GetWindowHeight() - 20.0f; + return (int)floor(height / mCharAdvance.y); + } -TextEditor::UndoRecord::UndoRecord( - const std::vector& aOperations, - TextEditor::EditorState& aBefore, - TextEditor::EditorState& aAfter) - : mOperations(aOperations) - , mBefore(aBefore) - , mAfter(aAfter) -{ - for (const UndoOperation& o : mOperations) - assert(o.mStart <= o.mEnd); -} + TextEditor::UndoRecord::UndoRecord( + const std::vector& aOperations, + TextEditor::EditorState& aBefore, + TextEditor::EditorState& aAfter) + : mOperations(aOperations) + , mBefore(aBefore) + , mAfter(aAfter) + { + for (const UndoOperation& o : mOperations) + assert(o.mStart <= o.mEnd); + } -void TextEditor::UndoRecord::Undo(TextEditor* aEditor) -{ - for (int i = mOperations.size() - 1; i > -1; i--) + void TextEditor::UndoRecord::Undo(TextEditor* aEditor) { - const UndoOperation& operation = mOperations[i]; - if (!operation.mText.empty()) + for (int i = mOperations.size() - 1; i > -1; i--) { - switch (operation.mType) - { - case UndoOperationType::Delete: - { - auto start = operation.mStart; - aEditor->InsertTextAt(start, operation.mText.c_str()); - aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2); - break; - } - case UndoOperationType::Add: + const UndoOperation& operation = mOperations[i]; + if (!operation.mText.empty()) { - aEditor->DeleteRange(operation.mStart, operation.mEnd); - aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2); - break; - } + switch (operation.mType) + { + case UndoOperationType::Delete: + { + auto start = operation.mStart; + aEditor->InsertTextAt(start, operation.mText.c_str()); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2); + break; + } + case UndoOperationType::Add: + { + aEditor->DeleteRange(operation.mStart, operation.mEnd); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 2); + break; + } + } } } - } - aEditor->mState = mBefore; - aEditor->EnsureCursorVisible(); -} + aEditor->mState = mBefore; + aEditor->EnsureCursorVisible(); + } -void TextEditor::UndoRecord::Redo(TextEditor* aEditor) -{ - for (int i = 0; i < mOperations.size(); i++) + void TextEditor::UndoRecord::Redo(TextEditor* aEditor) { - const UndoOperation& operation = mOperations[i]; - if (!operation.mText.empty()) + for (int i = 0; i < mOperations.size(); i++) { - switch (operation.mType) - { - case UndoOperationType::Delete: - { - aEditor->DeleteRange(operation.mStart, operation.mEnd); - aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1); - break; - } - case UndoOperationType::Add: + const UndoOperation& operation = mOperations[i]; + if (!operation.mText.empty()) { - auto start = operation.mStart; - aEditor->InsertTextAt(start, operation.mText.c_str()); - aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1); - break; - } + switch (operation.mType) + { + case UndoOperationType::Delete: + { + aEditor->DeleteRange(operation.mStart, operation.mEnd); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1); + break; + } + case UndoOperationType::Add: + { + auto start = operation.mStart; + aEditor->InsertTextAt(start, operation.mText.c_str()); + aEditor->Colorize(operation.mStart.mLine - 1, operation.mEnd.mLine - operation.mStart.mLine + 1); + break; + } + } } } - } - aEditor->mState = mAfter; - aEditor->EnsureCursorVisible(); -} + aEditor->mState = mAfter; + aEditor->EnsureCursorVisible(); + } +} \ No newline at end of file diff --git a/TextEditor.h b/TextEditor.h index b78b501c..7efb4243 100644 --- a/TextEditor.h +++ b/TextEditor.h @@ -12,493 +12,500 @@ #include #include "imgui.h" -class IMGUI_API TextEditor -{ -public: - enum class PaletteIndex - { - Default, - Keyword, - Number, - String, - CharLiteral, - Punctuation, - Preprocessor, - Identifier, - KnownIdentifier, - PreprocIdentifier, - Comment, - MultiLineComment, - Background, - Cursor, - Selection, - ErrorMarker, - ControlCharacter, - Breakpoint, - LineNumber, - CurrentLineFill, - CurrentLineFillInactive, - CurrentLineEdge, - Max - }; - - enum class SelectionMode - { - Normal, - Word, - Line - }; - - struct Breakpoint - { - int mLine; - bool mEnabled; - std::string mCondition; - - Breakpoint() - : mLine(-1) - , mEnabled(false) - {} - }; +namespace ImGuiColorTextEdit { - // Represents a character coordinate from the user's point of view, - // i. e. consider an uniform grid (assuming fixed-width font) on the - // screen as it is rendered, and each cell has its own coordinate, starting from 0. - // Tabs are counted as [1..mTabSize] count empty spaces, depending on - // how many space is necessary to reach the next tab stop. - // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4, - // because it is rendered as " ABC" on the screen. - struct Coordinates + class IMGUI_API TextEditor { - int mLine, mColumn; - Coordinates() : mLine(0), mColumn(0) {} - Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn) + public: + enum class PaletteIndex { - assert(aLine >= 0); - assert(aColumn >= 0); - } - static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; } - - bool operator ==(const Coordinates& o) const + Default, + Keyword, + Number, + String, + CharLiteral, + Punctuation, + Preprocessor, + Identifier, + KnownIdentifier, + PreprocIdentifier, + Comment, + MultiLineComment, + Background, + Cursor, + Selection, + ErrorMarker, + ControlCharacter, + Breakpoint, + LineNumber, + CurrentLineFill, + CurrentLineFillInactive, + CurrentLineEdge, + Max + }; + + enum class SelectionMode { - return - mLine == o.mLine && - mColumn == o.mColumn; - } + Normal, + Word, + Line + }; - bool operator !=(const Coordinates& o) const + struct Breakpoint { - return - mLine != o.mLine || - mColumn != o.mColumn; - } - - bool operator <(const Coordinates& o) const + int mLine; + bool mEnabled; + std::string mCondition; + + Breakpoint() + : mLine(-1) + , mEnabled(false) + {} + }; + + // Represents a character coordinate from the user's point of view, + // i. e. consider an uniform grid (assuming fixed-width font) on the + // screen as it is rendered, and each cell has its own coordinate, starting from 0. + // Tabs are counted as [1..mTabSize] count empty spaces, depending on + // how many space is necessary to reach the next tab stop. + // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4, + // because it is rendered as " ABC" on the screen. + struct Coordinates { - if (mLine != o.mLine) - return mLine < o.mLine; - return mColumn < o.mColumn; - } + int mLine, mColumn; + Coordinates() : mLine(0), mColumn(0) {} + Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn) + { + assert(aLine >= 0); + assert(aColumn >= 0); + } + static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; } - bool operator >(const Coordinates& o) const - { - if (mLine != o.mLine) - return mLine > o.mLine; - return mColumn > o.mColumn; - } + bool operator ==(const Coordinates& o) const + { + return + mLine == o.mLine && + mColumn == o.mColumn; + } - bool operator <=(const Coordinates& o) const - { - if (mLine != o.mLine) - return mLine < o.mLine; - return mColumn <= o.mColumn; - } + bool operator !=(const Coordinates& o) const + { + return + mLine != o.mLine || + mColumn != o.mColumn; + } - bool operator >=(const Coordinates& o) const - { - if (mLine != o.mLine) - return mLine > o.mLine; - return mColumn >= o.mColumn; - } + bool operator <(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn < o.mColumn; + } - Coordinates operator -(const Coordinates& o) - { - return Coordinates(mLine - o.mLine, mColumn - o.mColumn); - } + bool operator >(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn > o.mColumn; + } - Coordinates operator +(const Coordinates& o) - { - return Coordinates(mLine + o.mLine, mColumn + o.mColumn); - } - }; + bool operator <=(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn <= o.mColumn; + } - struct Identifier - { - Coordinates mLocation; - std::string mDeclaration; - }; + bool operator >=(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn >= o.mColumn; + } - typedef std::string String; - typedef std::unordered_map Identifiers; - typedef std::unordered_set Keywords; - typedef std::map ErrorMarkers; - typedef std::unordered_set Breakpoints; - typedef std::array Palette; - typedef uint8_t Char; + Coordinates operator -(const Coordinates& o) + { + return Coordinates(mLine - o.mLine, mColumn - o.mColumn); + } - struct Glyph - { - Char mChar; - PaletteIndex mColorIndex = PaletteIndex::Default; - bool mComment : 1; - bool mMultiLineComment : 1; - bool mPreprocessor : 1; - - Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), - mComment(false), mMultiLineComment(false), mPreprocessor(false) {} - }; + Coordinates operator +(const Coordinates& o) + { + return Coordinates(mLine + o.mLine, mColumn + o.mColumn); + } + }; - typedef std::vector Line; - typedef std::vector Lines; + struct Identifier + { + Coordinates mLocation; + std::string mDeclaration; + }; + + typedef std::string String; + typedef std::unordered_map Identifiers; + typedef std::unordered_set Keywords; + typedef std::map ErrorMarkers; + typedef std::unordered_set Breakpoints; + typedef std::array Palette; + typedef uint8_t Char; + + struct Glyph + { + Char mChar; + PaletteIndex mColorIndex = PaletteIndex::Default; + bool mComment : 1; + bool mMultiLineComment : 1; + bool mPreprocessor : 1; - struct LanguageDefinition - { - typedef std::pair TokenRegexString; - typedef std::vector TokenRegexStrings; - typedef bool(*TokenizeCallback)(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex); + Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), + mComment(false), mMultiLineComment(false), mPreprocessor(false) {} + }; - std::string mName; - Keywords mKeywords; - Identifiers mIdentifiers; - Identifiers mPreprocIdentifiers; - std::string mCommentStart, mCommentEnd, mSingleLineComment; - char mPreprocChar; - bool mAutoIndentation; + typedef std::vector Line; + typedef std::vector Lines; - TokenizeCallback mTokenize; + struct LanguageDefinition + { + typedef std::pair TokenRegexString; + typedef std::vector TokenRegexStrings; + typedef bool(*TokenizeCallback)(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex); - TokenRegexStrings mTokenRegexStrings; + std::string mName; + Keywords mKeywords; + Identifiers mIdentifiers; + Identifiers mPreprocIdentifiers; + std::string mCommentStart, mCommentEnd, mSingleLineComment; + char mPreprocChar; + bool mAutoIndentation; - bool mCaseSensitive; + TokenizeCallback mTokenize; - LanguageDefinition() - : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) - { - } + TokenRegexStrings mTokenRegexStrings; - static const LanguageDefinition& CPlusPlus(); - static const LanguageDefinition& HLSL(); - static const LanguageDefinition& GLSL(); - static const LanguageDefinition& Python(); - static const LanguageDefinition& C(); - static const LanguageDefinition& SQL(); - static const LanguageDefinition& AngelScript(); - static const LanguageDefinition& Lua(); - static const LanguageDefinition& CSharp(); - static const LanguageDefinition& Json(); - }; + bool mCaseSensitive; - enum class UndoOperationType { Add, Delete }; - struct UndoOperation - { - std::string mText; - TextEditor::Coordinates mStart; - TextEditor::Coordinates mEnd; - UndoOperationType mType; - }; + LanguageDefinition() + : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) + { + } + + static const LanguageDefinition& CPlusPlus(); + static const LanguageDefinition& HLSL(); + static const LanguageDefinition& GLSL(); + static const LanguageDefinition& Python(); + static const LanguageDefinition& C(); + static const LanguageDefinition& SQL(); + static const LanguageDefinition& AngelScript(); + static const LanguageDefinition& Lua(); + static const LanguageDefinition& CSharp(); + static const LanguageDefinition& Json(); + static const LanguageDefinition& Yaml(); + }; + + enum class UndoOperationType { Add, Delete }; + struct UndoOperation + { + std::string mText; + TextEditor::Coordinates mStart; + TextEditor::Coordinates mEnd; + UndoOperationType mType; + }; - TextEditor(); - ~TextEditor(); + TextEditor(); + ~TextEditor(); - void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); - const char* GetLanguageDefinitionName() const; + void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); + const char* GetLanguageDefinitionName() const; - const Palette& GetPalette() const { return mPaletteBase; } - void SetPalette(const Palette& aValue); + const Palette& GetPalette() const { return mPaletteBase; } + void SetPalette(const Palette& aValue); - void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; } - void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } + void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; } + void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } - bool Render(const char* aTitle, bool aParentIsFocused = false, const ImVec2& aSize = ImVec2(), bool aBorder = false); - void SetText(const std::string& aText); - std::string GetText() const; + bool Render(const char* aTitle, bool aParentIsFocused = false, const ImVec2& aSize = ImVec2(), bool aBorder = false); + void SetText(const std::string& aText); + std::string GetText() const; - void SetTextLines(const std::vector& aLines); - std::vector GetTextLines() const; + void SetTextLines(const std::vector& aLines); + std::vector GetTextLines() const; - std::string GetClipboardText() const; - std::string GetSelectedText(int aCursor = -1) const; - std::string GetCurrentLineText()const; + std::string GetClipboardText() const; + std::string GetSelectedText(int aCursor = -1) const; + std::string GetCurrentLineText()const; - int GetTotalLines() const { return (int)mLines.size(); } - bool IsOverwrite() const { return mOverwrite; } + int GetTotalLines() const { return (int)mLines.size(); } + bool IsOverwrite() const { return mOverwrite; } - void SetReadOnly(bool aValue); - bool IsReadOnly() const { return mReadOnly; } - bool IsTextChanged() const { return mTextChanged; } + void SetReadOnly(bool aValue); + bool IsReadOnly() const { return mReadOnly; } + bool IsTextChanged() const { return mTextChanged; } - void OnCursorPositionChanged(int aCursor); + void OnCursorPositionChanged(int aCursor); - bool IsColorizerEnabled() const { return mColorizerEnabled; } - void SetColorizerEnable(bool aValue); + bool IsColorizerEnabled() const { return mColorizerEnabled; } + void SetColorizerEnable(bool aValue); - Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } - void SetCursorPosition(const Coordinates& aPosition, int aCursor = -1); - void SetCursorPosition(int aLine, int aCharIndex, int aCursor = -1); + Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } + void SetCursorPosition(const Coordinates& aPosition, int aCursor = -1); + void SetCursorPosition(int aLine, int aCharIndex, int aCursor = -1); - inline void OnLineDeleted(int aLineIndex, const std::unordered_set* aHandledCursors = nullptr) - { - for (int c = 0; c <= mState.mCurrentCursor; c++) + inline void OnLineDeleted(int aLineIndex, const std::unordered_set* aHandledCursors = nullptr) { - if (mState.mCursors[c].mCursorPosition.mLine >= aLineIndex) + for (int c = 0; c <= mState.mCurrentCursor; c++) { - if (aHandledCursors == nullptr || aHandledCursors->find(c) == aHandledCursors->end()) // move up if has not been handled already - SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine - 1, mState.mCursors[c].mCursorPosition.mColumn }, c); + if (mState.mCursors[c].mCursorPosition.mLine >= aLineIndex) + { + if (aHandledCursors == nullptr || aHandledCursors->find(c) == aHandledCursors->end()) // move up if has not been handled already + SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine - 1, mState.mCursors[c].mCursorPosition.mColumn }, c); + } } } - } - inline void OnLinesDeleted(int aFirstLineIndex, int aLastLineIndex) - { - for (int c = 0; c <= mState.mCurrentCursor; c++) + inline void OnLinesDeleted(int aFirstLineIndex, int aLastLineIndex) { - if (mState.mCursors[c].mCursorPosition.mLine >= aFirstLineIndex) - SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine - (aLastLineIndex - aFirstLineIndex), mState.mCursors[c].mCursorPosition.mColumn }, c); + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].mCursorPosition.mLine >= aFirstLineIndex) + SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine - (aLastLineIndex - aFirstLineIndex), mState.mCursors[c].mCursorPosition.mColumn }, c); + } } - } - inline void OnLineAdded(int aLineIndex) - { - for (int c = 0; c <= mState.mCurrentCursor; c++) + inline void OnLineAdded(int aLineIndex) { - if (mState.mCursors[c].mCursorPosition.mLine >= aLineIndex) - SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine + 1, mState.mCursors[c].mCursorPosition.mColumn }, c); + for (int c = 0; c <= mState.mCurrentCursor; c++) + { + if (mState.mCursors[c].mCursorPosition.mLine >= aLineIndex) + SetCursorPosition({ mState.mCursors[c].mCursorPosition.mLine + 1, mState.mCursors[c].mCursorPosition.mColumn }, c); + } } - } - - inline void SetHandleMouseInputs(bool aValue) { mHandleMouseInputs = aValue; } - inline bool IsHandleMouseInputsEnabled() const { return mHandleMouseInputs; } - - inline void SetHandleKeyboardInputs(bool aValue) { mHandleKeyboardInputs = aValue; } - inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; } - - inline void SetImGuiChildIgnored(bool aValue) { mIgnoreImGuiChild = aValue; } - inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; } - - inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; } - inline bool IsShowingWhitespaces() const { return mShowWhitespaces; } - - inline void SetShowShortTabGlyphs(bool aValue) { mShowShortTabGlyphs = aValue; } - inline bool IsShowingShortTabGlyphs() const { return mShowShortTabGlyphs; } - - inline ImVec4 U32ColorToVec4(ImU32 in) { - float s = 1.0f / 255.0f; - return ImVec4( - ((in >> IM_COL32_A_SHIFT) & 0xFF) * s, - ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, - ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, - ((in >> IM_COL32_R_SHIFT) & 0xFF) * s); - } - - void SetTabSize(int aValue); - inline int GetTabSize() const { return mTabSize; } - - void InsertText(const std::string& aValue, int aCursor = -1); - void InsertText(const char* aValue, int aCursor = -1); - - void MoveUp(int aAmount = 1, bool aSelect = false); - void MoveDown(int aAmount = 1, bool aSelect = false); - void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); - void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); - void MoveTop(bool aSelect = false); - void MoveBottom(bool aSelect = false); - void MoveHome(bool aSelect = false); - void MoveEnd(bool aSelect = false); - - void SetSelectionStart(const Coordinates& aPosition, int aCursor = -1); - void SetSelectionEnd(const Coordinates& aPosition, int aCursor = -1); - void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal, int aCursor = -1, bool isSpawningNewCursor = false); - void SetSelection(int aStartLine, int aStartCharIndex, int aEndLine, int aEndCharIndex, SelectionMode aMode = SelectionMode::Normal, int aCursor = -1, bool isSpawningNewCursor = false); - void SelectWordUnderCursor(); - void SelectAll(); - bool HasSelection() const; - - void Copy(); - void Cut(); - void Paste(); - void Delete(bool aWordMode = false); - - int GetUndoIndex() const; - bool CanUndo() const; - bool CanRedo() const; - void Undo(int aSteps = 1); - void Redo(int aSteps = 1); - - void ClearExtraCursors(); - void ClearSelections(); - void SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor = -1); - void AddCursorForNextOccurrence(); - - static const Palette& GetMarianaPalette(); - static const Palette& GetDarkPalette(); - static const Palette& GetLightPalette(); - static const Palette& GetRetroBluePalette(); - - static bool IsGlyphWordChar(const Glyph& aGlyph); - - void ImGuiDebugPanel(const std::string& panelName = "Debug"); - void UnitTests(); -private: - typedef std::vector> RegexList; - - struct Cursor - { - Coordinates mCursorPosition = { 0, 0 }; - Coordinates mSelectionStart = { 0,0 }; - Coordinates mSelectionEnd = { 0,0 }; - Coordinates mInteractiveStart = { 0,0 }; - Coordinates mInteractiveEnd = { 0,0 }; - bool mCursorPositionChanged = false; - }; - struct EditorState - { - bool mPanning = false; - ImVec2 mLastMousePos; - int mCurrentCursor = 0; - int mLastAddedCursor = 0; - std::vector mCursors = { {{0,0}} }; - void AddCursor() - { - // vector is never resized to smaller size, mCurrentCursor points to last available cursor in vector - mCurrentCursor++; - mCursors.resize(mCurrentCursor + 1); - mLastAddedCursor = mCurrentCursor; - } - int GetLastAddedCursorIndex() - { - return mLastAddedCursor > mCurrentCursor ? 0 : mLastAddedCursor; - } - void SortCursorsFromTopToBottom() - { - Coordinates lastAddedCursorPos = mCursors[GetLastAddedCursorIndex()].mCursorPosition; - std::sort(mCursors.begin(), mCursors.begin() + (mCurrentCursor + 1), [](const Cursor& a, const Cursor& b) -> bool - { - return a.mSelectionStart < b.mSelectionStart; - }); - // update last added cursor index to be valid after sort - for (int c = mCurrentCursor; c > -1; c--) - if (mCursors[c].mCursorPosition == lastAddedCursorPos) - mLastAddedCursor = c; - } - }; + inline void SetHandleMouseInputs(bool aValue) { mHandleMouseInputs = aValue; } + inline bool IsHandleMouseInputsEnabled() const { return mHandleMouseInputs; } - void MergeCursorsIfPossible(); + inline void SetHandleKeyboardInputs(bool aValue) { mHandleKeyboardInputs = aValue; } + inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; } - class UndoRecord - { - public: - UndoRecord() {} - ~UndoRecord() {} + inline void SetImGuiChildIgnored(bool aValue) { mIgnoreImGuiChild = aValue; } + inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; } - UndoRecord( - const std::vector& aOperations, - TextEditor::EditorState& aBefore, - TextEditor::EditorState& aAfter); + inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; } + inline bool IsShowingWhitespaces() const { return mShowWhitespaces; } - void Undo(TextEditor* aEditor); - void Redo(TextEditor* aEditor); + inline void SetShowShortTabGlyphs(bool aValue) { mShowShortTabGlyphs = aValue; } + inline bool IsShowingShortTabGlyphs() const { return mShowShortTabGlyphs; } - std::vector mOperations; + inline ImVec4 U32ColorToVec4(ImU32 in) { + float s = 1.0f / 255.0f; + return ImVec4( + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s); + } - EditorState mBefore; - EditorState mAfter; - }; + void SetTabSize(int aValue); + inline int GetTabSize() const { return mTabSize; } + + void InsertText(const std::string& aValue, int aCursor = -1); + void InsertText(const char* aValue, int aCursor = -1); + + void MoveUp(int aAmount = 1, bool aSelect = false); + void MoveDown(int aAmount = 1, bool aSelect = false); + void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveTop(bool aSelect = false); + void MoveBottom(bool aSelect = false); + void MoveHome(bool aSelect = false); + void MoveEnd(bool aSelect = false); + + void SetSelectionStart(const Coordinates& aPosition, int aCursor = -1); + void SetSelectionEnd(const Coordinates& aPosition, int aCursor = -1); + void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal, int aCursor = -1, bool isSpawningNewCursor = false); + void SetSelection(int aStartLine, int aStartCharIndex, int aEndLine, int aEndCharIndex, SelectionMode aMode = SelectionMode::Normal, int aCursor = -1, bool isSpawningNewCursor = false); + void SelectWordUnderCursor(); + void SelectAll(); + bool HasSelection() const; + + void Copy(); + void Cut(); + void Paste(); + void Delete(bool aWordMode = false); + + int GetUndoIndex() const; + bool CanUndo() const; + bool CanRedo() const; + void Undo(int aSteps = 1); + void Redo(int aSteps = 1); + + void ClearExtraCursors(); + void ClearSelections(); + void SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor = -1); + void AddCursorForNextOccurrence(); + + static const Palette& GetMarianaPalette(); + static const Palette& GetDarkPalette(); + static const Palette& GetLightPalette(); + static const Palette& GetRetroBluePalette(); + + static bool IsGlyphWordChar(const Glyph& aGlyph); + + void ImGuiDebugPanel(const std::string& panelName = "Debug"); + void UnitTests(); + private: + typedef std::vector> RegexList; + + struct Cursor + { + Coordinates mCursorPosition = { 0, 0 }; + Coordinates mSelectionStart = { 0,0 }; + Coordinates mSelectionEnd = { 0,0 }; + Coordinates mInteractiveStart = { 0,0 }; + Coordinates mInteractiveEnd = { 0,0 }; + bool mCursorPositionChanged = false; + }; + + struct EditorState + { + bool mPanning = false; + ImVec2 mLastMousePos; + int mLineMin = 0; + int mLineMax = std::numeric_limits().max(); + int mCurrentCursor = 0; + int mLastAddedCursor = 0; + std::vector mCursors = { {{0,0}} }; + void AddCursor() + { + // vector is never resized to smaller size, mCurrentCursor points to last available cursor in vector + mCurrentCursor++; + mCursors.resize(mCurrentCursor + 1); + mLastAddedCursor = mCurrentCursor; + } + int GetLastAddedCursorIndex() + { + return mLastAddedCursor > mCurrentCursor ? 0 : mLastAddedCursor; + } + void SortCursorsFromTopToBottom() + { + Coordinates lastAddedCursorPos = mCursors[GetLastAddedCursorIndex()].mCursorPosition; + std::sort(mCursors.begin(), mCursors.begin() + (mCurrentCursor + 1), [](const Cursor& a, const Cursor& b) -> bool + { + return a.mSelectionStart < b.mSelectionStart; + }); + // update last added cursor index to be valid after sort + for (int c = mCurrentCursor; c > -1; c--) + if (mCursors[c].mCursorPosition == lastAddedCursorPos) + mLastAddedCursor = c; + } + }; + + void MergeCursorsIfPossible(); - typedef std::vector UndoBuffer; - - void ProcessInputs(); - void Colorize(int aFromLine = 0, int aCount = -1); - void ColorizeRange(int aFromLine = 0, int aToLine = 0); - void ColorizeInternal(); - float TextDistanceToLineStart(const Coordinates& aFrom) const; - void EnsureCursorVisible(int aCursor = -1); - int GetPageSize() const; - std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; - Coordinates GetActualCursorCoordinates(int aCursor = -1) const; - Coordinates SanitizeCoordinates(const Coordinates& aValue) const; - void Advance(Coordinates& aCoordinates) const; - void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); - int InsertTextAt(Coordinates& aWhere, const char* aValue); - void AddUndo(UndoRecord& aValue); - Coordinates ScreenPosToCoordinates(const ImVec2& aPosition, bool aInsertionMode = false, bool* isOverLineNumber = nullptr) const; - Coordinates FindWordStart(const Coordinates& aFrom) const; - Coordinates FindWordEnd(const Coordinates& aFrom) const; - Coordinates FindNextWord(const Coordinates& aFrom) const; - int GetCharacterIndexL(const Coordinates& aCoordinates) const; - int GetCharacterIndexR(const Coordinates& aCoordinates) const; - int GetCharacterColumn(int aLine, int aIndex) const; - int GetLineCharacterCount(int aLine) const; - int GetLineMaxColumn(int aLine) const; - bool IsOnWordBoundary(const Coordinates& aAt) const; - void RemoveLines(int aStart, int aEnd); - void RemoveLine(int aIndex, const std::unordered_set* aHandledCursors = nullptr); - void RemoveCurrentLines(); - void OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted); - void RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar = -1); - void AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd); - void AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph); - Line& InsertLine(int aIndex); - void ChangeCurrentLinesIndentation(bool aIncrease); - void ToggleLineComment(); - void EnterCharacter(ImWchar aChar, bool aShift); - void Backspace(bool aWordMode = false); - void DeleteSelection(int aCursor = -1); - std::string GetWordUnderCursor() const; - std::string GetWordAt(const Coordinates& aCoords) const; - ImU32 GetGlyphColor(const Glyph& aGlyph) const; - - void HandleKeyboardInputs(bool aParentIsFocused = false); - void HandleMouseInputs(); - void UpdatePalette(); - void Render(bool aParentIsFocused = false); - - bool FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd); - - float mLineSpacing; - Lines mLines; - EditorState mState; - UndoBuffer mUndoBuffer; - int mUndoIndex; - - int mTabSize; - bool mOverwrite; - bool mReadOnly; - bool mWithinRender; - bool mScrollToCursor; - bool mScrollToTop; - bool mTextChanged; - bool mColorizerEnabled; - float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor. - int mLeftMargin; - int mColorRangeMin, mColorRangeMax; - SelectionMode mSelectionMode; - bool mHandleKeyboardInputs; - bool mHandleMouseInputs; - bool mIgnoreImGuiChild; - bool mShowWhitespaces; - bool mShowShortTabGlyphs; - bool mDraggingSelection = false; - - Palette mPaletteBase; - Palette mPalette; - const LanguageDefinition* mLanguageDefinition = nullptr; - RegexList mRegexList; - - bool mCheckComments; - Breakpoints mBreakpoints; - ErrorMarkers mErrorMarkers; - ImVec2 mCharAdvance; - std::string mLineBuffer; - uint64_t mStartTime; - - float mLastClick; -}; + class UndoRecord + { + public: + UndoRecord() {} + ~UndoRecord() {} + + UndoRecord( + const std::vector& aOperations, + TextEditor::EditorState& aBefore, + TextEditor::EditorState& aAfter); + + void Undo(TextEditor* aEditor); + void Redo(TextEditor* aEditor); + + std::vector mOperations; + + EditorState mBefore; + EditorState mAfter; + }; + + typedef std::vector UndoBuffer; + + void ProcessInputs(); + void Colorize(int aFromLine = -1, int aCount = -1); + void ColorizeRange(int aFromLine = 0, int aToLine = 0); + void ColorizeInternal(); + float TextDistanceToLineStart(const Coordinates& aFrom) const; + void EnsureCursorVisible(int aCursor = -1); + int GetPageSize() const; + std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; + Coordinates GetActualCursorCoordinates(int aCursor = -1) const; + Coordinates SanitizeCoordinates(const Coordinates& aValue) const; + void Advance(Coordinates& aCoordinates) const; + void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); + int InsertTextAt(Coordinates& aWhere, const char* aValue); + void AddUndo(UndoRecord& aValue); + Coordinates ScreenPosToCoordinates(const ImVec2& aPosition, bool aInsertionMode = false, bool* isOverLineNumber = nullptr) const; + Coordinates FindWordStart(const Coordinates& aFrom) const; + Coordinates FindWordEnd(const Coordinates& aFrom) const; + Coordinates FindNextWord(const Coordinates& aFrom) const; + int GetCharacterIndexL(const Coordinates& aCoordinates) const; + int GetCharacterIndexR(const Coordinates& aCoordinates) const; + int GetCharacterColumn(int aLine, int aIndex) const; + int GetLineCharacterCount(int aLine) const; + int GetLineMaxColumn(int aLine) const; + bool IsOnWordBoundary(const Coordinates& aAt) const; + void RemoveLines(int aStart, int aEnd); + void RemoveLine(int aIndex, const std::unordered_set* aHandledCursors = nullptr); + void RemoveCurrentLines(); + void OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted); + void RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar = -1); + void AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd); + void AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph); + Line& InsertLine(int aIndex); + void ChangeCurrentLinesIndentation(bool aIncrease); + void ToggleLineComment(); + void EnterCharacter(ImWchar aChar, bool aShift); + void Backspace(bool aWordMode = false); + void DeleteSelection(int aCursor = -1); + std::string GetWordUnderCursor() const; + std::string GetWordAt(const Coordinates& aCoords) const; + ImU32 GetGlyphColor(const Glyph& aGlyph) const; + + void HandleKeyboardInputs(bool aParentIsFocused = false); + void HandleMouseInputs(); + void UpdatePalette(); + void Render(bool aParentIsFocused = false); + + bool FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd); + + float mLineSpacing; + Lines mLines; + EditorState mState; + UndoBuffer mUndoBuffer; + int mUndoIndex; + + int mTabSize; + bool mOverwrite; + bool mReadOnly; + bool mWithinRender; + bool mScrollToCursor; + bool mScrollToTop; + bool mTextChanged; + bool mColorizerEnabled; + float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor. + int mLeftMargin; + int mColorRangeMin, mColorRangeMax; + bool mNeedsWindowColorization; + SelectionMode mSelectionMode; + bool mHandleKeyboardInputs; + bool mHandleMouseInputs; + bool mIgnoreImGuiChild; + bool mShowWhitespaces; + bool mShowShortTabGlyphs; + bool mDraggingSelection = false; + + Palette mPaletteBase; + Palette mPalette; + const LanguageDefinition* mLanguageDefinition = nullptr; + RegexList mRegexList; + + bool mCheckComments; + Breakpoints mBreakpoints; + ErrorMarkers mErrorMarkers; + ImVec2 mCharAdvance; + std::string mLineBuffer; + uint64_t mStartTime; + + float mLastClick; + }; +} From e834f13695314a3da90a87fa610fa25318e460b4 Mon Sep 17 00:00:00 2001 From: S41L0R <60116462+S41L0R@users.noreply.github.com> Date: Sun, 4 Jun 2023 14:54:30 -0400 Subject: [PATCH 2/3] Put more stuff under namespace --- ImGuiDebugPanel.cpp | 72 ++++---- UnitTests.cpp | 414 ++++++++++++++++++++++---------------------- 2 files changed, 245 insertions(+), 241 deletions(-) diff --git a/ImGuiDebugPanel.cpp b/ImGuiDebugPanel.cpp index 3a52577b..1c088f55 100644 --- a/ImGuiDebugPanel.cpp +++ b/ImGuiDebugPanel.cpp @@ -1,48 +1,50 @@ #include "TextEditor.h" -void TextEditor::ImGuiDebugPanel(const std::string& panelName) -{ - ImGui::Begin(panelName.c_str()); - - if (ImGui::CollapsingHeader("Editor state info")) +namespace ImGuiColorTextEdit { + void TextEditor::ImGuiDebugPanel(const std::string& panelName) { - ImGui::Checkbox("Panning", &mState.mPanning); - ImGui::DragInt("Cursor count", &mState.mCurrentCursor); - for (int i = 0; i <= mState.mCurrentCursor; i++) + ImGui::Begin(panelName.c_str()); + + if (ImGui::CollapsingHeader("Editor state info")) { - ImGui::DragInt2("Cursor", &mState.mCursors[i].mCursorPosition.mLine); - ImGui::DragInt2("Selection start", &mState.mCursors[i].mSelectionStart.mLine); - ImGui::DragInt2("Selection end", &mState.mCursors[i].mSelectionEnd.mLine); - ImGui::DragInt2("Interactive start", &mState.mCursors[i].mInteractiveStart.mLine); - ImGui::DragInt2("Interactive end", &mState.mCursors[i].mInteractiveEnd.mLine); + ImGui::Checkbox("Panning", &mState.mPanning); + ImGui::DragInt("Cursor count", &mState.mCurrentCursor); + for (int i = 0; i <= mState.mCurrentCursor; i++) + { + ImGui::DragInt2("Cursor", &mState.mCursors[i].mCursorPosition.mLine); + ImGui::DragInt2("Selection start", &mState.mCursors[i].mSelectionStart.mLine); + ImGui::DragInt2("Selection end", &mState.mCursors[i].mSelectionEnd.mLine); + ImGui::DragInt2("Interactive start", &mState.mCursors[i].mInteractiveStart.mLine); + ImGui::DragInt2("Interactive end", &mState.mCursors[i].mInteractiveEnd.mLine); + } } - } - if (ImGui::CollapsingHeader("Undo")) - { - static std::string numberOfRecordsText; - numberOfRecordsText = "Number of records: " + std::to_string(mUndoBuffer.size()); - ImGui::Text("%s", numberOfRecordsText.c_str()); - ImGui::DragInt("Undo index", &mState.mCurrentCursor); - for (int i = 0; i < mUndoBuffer.size(); i++) + if (ImGui::CollapsingHeader("Undo")) { - if (ImGui::CollapsingHeader(std::to_string(i).c_str())) + static std::string numberOfRecordsText; + numberOfRecordsText = "Number of records: " + std::to_string(mUndoBuffer.size()); + ImGui::Text("%s", numberOfRecordsText.c_str()); + ImGui::DragInt("Undo index", &mState.mCurrentCursor); + for (int i = 0; i < mUndoBuffer.size(); i++) { - - ImGui::Text("Operations"); - for (int j = 0; j < mUndoBuffer[i].mOperations.size(); j++) + if (ImGui::CollapsingHeader(std::to_string(i).c_str())) { - ImGui::Text("%s", mUndoBuffer[i].mOperations[j].mText.c_str()); - ImGui::Text(mUndoBuffer[i].mOperations[j].mType == UndoOperationType::Add ? "Add" : "Delete"); - ImGui::DragInt2("Start", &mUndoBuffer[i].mOperations[j].mStart.mLine); - ImGui::DragInt2("End", &mUndoBuffer[i].mOperations[j].mEnd.mLine); - ImGui::Separator(); + + ImGui::Text("Operations"); + for (int j = 0; j < mUndoBuffer[i].mOperations.size(); j++) + { + ImGui::Text("%s", mUndoBuffer[i].mOperations[j].mText.c_str()); + ImGui::Text(mUndoBuffer[i].mOperations[j].mType == UndoOperationType::Add ? "Add" : "Delete"); + ImGui::DragInt2("Start", &mUndoBuffer[i].mOperations[j].mStart.mLine); + ImGui::DragInt2("End", &mUndoBuffer[i].mOperations[j].mEnd.mLine); + ImGui::Separator(); + } } } } + if (ImGui::Button("Run unit tests")) + { + UnitTests(); + } + ImGui::End(); } - if (ImGui::Button("Run unit tests")) - { - UnitTests(); - } - ImGui::End(); } \ No newline at end of file diff --git a/UnitTests.cpp b/UnitTests.cpp index 86da4f75..e38dda7b 100644 --- a/UnitTests.cpp +++ b/UnitTests.cpp @@ -1,221 +1,223 @@ #include "TextEditor.h" -void TextEditor::UnitTests() -{ - SetText(" \t \t \t \t\n"); - // --- GetCharacterColumn --- // +namespace ImGuiColorTextEdit { + void TextEditor::UnitTests() { - // Returns column given line and character index in that line. - // Column is on the left side of character - assert(GetCharacterColumn(0, 0) == 0); - assert(GetCharacterColumn(0, 1) == 1); - assert(GetCharacterColumn(0, 2) == 4); - assert(GetCharacterColumn(0, 3) == 5); - assert(GetCharacterColumn(0, 4) == 6); - assert(GetCharacterColumn(0, 5) == 8); - assert(GetCharacterColumn(0, 6) == 9); - assert(GetCharacterColumn(0, 7) == 10); - assert(GetCharacterColumn(0, 8) == 11); - assert(GetCharacterColumn(0, 9) == 12); - assert(GetCharacterColumn(0, 10) == 13); - assert(GetCharacterColumn(0, 11) == 16); - assert(GetCharacterColumn(0, 12) == 16); // out of range - // empty line - assert(GetCharacterColumn(1, 0) == 0); - assert(GetCharacterColumn(1, 1) == 0); // out of range - assert(GetCharacterColumn(1, 2) == 0); // out of range - // nonexistent line - assert(GetCharacterColumn(2, 0) == 0); - assert(GetCharacterColumn(2, 1) == 0); - assert(GetCharacterColumn(2, 2) == 0); - } + SetText(" \t \t \t \t\n"); + // --- GetCharacterColumn --- // + { + // Returns column given line and character index in that line. + // Column is on the left side of character + assert(GetCharacterColumn(0, 0) == 0); + assert(GetCharacterColumn(0, 1) == 1); + assert(GetCharacterColumn(0, 2) == 4); + assert(GetCharacterColumn(0, 3) == 5); + assert(GetCharacterColumn(0, 4) == 6); + assert(GetCharacterColumn(0, 5) == 8); + assert(GetCharacterColumn(0, 6) == 9); + assert(GetCharacterColumn(0, 7) == 10); + assert(GetCharacterColumn(0, 8) == 11); + assert(GetCharacterColumn(0, 9) == 12); + assert(GetCharacterColumn(0, 10) == 13); + assert(GetCharacterColumn(0, 11) == 16); + assert(GetCharacterColumn(0, 12) == 16); // out of range + // empty line + assert(GetCharacterColumn(1, 0) == 0); + assert(GetCharacterColumn(1, 1) == 0); // out of range + assert(GetCharacterColumn(1, 2) == 0); // out of range + // nonexistent line + assert(GetCharacterColumn(2, 0) == 0); + assert(GetCharacterColumn(2, 1) == 0); + assert(GetCharacterColumn(2, 2) == 0); + } - // --- GetCharacterIndexL --- // - { - // Returns character index from coordinates, if coordinates are in the middle of a tab character it returns character index of that tab character - assert(GetCharacterIndexL({ 0, 0 }) == 0); - assert(GetCharacterIndexL({ 0, 1 }) == 1); - assert(GetCharacterIndexL({ 0, 2 }) == 1); - assert(GetCharacterIndexL({ 0, 3 }) == 1); - assert(GetCharacterIndexL({ 0, 4 }) == 2); - assert(GetCharacterIndexL({ 0, 5 }) == 3); - assert(GetCharacterIndexL({ 0, 6 }) == 4); - assert(GetCharacterIndexL({ 0, 7 }) == 4); - assert(GetCharacterIndexL({ 0, 8 }) == 5); - assert(GetCharacterIndexL({ 0, 9 }) == 6); - assert(GetCharacterIndexL({ 0, 10 }) == 7); - assert(GetCharacterIndexL({ 0, 11 }) == 8); - assert(GetCharacterIndexL({ 0, 12 }) == 9); - assert(GetCharacterIndexL({ 0, 13 }) == 10); - assert(GetCharacterIndexL({ 0, 14 }) == 10); - assert(GetCharacterIndexL({ 0, 15 }) == 10); - assert(GetCharacterIndexL({ 0, 16 }) == 11); - assert(GetCharacterIndexL({ 0, 17 }) == 11); // out of range - assert(GetCharacterIndexL({ 0, 18 }) == 11); // out of range - // empty line - assert(GetCharacterIndexL({ 1, 0 }) == 0); - assert(GetCharacterIndexL({ 1, 1 }) == 0); // out of range - assert(GetCharacterIndexL({ 1, 2 }) == 0); // out of range - // nonexistent line - assert(GetCharacterIndexL({ 2, 0 }) == -1); - assert(GetCharacterIndexL({ 2, 1 }) == -1); - assert(GetCharacterIndexL({ 2, 2 }) == -1); - } + // --- GetCharacterIndexL --- // + { + // Returns character index from coordinates, if coordinates are in the middle of a tab character it returns character index of that tab character + assert(GetCharacterIndexL({ 0, 0 }) == 0); + assert(GetCharacterIndexL({ 0, 1 }) == 1); + assert(GetCharacterIndexL({ 0, 2 }) == 1); + assert(GetCharacterIndexL({ 0, 3 }) == 1); + assert(GetCharacterIndexL({ 0, 4 }) == 2); + assert(GetCharacterIndexL({ 0, 5 }) == 3); + assert(GetCharacterIndexL({ 0, 6 }) == 4); + assert(GetCharacterIndexL({ 0, 7 }) == 4); + assert(GetCharacterIndexL({ 0, 8 }) == 5); + assert(GetCharacterIndexL({ 0, 9 }) == 6); + assert(GetCharacterIndexL({ 0, 10 }) == 7); + assert(GetCharacterIndexL({ 0, 11 }) == 8); + assert(GetCharacterIndexL({ 0, 12 }) == 9); + assert(GetCharacterIndexL({ 0, 13 }) == 10); + assert(GetCharacterIndexL({ 0, 14 }) == 10); + assert(GetCharacterIndexL({ 0, 15 }) == 10); + assert(GetCharacterIndexL({ 0, 16 }) == 11); + assert(GetCharacterIndexL({ 0, 17 }) == 11); // out of range + assert(GetCharacterIndexL({ 0, 18 }) == 11); // out of range + // empty line + assert(GetCharacterIndexL({ 1, 0 }) == 0); + assert(GetCharacterIndexL({ 1, 1 }) == 0); // out of range + assert(GetCharacterIndexL({ 1, 2 }) == 0); // out of range + // nonexistent line + assert(GetCharacterIndexL({ 2, 0 }) == -1); + assert(GetCharacterIndexL({ 2, 1 }) == -1); + assert(GetCharacterIndexL({ 2, 2 }) == -1); + } - // --- GetCharacterIndexR --- // - { - // Returns character index from coordinates, if coordinates are in the middle of a tab character it returns character index of next character - assert(GetCharacterIndexR({ 0, 0 }) == 0); - assert(GetCharacterIndexR({ 0, 1 }) == 1); - assert(GetCharacterIndexR({ 0, 2 }) == 2); - assert(GetCharacterIndexR({ 0, 3 }) == 2); - assert(GetCharacterIndexR({ 0, 4 }) == 2); - assert(GetCharacterIndexR({ 0, 5 }) == 3); - assert(GetCharacterIndexR({ 0, 6 }) == 4); - assert(GetCharacterIndexR({ 0, 7 }) == 5); - assert(GetCharacterIndexR({ 0, 8 }) == 5); - assert(GetCharacterIndexR({ 0, 9 }) == 6); - assert(GetCharacterIndexR({ 0, 10 }) == 7); - assert(GetCharacterIndexR({ 0, 11 }) == 8); - assert(GetCharacterIndexR({ 0, 12 }) == 9); - assert(GetCharacterIndexR({ 0, 13 }) == 10); - assert(GetCharacterIndexR({ 0, 14 }) == 11); - assert(GetCharacterIndexR({ 0, 15 }) == 11); - assert(GetCharacterIndexR({ 0, 16 }) == 11); - assert(GetCharacterIndexR({ 0, 17 }) == 11); // out of range - assert(GetCharacterIndexR({ 0, 18 }) == 11); // out of range - // empty line - assert(GetCharacterIndexR({ 1, 0 }) == 0); - assert(GetCharacterIndexR({ 1, 1 }) == 0); // out of range - assert(GetCharacterIndexR({ 1, 2 }) == 0); // out of range - // nonexistent line - assert(GetCharacterIndexR({ 2, 0 }) == -1); - assert(GetCharacterIndexR({ 2, 1 }) == -1); - assert(GetCharacterIndexR({ 2, 2 }) == -1); - } + // --- GetCharacterIndexR --- // + { + // Returns character index from coordinates, if coordinates are in the middle of a tab character it returns character index of next character + assert(GetCharacterIndexR({ 0, 0 }) == 0); + assert(GetCharacterIndexR({ 0, 1 }) == 1); + assert(GetCharacterIndexR({ 0, 2 }) == 2); + assert(GetCharacterIndexR({ 0, 3 }) == 2); + assert(GetCharacterIndexR({ 0, 4 }) == 2); + assert(GetCharacterIndexR({ 0, 5 }) == 3); + assert(GetCharacterIndexR({ 0, 6 }) == 4); + assert(GetCharacterIndexR({ 0, 7 }) == 5); + assert(GetCharacterIndexR({ 0, 8 }) == 5); + assert(GetCharacterIndexR({ 0, 9 }) == 6); + assert(GetCharacterIndexR({ 0, 10 }) == 7); + assert(GetCharacterIndexR({ 0, 11 }) == 8); + assert(GetCharacterIndexR({ 0, 12 }) == 9); + assert(GetCharacterIndexR({ 0, 13 }) == 10); + assert(GetCharacterIndexR({ 0, 14 }) == 11); + assert(GetCharacterIndexR({ 0, 15 }) == 11); + assert(GetCharacterIndexR({ 0, 16 }) == 11); + assert(GetCharacterIndexR({ 0, 17 }) == 11); // out of range + assert(GetCharacterIndexR({ 0, 18 }) == 11); // out of range + // empty line + assert(GetCharacterIndexR({ 1, 0 }) == 0); + assert(GetCharacterIndexR({ 1, 1 }) == 0); // out of range + assert(GetCharacterIndexR({ 1, 2 }) == 0); // out of range + // nonexistent line + assert(GetCharacterIndexR({ 2, 0 }) == -1); + assert(GetCharacterIndexR({ 2, 1 }) == -1); + assert(GetCharacterIndexR({ 2, 2 }) == -1); + } - // --- Advance --- // - { - // Returns coordinates one character to the right, taking into account tab size - Coordinates testCoords = { 0, 0 }; - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 1); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 4); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 5); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 6); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 8); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 9); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 10); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 11); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 12); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 13); - Advance(testCoords); - assert(testCoords.mLine == 0 && testCoords.mColumn == 16); - Advance(testCoords); - assert(testCoords.mLine == 1 && testCoords.mColumn == 0); - Advance(testCoords); - assert(testCoords.mLine == 1 && testCoords.mColumn == 0); - } + // --- Advance --- // + { + // Returns coordinates one character to the right, taking into account tab size + Coordinates testCoords = { 0, 0 }; + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 1); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 4); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 5); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 6); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 8); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 9); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 10); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 11); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 12); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 13); + Advance(testCoords); + assert(testCoords.mLine == 0 && testCoords.mColumn == 16); + Advance(testCoords); + assert(testCoords.mLine == 1 && testCoords.mColumn == 0); + Advance(testCoords); + assert(testCoords.mLine == 1 && testCoords.mColumn == 0); + } - // --- GetText --- // - { - // Gets text from aStart to aEnd, tabs are counted on the start position - std::string text = GetText({ 0, 0 }, { 0, 1 }); - assert(text.compare(" ") == 0); - text = GetText({ 0, 1 }, { 0, 2 }); - assert(text.compare("\t") == 0); - text = GetText({ 0, 2 }, { 0, 3 }); - assert(text.compare("") == 0); - text = GetText({ 0, 3 }, { 0, 4 }); - assert(text.compare("") == 0); - text = GetText({ 0, 4 }, { 0, 5 }); - assert(text.compare(" ") == 0); - text = GetText({ 0, 5 }, { 0, 6 }); - assert(text.compare(" ") == 0); - text = GetText({ 0, 6 }, { 0, 7 }); - assert(text.compare("\t") == 0); - text = GetText({ 0, 7 }, { 0, 8 }); - assert(text.compare("") == 0); + // --- GetText --- // + { + // Gets text from aStart to aEnd, tabs are counted on the start position + std::string text = GetText({ 0, 0 }, { 0, 1 }); + assert(text.compare(" ") == 0); + text = GetText({ 0, 1 }, { 0, 2 }); + assert(text.compare("\t") == 0); + text = GetText({ 0, 2 }, { 0, 3 }); + assert(text.compare("") == 0); + text = GetText({ 0, 3 }, { 0, 4 }); + assert(text.compare("") == 0); + text = GetText({ 0, 4 }, { 0, 5 }); + assert(text.compare(" ") == 0); + text = GetText({ 0, 5 }, { 0, 6 }); + assert(text.compare(" ") == 0); + text = GetText({ 0, 6 }, { 0, 7 }); + assert(text.compare("\t") == 0); + text = GetText({ 0, 7 }, { 0, 8 }); + assert(text.compare("") == 0); - text = GetText({ 0, 0 }, { 0, 8 }); - assert(text.compare(" \t \t") == 0); - text = GetText({ 0, 0 }, { 0, 7 }); - assert(text.compare(" \t \t") == 0); - text = GetText({ 0, 0 }, { 0, 6 }); - assert(text.compare(" \t ") == 0); - text = GetText({ 0, 4 }, { 0, 12 }); - assert(text.compare(" \t \t") == 0); - text = GetText({ 0, 4 }, { 0, 13 }); - assert(text.compare(" \t \t ") == 0); - text = GetText({ 0, 4 }, { 0, 14 }); - assert(text.compare(" \t \t \t") == 0); - text = GetText({ 0, 4 }, { 0, 15 }); - assert(text.compare(" \t \t \t") == 0); - text = GetText({ 0, 4 }, { 0, 16 }); - assert(text.compare(" \t \t \t") == 0); + text = GetText({ 0, 0 }, { 0, 8 }); + assert(text.compare(" \t \t") == 0); + text = GetText({ 0, 0 }, { 0, 7 }); + assert(text.compare(" \t \t") == 0); + text = GetText({ 0, 0 }, { 0, 6 }); + assert(text.compare(" \t ") == 0); + text = GetText({ 0, 4 }, { 0, 12 }); + assert(text.compare(" \t \t") == 0); + text = GetText({ 0, 4 }, { 0, 13 }); + assert(text.compare(" \t \t ") == 0); + text = GetText({ 0, 4 }, { 0, 14 }); + assert(text.compare(" \t \t \t") == 0); + text = GetText({ 0, 4 }, { 0, 15 }); + assert(text.compare(" \t \t \t") == 0); + text = GetText({ 0, 4 }, { 0, 16 }); + assert(text.compare(" \t \t \t") == 0); - text = GetText({ 0, 0 }, { 1, 0 }); - assert(text.compare(" \t \t \t \t\n") == 0); - } + text = GetText({ 0, 0 }, { 1, 0 }); + assert(text.compare(" \t \t \t \t\n") == 0); + } - // --- DeleteRange --- // - { - // Deletes from start to end coordinates, any overlapping tabs will be deleted, doesn't allow out of range lines - DeleteRange({ 0, 0 }, { 0, 0 }); - assert(GetText() == " \t \t \t \t\n"); - DeleteRange({ 0, 0 }, { 0, 1 }); - assert(GetText() == "\t \t \t \t\n"); - DeleteRange({ 0, 0 }, { 0, 2 }); - assert(GetText() == " \t \t \t\n"); - DeleteRange({ 0, 12 }, { 0, 12 }); - assert(GetText() == " \t \t \t\n"); - DeleteRange({ 1, 0 }, { 1, 0 }); - assert(GetText() == " \t \t \t\n"); - DeleteRange({ 0, 11 }, { 0, 12 }); - assert(GetText() == " \t \t \n"); - DeleteRange({ 0, 2 }, { 0, 3 }); - assert(GetText() == " \t \n"); - DeleteRange({ 0, 6 }, { 0, 7 }); - assert(GetText() == " \n"); - SetText("a\nb\nc\nd\ne"); - DeleteRange({ 0, 0 }, { 2, 1 }); - assert(GetText() == "\nd\ne"); - DeleteRange({ 1, 1 }, { 2, 0 }); - assert(GetText() == "\nde"); - DeleteRange({ 1, 1 }, { 1, 15 }); // out of range column - assert(GetText() == "\nd"); - SetText("asdf\nzxcv\nqwer\npo"); - DeleteRange({ 1, 2 }, { 1, 200 }); // out of range column - assert(GetText() == "asdf\nzx\nqwer\npo"); - DeleteRange({ 0, 500 }, { 2, 500 }); // out of range column - assert(GetText() == "asdf\npo"); - } + // --- DeleteRange --- // + { + // Deletes from start to end coordinates, any overlapping tabs will be deleted, doesn't allow out of range lines + DeleteRange({ 0, 0 }, { 0, 0 }); + assert(GetText() == " \t \t \t \t\n"); + DeleteRange({ 0, 0 }, { 0, 1 }); + assert(GetText() == "\t \t \t \t\n"); + DeleteRange({ 0, 0 }, { 0, 2 }); + assert(GetText() == " \t \t \t\n"); + DeleteRange({ 0, 12 }, { 0, 12 }); + assert(GetText() == " \t \t \t\n"); + DeleteRange({ 1, 0 }, { 1, 0 }); + assert(GetText() == " \t \t \t\n"); + DeleteRange({ 0, 11 }, { 0, 12 }); + assert(GetText() == " \t \t \n"); + DeleteRange({ 0, 2 }, { 0, 3 }); + assert(GetText() == " \t \n"); + DeleteRange({ 0, 6 }, { 0, 7 }); + assert(GetText() == " \n"); + SetText("a\nb\nc\nd\ne"); + DeleteRange({ 0, 0 }, { 2, 1 }); + assert(GetText() == "\nd\ne"); + DeleteRange({ 1, 1 }, { 2, 0 }); + assert(GetText() == "\nde"); + DeleteRange({ 1, 1 }, { 1, 15 }); // out of range column + assert(GetText() == "\nd"); + SetText("asdf\nzxcv\nqwer\npo"); + DeleteRange({ 1, 2 }, { 1, 200 }); // out of range column + assert(GetText() == "asdf\nzx\nqwer\npo"); + DeleteRange({ 0, 500 }, { 2, 500 }); // out of range column + assert(GetText() == "asdf\npo"); + } - // --- RemoveGlyphsFromLine --- // - { - // - } + // --- RemoveGlyphsFromLine --- // + { + // + } - SetText("asdf asdf\nasdf\nasdf\tasdf\n zxcv zxcv"); - // --- FindNextOccurrence --- // - { - Coordinates outStart, outEnd; - assert(FindNextOccurrence("asdf", 4, { 0, 0 }, outStart, outEnd) && outStart == Coordinates(0, 0) && outEnd == Coordinates(0, 4)); - assert(FindNextOccurrence("asdf", 4, { 0, 1 }, outStart, outEnd) && outStart == Coordinates(0, 5) && outEnd == Coordinates(0, 9)); - assert(FindNextOccurrence("asdf", 4, { 0, 5 }, outStart, outEnd) && outStart == Coordinates(0, 5) && outEnd == Coordinates(0, 9)); - assert(FindNextOccurrence("asdf", 4, { 0, 6 }, outStart, outEnd) && outStart == Coordinates(1, 0) && outEnd == Coordinates(1, 4)); - assert(FindNextOccurrence("asdf", 4, { 3, 3 }, outStart, outEnd) && outStart == Coordinates(0, 0) && outEnd == Coordinates(0, 4)); // go to line 0 if reach end of file - assert(FindNextOccurrence("zxcv", 4, { 3, 10 }, outStart, outEnd) && outStart == Coordinates(3, 1) && outEnd == Coordinates(3, 5)); // from behind in same line - assert(!FindNextOccurrence("lalal", 4, { 3, 5 }, outStart, outEnd)); // not found + SetText("asdf asdf\nasdf\nasdf\tasdf\n zxcv zxcv"); + // --- FindNextOccurrence --- // + { + Coordinates outStart, outEnd; + assert(FindNextOccurrence("asdf", 4, { 0, 0 }, outStart, outEnd) && outStart == Coordinates(0, 0) && outEnd == Coordinates(0, 4)); + assert(FindNextOccurrence("asdf", 4, { 0, 1 }, outStart, outEnd) && outStart == Coordinates(0, 5) && outEnd == Coordinates(0, 9)); + assert(FindNextOccurrence("asdf", 4, { 0, 5 }, outStart, outEnd) && outStart == Coordinates(0, 5) && outEnd == Coordinates(0, 9)); + assert(FindNextOccurrence("asdf", 4, { 0, 6 }, outStart, outEnd) && outStart == Coordinates(1, 0) && outEnd == Coordinates(1, 4)); + assert(FindNextOccurrence("asdf", 4, { 3, 3 }, outStart, outEnd) && outStart == Coordinates(0, 0) && outEnd == Coordinates(0, 4)); // go to line 0 if reach end of file + assert(FindNextOccurrence("zxcv", 4, { 3, 10 }, outStart, outEnd) && outStart == Coordinates(3, 1) && outEnd == Coordinates(3, 5)); // from behind in same line + assert(!FindNextOccurrence("lalal", 4, { 3, 5 }, outStart, outEnd)); // not found + } } } From 049c88bf093e3e9a34c34a934242e4009e588f33 Mon Sep 17 00:00:00 2001 From: S41L0R <60116462+S41L0R@users.noreply.github.com> Date: Mon, 19 Jun 2023 13:41:24 -0400 Subject: [PATCH 3/3] Make .gitmodules not insane --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index a646a310..fb5e177e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "vendor/regex"] path = vendor/regex - url = git@github.com:boostorg/regex.git + url = https://github.com/boostorg/regex.git