diff --git a/Amalgam/Amalgam.vcxproj b/Amalgam/Amalgam.vcxproj
index 52c9b6f2..ebec96b9 100644
--- a/Amalgam/Amalgam.vcxproj
+++ b/Amalgam/Amalgam.vcxproj
@@ -827,6 +827,7 @@
+
@@ -912,6 +913,7 @@
+
diff --git a/Amalgam/Amalgam.vcxproj.filters b/Amalgam/Amalgam.vcxproj.filters
index 87c5b794..deb09317 100644
--- a/Amalgam/Amalgam.vcxproj.filters
+++ b/Amalgam/Amalgam.vcxproj.filters
@@ -189,6 +189,8 @@
+
+
diff --git a/Amalgam/include/ImGui/LanguageDefinitions.cpp b/Amalgam/include/ImGui/LanguageDefinitions.cpp
new file mode 100644
index 00000000..581d6b8d
--- /dev/null
+++ b/Amalgam/include/ImGui/LanguageDefinitions.cpp
@@ -0,0 +1,937 @@
+#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;
+
+ if (*p == '"')
+ {
+ p++;
+
+ while (p < in_end)
+ {
+ // 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++;
+
+ 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;
+
+ if (*p == '\'')
+ {
+ p++;
+
+ // handle escape characters
+ if (p < in_end && *p == '\\')
+ p++;
+
+ if (p < in_end)
+ p++;
+
+ // handle end of character literal
+ if (p < in_end && *p == '\'')
+ {
+ out_begin = in_begin;
+ out_end = p + 1;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool TokenizeCStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end)
+{
+ const char* p = in_begin;
+
+ if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')
+ {
+ p++;
+
+ while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *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;
+
+ const bool startsWithNumber = *p >= '0' && *p <= '9';
+
+ if (*p != '+' && *p != '-' && !startsWithNumber)
+ return false;
+
+ p++;
+
+ bool hasNumber = startsWithNumber;
+
+ while (p < in_end && (*p >= '0' && *p <= '9'))
+ {
+ hasNumber = true;
+
+ p++;
+ }
+
+ if (hasNumber == false)
+ return false;
+
+ bool isFloat = false;
+ bool isHex = false;
+ bool isBinary = false;
+
+ if (p < in_end)
+ {
+ if (*p == '.')
+ {
+ isFloat = 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;
+
+ p++;
+
+ 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
+
+ isBinary = true;
+
+ p++;
+
+ 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;
+
+ p++;
+
+ if (p < in_end && (*p == '+' || *p == '-'))
+ p++;
+
+ bool hasDigits = false;
+
+ while (p < in_end && (*p >= '0' && *p <= '9'))
+ {
+ hasDigits = true;
+
+ p++;
+ }
+
+ 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++;
+ }
+
+ out_begin = in_begin;
+ out_end = p;
+ return true;
+}
+
+static bool TokenizeCStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end)
+{
+ (void)in_end;
+
+ 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;
+ }
+
+ 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;
+
+ 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)
+ {
+ p++;
+
+ while (p < in_end)
+ {
+ // 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;
+
+ return true;
+ }
+
+ // handle escape character for "
+ if (*p == '\\' && p + 1 < in_end && (is_single_quote || is_double_quotes))
+ 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;
+
+ if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')
+ {
+ p++;
+
+ while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *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;
+
+ const bool startsWithNumber = *p >= '0' && *p <= '9';
+
+ if (*p != '+' && *p != '-' && !startsWithNumber)
+ return false;
+
+ p++;
+
+ bool hasNumber = startsWithNumber;
+
+ while (p < in_end && (*p >= '0' && *p <= '9'))
+ {
+ hasNumber = true;
+
+ p++;
+ }
+
+ if (hasNumber == false)
+ return false;
+
+ if (p < in_end)
+ {
+ if (*p == '.')
+ {
+ p++;
+
+ while (p < in_end && (*p >= '0' && *p <= '9'))
+ p++;
+ }
+
+ // floating point exponent
+ if (p < in_end && (*p == 'e' || *p == 'E'))
+ {
+ p++;
+
+ if (p < in_end && (*p == '+' || *p == '-'))
+ p++;
+
+ bool hasDigits = false;
+
+ while (p < in_end && (*p >= '0' && *p <= '9'))
+ {
+ hasDigits = true;
+
+ p++;
+ }
+
+ if (hasDigits == false)
+ return false;
+ }
+ }
+
+ out_begin = in_begin;
+ out_end = p;
+ return true;
+}
+
+static bool TokenizeLuaStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end)
+{
+ (void)in_end;
+
+ 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;
+ return true;
+ }
+
+ return false;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Cpp()
+{
+ 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)
+ {
+ 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 (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.mName = "C++";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Hlsl()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ 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(R"##([ \t]*#[ \t]*[a-zA-Z_]+)##", PaletteIndex::Preprocessor));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'\\?[^\']\')##", PaletteIndex::CharLiteral));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation));
+
+ langDef.mCommentStart = "/*";
+ langDef.mCommentEnd = "*/";
+ langDef.mSingleLineComment = "//";
+
+ langDef.mCaseSensitive = true;
+
+ langDef.mName = "HLSL";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Glsl()
+{
+ 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));
+ }
+
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([ \t]*#[ \t]*[a-zA-Z_]+)##", PaletteIndex::Preprocessor));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'\\?[^\']\')##", PaletteIndex::CharLiteral));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation));
+
+ langDef.mCommentStart = "/*";
+ langDef.mCommentEnd = "*/";
+ langDef.mSingleLineComment = "//";
+
+ langDef.mCaseSensitive = true;
+
+ langDef.mName = "GLSL";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Python()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ 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(R"##((b|u|f|r)?\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##((b|u|f|r)?'(\\.|[^'])*')##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.\:])##", PaletteIndex::Punctuation));
+
+ langDef.mCommentStart = "\"\"\"";
+ langDef.mCommentEnd = "\"\"\"";
+ langDef.mSingleLineComment = "#";
+
+ langDef.mCaseSensitive = true;
+
+ 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));
+ }
+
+ 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.mName = "C";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Sql()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ 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(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'[^\']*\')##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation));
+
+ langDef.mCommentStart = "/*";
+ langDef.mCommentEnd = "*/";
+ langDef.mSingleLineComment = "--";
+
+ langDef.mCaseSensitive = false;
+
+ langDef.mName = "SQL";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ 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(R"##(L?\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\'\\?[^\']\')##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation));
+
+ langDef.mCommentStart = "/*";
+ langDef.mCommentEnd = "*/";
+ langDef.mSingleLineComment = "//";
+
+ langDef.mCaseSensitive = true;
+
+ langDef.mName = "AngelScript";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ 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.mName = "Lua";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Cs()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ 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)
+ {
+ Identifier id;
+ id.mDeclaration = "Built-in function";
+ langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
+ }
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(($|@)?\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?[0-9]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[0-7]+[Uu]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([a-zA-Z_][a-zA-Z0-9_]*)##", PaletteIndex::Identifier));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])##", PaletteIndex::Punctuation));
+
+ langDef.mCommentStart = "/*";
+ langDef.mCommentEnd = "*/";
+ langDef.mSingleLineComment = "//";
+
+ langDef.mCaseSensitive = true;
+
+ langDef.mName = "C#";
+
+ inited = true;
+ }
+ return langDef;
+}
+
+const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Json()
+{
+ static bool inited = false;
+ static LanguageDefinition langDef;
+ if (!inited)
+ {
+ langDef.mKeywords.clear();
+ langDef.mIdentifiers.clear();
+
+
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(\"(\\.|[^\"])*\")##", PaletteIndex::String));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?)##", PaletteIndex::Number));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.\:])##", PaletteIndex::Punctuation));
+ langDef.mTokenRegexStrings.push_back(std::make_pair(R"##(false|true)##", PaletteIndex::Keyword));
+
+ langDef.mCommentStart = "/*";
+ langDef.mCommentEnd = "*/";
+ langDef.mSingleLineComment = "//";
+
+ langDef.mCaseSensitive = true;
+
+ langDef.mName = "Json";
+
+ inited = true;
+ }
+ return langDef;
+}
\ No newline at end of file
diff --git a/Amalgam/include/ImGui/TextEditor.cpp b/Amalgam/include/ImGui/TextEditor.cpp
index def05927..6d62b8e7 100644
--- a/Amalgam/include/ImGui/TextEditor.cpp
+++ b/Amalgam/include/ImGui/TextEditor.cpp
@@ -1,56 +1,30 @@
#include
-#include
#include
-#include
-#include
+#include
+#include
#include "TextEditor.h"
+#define IMGUI_SCROLLBAR_WIDTH 14.0f
+#define POS_TO_COORDS_COLUMN_OFFSET 0.33f
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h" // for imGui::GetCurrentWindow()
-// TODO
-// - multiline comments vs single-line: latter is blocking start of a ML
+#pragma warning( disable : 4267 )
+#pragma warning( disable : 4244 )
-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;
-}
+struct TextEditor::RegexList {
+ std::vector> mValue;
+};
+
+
+// --------------------------------------- //
+// ------------- Exposed API ------------- //
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)
- , mCursorPositionChanged(false)
- , mColorRangeMin(0)
- , mColorRangeMax(0)
- , mSelectionMode(SelectionMode::Normal)
- , mCheckComments(true)
- , mLastClick(-1.0f)
- , mHandleKeyboardInputs(true)
- , mHandleMouseInputs(true)
- , mIgnoreImGuiChild(false)
- , mShowWhitespaces(true)
- , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count())
+ : mRegexList(std::make_shared())
{
- SetPalette(GetDarkPalette());
- SetLanguageDefinition(LanguageDefinition::HLSL());
+ SetPalette(defaultPalette);
mLines.push_back(Line());
}
@@ -58,92 +32,429 @@ TextEditor::~TextEditor()
{
}
-void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef)
+void TextEditor::SetPalette(PaletteId aValue)
{
- mLanguageDefinition = aLanguageDef;
- mRegexList.clear();
+ mPaletteId = aValue;
+ const Palette* palletteBase;
+ switch (mPaletteId)
+ {
+ case PaletteId::Dark:
+ palletteBase = &(GetDarkPalette());
+ break;
+ case PaletteId::Light:
+ palletteBase = &(GetLightPalette());
+ break;
+ case PaletteId::Mariana:
+ palletteBase = &(GetMarianaPalette());
+ break;
+ case PaletteId::RetroBlue:
+ palletteBase = &(GetRetroBluePalette());
+ break;
+ }
+ /* Update palette with the current alpha from style */
+ for (int i = 0; i < (int)PaletteIndex::Max; ++i)
+ {
+ ImVec4 color = U32ColorToVec4((*palletteBase)[i]);
+ color.w *= ImGui::GetStyle().Alpha;
+ mPalette[i] = ImGui::ColorConvertFloat4ToU32(color);
+ }
+}
- for (auto& r : mLanguageDefinition.mTokenRegexStrings)
- mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second));
+void TextEditor::SetPaletteIndex(PaletteIndex iIndex, ImU32 aValue)
+{
+ mPalette[int(iIndex)] = aValue;
+}
+
+void TextEditor::SetLanguageDefinition(LanguageDefinitionId aValue)
+{
+ mLanguageDefinitionId = aValue;
+ switch (mLanguageDefinitionId)
+ {
+ case LanguageDefinitionId::None:
+ mLanguageDefinition = nullptr;
+ return;
+ case LanguageDefinitionId::Cpp:
+ mLanguageDefinition = &(LanguageDefinition::Cpp());
+ break;
+ case LanguageDefinitionId::C:
+ mLanguageDefinition = &(LanguageDefinition::C());
+ break;
+ case LanguageDefinitionId::Cs:
+ mLanguageDefinition = &(LanguageDefinition::Cs());
+ break;
+ case LanguageDefinitionId::Python:
+ mLanguageDefinition = &(LanguageDefinition::Python());
+ break;
+ case LanguageDefinitionId::Lua:
+ mLanguageDefinition = &(LanguageDefinition::Lua());
+ break;
+ case LanguageDefinitionId::Json:
+ mLanguageDefinition = &(LanguageDefinition::Json());
+ break;
+ case LanguageDefinitionId::Sql:
+ mLanguageDefinition = &(LanguageDefinition::Sql());
+ break;
+ case LanguageDefinitionId::AngelScript:
+ mLanguageDefinition = &(LanguageDefinition::AngelScript());
+ break;
+ case LanguageDefinitionId::Glsl:
+ mLanguageDefinition = &(LanguageDefinition::Glsl());
+ break;
+ case LanguageDefinitionId::Hlsl:
+ mLanguageDefinition = &(LanguageDefinition::Hlsl());
+ break;
+ }
+
+ mRegexList->mValue.clear();
+ for (const auto& r : mLanguageDefinition->mTokenRegexStrings)
+ mRegexList->mValue.push_back(std::make_pair(boost::regex(r.first, boost::regex_constants::optimize), r.second));
Colorize();
}
-void TextEditor::SetPalette(const Palette & aValue)
+const char* TextEditor::GetLanguageDefinitionName() const
{
- mPaletteBase = aValue;
+ return mLanguageDefinition != nullptr ? mLanguageDefinition->mName.c_str() : "None";
}
-std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const
+void TextEditor::SetTabSize(int aValue)
{
- std::string result;
+ mTabSize = Max(1, Min(8, aValue));
+}
- auto lstart = aStart.mLine;
- auto lend = aEnd.mLine;
- auto istart = GetCharacterIndex(aStart);
- auto iend = GetCharacterIndex(aEnd);
- size_t s = 0;
+void TextEditor::SetLineSpacing(float aValue)
+{
+ mLineSpacing = Max(1.0f, Min(2.0f, aValue));
+}
- for (size_t i = lstart; i < lend; i++)
- s += mLines[i].size();
+void TextEditor::SelectAll()
+{
+ ClearSelections();
+ ClearExtraCursors();
+ MoveTop();
+ MoveBottom(true);
+}
- result.reserve(s + s / 8);
+void TextEditor::SelectLine(int aLine)
+{
+ ClearSelections();
+ ClearExtraCursors();
+ SetSelection({ aLine, 0 }, { aLine, GetLineMaxColumn(aLine) });
+}
- while (istart < iend || lstart < lend)
+void TextEditor::SelectRegion(int aStartLine, int aStartChar, int aEndLine, int aEndChar)
+{
+ ClearSelections();
+ ClearExtraCursors();
+ SetSelection(aStartLine, aStartChar, aEndLine, aEndChar);
+}
+
+void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, bool aCaseSensitive)
+{
+ ClearSelections();
+ ClearExtraCursors();
+ SelectNextOccurrenceOf(aText, aTextSize, -1, aCaseSensitive);
+}
+
+void TextEditor::SelectAllOccurrencesOf(const char* aText, int aTextSize, bool aCaseSensitive)
+{
+ ClearSelections();
+ ClearExtraCursors();
+ SelectNextOccurrenceOf(aText, aTextSize, -1, aCaseSensitive);
+ Coordinates startPos = mState.mCursors[mState.GetLastAddedCursorIndex()].mInteractiveEnd;
+ while (true)
{
- if (lstart >= (int)mLines.size())
+ AddCursorForNextOccurrence(aCaseSensitive);
+ Coordinates lastAddedPos = mState.mCursors[mState.GetLastAddedCursorIndex()].mInteractiveEnd;
+ if (lastAddedPos == startPos)
break;
+ }
+}
- auto& line = mLines[lstart];
- if (istart < (int)line.size())
+bool TextEditor::AnyCursorHasSelection() const
+{
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ if (mState.mCursors[c].HasSelection())
+ return true;
+ return false;
+}
+
+bool TextEditor::AllCursorsHaveSelection() const
+{
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ if (!mState.mCursors[c].HasSelection())
+ return false;
+ return true;
+}
+
+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].GetSelectionEnd();
+}
+
+void TextEditor::SetCursorPosition(int aLine, int aCharIndex)
+{
+ SetCursorPosition({ aLine, GetCharacterColumn(aLine, aCharIndex) }, -1, true);
+}
+
+int TextEditor::GetFirstVisibleLine()
+{
+ return mFirstVisibleLine;
+}
+
+int TextEditor::GetLastVisibleLine()
+{
+ return mLastVisibleLine;
+}
+
+void TextEditor::SetViewAtLine(int aLine, SetViewAtLineMode aMode)
+{
+ mSetViewAtLine = aLine;
+ mSetViewAtLineMode = aMode;
+}
+
+void TextEditor::Copy()
+{
+ if (AnyCursorHasSelection())
+ {
+ std::string clipboardText = GetClipboardText();
+ ImGui::SetClipboardText(clipboardText.c_str());
+ }
+ else
+ {
+ if (!mLines.empty())
{
- result += line[istart].mChar;
- istart++;
+ std::string str;
+ auto& line = mLines[GetSanitizedCursorCoordinates().mLine];
+ for (auto& g : line)
+ str.push_back(g.mChar);
+ ImGui::SetClipboardText(str.c_str());
}
+ }
+}
+
+void TextEditor::Cut()
+{
+ if (mReadOnly)
+ {
+ Copy();
+ }
+ else
+ {
+ if (AnyCursorHasSelection())
+ {
+ UndoRecord u;
+ u.mBefore = mState;
+
+ Copy();
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
+ DeleteSelection(c);
+ }
+
+ u.mAfter = mState;
+ AddUndo(u);
+ }
+ }
+}
+
+void TextEditor::Paste()
+{
+ if (mReadOnly)
+ return;
+
+ if (ImGui::GetClipboardText() == nullptr)
+ return; // something other than text in the clipboard
+
+ // check if we should do multicursor paste
+ std::string clipText = ImGui::GetClipboardText();
+ bool canPasteToMultipleCursors = false;
+ std::vector> clipTextLines;
+ if (mState.mCurrentCursor > 0)
+ {
+ clipTextLines.push_back({ 0,0 });
+ for (int i = 0; i < clipText.length(); i++)
+ {
+ 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;
+ }
+
+ if (clipText.length() > 0)
+ {
+ UndoRecord u;
+ u.mBefore = mState;
+
+ if (AnyCursorHasSelection())
+ {
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
+ DeleteSelection(c);
+ }
+ }
+
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ Coordinates start = GetSanitizedCursorCoordinates(c);
+ if (canPasteToMultipleCursors)
+ {
+ std::string clipSubText = clipText.substr(clipTextLines[c].first, clipTextLines[c].second - clipTextLines[c].first);
+ InsertTextAtCursor(clipSubText.c_str(), c);
+ u.mOperations.push_back({ clipSubText, start, GetSanitizedCursorCoordinates(c), UndoOperationType::Add });
+ }
+ else
+ {
+ InsertTextAtCursor(clipText.c_str(), c);
+ u.mOperations.push_back({ clipText, start, GetSanitizedCursorCoordinates(c), UndoOperationType::Add });
+ }
+ }
+
+ u.mAfter = mState;
+ AddUndo(u);
+ }
+}
+
+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::SetText(const std::string& aText)
+{
+ mLines.clear();
+ mLines.emplace_back(Line());
+ for (auto chr : aText)
+ {
+ if (chr == '\r')
+ continue;
+
+ if (chr == '\n')
+ mLines.emplace_back(Line());
else
{
- istart = 0;
- ++lstart;
- result += '\n';
+ mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default));
}
}
+ mScrollToTop = true;
+
+ mUndoBuffer.clear();
+ mUndoIndex = 0;
+
+ Colorize();
+}
+
+std::string TextEditor::GetText() const
+{
+ auto lastLine = (int)mLines.size() - 1;
+ auto lastLineLength = GetLineMaxColumn(lastLine);
+ Coordinates startCoords = Coordinates();
+ Coordinates endCoords = Coordinates(lastLine, lastLineLength);
+ return startCoords < endCoords ? GetText(startCoords, endCoords) : "";
+}
+
+void TextEditor::SetTextLines(const std::vector& aLines)
+{
+ mLines.clear();
+
+ if (aLines.empty())
+ mLines.emplace_back(Line());
+ else
+ {
+ mLines.resize(aLines.size());
+
+ for (size_t i = 0; i < aLines.size(); ++i)
+ {
+ 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));
+ }
+ }
+
+ mScrollToTop = true;
+
+ mUndoBuffer.clear();
+ mUndoIndex = 0;
+
+ Colorize();
+}
+
+std::vector TextEditor::GetTextLines() const
+{
+ std::vector result;
+
+ result.reserve(mLines.size());
+
+ for (auto& line : mLines)
+ {
+ std::string text;
+
+ text.resize(line.size());
+
+ for (size_t i = 0; i < line.size(); ++i)
+ text[i] = line[i].mChar;
+
+ result.emplace_back(std::move(text));
+ }
+
return result;
}
-TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const
+bool TextEditor::Render(const char* aTitle, bool aParentIsFocused, const ImVec2& aSize, bool aBorder)
{
- return SanitizeCoordinates(mState.mCursorPosition);
+ if (mCursorPositionChanged)
+ OnCursorPositionChanged();
+ mCursorPositionChanged = false;
+
+ ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background]));
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
+
+ ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs);
+
+ bool isFocused = ImGui::IsWindowFocused();
+ HandleKeyboardInputs(aParentIsFocused);
+ HandleMouseInputs();
+ ColorizeInternal();
+ Render(aParentIsFocused);
+
+ ImGui::EndChild();
+
+ ImGui::PopStyleVar();
+ ImGui::PopStyleColor();
+
+ return isFocused;
}
-TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const
-{
- auto line = aValue.mLine;
- auto column = aValue.mColumn;
- if (line >= (int)mLines.size())
- {
- if (mLines.empty())
- {
- line = 0;
- column = 0;
- }
- 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);
- }
-}
+// ------------------------------------ //
+// ---------- Generic utils ----------- //
// 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)
+static int UTF8CharLength(char c)
{
if ((c & 0xFE) == 0xFC)
return 6;
@@ -196,72 +507,203 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
}
}
-void TextEditor::Advance(Coordinates & aCoordinates) const
+static inline bool CharIsWordChar(char ch)
{
- if (aCoordinates.mLine < (int)mLines.size())
- {
- auto& line = mLines[aCoordinates.mLine];
- auto cindex = GetCharacterIndex(aCoordinates);
+ int sizeInBytes = UTF8CharLength(ch);
+ return sizeInBytes > 1 ||
+ ch >= 'a' && ch <= 'z' ||
+ ch >= 'A' && ch <= 'Z' ||
+ ch >= '0' && ch <= '9' ||
+ ch == '_';
+}
- if (cindex + 1 < (int)line.size())
+// ------------------------------------ //
+// ------------- Internal ------------- //
+
+
+// ---------- Editor state functions --------- //
+
+void TextEditor::EditorState::AddCursor()
+{
+ // vector is never resized to smaller size, mCurrentCursor points to last available cursor in vector
+ mCurrentCursor++;
+ mCursors.resize(mCurrentCursor + 1);
+ mLastAddedCursor = mCurrentCursor;
+}
+
+int TextEditor::EditorState::GetLastAddedCursorIndex()
+{
+ return mLastAddedCursor > mCurrentCursor ? 0 : mLastAddedCursor;
+}
+
+void TextEditor::EditorState::SortCursorsFromTopToBottom()
+{
+ Coordinates lastAddedCursorPos = mCursors[GetLastAddedCursorIndex()].mInteractiveEnd;
+ std::sort(mCursors.begin(), mCursors.begin() + (mCurrentCursor + 1), [](const Cursor& a, const Cursor& b) -> bool
{
- auto delta = UTF8CharLength(line[cindex].mChar);
- cindex = std::min(cindex + delta, (int)line.size() - 1);
+ return a.GetSelectionStart() < b.GetSelectionStart();
+ });
+ // update last added cursor index to be valid after sort
+ for (int c = mCurrentCursor; c > -1; c--)
+ if (mCursors[c].mInteractiveEnd == lastAddedCursorPos)
+ mLastAddedCursor = c;
+}
+
+// ---------- Undo record functions --------- //
+
+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--)
+ {
+ const UndoOperation& operation = mOperations[i];
+ if (!operation.mText.empty())
+ {
+ 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();
+}
+
+void TextEditor::UndoRecord::Redo(TextEditor* aEditor)
+{
+ for (int i = 0; i < mOperations.size(); i++)
+ {
+ const UndoOperation& operation = mOperations[i];
+ if (!operation.mText.empty())
+ {
+ 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();
+}
+
+// ---------- Text editor internal functions --------- //
+
+std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const
+{
+ assert(aStart < aEnd);
+
+ 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
{
- ++aCoordinates.mLine;
- cindex = 0;
+ istart = 0;
+ ++lstart;
+ result += '\n';
}
- aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex);
+ }
+
+ return result;
+}
+
+std::string TextEditor::GetClipboardText() const
+{
+ std::string result;
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ if (mState.mCursors[c].GetSelectionStart() < mState.mCursors[c].GetSelectionEnd())
+ {
+ if (result.length() != 0)
+ result += '\n';
+ result += GetText(mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd());
+ }
+ }
+ return result;
+}
+
+std::string TextEditor::GetSelectedText(int aCursor) const
+{
+ if (aCursor == -1)
+ aCursor = mState.mCurrentCursor;
+
+ return GetText(mState.mCursors[aCursor].GetSelectionStart(), mState.mCursors[aCursor].GetSelectionEnd());
+}
+
+void TextEditor::SetCursorPosition(const Coordinates& aPosition, int aCursor, bool aClearSelection)
+{
+ if (aCursor == -1)
+ aCursor = mState.mCurrentCursor;
+
+ mCursorPositionChanged = true;
+ if (aClearSelection)
+ mState.mCursors[aCursor].mInteractiveStart = aPosition;
+ if (mState.mCursors[aCursor].mInteractiveEnd != aPosition)
+ {
+ mState.mCursors[aCursor].mInteractiveEnd = aPosition;
+ EnsureCursorVisible();
}
}
-void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd)
-{
- assert(aEnd >= aStart);
- assert(!mReadOnly);
-
- //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn);
-
- if (aEnd == aStart)
- return;
-
- auto start = GetCharacterIndex(aStart);
- auto end = GetCharacterIndex(aEnd);
-
- if (aStart.mLine == aEnd.mLine)
- {
- auto& line = mLines[aStart.mLine];
- auto n = GetLineMaxColumn(aStart.mLine);
- if (aEnd.mColumn >= n)
- line.erase(line.begin() + start, line.end());
- else
- line.erase(line.begin() + start, line.begin() + end);
- }
- else
- {
- auto& firstLine = mLines[aStart.mLine];
- auto& lastLine = mLines[aEnd.mLine];
-
- firstLine.erase(firstLine.begin() + start, firstLine.end());
- lastLine.erase(lastLine.begin(), lastLine.begin() + end);
-
- if (aStart.mLine < aEnd.mLine)
- firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
-
- if (aStart.mLine < aEnd.mLine)
- RemoveLine(aStart.mLine + 1, aEnd.mLine + 1);
- }
-
- mTextChanged = true;
-}
-
-int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue)
+int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue)
{
assert(!mReadOnly);
- int cindex = GetCharacterIndex(aWhere);
+ int cindex = GetCharacterIndexR(aWhere);
int totalLines = 0;
while (*aValue != '\0')
{
@@ -278,8 +720,8 @@ int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValu
{
auto& newLine = InsertLine(aWhere.mLine + 1);
auto& line = mLines[aWhere.mLine];
- newLine.insert(newLine.begin(), line.begin() + cindex, line.end());
- line.erase(line.begin() + cindex, line.end());
+ AddGlyphsToLine(aWhere.mLine + 1, 0, line.begin() + cindex, line.end());
+ RemoveGlyphsFromLine(aWhere.mLine, cindex);
}
else
{
@@ -296,387 +738,1225 @@ int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValu
auto& line = mLines[aWhere.mLine];
auto d = UTF8CharLength(*aValue);
while (d-- > 0 && *aValue != '\0')
- line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default));
- ++aWhere.mColumn;
+ AddGlyphToLine(aWhere.mLine, cindex++, Glyph(*aValue++, PaletteIndex::Default));
+ aWhere.mColumn = GetCharacterColumn(aWhere.mLine, cindex);
}
-
- mTextChanged = true;
}
return totalLines;
}
-void TextEditor::AddUndo(UndoRecord& aValue)
+void TextEditor::InsertTextAtCursor(const char* aValue, int aCursor)
{
- 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
- // );
+ if (aValue == nullptr)
+ return;
+ if (aCursor == -1)
+ aCursor = mState.mCurrentCursor;
- mUndoBuffer.resize((size_t)(mUndoIndex + 1));
- mUndoBuffer.back() = aValue;
- ++mUndoIndex;
+ auto pos = GetSanitizedCursorCoordinates(aCursor);
+ auto start = std::min(pos, mState.mCursors[aCursor].GetSelectionStart());
+ int totalLines = pos.mLine - start.mLine;
+
+ totalLines += InsertTextAt(pos, aValue);
+
+ SetCursorPosition(pos, aCursor);
+ Colorize(start.mLine - 1, totalLines + 2);
}
-TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const
+bool TextEditor::Move(int& aLine, int& aCharIndex, bool aLeft, bool aLockLine) const
{
- ImVec2 origin = ImGui::GetCursorScreenPos();
- ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y);
+ // assumes given char index is not in the middle of utf8 sequence
+ // char index can be line.length()
- int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y));
+ // invalid line
+ if (aLine >= mLines.size())
+ return false;
- int columnCoord = 0;
-
- if (lineNo >= 0 && lineNo < (int)mLines.size())
+ if (aLeft)
{
- auto& line = mLines.at(lineNo);
-
- int columnIndex = 0;
- float columnX = 0.0f;
-
- while ((size_t)columnIndex < line.size())
+ if (aCharIndex == 0)
{
- float columnWidth = 0.0f;
+ if (aLockLine || aLine == 0)
+ return false;
+ aLine--;
+ aCharIndex = mLines[aLine].size();
+ }
+ else
+ {
+ aCharIndex--;
+ while (aCharIndex > 0 && IsUTFSequence(mLines[aLine][aCharIndex].mChar))
+ aCharIndex--;
+ }
+ }
+ else // right
+ {
+ if (aCharIndex == mLines[aLine].size())
+ {
+ if (aLockLine || aLine == mLines.size() - 1)
+ return false;
+ aLine++;
+ aCharIndex = 0;
+ }
+ else
+ {
+ int seqLength = UTF8CharLength(mLines[aLine][aCharIndex].mChar);
+ aCharIndex = std::min(aCharIndex + seqLength, (int)mLines[aLine].size());
+ }
+ }
+ return true;
+}
- if (line[columnIndex].mChar == '\t')
+void TextEditor::MoveCharIndexAndColumn(int aLine, int& aCharIndex, int& aColumn) const
+{
+ assert(aLine < mLines.size());
+ assert(aCharIndex < mLines[aLine].size());
+ char c = mLines[aLine][aCharIndex].mChar;
+ aCharIndex += UTF8CharLength(c);
+ if (c == '\t')
+ aColumn = (aColumn / mTabSize) * mTabSize + mTabSize;
+ else
+ aColumn++;
+}
+
+void TextEditor::MoveCoords(Coordinates& aCoords, MoveDirection aDirection, bool aWordMode, int aLineCount) const
+{
+ int charIndex = GetCharacterIndexR(aCoords);
+ int lineIndex = aCoords.mLine;
+ switch (aDirection)
+ {
+ case MoveDirection::Right:
+ if (charIndex >= mLines[lineIndex].size())
+ {
+ if (lineIndex < mLines.size() - 1)
{
- float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x;
- float oldX = columnX;
- float newColumnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
- columnWidth = newColumnX - oldX;
- if (mTextStart + columnX + columnWidth * 0.5f > local.x)
- break;
- columnX = newColumnX;
- columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize;
- columnIndex++;
+ aCoords.mLine = std::max(0, std::min((int)mLines.size() - 1, lineIndex + 1));
+ aCoords.mColumn = 0;
+ }
+ }
+ else
+ {
+ Move(lineIndex, charIndex);
+ int oneStepRightColumn = GetCharacterColumn(lineIndex, charIndex);
+ if (aWordMode)
+ {
+ aCoords = FindWordEnd(aCoords);
+ aCoords.mColumn = std::max(aCoords.mColumn, oneStepRightColumn);
+ }
+ else
+ aCoords.mColumn = oneStepRightColumn;
+ }
+ break;
+ case MoveDirection::Left:
+ if (charIndex == 0)
+ {
+ if (lineIndex > 0)
+ {
+ aCoords.mLine = lineIndex - 1;
+ aCoords.mColumn = GetLineMaxColumn(aCoords.mLine);
+ }
+ }
+ else
+ {
+ Move(lineIndex, charIndex, true);
+ aCoords.mColumn = GetCharacterColumn(lineIndex, charIndex);
+ if (aWordMode)
+ aCoords = FindWordStart(aCoords);
+ }
+ break;
+ case MoveDirection::Up:
+ aCoords.mLine = std::max(0, lineIndex - aLineCount);
+ break;
+ case MoveDirection::Down:
+ aCoords.mLine = std::max(0, std::min((int)mLines.size() - 1, lineIndex + aLineCount));
+ break;
+ }
+}
+
+void TextEditor::MoveUp(int aAmount, bool aSelect)
+{
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
+ MoveCoords(newCoords, MoveDirection::Up, false, aAmount);
+ SetCursorPosition(newCoords, c, !aSelect);
+ }
+ EnsureCursorVisible();
+}
+
+void TextEditor::MoveDown(int aAmount, bool aSelect)
+{
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ assert(mState.mCursors[c].mInteractiveEnd.mColumn >= 0);
+ Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
+ MoveCoords(newCoords, MoveDirection::Down, false, aAmount);
+ SetCursorPosition(newCoords, c, !aSelect);
+ }
+ EnsureCursorVisible();
+}
+
+void TextEditor::MoveLeft(bool aSelect, bool aWordMode)
+{
+ if (mLines.empty())
+ return;
+
+ if (AnyCursorHasSelection() && !aSelect && !aWordMode)
+ {
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ SetCursorPosition(mState.mCursors[c].GetSelectionStart(), c);
+ }
+ else
+ {
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
+ MoveCoords(newCoords, MoveDirection::Left, aWordMode);
+ SetCursorPosition(newCoords, c, !aSelect);
+ }
+ }
+ EnsureCursorVisible();
+}
+
+void TextEditor::MoveRight(bool aSelect, bool aWordMode)
+{
+ if (mLines.empty())
+ return;
+
+ if (AnyCursorHasSelection() && !aSelect && !aWordMode)
+ {
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ SetCursorPosition(mState.mCursors[c].GetSelectionEnd(), c);
+ }
+ else
+ {
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ Coordinates newCoords = mState.mCursors[c].mInteractiveEnd;
+ MoveCoords(newCoords, MoveDirection::Right, aWordMode);
+ SetCursorPosition(newCoords, c, !aSelect);
+ }
+ }
+ EnsureCursorVisible();
+}
+
+void TextEditor::MoveTop(bool aSelect)
+{
+ SetCursorPosition(Coordinates(0, 0), mState.mCurrentCursor, !aSelect);
+}
+
+void TextEditor::TextEditor::MoveBottom(bool aSelect)
+{
+ int maxLine = (int)mLines.size() - 1;
+ Coordinates newPos = Coordinates(maxLine, GetLineMaxColumn(maxLine));
+ SetCursorPosition(newPos, mState.mCurrentCursor, !aSelect);
+}
+
+void TextEditor::MoveHome(bool aSelect)
+{
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ SetCursorPosition(Coordinates(mState.mCursors[c].mInteractiveEnd.mLine, 0), c, !aSelect);
+}
+
+void TextEditor::MoveEnd(bool aSelect)
+{
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ int lindex = mState.mCursors[c].mInteractiveEnd.mLine;
+ SetCursorPosition(Coordinates(lindex, GetLineMaxColumn(lindex)), c, !aSelect);
+ }
+}
+
+void TextEditor::EnterCharacter(ImWchar aChar, bool aShift)
+{
+ assert(!mReadOnly);
+
+ bool hasSelection = AnyCursorHasSelection();
+ bool anyCursorHasMultilineSelection = false;
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ if (mState.mCursors[c].GetSelectionStart().mLine != mState.mCursors[c].GetSelectionEnd().mLine)
+ {
+ anyCursorHasMultilineSelection = true;
+ break;
+ }
+ bool isIndentOperation = hasSelection && anyCursorHasMultilineSelection && aChar == '\t';
+ if (isIndentOperation)
+ {
+ ChangeCurrentLinesIndentation(!aShift);
+ 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].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
+ DeleteSelection(c);
+ }
+ }
+
+ 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 = GetSanitizedCursorCoordinates(c);
+ coords.push_back(coord);
+ UndoOperation added;
+ added.mType = UndoOperationType::Add;
+ added.mStart = coord;
+
+ assert(!mLines.empty());
+
+ if (aChar == '\n')
+ {
+ InsertLine(coord.mLine + 1);
+ auto& line = mLines[coord.mLine];
+ auto& newLine = mLines[coord.mLine + 1];
+
+ added.mText = "";
+ added.mText += (char)aChar;
+ if (mAutoIndent)
+ for (int i = 0; i < line.size() && isascii(line[i].mChar) && isblank(line[i].mChar); ++i)
+ {
+ newLine.push_back(line[i]);
+ added.mText += line[i].mChar;
+ }
+
+ 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);
+
+ 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);
+ }
+ else
+ continue;
+ }
+
+ added.mEnd = GetSanitizedCursorCoordinates(c);
+ u.mOperations.push_back(added);
+ }
+
+ u.mAfter = mState;
+ AddUndo(u);
+
+ for (const auto& coord : coords)
+ Colorize(coord.mLine - 1, 3);
+ EnsureCursorVisible();
+}
+
+void TextEditor::Backspace(bool aWordMode)
+{
+ assert(!mReadOnly);
+
+ if (mLines.empty())
+ return;
+
+ if (AnyCursorHasSelection())
+ Delete(aWordMode);
+ else
+ {
+ EditorState stateBeforeDeleting = mState;
+ MoveLeft(true, aWordMode);
+ if (!AllCursorsHaveSelection()) // can't do backspace if any cursor at {0,0}
+ {
+ if (AnyCursorHasSelection())
+ MoveRight();
+ return;
+ }
+
+ OnCursorPositionChanged(); // might combine cursors
+ Delete(aWordMode, &stateBeforeDeleting);
+ }
+}
+
+void TextEditor::Delete(bool aWordMode, const EditorState* aEditorState)
+{
+ assert(!mReadOnly);
+
+ if (mLines.empty())
+ return;
+
+ if (AnyCursorHasSelection())
+ {
+ UndoRecord u;
+ u.mBefore = aEditorState == nullptr ? mState : *aEditorState;
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ if (!mState.mCursors[c].HasSelection())
+ continue;
+ u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
+ DeleteSelection(c);
+ }
+ u.mAfter = mState;
+ AddUndo(u);
+ }
+ else
+ {
+ EditorState stateBeforeDeleting = mState;
+ MoveRight(true, aWordMode);
+ if (!AllCursorsHaveSelection()) // can't do delete if any cursor at end of last line
+ {
+ if (AnyCursorHasSelection())
+ MoveLeft();
+ return;
+ }
+
+ OnCursorPositionChanged(); // might combine cursors
+ Delete(aWordMode, &stateBeforeDeleting);
+ }
+}
+
+void TextEditor::SetSelection(Coordinates aStart, Coordinates aEnd, int aCursor)
+{
+ if (aCursor == -1)
+ aCursor = mState.mCurrentCursor;
+
+ Coordinates minCoords = Coordinates(0, 0);
+ int maxLine = (int)mLines.size() - 1;
+ Coordinates maxCoords = Coordinates(maxLine, GetLineMaxColumn(maxLine));
+ if (aStart < minCoords)
+ aStart = minCoords;
+ else if (aStart > maxCoords)
+ aStart = maxCoords;
+ if (aEnd < minCoords)
+ aEnd = minCoords;
+ else if (aEnd > maxCoords)
+ aEnd = maxCoords;
+
+ mState.mCursors[aCursor].mInteractiveStart = aStart;
+ SetCursorPosition(aEnd, aCursor, false);
+}
+
+void TextEditor::SetSelection(int aStartLine, int aStartChar, int aEndLine, int aEndChar, int aCursor)
+{
+ Coordinates startCoords = { aStartLine, GetCharacterColumn(aStartLine, aStartChar) };
+ Coordinates endCoords = { aEndLine, GetCharacterColumn(aEndLine, aEndChar) };
+ SetSelection(startCoords, endCoords, aCursor);
+}
+
+void TextEditor::SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor, bool aCaseSensitive)
+{
+ if (aCursor == -1)
+ aCursor = mState.mCurrentCursor;
+ Coordinates nextStart, nextEnd;
+ FindNextOccurrence(aText, aTextSize, mState.mCursors[aCursor].mInteractiveEnd, nextStart, nextEnd, aCaseSensitive);
+ SetSelection(nextStart, nextEnd, aCursor);
+ EnsureCursorVisible(aCursor, true);
+}
+
+void TextEditor::AddCursorForNextOccurrence(bool aCaseSensitive)
+{
+ const Cursor& currentCursor = mState.mCursors[mState.GetLastAddedCursorIndex()];
+ if (currentCursor.GetSelectionStart() == currentCursor.GetSelectionEnd())
+ return;
+
+ std::string selectionText = GetText(currentCursor.GetSelectionStart(), currentCursor.GetSelectionEnd());
+ Coordinates nextStart, nextEnd;
+ if (!FindNextOccurrence(selectionText.c_str(), selectionText.length(), currentCursor.GetSelectionEnd(), nextStart, nextEnd, aCaseSensitive))
+ return;
+
+ mState.AddCursor();
+ SetSelection(nextStart, nextEnd, mState.mCurrentCursor);
+ mState.SortCursorsFromTopToBottom();
+ MergeCursorsIfPossible();
+ EnsureCursorVisible(-1, true);
+}
+
+bool TextEditor::FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd, bool aCaseSensitive)
+{
+ assert(aTextSize > 0);
+ bool fmatches = false;
+ int fline, ifline;
+ int findex, ifindex;
+
+ ifline = fline = aFrom.mLine;
+ ifindex = findex = GetCharacterIndexR(aFrom);
+
+ while (true)
+ {
+ bool matches;
+ { // match function
+ int lineOffset = 0;
+ int currentCharIndex = findex;
+ int i = 0;
+ for (; i < aTextSize; i++)
+ {
+ if (currentCharIndex == mLines[fline + lineOffset].size())
+ {
+ if (aText[i] == '\n' && fline + lineOffset + 1 < mLines.size())
+ {
+ currentCharIndex = 0;
+ lineOffset++;
+ }
+ else
+ break;
+ }
+ else
+ {
+ char toCompareA = mLines[fline + lineOffset][currentCharIndex].mChar;
+ char toCompareB = aText[i];
+ toCompareA = (!aCaseSensitive && toCompareA >= 'A' && toCompareA <= 'Z') ? toCompareA - 'A' + 'a' : toCompareA;
+ toCompareB = (!aCaseSensitive && toCompareB >= 'A' && toCompareB <= 'Z') ? toCompareB - 'A' + 'a' : toCompareB;
+ if (toCompareA != toCompareB)
+ break;
+ else
+ currentCharIndex++;
+ }
+ }
+ matches = i == aTextSize;
+ if (matches)
+ {
+ outStart = { fline, GetCharacterColumn(fline, findex) };
+ outEnd = { fline + lineOffset, GetCharacterColumn(fline + lineOffset, currentCharIndex) };
+ return true;
+ }
+ }
+
+ // move forward
+ if (findex == mLines[fline].size()) // need to consider line breaks
+ {
+ if (fline == mLines.size() - 1)
+ {
+ fline = 0;
+ findex = 0;
}
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;
- if (mTextStart + columnX + columnWidth * 0.5f > local.x)
- break;
- columnX += columnWidth;
- columnCoord++;
+ fline++;
+ findex = 0;
}
}
+ else
+ findex++;
+
+ // detect complete scan
+ if (findex == ifindex && fline == ifline)
+ return false;
}
- return SanitizeCoordinates(Coordinates(lineNo, columnCoord));
+ return false;
}
-TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const
+bool TextEditor::FindMatchingBracket(int aLine, int aCharIndex, Coordinates& out)
{
- Coordinates at = aFrom;
- if (at.mLine >= (int)mLines.size())
- return at;
+ if (aLine > mLines.size() - 1)
+ return false;
+ int maxCharIndex = mLines[aLine].size() - 1;
+ if (aCharIndex > maxCharIndex)
+ return false;
- auto& line = mLines[at.mLine];
- auto cindex = GetCharacterIndex(at);
-
- if (cindex >= (int)line.size())
- return at;
-
- while (cindex > 0 && isspace(line[cindex].mChar))
- --cindex;
-
- auto cstart = (PaletteIndex)line[cindex].mColorIndex;
- while (cindex > 0)
+ int currentLine = aLine;
+ int currentCharIndex = aCharIndex;
+ int counter = 1;
+ if (CLOSE_TO_OPEN_CHAR.find(mLines[aLine][aCharIndex].mChar) != CLOSE_TO_OPEN_CHAR.end())
{
- auto c = line[cindex].mChar;
- if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx
+ char closeChar = mLines[aLine][aCharIndex].mChar;
+ char openChar = CLOSE_TO_OPEN_CHAR.at(closeChar);
+ while (Move(currentLine, currentCharIndex, true))
{
- if (c <= 32 && isspace(c))
+ if (currentCharIndex < mLines[currentLine].size())
{
- cindex++;
- break;
+ char currentChar = mLines[currentLine][currentCharIndex].mChar;
+ if (currentChar == openChar)
+ {
+ counter--;
+ if (counter == 0)
+ {
+ out = { currentLine, GetCharacterColumn(currentLine, currentCharIndex) };
+ return true;
+ }
+ }
+ else if (currentChar == closeChar)
+ counter++;
}
- if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex)
- break;
}
- --cindex;
}
- return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
+ else if (OPEN_TO_CLOSE_CHAR.find(mLines[aLine][aCharIndex].mChar) != OPEN_TO_CLOSE_CHAR.end())
+ {
+ char openChar = mLines[aLine][aCharIndex].mChar;
+ char closeChar = OPEN_TO_CLOSE_CHAR.at(openChar);
+ while (Move(currentLine, currentCharIndex))
+ {
+ if (currentCharIndex < mLines[currentLine].size())
+ {
+ char currentChar = mLines[currentLine][currentCharIndex].mChar;
+ if (currentChar == closeChar)
+ {
+ counter--;
+ if (counter == 0)
+ {
+ out = { currentLine, GetCharacterColumn(currentLine, currentCharIndex) };
+ return true;
+ }
+ }
+ else if (currentChar == openChar)
+ counter++;
+ }
+ }
+ }
+ return false;
}
-TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const
+void TextEditor::ChangeCurrentLinesIndentation(bool aIncrease)
{
- Coordinates at = aFrom;
- if (at.mLine >= (int)mLines.size())
- return at;
+ assert(!mReadOnly);
- auto& line = mLines[at.mLine];
- auto cindex = GetCharacterIndex(at);
+ UndoRecord u;
+ u.mBefore = mState;
- if (cindex >= (int)line.size())
- return at;
-
- bool prevspace = (bool)isspace(line[cindex].mChar);
- auto cstart = (PaletteIndex)line[cindex].mColorIndex;
- while (cindex < (int)line.size())
+ for (int c = mState.mCurrentCursor; c > -1; c--)
{
- auto c = line[cindex].mChar;
- auto d = UTF8CharLength(c);
- if (cstart != (PaletteIndex)line[cindex].mColorIndex)
- break;
-
- if (prevspace != !!isspace(c))
+ for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
{
- if (isspace(c))
- while (cindex < (int)line.size() && isspace(line[cindex].mChar))
- ++cindex;
- break;
+ if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
+ continue;
+
+ if (aIncrease)
+ {
+ if (mLines[currentLine].size() > 0)
+ {
+ Coordinates lineStart = { currentLine, 0 };
+ Coordinates insertionEnd = lineStart;
+ InsertTextAt(insertionEnd, "\t"); // sets insertion end
+ u.mOperations.push_back({ "\t", lineStart, insertionEnd, UndoOperationType::Add });
+ Colorize(lineStart.mLine, 1);
+ }
+ }
+ else
+ {
+ Coordinates start = { currentLine, 0 };
+ Coordinates end = { currentLine, mTabSize };
+ int charIndex = GetCharacterIndexL(end) - 1;
+ while (charIndex > -1 && (mLines[currentLine][charIndex].mChar == ' ' || mLines[currentLine][charIndex].mChar == '\t')) charIndex--;
+ bool onlySpaceCharactersFound = charIndex == -1;
+ if (onlySpaceCharactersFound)
+ {
+ u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete });
+ DeleteRange(start, end);
+ Colorize(currentLine, 1);
+ }
+ }
}
- cindex += d;
}
- return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex));
+
+ if (u.mOperations.size() > 0)
+ AddUndo(u);
}
-TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates & aFrom) const
+void TextEditor::MoveUpCurrentLines()
{
- Coordinates at = aFrom;
- if (at.mLine >= (int)mLines.size())
- return at;
+ assert(!mReadOnly);
- // skip to the next non-word character
- auto cindex = GetCharacterIndex(aFrom);
- bool isword = false;
- bool skip = false;
- if (cindex < (int)mLines[at.mLine].size())
+ UndoRecord u;
+ u.mBefore = mState;
+
+ std::set affectedLines;
+ int minLine = -1;
+ int maxLine = -1;
+ for (int c = mState.mCurrentCursor; c > -1; c--) // cursors are expected to be sorted from top to bottom
{
- auto& line = mLines[at.mLine];
- isword = isalnum(line[cindex].mChar);
- skip = isword;
+ for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
+ {
+ if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
+ continue;
+ affectedLines.insert(currentLine);
+ minLine = minLine == -1 ? currentLine : (currentLine < minLine ? currentLine : minLine);
+ maxLine = maxLine == -1 ? currentLine : (currentLine > maxLine ? currentLine : maxLine);
+ }
+ }
+ if (minLine == 0) // can't move up anymore
+ return;
+
+ Coordinates start = { minLine - 1, 0 };
+ Coordinates end = { maxLine, GetLineMaxColumn(maxLine) };
+ u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete });
+
+ for (int line : affectedLines) // lines should be sorted here
+ std::swap(mLines[line - 1], mLines[line]);
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ mState.mCursors[c].mInteractiveStart.mLine -= 1;
+ mState.mCursors[c].mInteractiveEnd.mLine -= 1;
+ // no need to set mCursorPositionChanged as cursors will remain sorted
}
- while (!isword || skip)
+ end = { maxLine, GetLineMaxColumn(maxLine) }; // this line is swapped with line above, need to find new max column
+ u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Add });
+ u.mAfter = mState;
+ AddUndo(u);
+}
+
+void TextEditor::MoveDownCurrentLines()
+{
+ assert(!mReadOnly);
+
+ UndoRecord u;
+ u.mBefore = mState;
+
+ std::set affectedLines;
+ int minLine = -1;
+ int maxLine = -1;
+ for (int c = 0; c <= mState.mCurrentCursor; c++) // cursors are expected to be sorted from top to bottom
{
- if (at.mLine >= mLines.size())
+ for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
{
- auto l = std::max(0, (int) mLines.size() - 1);
- return Coordinates(l, GetLineMaxColumn(l));
+ if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
+ continue;
+ affectedLines.insert(currentLine);
+ minLine = minLine == -1 ? currentLine : (currentLine < minLine ? currentLine : minLine);
+ maxLine = maxLine == -1 ? currentLine : (currentLine > maxLine ? currentLine : maxLine);
}
+ }
+ if (maxLine == mLines.size() - 1) // can't move down anymore
+ return;
- auto& line = mLines[at.mLine];
- if (cindex < (int)line.size())
+ Coordinates start = { minLine, 0 };
+ Coordinates end = { maxLine + 1, GetLineMaxColumn(maxLine + 1) };
+ u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Delete });
+
+ std::set::reverse_iterator rit;
+ for (rit = affectedLines.rbegin(); rit != affectedLines.rend(); rit++) // lines should be sorted here
+ std::swap(mLines[*rit + 1], mLines[*rit]);
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ mState.mCursors[c].mInteractiveStart.mLine += 1;
+ mState.mCursors[c].mInteractiveEnd.mLine += 1;
+ // no need to set mCursorPositionChanged as cursors will remain sorted
+ }
+
+ end = { maxLine + 1, GetLineMaxColumn(maxLine + 1) }; // this line is swapped with line below, need to find new max column
+ u.mOperations.push_back({ GetText(start, end), start, end, UndoOperationType::Add });
+ u.mAfter = mState;
+ 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;
+ std::unordered_set affectedLines;
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ for (int currentLine = mState.mCursors[c].GetSelectionEnd().mLine; currentLine >= mState.mCursors[c].GetSelectionStart().mLine; currentLine--)
{
- isword = isalnum(line[cindex].mChar);
+ if (Coordinates{ currentLine, 0 } == mState.mCursors[c].GetSelectionEnd() && mState.mCursors[c].GetSelectionEnd() != mState.mCursors[c].GetSelectionStart()) // when selection ends at line start
+ continue;
+ affectedLines.insert(currentLine);
+ 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;
+ }
+ }
- if (isword && !skip)
- return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
+ if (shouldAddComment)
+ {
+ for (int currentLine : affectedLines) // order doesn't matter as changes are not multiline
+ {
+ 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 currentLine : affectedLines) // order doesn't matter as changes are not multiline
+ {
+ 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++;
- if (!isword)
- skip = false;
+ 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);
+ }
+ }
- cindex++;
+ u.mAfter = mState;
+ AddUndo(u);
+}
+
+void TextEditor::RemoveCurrentLines()
+{
+ UndoRecord u;
+ u.mBefore = mState;
+
+ if (AnyCursorHasSelection())
+ {
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ if (!mState.mCursors[c].HasSelection())
+ continue;
+ u.mOperations.push_back({ GetSelectedText(c), mState.mCursors[c].GetSelectionStart(), mState.mCursors[c].GetSelectionEnd(), UndoOperationType::Delete });
+ DeleteSelection(c);
+ }
+ }
+ MoveHome();
+ OnCursorPositionChanged(); // might combine cursors
+
+ for (int c = mState.mCurrentCursor; c > -1; c--)
+ {
+ int currentLine = mState.mCursors[c].mInteractiveEnd.mLine;
+ int nextLine = currentLine + 1;
+ int prevLine = currentLine - 1;
+
+ Coordinates toDeleteStart, toDeleteEnd;
+ if (mLines.size() > nextLine) // next line exists
+ {
+ toDeleteStart = Coordinates(currentLine, 0);
+ toDeleteEnd = Coordinates(nextLine, 0);
+ SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.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
{
- cindex = 0;
- ++at.mLine;
- skip = false;
- isword = false;
+ toDeleteStart = Coordinates(currentLine, 0);
+ toDeleteEnd = Coordinates(currentLine, GetLineMaxColumn(currentLine));
+ SetCursorPosition({ currentLine, 0 }, c);
}
+
+ u.mOperations.push_back({ GetText(toDeleteStart, toDeleteEnd), toDeleteStart, toDeleteEnd, UndoOperationType::Delete });
+
+ std::unordered_set handledCursors = { c };
+ if (toDeleteStart.mLine != toDeleteEnd.mLine)
+ RemoveLine(currentLine, &handledCursors);
+ else
+ DeleteRange(toDeleteStart, toDeleteEnd);
}
- return at;
+ u.mAfter = mState;
+ AddUndo(u);
}
-int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const
+float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom, bool aSanitizeCoords) const
{
- if (aCoordinates.mLine >= mLines.size())
+ if (aSanitizeCoords)
+ return SanitizeCoordinates(aFrom).mColumn * mCharAdvance.x;
+ else
+ return aFrom.mColumn * mCharAdvance.x;
+}
+
+void TextEditor::EnsureCursorVisible(int aCursor, bool aStartToo)
+{
+ if (aCursor == -1)
+ aCursor = mState.GetLastAddedCursorIndex();
+
+ mEnsureCursorVisible = aCursor;
+ mEnsureCursorVisibleStartToo = aStartToo;
+ return;
+}
+
+TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const
+{
+ // Clamp in document and line limits
+ auto line = Max(aValue.mLine, 0);
+ auto column = Max(aValue.mColumn, 0);
+ Coordinates out;
+ if (line >= (int)mLines.size())
+ {
+ if (mLines.empty())
+ {
+ line = 0;
+ column = 0;
+ }
+ else
+ {
+ line = (int)mLines.size() - 1;
+ column = GetLineMaxColumn(line);
+ }
+ out = Coordinates(line, column);
+ }
+ else
+ {
+ column = mLines.empty() ? 0 : GetLineMaxColumn(line, column);
+ out = Coordinates(line, column);
+ }
+
+ // Move if inside a tab character
+ int charIndex = GetCharacterIndexL(out);
+ if (charIndex > -1 && charIndex < mLines[out.mLine].size() && mLines[out.mLine][charIndex].mChar == '\t')
+ {
+ int columnToLeft = GetCharacterColumn(out.mLine, charIndex);
+ int columnToRight = GetCharacterColumn(out.mLine, GetCharacterIndexR(out));
+ if (out.mColumn - columnToLeft <= columnToRight - out.mColumn)
+ out.mColumn = columnToLeft;
+ else
+ out.mColumn = columnToRight;
+ }
+ return out;
+}
+
+TextEditor::Coordinates TextEditor::GetSanitizedCursorCoordinates(int aCursor, bool aStart) const
+{
+ aCursor = aCursor == -1 ? mState.mCurrentCursor : aCursor;
+ return SanitizeCoordinates(aStart ? mState.mCursors[aCursor].mInteractiveStart : mState.mCursors[aCursor].mInteractiveEnd);
+}
+
+TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition, bool* isOverLineNumber) const
+{
+ ImVec2 origin = ImGui::GetCursorScreenPos();
+ ImVec2 local(aPosition.x - origin.x + 3.0f, aPosition.y - origin.y);
+
+ if (isOverLineNumber != nullptr)
+ *isOverLineNumber = local.x < mTextStart;
+
+ Coordinates out = {
+ Max(0, (int)floor(local.y / mCharAdvance.y)),
+ Max(0, (int)floor((local.x - mTextStart) / mCharAdvance.x))
+ };
+ out.mColumn = Max(0, (int)floor((local.x - mTextStart + POS_TO_COORDS_COLUMN_OFFSET * mCharAdvance.x) / mCharAdvance.x));
+
+ return SanitizeCoordinates(out);
+}
+
+TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const
+{
+ if (aFrom.mLine >= (int)mLines.size())
+ return aFrom;
+
+ int lineIndex = aFrom.mLine;
+ auto& line = mLines[lineIndex];
+ int charIndex = GetCharacterIndexL(aFrom);
+
+ if (charIndex > (int)line.size() || line.size() == 0)
+ return aFrom;
+ if (charIndex == (int)line.size())
+ charIndex--;
+
+ bool initialIsWordChar = CharIsWordChar(line[charIndex].mChar);
+ bool initialIsSpace = isspace(line[charIndex].mChar);
+ char initialChar = line[charIndex].mChar;
+ while (Move(lineIndex, charIndex, true, true))
+ {
+ bool isWordChar = CharIsWordChar(line[charIndex].mChar);
+ bool isSpace = isspace(line[charIndex].mChar);
+ if (initialIsSpace && !isSpace ||
+ initialIsWordChar && !isWordChar ||
+ !initialIsWordChar && !initialIsSpace && initialChar != line[charIndex].mChar)
+ {
+ Move(lineIndex, charIndex, false, true); // one step to the right
+ break;
+ }
+ }
+ return { aFrom.mLine, GetCharacterColumn(aFrom.mLine, charIndex) };
+}
+
+TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const
+{
+ if (aFrom.mLine >= (int)mLines.size())
+ return aFrom;
+
+ int lineIndex = aFrom.mLine;
+ auto& line = mLines[lineIndex];
+ auto charIndex = GetCharacterIndexL(aFrom);
+
+ if (charIndex >= (int)line.size())
+ return aFrom;
+
+ bool initialIsWordChar = CharIsWordChar(line[charIndex].mChar);
+ bool initialIsSpace = isspace(line[charIndex].mChar);
+ char initialChar = line[charIndex].mChar;
+ while (Move(lineIndex, charIndex, false, true))
+ {
+ if (charIndex == line.size())
+ break;
+ bool isWordChar = CharIsWordChar(line[charIndex].mChar);
+ bool isSpace = isspace(line[charIndex].mChar);
+ if (initialIsSpace && !isSpace ||
+ initialIsWordChar && !isWordChar ||
+ !initialIsWordChar && !initialIsSpace && initialChar != line[charIndex].mChar)
+ break;
+ }
+ return { lineIndex, GetCharacterColumn(aFrom.mLine, charIndex) };
+}
+
+int TextEditor::GetCharacterIndexL(const Coordinates& aCoords) const
+{
+ if (aCoords.mLine >= mLines.size())
return -1;
- auto& line = mLines[aCoordinates.mLine];
+
+ auto& line = mLines[aCoords.mLine];
int c = 0;
int i = 0;
- for (; i < line.size() && c < aCoordinates.mColumn;)
+ int tabCoordsLeft = 0;
+
+ for (; i < line.size() && c < aCoords.mColumn;)
{
if (line[i].mChar == '\t')
- c = (c / mTabSize) * mTabSize + mTabSize;
+ {
+ if (tabCoordsLeft == 0)
+ tabCoordsLeft = TabSizeAtColumn(c);
+ if (tabCoordsLeft > 0)
+ tabCoordsLeft--;
+ c++;
+ }
else
++c;
- i += UTF8CharLength(line[i].mChar);
+ if (tabCoordsLeft == 0)
+ i += UTF8CharLength(line[i].mChar);
}
return i;
}
+int TextEditor::GetCharacterIndexR(const Coordinates& aCoords) const
+{
+ if (aCoords.mLine >= mLines.size())
+ return -1;
+ int c = 0;
+ int i = 0;
+ for (; i < mLines[aCoords.mLine].size() && c < aCoords.mColumn;)
+ MoveCharIndexAndColumn(aCoords.mLine, i, c);
+ 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);
+ int i = 0;
+ while (i < aIndex && i < mLines[aLine].size())
+ MoveCharIndexAndColumn(aLine, i, c);
return c;
}
-int TextEditor::GetLineMaxColumn(int aLine) const
+int TextEditor::GetFirstVisibleCharacterIndex(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;
+ int c = 0;
+ int i = 0;
+ while (c < mFirstVisibleColumn && i < mLines[aLine].size())
+ MoveCharIndexAndColumn(aLine, i, c);
+ if (c > mFirstVisibleColumn)
+ i--;
+ return i;
}
-bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const
+int TextEditor::GetLineMaxColumn(int aLine, int aLimit) const
{
- if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0)
- return true;
-
- auto& line = mLines[aAt.mLine];
- auto cindex = GetCharacterIndex(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::RemoveLine(int aStart, int aEnd)
-{
- assert(!mReadOnly);
- assert(aEnd >= aStart);
- assert(mLines.size() > (size_t)(aEnd - aStart));
-
- ErrorMarkers etmp;
- for (auto& i : mErrorMarkers)
+ if (aLine >= mLines.size())
+ return 0;
+ int c = 0;
+ if (aLimit == -1)
{
- 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);
+ for (int i = 0; i < mLines[aLine].size(); )
+ MoveCharIndexAndColumn(aLine, i, c);
}
- mErrorMarkers = std::move(etmp);
-
- Breakpoints btmp;
- for (auto i : mBreakpoints)
+ else
{
- if (i >= aStart && i <= aEnd)
- continue;
- btmp.insert(i >= aStart ? i - 1 : i);
+ for (int i = 0; i < mLines[aLine].size(); )
+ {
+ MoveCharIndexAndColumn(aLine, i, c);
+ if (c > aLimit)
+ return aLimit;
+ }
}
- mBreakpoints = std::move(btmp);
-
- mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);
- assert(!mLines.empty());
-
- mTextChanged = true;
-}
-
-void TextEditor::RemoveLine(int aIndex)
-{
- assert(!mReadOnly);
- assert(mLines.size() > 1);
-
- 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);
- }
- mErrorMarkers = std::move(etmp);
-
- Breakpoints btmp;
- for (auto i : mBreakpoints)
- {
- if (i == aIndex)
- continue;
- btmp.insert(i >= aIndex ? i - 1 : i);
- }
- mBreakpoints = std::move(btmp);
-
- mLines.erase(mLines.begin() + aIndex);
- assert(!mLines.empty());
-
- mTextChanged = true;
+ return c;
}
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);
+ for (int c = 0; c <= mState.mCurrentCursor; c++) // handle multiple cursors
+ {
+ if (mState.mCursors[c].mInteractiveEnd.mLine >= aIndex)
+ SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine + 1, mState.mCursors[c].mInteractiveEnd.mColumn }, c);
+ }
return result;
}
-std::string TextEditor::GetWordUnderCursor() const
+void TextEditor::RemoveLine(int aIndex, const std::unordered_set* aHandledCursors)
{
- auto c = GetCursorPosition();
- return GetWordAt(c);
+ assert(!mReadOnly);
+ assert(mLines.size() > 1);
+
+ mLines.erase(mLines.begin() + aIndex);
+ assert(!mLines.empty());
+
+ // handle multiple cursors
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ if (mState.mCursors[c].mInteractiveEnd.mLine >= aIndex)
+ {
+ if (aHandledCursors == nullptr || aHandledCursors->find(c) == aHandledCursors->end()) // move up if has not been handled already
+ SetCursorPosition({ mState.mCursors[c].mInteractiveEnd.mLine - 1, mState.mCursors[c].mInteractiveEnd.mColumn }, c);
+ }
+ }
}
-std::string TextEditor::GetWordAt(const Coordinates & aCoords) const
+void TextEditor::RemoveLines(int aStart, int aEnd)
{
- auto start = FindWordStart(aCoords);
- auto end = FindWordEnd(aCoords);
+ assert(!mReadOnly);
+ assert(aEnd >= aStart);
+ assert(mLines.size() > (size_t)(aEnd - aStart));
- std::string r;
+ mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);
+ assert(!mLines.empty());
- auto istart = GetCharacterIndex(start);
- auto iend = GetCharacterIndex(end);
-
- for (auto it = istart; it < iend; ++it)
- r.push_back(mLines[aCoords.mLine][it].mChar);
-
- return r;
+ // handle multiple cursors
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ if (mState.mCursors[c].mInteractiveEnd.mLine >= aStart)
+ {
+ int targetLine = mState.mCursors[c].mInteractiveEnd.mLine - (aEnd - aStart);
+ targetLine = targetLine < 0 ? 0 : targetLine;
+ mState.mCursors[c].mInteractiveEnd.mLine = targetLine;
+ }
+ if (mState.mCursors[c].mInteractiveStart.mLine >= aStart)
+ {
+ int targetLine = mState.mCursors[c].mInteractiveStart.mLine - (aEnd - aStart);
+ targetLine = targetLine < 0 ? 0 : targetLine;
+ mState.mCursors[c].mInteractiveStart.mLine = targetLine;
+ }
+ }
}
-ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const
+void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd)
{
- if (!mColorizerEnabled)
+ assert(aEnd >= aStart);
+ assert(!mReadOnly);
+
+ if (aEnd == aStart)
+ return;
+
+ auto start = GetCharacterIndexL(aStart);
+ auto end = GetCharacterIndexR(aEnd);
+
+ 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 (aStart.mLine < aEnd.mLine)
+ {
+ AddGlyphsToLine(aStart.mLine, firstLine.size(), lastLine.begin(), lastLine.end());
+ for (int c = 0; c <= mState.mCurrentCursor; c++) // move up cursors in line that is being moved up
+ {
+ // if cursor is selecting the same range we are deleting, it's because this is being called from
+ // DeleteSelection which already sets the cursor position after the range is deleted
+ if (mState.mCursors[c].GetSelectionStart() == aStart && mState.mCursors[c].GetSelectionEnd() == aEnd)
+ continue;
+ if (mState.mCursors[c].mInteractiveEnd.mLine > aEnd.mLine)
+ break;
+ else if (mState.mCursors[c].mInteractiveEnd.mLine != aEnd.mLine)
+ continue;
+ int otherCursorEndCharIndex = GetCharacterIndexR(mState.mCursors[c].mInteractiveEnd);
+ int otherCursorStartCharIndex = GetCharacterIndexR(mState.mCursors[c].mInteractiveStart);
+ int otherCursorNewEndCharIndex = GetCharacterIndexR(aStart) + otherCursorEndCharIndex;
+ int otherCursorNewStartCharIndex = GetCharacterIndexR(aStart) + otherCursorStartCharIndex;
+ auto targetEndCoords = Coordinates(aStart.mLine, GetCharacterColumn(aStart.mLine, otherCursorNewEndCharIndex));
+ auto targetStartCoords = Coordinates(aStart.mLine, GetCharacterColumn(aStart.mLine, otherCursorNewStartCharIndex));
+ SetCursorPosition(targetStartCoords, c, true);
+ SetCursorPosition(targetEndCoords, c, false);
+ }
+ RemoveLines(aStart.mLine + 1, aEnd.mLine + 1);
+ }
+ }
+}
+
+void TextEditor::DeleteSelection(int aCursor)
+{
+ if (aCursor == -1)
+ aCursor = mState.mCurrentCursor;
+
+ if (mState.mCursors[aCursor].GetSelectionEnd() == mState.mCursors[aCursor].GetSelectionStart())
+ return;
+
+ Coordinates newCursorPos = mState.mCursors[aCursor].GetSelectionStart();
+ DeleteRange(newCursorPos, mState.mCursors[aCursor].GetSelectionEnd());
+ SetCursorPosition(newCursorPos, aCursor);
+ Colorize(newCursorPos.mLine, 1);
+}
+
+void TextEditor::RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar)
+{
+ int column = GetCharacterColumn(aLine, aStartChar);
+ 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);
+}
+
+ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const
+{
+ if (mLanguageDefinition == nullptr)
return mPalette[(int)PaletteIndex::Default];
if (aGlyph.mComment)
return mPalette[(int)PaletteIndex::Comment];
@@ -695,74 +1975,96 @@ ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const
return color;
}
-void TextEditor::HandleKeyboardInputs()
+void TextEditor::HandleKeyboardInputs(bool aParentIsFocused)
{
- ImGuiIO& io = ImGui::GetIO();
- auto shift = io.KeyShift;
- auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
- auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
-
- if (ImGui::IsWindowFocused())
+ 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() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z)))
+ if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGuiKey_Z))
Undo();
- else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
+ else if (!mReadOnly && isAltOnly && ImGui::IsKeyPressed(ImGuiKey_Backspace))
Undo();
- else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y)))
+ else if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGuiKey_Y))
Redo();
- else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
+ else if (!mReadOnly && isShiftShortcut && ImGui::IsKeyPressed(ImGuiKey_Z))
+ Redo();
+ else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_UpArrow))
MoveUp(1, shift);
- else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
+ else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_DownArrow))
MoveDown(1, shift);
- else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
- MoveLeft(1, shift, ctrl);
- else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
- MoveRight(1, shift, ctrl);
- else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
- MoveUp(GetPageSize() - 4, shift);
- else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
- MoveDown(GetPageSize() - 4, shift);
- else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
+ else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
+ MoveLeft(shift, isWordmoveKey);
+ else if ((isOSX ? !ctrl : !alt) && !super && ImGui::IsKeyPressed(ImGuiKey_RightArrow))
+ MoveRight(shift, isWordmoveKey);
+ else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_PageUp))
+ MoveUp(mVisibleLineCount - 2, shift);
+ else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_PageDown))
+ MoveDown(mVisibleLineCount - 2, shift);
+ else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGuiKey_Home))
MoveTop(shift);
- else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
+ else if (ctrl && !alt && !super && ImGui::IsKeyPressed(ImGuiKey_End))
MoveBottom(shift);
- else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
+ else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_Home))
MoveHome(shift);
- else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
+ else if (!alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_End))
MoveEnd(shift);
- else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
- Delete();
- else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
- Backspace();
- else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
- mOverwrite ^= true;
- else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
+ else if (!mReadOnly && !alt && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_Delete))
+ Delete(ctrl);
+ else if (!mReadOnly && !alt && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_Backspace))
+ Backspace(ctrl);
+ else if (!mReadOnly && !alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGuiKey_K))
+ RemoveCurrentLines();
+ else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_LeftBracket))
+ ChangeCurrentLinesIndentation(false);
+ else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_RightBracket))
+ ChangeCurrentLinesIndentation(true);
+ else if (!alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGuiKey_UpArrow))
+ MoveUpCurrentLines();
+ else if (!alt && ctrl && shift && !super && ImGui::IsKeyPressed(ImGuiKey_DownArrow))
+ MoveDownCurrentLines();
+ else if (!mReadOnly && !alt && ctrl && !shift && !super && ImGui::IsKeyPressed(ImGuiKey_Slash))
+ ToggleLineComment();
+ else if (isCtrlOnly && ImGui::IsKeyPressed(ImGuiKey_Insert))
Copy();
- else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
+ else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_C))
Copy();
- else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
+ else if (!mReadOnly && isShiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert))
Paste();
- else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V)))
+ else if (!mReadOnly && isShortcut && ImGui::IsKeyPressed(ImGuiKey_V))
Paste();
- else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X)))
+ else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_X))
Cut();
- else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
+ else if (isShiftOnly && ImGui::IsKeyPressed(ImGuiKey_Delete))
Cut();
- else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A)))
+ else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_A))
SelectAll();
- else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)))
+ else if (isShortcut && ImGui::IsKeyPressed(ImGuiKey_D))
+ AddCursorForNextOccurrence();
+ else if (!mReadOnly && !alt && !ctrl && !shift && !super && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)))
EnterCharacter('\n', false);
- else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab)))
+ else if (!mReadOnly && !alt && !ctrl && !super && ImGui::IsKeyPressed(ImGuiKey_Tab))
EnterCharacter('\t', shift);
-
- if (!IsReadOnly() && !io.InputQueueCharacters.empty())
+ if (!mReadOnly && !io.InputQueueCharacters.empty() && ctrl == alt && !super)
{
for (int i = 0; i < io.InputQueueCharacters.Size; i++)
{
@@ -782,14 +2084,55 @@ void TextEditor::HandleMouseInputs()
auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
+ /*
+ Pan with middle mouse button
+ */
+ mPanning &= ImGui::IsMouseDown(2);
+ if (mPanning && ImGui::IsMouseDragging(2))
+ {
+ ImVec2 scroll = { ImGui::GetScrollX(), ImGui::GetScrollY() };
+ ImVec2 currentMousePos = ImGui::GetMouseDragDelta(2);
+ ImVec2 mouseDelta = {
+ currentMousePos.x - mLastMousePos.x,
+ currentMousePos.y - mLastMousePos.y
+ };
+ ImGui::SetScrollY(scroll.y - mouseDelta.y);
+ ImGui::SetScrollX(scroll.x - mouseDelta.x);
+ mLastMousePos = currentMousePos;
+ }
+
+ // Mouse left button dragging (=> update selection)
+ mDraggingSelection &= ImGui::IsMouseDown(0);
+ if (mDraggingSelection && ImGui::IsMouseDragging(0))
+ {
+ io.WantCaptureMouse = true;
+ Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos());
+ SetCursorPosition(cursorCoords, mState.GetLastAddedCursorIndex(), false);
+ }
+
if (ImGui::IsWindowHovered())
{
+ auto click = ImGui::IsMouseClicked(0);
if (!shift && !alt)
{
- auto click = ImGui::IsMouseClicked(0);
auto doubleClick = ImGui::IsMouseDoubleClicked(0);
auto t = ImGui::GetTime();
- auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime);
+ auto tripleClick = click && !doubleClick &&
+ (mLastClickTime != -1.0f && (t - mLastClickTime) < io.MouseDoubleClickTime &&
+ Distance(io.MousePos, mLastClickPos) < 0.01f);
+
+ if (click)
+ mDraggingSelection = true;
+
+ /*
+ Pan with middle mouse button
+ */
+
+ if (ImGui::IsMouseClicked(2))
+ {
+ mPanning = true;
+ mLastMousePos = ImGui::GetMouseDragDelta(2);
+ }
/*
Left mouse button triple click
@@ -797,14 +2140,18 @@ void TextEditor::HandleMouseInputs()
if (tripleClick)
{
- if (!ctrl)
- {
- mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
- mSelectionMode = SelectionMode::Line;
- SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
- }
+ if (ctrl)
+ mState.AddCursor();
+ else
+ mState.mCurrentCursor = 0;
- mLastClick = -1.0f;
+ Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos());
+ Coordinates targetCursorPos = cursorCoords.mLine < mLines.size() - 1 ?
+ Coordinates{ cursorCoords.mLine + 1, 0 } :
+ Coordinates{ cursorCoords.mLine, GetLineMaxColumn(cursorCoords.mLine) };
+ SetSelection({ cursorCoords.mLine, 0 }, targetCursorPos, mState.mCurrentCursor);
+
+ mLastClickTime = -1.0f;
}
/*
@@ -813,17 +2160,16 @@ void TextEditor::HandleMouseInputs()
else if (doubleClick)
{
- if (!ctrl)
- {
- mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
- if (mSelectionMode == SelectionMode::Line)
- mSelectionMode = SelectionMode::Normal;
- else
- mSelectionMode = SelectionMode::Word;
- SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
- }
+ if (ctrl)
+ mState.AddCursor();
+ else
+ mState.mCurrentCursor = 0;
- mLastClick = (float)ImGui::GetTime();
+ Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos());
+ SetSelection(FindWordStart(cursorCoords), FindWordEnd(cursorCoords), mState.mCurrentCursor);
+
+ mLastClickTime = (float)ImGui::GetTime();
+ mLastClickPos = io.MousePos;
}
/*
@@ -831,222 +2177,198 @@ void TextEditor::HandleMouseInputs()
*/
else if (click)
{
- mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
if (ctrl)
- mSelectionMode = SelectionMode::Word;
+ mState.AddCursor();
else
- mSelectionMode = SelectionMode::Normal;
- SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
+ mState.mCurrentCursor = 0;
- mLastClick = (float)ImGui::GetTime();
+ bool isOverLineNumber;
+ Coordinates cursorCoords = ScreenPosToCoordinates(ImGui::GetMousePos(), &isOverLineNumber);
+ if (isOverLineNumber)
+ {
+ Coordinates targetCursorPos = cursorCoords.mLine < mLines.size() - 1 ?
+ Coordinates{ cursorCoords.mLine + 1, 0 } :
+ Coordinates{ cursorCoords.mLine, GetLineMaxColumn(cursorCoords.mLine) };
+ SetSelection({ cursorCoords.mLine, 0 }, targetCursorPos, mState.mCurrentCursor);
+ }
+ else
+ SetCursorPosition(cursorCoords, mState.GetLastAddedCursorIndex());
+
+ mLastClickTime = (float)ImGui::GetTime();
+ mLastClickPos = io.MousePos;
}
- // Mouse left button dragging (=> update selection)
- else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))
+ else if (ImGui::IsMouseReleased(0))
{
- io.WantCaptureMouse = true;
- mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
- SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
+ mState.SortCursorsFromTopToBottom();
+ MergeCursorsIfPossible();
+ }
+ }
+ else if (shift)
+ {
+ if (click)
+ {
+ Coordinates newSelection = ScreenPosToCoordinates(ImGui::GetMousePos());
+ SetCursorPosition(newSelection, mState.mCurrentCursor, false);
}
}
}
}
-void TextEditor::Render()
+void TextEditor::UpdateViewVariables(float aScrollX, float aScrollY)
+{
+ mContentHeight = ImGui::GetWindowHeight() - (IsHorizontalScrollbarVisible() ? IMGUI_SCROLLBAR_WIDTH : 0.0f);
+ mContentWidth = ImGui::GetWindowWidth() - (IsVerticalScrollbarVisible() ? IMGUI_SCROLLBAR_WIDTH : 0.0f);
+
+ mVisibleLineCount = Max((int)ceil(mContentHeight / mCharAdvance.y), 0);
+ mFirstVisibleLine = Max((int)(aScrollY / mCharAdvance.y), 0);
+ mLastVisibleLine = Max((int)((mContentHeight + aScrollY) / mCharAdvance.y), 0);
+
+ mVisibleColumnCount = Max((int)ceil((mContentWidth - Max(mTextStart - aScrollX, 0.0f)) / mCharAdvance.x), 0);
+ mFirstVisibleColumn = Max((int)(Max(aScrollX - mTextStart, 0.0f) / mCharAdvance.x), 0);
+ mLastVisibleColumn = Max((int)((mContentWidth + aScrollX - mTextStart) / mCharAdvance.x), 0);
+}
+
+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);
+ const float fontWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
+ const float fontHeight = ImGui::GetTextLineHeightWithSpacing();
+ mCharAdvance = ImVec2(fontWidth, fontHeight * mLineSpacing);
- /* Update palette with the current alpha from style */
- for (int i = 0; i < (int)PaletteIndex::Max; ++i)
+ // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
+ mTextStart = mLeftMargin;
+ static char lineNumberBuffer[16];
+ if (mShowLineNumbers)
{
- auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]);
- color.w *= ImGui::GetStyle().Alpha;
- mPalette[i] = ImGui::ColorConvertFloat4ToU32(color);
- }
-
- assert(mLineBuffer.empty());
-
- auto contentSize = ImGui::GetWindowContentRegionMax();
- auto drawList = ImGui::GetWindowDrawList();
- float longest(mTextStart);
-
- if (mScrollToTop)
- {
- mScrollToTop = false;
- ImGui::SetScrollY(0.f);
+ snprintf(lineNumberBuffer, 16, " %zu ", mLines.size());
+ mTextStart += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberBuffer, nullptr, nullptr).x;
}
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)));
-
- // 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;
+ mScrollX = ImGui::GetScrollX();
+ mScrollY = ImGui::GetScrollY();
+ UpdateViewVariables(mScrollX, mScrollY);
+ int maxColumnLimited = 0;
if (!mLines.empty())
{
+ auto drawList = ImGui::GetWindowDrawList();
float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
- while (lineNo <= lineMax)
+ for (int lineNo = mFirstVisibleLine; lineNo <= mLastVisibleLine && lineNo < mLines.size(); lineNo++)
{
ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);
auto& line = mLines[lineNo];
- longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);
- auto columnNo = 0;
+ maxColumnLimited = Max(GetLineMaxColumn(lineNo, mLastVisibleColumn), maxColumnLimited);
+
Coordinates lineStartCoord(lineNo, 0);
- Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo));
+ Coordinates lineEndCoord(lineNo, maxColumnLimited);
// Draw selection for the current line
- float sstart = -1.0f;
- float ssend = -1.0f;
-
- assert(mState.mSelectionStart <= mState.mSelectionEnd);
- if (mState.mSelectionStart <= lineEndCoord)
- sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f;
- if (mState.mSelectionEnd > lineStartCoord)
- ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord);
-
- if (mState.mSelectionEnd.mLine > lineNo)
- ssend += mCharAdvance.x;
-
- if (sstart != -1 && ssend != -1 && sstart < ssend)
+ 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 rectStart = -1.0f;
+ float rectEnd = -1.0f;
+ Coordinates cursorSelectionStart = mState.mCursors[c].GetSelectionStart();
+ Coordinates cursorSelectionEnd = mState.mCursors[c].GetSelectionEnd();
+ assert(cursorSelectionStart <= cursorSelectionEnd);
- // Draw breakpoints
- auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);
+ if (cursorSelectionStart <= lineEndCoord)
+ rectStart = cursorSelectionStart > lineStartCoord ? TextDistanceToLineStart(cursorSelectionStart) : 0.0f;
+ if (cursorSelectionEnd > lineStartCoord)
+ rectEnd = TextDistanceToLineStart(cursorSelectionEnd < lineEndCoord ? cursorSelectionEnd : lineEndCoord);
+ if (cursorSelectionEnd.mLine > lineNo || cursorSelectionEnd.mLine == lineNo && cursorSelectionEnd > lineEndCoord)
+ rectEnd += mCharAdvance.x;
- 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]);
- }
-
- // 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 (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();
- }
+ if (rectStart != -1 && rectEnd != -1 && rectStart < rectEnd)
+ drawList->AddRectFilled(
+ ImVec2{ lineStartScreenPos.x + mTextStart + rectStart, lineStartScreenPos.y },
+ ImVec2{ lineStartScreenPos.x + mTextStart + rectEnd, lineStartScreenPos.y + mCharAdvance.y },
+ mPalette[(int)PaletteIndex::Selection]);
}
// Draw line number (right aligned)
- snprintf(buf, 16, "%d ", lineNo + 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);
-
- if (mState.mCursorPosition.mLine == lineNo)
+ if (mShowLineNumbers)
{
- auto focused = ImGui::IsWindowFocused();
+ snprintf(lineNumberBuffer, 16, "%d ", lineNo + 1);
+ float lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberBuffer, nullptr, nullptr).x;
+ drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], lineNumberBuffer);
+ }
- // Highlight the current line (where the cursor is)
- if (!HasSelection())
- {
- auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y);
- drawList->AddRectFilled(start, end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
- drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f);
- }
+ std::vector cursorCoordsInThisLine;
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ if (mState.mCursors[c].mInteractiveEnd.mLine == lineNo)
+ cursorCoordsInThisLine.push_back(mState.mCursors[c].mInteractiveEnd);
+ }
+ if (cursorCoordsInThisLine.size() > 0)
+ {
+ bool focused = ImGui::IsWindowFocused() || aParentIsFocused;
- // Render the cursor
+ // Render the cursors
if (focused)
{
- auto timeEnd = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count();
- auto elapsed = timeEnd - mStartTime;
- if (elapsed > 400)
+ for (const auto& cursorCoords : cursorCoordsInThisLine)
{
float width = 1.0f;
- auto cindex = GetCharacterIndex(mState.mCursorPosition);
- float cx = TextDistanceToLineStart(mState.mCursorPosition);
+ auto cindex = GetCharacterIndexR(cursorCoords);
+ float cx = TextDistanceToLineStart(cursorCoords);
- if (mOverwrite && cindex < (int)line.size())
- {
- 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]);
- if (elapsed > 800)
- mStartTime = timeEnd;
+ if (mCursorOnBracket)
+ {
+ ImVec2 topLeft = { cstart.x, lineStartScreenPos.y + fontHeight + 1.0f };
+ ImVec2 bottomRight = { topLeft.x + mCharAdvance.x, topLeft.y + 1.0f };
+ drawList->AddRectFilled(topLeft, bottomRight, 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();)
+ static std::string glyphBuffer;
+ int charIndex = GetFirstVisibleCharacterIndex(lineNo);
+ int column = mFirstVisibleColumn; // can be in the middle of tab character
+ while (charIndex < mLines[lineNo].size() && column <= mLastVisibleColumn)
{
- auto& glyph = line[i];
+ auto& glyph = line[charIndex];
auto color = GetGlyphColor(glyph);
-
- 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;
+ ImVec2 targetGlyphPos = { lineStartScreenPos.x + mTextStart + TextDistanceToLineStart({lineNo, column}, false), lineStartScreenPos.y };
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 (mShowWhitespaces)
{
+ ImVec2 p1, p2, p3, p4;
+
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;
- const ImVec2 p1(x1, y);
- const ImVec2 p2(x2, y);
- const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f);
- const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f);
- drawList->AddLine(p1, p2, 0x90909090);
- drawList->AddLine(p2, p3, 0x90909090);
- drawList->AddLine(p2, p4, 0x90909090);
+ const auto x1 = targetGlyphPos.x + mCharAdvance.x * 0.3f;
+ const auto y = targetGlyphPos.y + fontHeight * 0.5f;
+
+ if (mShortTabs)
+ {
+ const auto x2 = targetGlyphPos.x + mCharAdvance.x;
+ 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 x2 = targetGlyphPos.x + TabSizeAtColumn(column) * mCharAdvance.x - mCharAdvance.x * 0.3f;
+ 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 if (glyph.mChar == ' ')
@@ -1054,1083 +2376,190 @@ void TextEditor::Render()
if (mShowWhitespaces)
{
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);
+ const auto x = targetGlyphPos.x + spaceSize * 0.5f;
+ const auto y = targetGlyphPos.y + s * 0.5f;
+ drawList->AddCircleFilled(ImVec2(x, y), 1.5f, mPalette[(int)PaletteIndex::ControlCharacter], 4);
}
- bufferOffset.x += spaceSize;
- i++;
}
else
{
- auto l = UTF8CharLength(glyph.mChar);
- while (l-- > 0)
- mLineBuffer.push_back(line[i++].mChar);
- }
- ++columnNo;
- }
-
- if (!mLineBuffer.empty())
- {
- const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y);
- drawList->AddText(newOffset, prevColor, mLineBuffer.c_str());
- mLineBuffer.clear();
- }
-
- ++lineNo;
- }
-
- // Draw a tooltip on known identifiers/preprocessor symbols
- if (ImGui::IsMousePosValid())
- {
- auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos()));
- if (!id.empty())
- {
- auto it = mLanguageDefinition.mIdentifiers.find(id);
- if (it != mLanguageDefinition.mIdentifiers.end())
- {
- ImGui::BeginTooltip();
- ImGui::TextUnformatted(it->second.mDeclaration.c_str());
- ImGui::EndTooltip();
- }
- else
- {
- auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id);
- if (pi != mLanguageDefinition.mPreprocIdentifiers.end())
+ int seqLength = UTF8CharLength(glyph.mChar);
+ if (mCursorOnBracket && seqLength == 1 && mMatchingBracketCoords == Coordinates{ lineNo, column })
{
- ImGui::BeginTooltip();
- ImGui::TextUnformatted(pi->second.mDeclaration.c_str());
- ImGui::EndTooltip();
+ ImVec2 topLeft = { targetGlyphPos.x, targetGlyphPos.y + fontHeight + 1.0f };
+ ImVec2 bottomRight = { topLeft.x + mCharAdvance.x, topLeft.y + 1.0f };
+ drawList->AddRectFilled(topLeft, bottomRight, mPalette[(int)PaletteIndex::Cursor]);
}
+ glyphBuffer.clear();
+ for (int i = 0; i < seqLength; i++)
+ glyphBuffer.push_back(line[charIndex + i].mChar);
+ drawList->AddText(targetGlyphPos, color, glyphBuffer.c_str());
}
+
+ MoveCharIndexAndColumn(lineNo, charIndex, column);
}
}
}
+ mCurrentSpaceHeight = (mLines.size() + Min(mVisibleLineCount - 1, (int)mLines.size())) * mCharAdvance.y;
+ mCurrentSpaceWidth = Max((maxColumnLimited + Min(mVisibleColumnCount - 1, maxColumnLimited)) * mCharAdvance.x, mCurrentSpaceWidth);
+ ImGui::SetCursorPos(ImVec2(0, 0));
+ ImGui::Dummy(ImVec2(mCurrentSpaceWidth, mCurrentSpaceHeight));
- ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
-
- if (mScrollToCursor)
+ if (mEnsureCursorVisible > -1)
{
- EnsureCursorVisible();
- ImGui::SetWindowFocus();
- mScrollToCursor = false;
- }
-}
-
-void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder)
-{
- mWithinRender = true;
- mTextChanged = false;
- mCursorPositionChanged = false;
-
- 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_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove);
-
- if (mHandleKeyboardInputs)
- {
- HandleKeyboardInputs();
- ImGui::PushAllowKeyboardFocus(true);
- }
-
- if (mHandleMouseInputs)
- HandleMouseInputs();
-
- ColorizeInternal();
- Render();
-
- if (mHandleKeyboardInputs)
- ImGui::PopAllowKeyboardFocus();
-
- if (!mIgnoreImGuiChild)
- ImGui::EndChild();
-
- ImGui::PopStyleVar();
- ImGui::PopStyleColor();
-
- mWithinRender = false;
-}
-
-void TextEditor::SetText(const std::string & aText)
-{
- mLines.clear();
- mLines.emplace_back(Line());
- for (auto chr : aText)
- {
- if (chr == '\r')
+ for (int i = 0; i < (mEnsureCursorVisibleStartToo ? 2 : 1); i++) // first pass for interactive end and second pass for interactive start
{
- // ignore the carriage return character
- }
- else if (chr == '\n')
- mLines.emplace_back(Line());
- else
- {
- mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default));
+ if (i) UpdateViewVariables(mScrollX, mScrollY); // second pass depends on changes made in first pass
+ Coordinates targetCoords = GetSanitizedCursorCoordinates(mEnsureCursorVisible, i); // cursor selection end or start
+ if (targetCoords.mLine <= mFirstVisibleLine)
+ {
+ float targetScroll = std::max(0.0f, (targetCoords.mLine - 0.5f) * mCharAdvance.y);
+ if (targetScroll < mScrollY)
+ ImGui::SetScrollY(targetScroll);
+ }
+ if (targetCoords.mLine >= mLastVisibleLine)
+ {
+ float targetScroll = std::max(0.0f, (targetCoords.mLine + 1.5f) * mCharAdvance.y - mContentHeight);
+ if (targetScroll > mScrollY)
+ ImGui::SetScrollY(targetScroll);
+ }
+ if (targetCoords.mColumn <= mFirstVisibleColumn)
+ {
+ float targetScroll = std::max(0.0f, mTextStart + (targetCoords.mColumn - 0.5f) * mCharAdvance.x);
+ if (targetScroll < mScrollX)
+ ImGui::SetScrollX(mScrollX = targetScroll);
+ }
+ if (targetCoords.mColumn >= mLastVisibleColumn)
+ {
+ float targetScroll = std::max(0.0f, mTextStart + (targetCoords.mColumn + 0.5f) * mCharAdvance.x - mContentWidth);
+ if (targetScroll > mScrollX)
+ ImGui::SetScrollX(mScrollX = targetScroll);
+ }
}
+ mEnsureCursorVisible = -1;
+ }
+ if (mScrollToTop)
+ {
+ ImGui::SetScrollY(0.0f);
+ mScrollToTop = false;
+ }
+ if (mSetViewAtLine > -1)
+ {
+ float targetScroll;
+ switch (mSetViewAtLineMode)
+ {
+ default:
+ case SetViewAtLineMode::FirstVisibleLine:
+ targetScroll = std::max(0.0f, (float)mSetViewAtLine * mCharAdvance.y);
+ break;
+ case SetViewAtLineMode::LastVisibleLine:
+ targetScroll = std::max(0.0f, (float)(mSetViewAtLine - (mLastVisibleLine - mFirstVisibleLine)) * mCharAdvance.y);
+ break;
+ case SetViewAtLineMode::Centered:
+ targetScroll = std::max(0.0f, ((float)mSetViewAtLine - (float)(mLastVisibleLine - mFirstVisibleLine) * 0.5f) * mCharAdvance.y);
+ break;
+ }
+ ImGui::SetScrollY(targetScroll);
+ mSetViewAtLine = -1;
}
-
- mTextChanged = true;
- mScrollToTop = true;
-
- mUndoBuffer.clear();
- mUndoIndex = 0;
-
- Colorize();
}
-void TextEditor::SetTextLines(const std::vector & aLines)
+void TextEditor::OnCursorPositionChanged()
{
- mLines.clear();
+ if (mState.mCurrentCursor == 0 && !mState.mCursors[0].HasSelection()) // only one cursor without selection
+ mCursorOnBracket = FindMatchingBracket(mState.mCursors[0].mInteractiveEnd.mLine,
+ GetCharacterIndexR(mState.mCursors[0].mInteractiveEnd), mMatchingBracketCoords);
+ else
+ mCursorOnBracket = false;
- if (aLines.empty())
+ if (!mDraggingSelection)
{
- mLines.emplace_back(Line());
+ mState.SortCursorsFromTopToBottom();
+ MergeCursorsIfPossible();
+ }
+}
+
+void TextEditor::OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted) // adjusts cursor position when other cursor writes/deletes in the same line
+{
+ static std::unordered_map cursorCharIndices;
+ if (aBeforeChange)
+ {
+ cursorCharIndices.clear();
+ for (int c = 0; c <= mState.mCurrentCursor; c++)
+ {
+ if (mState.mCursors[c].mInteractiveEnd.mLine == aLine && // cursor is at the line
+ mState.mCursors[c].mInteractiveEnd.mColumn > aColumn && // cursor is to the right of changing part
+ mState.mCursors[c].GetSelectionEnd() == mState.mCursors[c].GetSelectionStart()) // cursor does not have a selection
+ {
+ cursorCharIndices[c] = GetCharacterIndexR({ aLine, mState.mCursors[c].mInteractiveEnd.mColumn });
+ cursorCharIndices[c] += aDeleted ? -aCharCount : aCharCount;
+ }
+ }
}
else
{
- mLines.resize(aLines.size());
-
- for (size_t i = 0; i < aLines.size(); ++i)
- {
- 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));
- }
+ for (auto& item : cursorCharIndices)
+ SetCursorPosition({ aLine, GetCharacterColumn(aLine, item.second) }, item.first);
}
-
- mTextChanged = true;
- mScrollToTop = true;
-
- mUndoBuffer.clear();
- mUndoIndex = 0;
-
- Colorize();
}
-void TextEditor::EnterCharacter(ImWchar aChar, bool aShift)
+void TextEditor::MergeCursorsIfPossible()
+{
+ // requires the cursors to be sorted from top to bottom
+ std::unordered_set cursorsToDelete;
+ if (AnyCursorHasSelection())
+ {
+ // 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].GetSelectionEnd() >= mState.mCursors[c].GetSelectionEnd();
+ bool pcContainsStartOfC = mState.mCursors[pc].GetSelectionEnd() > mState.mCursors[c].GetSelectionStart();
+
+ if (pcContainsC)
+ {
+ cursorsToDelete.insert(c);
+ }
+ else if (pcContainsStartOfC)
+ {
+ Coordinates pcStart = mState.mCursors[pc].GetSelectionStart();
+ Coordinates cEnd = mState.mCursors[c].GetSelectionEnd();
+ mState.mCursors[pc].mInteractiveEnd = cEnd;
+ mState.mCursors[pc].mInteractiveStart = pcStart;
+ 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].mInteractiveEnd == mState.mCursors[c].mInteractiveEnd)
+ 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();
+}
+
+void TextEditor::AddUndo(UndoRecord& aValue)
{
assert(!mReadOnly);
-
- UndoRecord u;
-
- u.mBefore = mState;
-
- if (HasSelection())
- {
- if (aChar == '\t' && mState.mSelectionStart.mLine != mState.mSelectionEnd.mLine)
- {
-
- auto start = mState.mSelectionStart;
- auto end = mState.mSelectionEnd;
- auto originalEnd = end;
-
- 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);
-
- //if (end.mColumn >= GetLineMaxColumn(end.mLine))
- // end.mColumn = GetLineMaxColumn(end.mLine) - 1;
-
- u.mRemovedStart = start;
- u.mRemovedEnd = end;
- u.mRemoved = GetText(start, end);
-
- bool modified = false;
-
- for (int i = start.mLine; i <= end.mLine; i++)
- {
- auto& line = mLines[i];
- if (aShift)
- {
- if (!line.empty())
- {
- if (line.front().mChar == '\t')
- {
- line.erase(line.begin());
- modified = true;
- }
- else
- {
- for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++)
- {
- line.erase(line.begin());
- modified = true;
- }
- }
- }
- }
- else
- {
- line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background));
- modified = true;
- }
- }
-
- if (modified)
- {
- start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0));
- Coordinates rangeEnd;
- if (originalEnd.mColumn != 0)
- {
- end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine));
- rangeEnd = end;
- u.mAdded = GetText(start, end);
- }
- else
- {
- end = Coordinates(originalEnd.mLine, 0);
- rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1));
- u.mAdded = GetText(start, rangeEnd);
- }
-
- u.mAddedStart = start;
- u.mAddedEnd = rangeEnd;
- u.mAfter = mState;
-
- mState.mSelectionStart = start;
- mState.mSelectionEnd = end;
- AddUndo(u);
-
- mTextChanged = true;
-
- EnsureCursorVisible();
- }
-
- return;
- } // c == '\t'
- else
- {
- u.mRemoved = GetSelectedText();
- u.mRemovedStart = mState.mSelectionStart;
- u.mRemovedEnd = mState.mSelectionEnd;
- DeleteSelection();
- }
- } // HasSelection
-
- auto coord = GetActualCursorCoordinates();
- u.mAddedStart = coord;
-
- assert(!mLines.empty());
-
- if (aChar == '\n')
- {
- InsertLine(coord.mLine + 1);
- auto& line = mLines[coord.mLine];
- auto& newLine = mLines[coord.mLine + 1];
-
- if (mLanguageDefinition.mAutoIndentation)
- for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it)
- newLine.push_back(line[it]);
-
- const size_t whitespaceSize = newLine.size();
- auto cindex = GetCharacterIndex(coord);
- newLine.insert(newLine.end(), line.begin() + cindex, line.end());
- line.erase(line.begin() + cindex, line.begin() + line.size());
- SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)));
- u.mAdded = (char)aChar;
- }
- else
- {
- char buf[7];
- int e = ImTextCharToUtf8(buf, 7, aChar);
- if (e > 0)
- {
- buf[e] = '\0';
- auto& line = mLines[coord.mLine];
- auto cindex = GetCharacterIndex(coord);
-
- if (mOverwrite && cindex < (int)line.size())
- {
- auto d = UTF8CharLength(line[cindex].mChar);
-
- u.mRemovedStart = mState.mCursorPosition;
- u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d));
-
- while (d-- > 0 && cindex < (int)line.size())
- {
- u.mRemoved += line[cindex].mChar;
- line.erase(line.begin() + cindex);
- }
- }
-
- for (auto p = buf; *p != '\0'; p++, ++cindex)
- line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default));
- u.mAdded = buf;
-
- SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)));
- }
- else
- return;
- }
-
- mTextChanged = true;
-
- u.mAddedEnd = GetActualCursorCoordinates();
- u.mAfter = mState;
-
- AddUndo(u);
-
- Colorize(coord.mLine - 1, 3);
- EnsureCursorVisible();
-}
-
-void TextEditor::SetReadOnly(bool aValue)
-{
- mReadOnly = aValue;
-}
-
-void TextEditor::SetColorizerEnable(bool aValue)
-{
- mColorizerEnabled = aValue;
-}
-
-void TextEditor::SetCursorPosition(const Coordinates & aPosition)
-{
- if (mState.mCursorPosition != aPosition)
- {
- mState.mCursorPosition = aPosition;
- mCursorPositionChanged = true;
- EnsureCursorVisible();
- }
-}
-
-void TextEditor::SetSelectionStart(const Coordinates & aPosition)
-{
- mState.mSelectionStart = SanitizeCoordinates(aPosition);
- if (mState.mSelectionStart > mState.mSelectionEnd)
- std::swap(mState.mSelectionStart, mState.mSelectionEnd);
-}
-
-void TextEditor::SetSelectionEnd(const Coordinates & aPosition)
-{
- mState.mSelectionEnd = SanitizeCoordinates(aPosition);
- if (mState.mSelectionStart > mState.mSelectionEnd)
- std::swap(mState.mSelectionStart, mState.mSelectionEnd);
-}
-
-void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode)
-{
- auto oldSelStart = mState.mSelectionStart;
- auto oldSelEnd = mState.mSelectionEnd;
-
- mState.mSelectionStart = SanitizeCoordinates(aStart);
- mState.mSelectionEnd = SanitizeCoordinates(aEnd);
- if (mState.mSelectionStart > mState.mSelectionEnd)
- std::swap(mState.mSelectionStart, mState.mSelectionEnd);
-
- switch (aMode)
- {
- case TextEditor::SelectionMode::Normal:
- break;
- case TextEditor::SelectionMode::Word:
- {
- mState.mSelectionStart = FindWordStart(mState.mSelectionStart);
- if (!IsOnWordBoundary(mState.mSelectionEnd))
- mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd));
- break;
- }
- case TextEditor::SelectionMode::Line:
- {
- const auto lineNo = mState.mSelectionEnd.mLine;
- const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0;
- mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0);
- mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo));
- break;
- }
- default:
- break;
- }
-
- if (mState.mSelectionStart != oldSelStart ||
- mState.mSelectionEnd != oldSelEnd)
- mCursorPositionChanged = true;
-}
-
-void TextEditor::SetTabSize(int aValue)
-{
- mTabSize = std::max(0, std::min(32, aValue));
-}
-
-void TextEditor::InsertText(const std::string & aValue)
-{
- InsertText(aValue.c_str());
-}
-
-void TextEditor::InsertText(const char * aValue)
-{
- if (aValue == nullptr)
- return;
-
- auto pos = GetActualCursorCoordinates();
- auto start = std::min(pos, mState.mSelectionStart);
- int totalLines = pos.mLine - start.mLine;
-
- totalLines += InsertTextAt(pos, aValue);
-
- SetSelection(pos, pos);
- SetCursorPosition(pos);
- Colorize(start.mLine - 1, totalLines + 2);
-}
-
-void TextEditor::DeleteSelection()
-{
- assert(mState.mSelectionEnd >= mState.mSelectionStart);
-
- if (mState.mSelectionEnd == mState.mSelectionStart)
- return;
-
- DeleteRange(mState.mSelectionStart, mState.mSelectionEnd);
-
- SetSelection(mState.mSelectionStart, mState.mSelectionStart);
- SetCursorPosition(mState.mSelectionStart);
- Colorize(mState.mSelectionStart.mLine, 1);
-}
-
-void TextEditor::MoveUp(int aAmount, bool aSelect)
-{
- auto oldPos = mState.mCursorPosition;
- mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount);
- if (oldPos != mState.mCursorPosition)
- {
- if (aSelect)
- {
- if (oldPos == mInteractiveStart)
- mInteractiveStart = mState.mCursorPosition;
- else if (oldPos == mInteractiveEnd)
- mInteractiveEnd = mState.mCursorPosition;
- else
- {
- mInteractiveStart = mState.mCursorPosition;
- mInteractiveEnd = oldPos;
- }
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd);
-
- EnsureCursorVisible();
- }
-}
-
-void TextEditor::MoveDown(int aAmount, bool aSelect)
-{
- assert(mState.mCursorPosition.mColumn >= 0);
- auto oldPos = mState.mCursorPosition;
- mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount));
-
- if (mState.mCursorPosition != oldPos)
- {
- if (aSelect)
- {
- if (oldPos == mInteractiveEnd)
- mInteractiveEnd = mState.mCursorPosition;
- else if (oldPos == mInteractiveStart)
- mInteractiveStart = mState.mCursorPosition;
- else
- {
- mInteractiveStart = oldPos;
- mInteractiveEnd = mState.mCursorPosition;
- }
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd);
-
- EnsureCursorVisible();
- }
-}
-
-static bool IsUTFSequence(char c)
-{
- return (c & 0xC0) == 0x80;
-}
-
-void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode)
-{
- if (mLines.empty())
- return;
-
- auto oldPos = mState.mCursorPosition;
- mState.mCursorPosition = GetActualCursorCoordinates();
- auto line = mState.mCursorPosition.mLine;
- auto cindex = GetCharacterIndex(mState.mCursorPosition);
-
- while (aAmount-- > 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.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));
- if (aWordMode)
- {
- mState.mCursorPosition = FindWordStart(mState.mCursorPosition);
- cindex = GetCharacterIndex(mState.mCursorPosition);
- }
- }
-
- mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));
-
- assert(mState.mCursorPosition.mColumn >= 0);
- if (aSelect)
- {
- if (oldPos == mInteractiveStart)
- mInteractiveStart = mState.mCursorPosition;
- else if (oldPos == mInteractiveEnd)
- mInteractiveEnd = mState.mCursorPosition;
- else
- {
- mInteractiveStart = mState.mCursorPosition;
- mInteractiveEnd = oldPos;
- }
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
-
- EnsureCursorVisible();
-}
-
-void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode)
-{
- auto oldPos = mState.mCursorPosition;
-
- if (mLines.empty() || oldPos.mLine >= mLines.size())
- return;
-
- auto cindex = GetCharacterIndex(mState.mCursorPosition);
- while (aAmount-- > 0)
- {
- auto lindex = mState.mCursorPosition.mLine;
- auto& line = mLines[lindex];
-
- if (cindex >= line.size())
- {
- if (mState.mCursorPosition.mLine < mLines.size() - 1)
- {
- mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1));
- mState.mCursorPosition.mColumn = 0;
- }
- else
- return;
- }
- else
- {
- cindex += UTF8CharLength(line[cindex].mChar);
- mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex));
- if (aWordMode)
- mState.mCursorPosition = FindNextWord(mState.mCursorPosition);
- }
- }
-
- if (aSelect)
- {
- if (oldPos == mInteractiveEnd)
- mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition);
- else if (oldPos == mInteractiveStart)
- mInteractiveStart = mState.mCursorPosition;
- else
- {
- mInteractiveStart = oldPos;
- mInteractiveEnd = mState.mCursorPosition;
- }
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
-
- EnsureCursorVisible();
-}
-
-void TextEditor::MoveTop(bool aSelect)
-{
- auto oldPos = mState.mCursorPosition;
- SetCursorPosition(Coordinates(0, 0));
-
- if (mState.mCursorPosition != oldPos)
- {
- if (aSelect)
- {
- mInteractiveEnd = oldPos;
- mInteractiveStart = mState.mCursorPosition;
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd);
- }
-}
-
-void TextEditor::TextEditor::MoveBottom(bool aSelect)
-{
- auto oldPos = GetCursorPosition();
- auto newPos = Coordinates((int)mLines.size() - 1, 0);
- SetCursorPosition(newPos);
- if (aSelect)
- {
- mInteractiveStart = oldPos;
- mInteractiveEnd = newPos;
- }
- else
- mInteractiveStart = mInteractiveEnd = newPos;
- SetSelection(mInteractiveStart, mInteractiveEnd);
-}
-
-void TextEditor::MoveHome(bool aSelect)
-{
- auto oldPos = mState.mCursorPosition;
- SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0));
-
- if (mState.mCursorPosition != oldPos)
- {
- if (aSelect)
- {
- if (oldPos == mInteractiveStart)
- mInteractiveStart = mState.mCursorPosition;
- else if (oldPos == mInteractiveEnd)
- mInteractiveEnd = mState.mCursorPosition;
- else
- {
- mInteractiveStart = mState.mCursorPosition;
- mInteractiveEnd = oldPos;
- }
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd);
- }
-}
-
-void TextEditor::MoveEnd(bool aSelect)
-{
- auto oldPos = mState.mCursorPosition;
- SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine)));
-
- if (mState.mCursorPosition != oldPos)
- {
- if (aSelect)
- {
- if (oldPos == mInteractiveEnd)
- mInteractiveEnd = mState.mCursorPosition;
- else if (oldPos == mInteractiveStart)
- mInteractiveStart = mState.mCursorPosition;
- else
- {
- mInteractiveStart = oldPos;
- mInteractiveEnd = mState.mCursorPosition;
- }
- }
- else
- mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
- SetSelection(mInteractiveStart, mInteractiveEnd);
- }
-}
-
-void TextEditor::Delete()
-{
- assert(!mReadOnly);
-
- if (mLines.empty())
- return;
-
- UndoRecord u;
- u.mBefore = mState;
-
- if (HasSelection())
- {
- u.mRemoved = GetSelectedText();
- u.mRemovedStart = mState.mSelectionStart;
- u.mRemovedEnd = mState.mSelectionEnd;
-
- DeleteSelection();
- }
- else
- {
- auto pos = GetActualCursorCoordinates();
- SetCursorPosition(pos);
- auto& line = mLines[pos.mLine];
-
- if (pos.mColumn == GetLineMaxColumn(pos.mLine))
- {
- if (pos.mLine == (int)mLines.size() - 1)
- return;
-
- u.mRemoved = '\n';
- u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
- Advance(u.mRemovedEnd);
-
- auto& nextLine = mLines[pos.mLine + 1];
- line.insert(line.end(), nextLine.begin(), nextLine.end());
- RemoveLine(pos.mLine + 1);
- }
- else
- {
- auto cindex = GetCharacterIndex(pos);
- u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
- u.mRemovedEnd.mColumn++;
- u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd);
-
- auto d = UTF8CharLength(line[cindex].mChar);
- while (d-- > 0 && cindex < (int)line.size())
- line.erase(line.begin() + cindex);
- }
-
- mTextChanged = true;
-
- Colorize(pos.mLine, 1);
- }
-
- u.mAfter = mState;
- AddUndo(u);
-}
-
-void TextEditor::Backspace()
-{
- assert(!mReadOnly);
-
- if (mLines.empty())
- return;
-
- UndoRecord u;
- u.mBefore = mState;
-
- if (HasSelection())
- {
- u.mRemoved = GetSelectedText();
- u.mRemovedStart = mState.mSelectionStart;
- u.mRemovedEnd = mState.mSelectionEnd;
-
- DeleteSelection();
- }
- else
- {
- auto pos = GetActualCursorCoordinates();
- SetCursorPosition(pos);
-
- if (mState.mCursorPosition.mColumn == 0)
- {
- if (mState.mCursorPosition.mLine == 0)
- return;
-
- u.mRemoved = '\n';
- u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1));
- Advance(u.mRemovedEnd);
-
- auto& line = mLines[mState.mCursorPosition.mLine];
- auto& prevLine = mLines[mState.mCursorPosition.mLine - 1];
- auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1);
- prevLine.insert(prevLine.end(), line.begin(), line.end());
-
- ErrorMarkers etmp;
- for (auto& i : mErrorMarkers)
- etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second));
- mErrorMarkers = std::move(etmp);
-
- RemoveLine(mState.mCursorPosition.mLine);
- --mState.mCursorPosition.mLine;
- mState.mCursorPosition.mColumn = prevSize;
- }
- else
- {
- auto& line = mLines[mState.mCursorPosition.mLine];
- auto cindex = GetCharacterIndex(pos) - 1;
- auto cend = cindex + 1;
- while (cindex > 0 && IsUTFSequence(line[cindex].mChar))
- --cindex;
-
- //if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1)
- // --cindex;
-
- u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
- --u.mRemovedStart.mColumn;
- --mState.mCursorPosition.mColumn;
-
- while (cindex < line.size() && cend-- > cindex)
- {
- u.mRemoved += line[cindex].mChar;
- line.erase(line.begin() + cindex);
- }
- }
-
- mTextChanged = true;
-
- EnsureCursorVisible();
- Colorize(mState.mCursorPosition.mLine, 1);
- }
-
- u.mAfter = mState;
- AddUndo(u);
-}
-
-void TextEditor::SelectWordUnderCursor()
-{
- auto c = GetCursorPosition();
- SetSelection(FindWordStart(c), FindWordEnd(c));
-}
-
-void TextEditor::SelectAll()
-{
- SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0));
-}
-
-bool TextEditor::HasSelection() const
-{
- return mState.mSelectionEnd > mState.mSelectionStart;
-}
-
-void TextEditor::Copy()
-{
- if (HasSelection())
- {
- ImGui::SetClipboardText(GetSelectedText().c_str());
- }
- else
- {
- 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());
- }
- }
-}
-
-void TextEditor::Cut()
-{
- if (IsReadOnly())
- {
- Copy();
- }
- else
- {
- if (HasSelection())
- {
- UndoRecord u;
- u.mBefore = mState;
- u.mRemoved = GetSelectedText();
- u.mRemovedStart = mState.mSelectionStart;
- u.mRemovedEnd = mState.mSelectionEnd;
-
- Copy();
- DeleteSelection();
-
- u.mAfter = mState;
- AddUndo(u);
- }
- }
-}
-
-void TextEditor::Paste()
-{
- if (IsReadOnly())
- return;
-
- auto clipText = ImGui::GetClipboardText();
- if (clipText != nullptr && strlen(clipText) > 0)
- {
- UndoRecord u;
- u.mBefore = mState;
-
- if (HasSelection())
- {
- u.mRemoved = GetSelectedText();
- u.mRemovedStart = mState.mSelectionStart;
- u.mRemovedEnd = mState.mSelectionEnd;
- DeleteSelection();
- }
-
- u.mAdded = clipText;
- u.mAddedStart = GetActualCursorCoordinates();
-
- InsertText(clipText);
-
- u.mAddedEnd = GetActualCursorCoordinates();
- u.mAfter = mState;
- AddUndo(u);
- }
-}
-
-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);
-}
-
-const TextEditor::Palette & TextEditor::GetDarkPalette()
-{
- const static Palette p = { {
- 0xff7f7f7f, // Default
- 0xffd69c56, // Keyword
- 0xff00ff00, // Number
- 0xff7070e0, // String
- 0xff70a0e0, // Char literal
- 0xffffffff, // Punctuation
- 0xff408080, // Preprocessor
- 0xffaaaaaa, // Identifier
- 0xff9bc64d, // Known identifier
- 0xffc040a0, // Preproc identifier
- 0xff206020, // Comment (single line)
- 0xff406020, // Comment (multi line)
- 0xff101010, // Background
- 0xffe0e0e0, // Cursor
- 0x80a06020, // Selection
- 0x800020ff, // ErrorMarker
- 0x40f08000, // Breakpoint
- 0xff707000, // Line number
- 0x40000000, // Current line fill
- 0x40808080, // Current line fill (inactive)
- 0x40a0a0a0, // Current line edge
- } };
- return p;
-}
-
-const TextEditor::Palette & TextEditor::GetLightPalette()
-{
- const static Palette p = { {
- 0xff7f7f7f, // None
- 0xffff0c06, // Keyword
- 0xff008000, // Number
- 0xff2020a0, // String
- 0xff304070, // Char literal
- 0xff000000, // Punctuation
- 0xff406060, // Preprocessor
- 0xff404040, // Identifier
- 0xff606010, // Known identifier
- 0xffc040a0, // Preproc identifier
- 0xff205020, // Comment (single line)
- 0xff405020, // Comment (multi line)
- 0xffffffff, // Background
- 0xff000000, // Cursor
- 0x80600000, // Selection
- 0xa00010ff, // ErrorMarker
- 0x80f08000, // Breakpoint
- 0xff505000, // Line number
- 0x40000000, // Current line fill
- 0x40808080, // Current line fill (inactive)
- 0x40000000, // Current line edge
- } };
- return p;
-}
-
-const TextEditor::Palette & TextEditor::GetRetroBluePalette()
-{
- const static Palette p = { {
- 0xff00ffff, // None
- 0xffffff00, // Keyword
- 0xff00ff00, // Number
- 0xff808000, // String
- 0xff808000, // Char literal
- 0xffffffff, // Punctuation
- 0xff008000, // Preprocessor
- 0xff00ffff, // Identifier
- 0xffffffff, // Known identifier
- 0xffff00ff, // Preproc identifier
- 0xff808080, // Comment (single line)
- 0xff404040, // Comment (multi line)
- 0xff800000, // Background
- 0xff0080ff, // Cursor
- 0x80ffff00, // Selection
- 0xa00000ff, // ErrorMarker
- 0x80ff8000, // Breakpoint
- 0xff808000, // Line number
- 0x40000000, // Current line fill
- 0x40808080, // Current line fill (inactive)
- 0x40000000, // Current line edge
- } };
- return p;
-}
-
-
-std::string TextEditor::GetText() const
-{
- return GetText(Coordinates(), Coordinates((int)mLines.size(), 0));
-}
-
-std::vector TextEditor::GetTextLines() const
-{
- std::vector result;
-
- result.reserve(mLines.size());
-
- for (auto & line : mLines)
- {
- std::string text;
-
- text.resize(line.size());
-
- for (size_t i = 0; i < line.size(); ++i)
- text[i] = line[i].mChar;
-
- result.emplace_back(std::move(text));
- }
-
- return result;
-}
-
-std::string TextEditor::GetSelectedText() const
-{
- return GetText(mState.mSelectionStart, mState.mSelectionEnd);
-}
-
-std::string TextEditor::GetCurrentLineText()const
-{
- auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine);
- return GetText(
- Coordinates(mState.mCursorPosition.mLine, 0),
- Coordinates(mState.mCursorPosition.mLine, lineLength));
-}
-
-void TextEditor::ProcessInputs()
-{
+ mUndoBuffer.resize((size_t)(mUndoIndex + 1));
+ mUndoBuffer.back() = aValue;
+ ++mUndoIndex;
}
+// TODO
+// - multiline comments vs single-line: latter is blocking start of a ML
void TextEditor::Colorize(int aFromLine, int aLines)
{
int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines);
@@ -2143,11 +2572,11 @@ void TextEditor::Colorize(int aFromLine, int aLines)
void TextEditor::ColorizeRange(int aFromLine, int aToLine)
{
- if (mLines.empty() || aFromLine >= aToLine)
+ if (mLines.empty() || aFromLine >= aToLine || mLanguageDefinition == nullptr)
return;
std::string buffer;
- std::cmatch results;
+ boost::cmatch results;
std::string id;
int endLine = std::max(0, std::min((int)mLines.size(), aToLine));
@@ -2166,22 +2595,22 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine)
col.mColorIndex = PaletteIndex::Default;
}
- const char * bufferBegin = &buffer.front();
- const char * bufferEnd = bufferBegin + buffer.size();
+ const char* bufferBegin = &buffer.front();
+ const char* bufferEnd = bufferBegin + buffer.size();
auto last = bufferEnd;
for (auto first = bufferBegin; first != last; )
{
- const char * token_begin = nullptr;
- const char * token_end = nullptr;
+ const char* token_begin = nullptr;
+ const char* token_end = nullptr;
PaletteIndex token_color = PaletteIndex::Default;
bool hasTokenizeResult = false;
- if (mLanguageDefinition.mTokenize != nullptr)
+ if (mLanguageDefinition->mTokenize != nullptr)
{
- if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color))
+ if (mLanguageDefinition->mTokenize(first, last, token_begin, token_end, token_color))
hasTokenizeResult = true;
}
@@ -2190,9 +2619,12 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine)
// todo : remove
//printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first);
- for (auto& p : mRegexList)
+ for (const auto& p : mRegexList->mValue)
{
- if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous))
+ bool regexSearchResult = false;
+ try { regexSearchResult = boost::regex_search(first, last, results, p.first, boost::regex_constants::match_continuous); }
+ catch (...) {}
+ if (regexSearchResult)
{
hasTokenizeResult = true;
@@ -2218,21 +2650,21 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine)
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)
+ if (!mLanguageDefinition->mCaseSensitive)
std::transform(id.begin(), id.end(), id.begin(), ::toupper);
if (!line[first - bufferBegin].mPreprocessor)
{
- if (mLanguageDefinition.mKeywords.count(id) != 0)
+ if (mLanguageDefinition->mKeywords.count(id) != 0)
token_color = PaletteIndex::Keyword;
- else if (mLanguageDefinition.mIdentifiers.count(id) != 0)
+ else if (mLanguageDefinition->mIdentifiers.count(id) != 0)
token_color = PaletteIndex::KnownIdentifier;
- else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0)
+ else if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0)
token_color = PaletteIndex::PreprocIdentifier;
}
else
{
- if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0)
+ if (mLanguageDefinition->mPreprocIdentifiers.count(id) != 0)
token_color = PaletteIndex::PreprocIdentifier;
}
}
@@ -2246,9 +2678,20 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine)
}
}
+template
+bool ColorizerEquals(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;
+}
void TextEditor::ColorizeInternal()
{
- if (mLines.empty() || !mColorizerEnabled)
+ if (mLines.empty() || mLanguageDefinition == nullptr)
return;
if (mCheckComments)
@@ -2282,7 +2725,7 @@ void TextEditor::ColorizeInternal()
auto& g = line[currentIndex];
auto c = g.mChar;
- if (c != mLanguageDefinition.mPreprocChar && !isspace(c))
+ if (c != mLanguageDefinition->mPreprocChar && !isspace(c))
firstChar = false;
if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\')
@@ -2314,7 +2757,7 @@ void TextEditor::ColorizeInternal()
}
else
{
- if (firstChar && c == mLanguageDefinition.mPreprocChar)
+ if (firstChar && c == mLanguageDefinition->mPreprocChar)
withinPreproc = true;
if (c == '\"')
@@ -2326,37 +2769,38 @@ void TextEditor::ColorizeInternal()
{
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;
+ auto& startStr = mLanguageDefinition->mCommentStart;
+ auto& singleStartStr = mLanguageDefinition->mSingleLineComment;
- if (singleStartStr.size() > 0 &&
- currentIndex + singleStartStr.size() <= line.size() &&
- equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred))
- {
- withinSingleLineComment = true;
- }
- else if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() &&
- equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred))
+ if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() &&
+ ColorizerEquals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred))
{
commentStartLine = currentLine;
commentStartIndex = currentIndex;
}
+ else if (singleStartStr.size() > 0 &&
+ currentIndex + singleStartStr.size() <= line.size() &&
+ ColorizerEquals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred))
+ {
+ withinSingleLineComment = true;
+ }
- inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
+ inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
line[currentIndex].mMultiLineComment = inComment;
line[currentIndex].mComment = withinSingleLineComment;
- auto& endStr = mLanguageDefinition.mCommentEnd;
+ auto& endStr = mLanguageDefinition->mCommentEnd;
if (currentIndex + 1 >= (int)endStr.size() &&
- equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred))
+ ColorizerEquals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred))
{
commentStartIndex = endIndex;
commentStartLine = endLine;
}
}
}
- line[currentIndex].mPreprocessor = withinPreproc;
+ if (currentIndex < (int)line.size())
+ line[currentIndex].mPreprocessor = withinPreproc;
currentIndex += UTF8CharLength(c);
if (currentIndex >= (int)line.size())
{
@@ -2375,7 +2819,7 @@ void TextEditor::ColorizeInternal()
if (mColorRangeMin < mColorRangeMax)
{
- const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000;
+ const int increment = (mLanguageDefinition->mTokenize == nullptr) ? 10 : 10000;
const int to = std::min(mColorRangeMin + increment, mColorRangeMax);
ColorizeRange(mColorRangeMin, to);
mColorRangeMin = to;
@@ -2389,772 +2833,130 @@ void TextEditor::ColorizeInternal()
}
}
-float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const
+const TextEditor::Palette& TextEditor::GetDarkPalette()
{
- 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 = GetCharacterIndex(aFrom);
- for (size_t it = 0u; it < line.size() && it < colIndex; )
- {
- 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;
- }
- }
-
- return distance;
+ const static Palette p = { {
+ 0xdcdfe4ff, // Default
+ 0xe06c75ff, // Keyword
+ 0xe5c07bff, // Number
+ 0x98c379ff, // String
+ 0xe0a070ff, // Char literal
+ 0x6a7384ff, // Punctuation
+ 0x808040ff, // Preprocessor
+ 0xdcdfe4ff, // Identifier
+ 0x61afefff, // Known identifier
+ 0xc678ddff, // Preproc identifier
+ 0x3696a2ff, // Comment (single line)
+ 0x3696a2ff, // Comment (multi line)
+ 0x282c34ff, // Background
+ 0xe0e0e0ff, // Cursor
+ 0x2060a080, // Selection
+ 0xff200080, // ErrorMarker
+ 0xffffff15, // ControlCharacter
+ 0x0080f040, // Breakpoint
+ 0x7a8394ff, // Line number
+ 0x00000040, // Current line fill
+ 0x80808040, // Current line fill (inactive)
+ 0xa0a0a040, // Current line edge
+ } };
+ return p;
}
-void TextEditor::EnsureCursorVisible()
+const TextEditor::Palette& TextEditor::GetMarianaPalette()
{
- if (!mWithinRender)
- {
- mScrollToCursor = true;
- return;
- }
-
- float scrollX = ImGui::GetScrollX();
- float scrollY = ImGui::GetScrollY();
-
- auto height = ImGui::GetWindowHeight();
- auto width = ImGui::GetWindowWidth();
-
- auto top = 1 + (int)ceil(scrollY / mCharAdvance.y);
- auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y);
-
- auto left = (int)ceil(scrollX / mCharAdvance.x);
- auto right = (int)ceil((scrollX + width) / mCharAdvance.x);
-
- auto pos = GetActualCursorCoordinates();
- 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));
+ 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
+ 0x6e7a8580, // 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;
}
-int TextEditor::GetPageSize() const
+const TextEditor::Palette& TextEditor::GetLightPalette()
{
- auto height = ImGui::GetWindowHeight() - 20.0f;
- return (int)floor(height / mCharAdvance.y);
+ 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;
}
-TextEditor::UndoRecord::UndoRecord(
- const std::string& aAdded,
- const TextEditor::Coordinates aAddedStart,
- const TextEditor::Coordinates aAddedEnd,
- const std::string& aRemoved,
- const TextEditor::Coordinates aRemovedStart,
- const TextEditor::Coordinates aRemovedEnd,
- TextEditor::EditorState& aBefore,
- TextEditor::EditorState& aAfter)
- : mAdded(aAdded)
- , mAddedStart(aAddedStart)
- , mAddedEnd(aAddedEnd)
- , mRemoved(aRemoved)
- , mRemovedStart(aRemovedStart)
- , mRemovedEnd(aRemovedEnd)
- , mBefore(aBefore)
- , mAfter(aAfter)
+const TextEditor::Palette& TextEditor::GetRetroBluePalette()
{
- assert(mAddedStart <= mAddedEnd);
- assert(mRemovedStart <= mRemovedEnd);
+ 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::UndoRecord::Undo(TextEditor * aEditor)
-{
- if (!mAdded.empty())
- {
- aEditor->DeleteRange(mAddedStart, mAddedEnd);
- aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2);
- }
-
- if (!mRemoved.empty())
- {
- auto start = mRemovedStart;
- aEditor->InsertTextAt(start, mRemoved.c_str());
- aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2);
- }
-
- aEditor->mState = mBefore;
- aEditor->EnsureCursorVisible();
-
-}
-
-void TextEditor::UndoRecord::Redo(TextEditor * aEditor)
-{
- if (!mRemoved.empty())
- {
- aEditor->DeleteRange(mRemovedStart, mRemovedEnd);
- aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1);
- }
-
- if (!mAdded.empty())
- {
- auto start = mAddedStart;
- aEditor->InsertTextAt(start, mAdded.c_str());
- aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1);
- }
-
- aEditor->mState = mAfter;
- aEditor->EnsureCursorVisible();
-}
-
-static bool TokenizeCStyleString(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end)
-{
- const char * p = in_begin;
-
- if (*p == '"')
- {
- p++;
-
- while (p < in_end)
- {
- // 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++;
-
- 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;
-
- if (*p == '\'')
- {
- p++;
-
- // handle escape characters
- if (p < in_end && *p == '\\')
- p++;
-
- if (p < in_end)
- p++;
-
- // handle end of character literal
- if (p < in_end && *p == '\'')
- {
- out_begin = in_begin;
- out_end = p + 1;
- return true;
- }
- }
-
- return false;
-}
-
-static bool TokenizeCStyleIdentifier(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end)
-{
- const char * p = in_begin;
-
- if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')
- {
- p++;
-
- while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *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;
-
- const bool startsWithNumber = *p >= '0' && *p <= '9';
-
- if (*p != '+' && *p != '-' && !startsWithNumber)
- return false;
-
- p++;
-
- bool hasNumber = startsWithNumber;
-
- while (p < in_end && (*p >= '0' && *p <= '9'))
- {
- hasNumber = true;
-
- p++;
- }
-
- if (hasNumber == false)
- return false;
-
- bool isFloat = false;
- bool isHex = false;
- bool isBinary = false;
-
- if (p < in_end)
- {
- if (*p == '.')
- {
- isFloat = 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;
-
- p++;
-
- 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
-
- isBinary = true;
-
- p++;
-
- 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;
-
- p++;
-
- if (p < in_end && (*p == '+' || *p == '-'))
- p++;
-
- bool hasDigits = false;
-
- while (p < in_end && (*p >= '0' && *p <= '9'))
- {
- hasDigits = true;
-
- p++;
- }
-
- 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++;
- }
-
- out_begin = in_begin;
- out_end = p;
- return true;
-}
-
-static bool TokenizeCStylePunctuation(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end)
-{
- (void)in_end;
-
- 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;
- }
-
- return false;
-}
-
-const TextEditor::LanguageDefinition& 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)
- {
- 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 (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;
-}
-
-const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL()
-{
- static bool inited = false;
- static LanguageDefinition langDef;
- if (!inited)
- {
- 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.mCommentStart = "/*";
- langDef.mCommentEnd = "*/";
- langDef.mSingleLineComment = "//";
-
- langDef.mCaseSensitive = true;
- langDef.mAutoIndentation = true;
-
- langDef.mName = "HLSL";
-
- inited = true;
- }
- return langDef;
-}
-
-const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL()
-{
- 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));
- }
-
- 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.mCaseSensitive = true;
- langDef.mAutoIndentation = true;
-
- langDef.mName = "GLSL";
-
- 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));
- }
-
- 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;
-}
-
-const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL()
-{
- static bool inited = false;
- static LanguageDefinition langDef;
- if (!inited)
- {
- 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.mCommentStart = "/*";
- langDef.mCommentEnd = "*/";
- langDef.mSingleLineComment = "//";
-
- langDef.mCaseSensitive = false;
- langDef.mAutoIndentation = false;
-
- langDef.mName = "SQL";
-
- inited = true;
- }
- return langDef;
-}
-
-const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript()
-{
- static bool inited = false;
- static LanguageDefinition langDef;
- if (!inited)
- {
- 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.mCommentStart = "/*";
- langDef.mCommentEnd = "*/";
- langDef.mSingleLineComment = "//";
-
- langDef.mCaseSensitive = true;
- langDef.mAutoIndentation = true;
-
- langDef.mName = "AngelScript";
-
- inited = true;
- }
- return langDef;
-}
-
-const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua()
-{
- static bool inited = false;
- static LanguageDefinition langDef;
- if (!inited)
- {
- static const char* const keywords[] = {
- "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "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.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[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
- 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("[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.mCaseSensitive = true;
- langDef.mAutoIndentation = false;
-
- langDef.mName = "Lua";
-
- inited = true;
- }
- return langDef;
-}
\ No newline at end of file
+const std::unordered_map TextEditor::OPEN_TO_CLOSE_CHAR = {
+ {'{', '}'},
+ {'(' , ')'},
+ {'[' , ']'}
+};
+const std::unordered_map TextEditor::CLOSE_TO_OPEN_CHAR = {
+ {'}', '{'},
+ {')' , '('},
+ {']' , '['}
+};
+
+TextEditor::PaletteId TextEditor::defaultPalette = TextEditor::PaletteId::Dark;
\ No newline at end of file
diff --git a/Amalgam/include/ImGui/TextEditor.h b/Amalgam/include/ImGui/TextEditor.h
index 692edb30..a5fbc646 100644
--- a/Amalgam/include/ImGui/TextEditor.h
+++ b/Amalgam/include/ImGui/TextEditor.h
@@ -1,5 +1,8 @@
#pragma once
+#include
+#include
+#include
#include
#include
#include
@@ -7,12 +10,24 @@
#include
#include
#include