diff --git a/Amalgam.sln b/Amalgam.sln
new file mode 100644
index 0000000..241a037
--- /dev/null
+++ b/Amalgam.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34525.116
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Amalgam", "Amalgam\Amalgam.vcxproj", "{2550C133-72A8-4AF7-B22A-A8012BD9F376}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Debug|x64.ActiveCfg = Debug|x64
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Debug|x64.Build.0 = Debug|x64
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Debug|x86.ActiveCfg = Debug|Win32
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Debug|x86.Build.0 = Debug|Win32
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Release|x64.ActiveCfg = Release|x64
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Release|x64.Build.0 = Release|x64
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Release|x86.ActiveCfg = Release|Win32
+ {2550C133-72A8-4AF7-B22A-A8012BD9F376}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5E35C5DE-7694-4637-AC86-EF5BEB5B0763}
+ EndGlobalSection
+EndGlobal
diff --git a/Amalgam/Amalgam.vcxproj b/Amalgam/Amalgam.vcxproj
new file mode 100644
index 0000000..6029559
--- /dev/null
+++ b/Amalgam/Amalgam.vcxproj
@@ -0,0 +1,641 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {2550c133-72a8-4af7-b22a-a8012bd9f376}
+ Amalgam
+ 10.0
+
+
+
+ DynamicLibrary
+ true
+ v143
+ MultiByte
+ true
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ MultiByte
+
+
+ DynamicLibrary
+ true
+ v143
+ MultiByte
+ true
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(ProjectName)$(Platform)$(Configuration)
+ $(SolutionDir)build\$(Platform)\$(Configuration)\
+ $(ProjectDir)include\;$(IncludePath)
+ $(SolutionDir)output\$(Platform)\$(Configuration)\
+
+
+ $(ProjectDir)include\;$(IncludePath)
+ $(SolutionDir)build\$(Platform)\$(Configuration)\
+ $(ProjectName)$(Platform)$(Configuration)
+ $(SolutionDir)output\$(Platform)\$(Configuration)\
+
+
+ $(SolutionDir)output\$(Platform)\$(Configuration)\
+ $(SolutionDir)build\$(Platform)\$(Configuration)\
+ $(ProjectName)$(Platform)$(Configuration)
+ $(ProjectDir)include\;$(IncludePath)
+ $(VC_ExecutablePath_x64);$(CommonExecutablePath)
+ $(VC_ReferencesPath_x64);
+ $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
+ $(CommonExcludePath);$(VC_ExecutablePath_x64);$(VC_LibraryPath_x64)
+
+
+ $(SolutionDir)output\$(Platform)\$(Configuration)\
+ $(SolutionDir)build\$(Platform)\$(Configuration)\
+ $(ProjectName)$(Platform)$(Configuration)
+ $(ProjectDir)include\;$(IncludePath)
+ $(VC_ExecutablePath_x64);$(CommonExecutablePath)
+ $(VC_ReferencesPath_x64);
+ $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
+ $(CommonExcludePath);$(VC_ExecutablePath_x64);$(VC_LibraryPath_x64)
+ false
+
+
+
+ Level3
+ false
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ Default
+ true
+ MaxSpeed
+ Speed
+ true
+ true
+ true
+ AdvancedVectorExtensions2
+
+ true
+ ProgramDatabase
+ MultiThreadedDebugDLL
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ false
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ true
+ Speed
+ true
+ AdvancedVectorExtensions2
+
+ false
+ ProgramDatabase
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ false
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ true
+ Speed
+
+
+ true
+ MaxSpeed
+ true
+ Default
+ true
+ AdvancedVectorExtensions2
+ true
+ ProgramDatabase
+ true
+ MultiThreadedDebugDLL
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ false
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ true
+ Speed
+
+
+ true
+ AdvancedVectorExtensions2
+ ProgramDatabase
+ false
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
\ No newline at end of file
diff --git a/Amalgam/Amalgam.vcxproj.filters b/Amalgam/Amalgam.vcxproj.filters
new file mode 100644
index 0000000..9ce4cad
--- /dev/null
+++ b/Amalgam/Amalgam.vcxproj.filters
@@ -0,0 +1,424 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Amalgam/cpp.hint b/Amalgam/cpp.hint
new file mode 100644
index 0000000..ef70ae1
--- /dev/null
+++ b/Amalgam/cpp.hint
@@ -0,0 +1,59 @@
+#define MAKE_HOOK(name, address, type, callconvo, ...) namespace Hooks \
+{\
+ namespace name\
+ {\
+ void Init(); \
+ inline CHook Hook(Init); \
+ using FN = type(callconvo *)(__VA_ARGS__); \
+ type callconvo Func(__VA_ARGS__); \
+ }\
+} \
+void Hooks::name::Init() { Hook.Create(reinterpret_cast(address), Func); } \
+type callconvo Hooks::name::Func(__VA_ARGS__)
+#define CALL_ORIGINAL Hook.Original()
+#define MAKE_INTERFACE_VERSION(type, name, dll, version) namespace I { inline type *name = nullptr; } \
+namespace MAKE_INTERFACE_SCOPE \
+{\
+ inline InterfaceInit_t name##InterfaceInit_t(reinterpret_cast(&I::name), dll, version, -1, 0); \
+}
+#define MAKE_INTERFACE_SIGNATURE(type, name, dll, version, offset, deref) namespace I { inline type *name = nullptr; } \
+namespace MAKE_INTERFACE_SCOPE \
+{\
+ inline InterfaceInit_t name##InterfaceInit_t(reinterpret_cast(&I::name), dll, version, offset, deref); \
+}
+#define MAKE_INTERFACE_NULL(type, name) namespace I { inline type *name = nullptr; }
+#define MAKE_INTERFACE_VERSION_SEARCH(type, name, dll, version) namespace I { inline type *name = nullptr; } \
+namespace MAKE_INTERFACE_SCOPE \
+{\
+ inline InterfaceInit_t name##InterfaceInit_t(reinterpret_cast(&I::name), dll, version, -1, 0, true); \
+}
+#define MAKE_INTERFACE_SIGNATURE_SEARCH(type, name, dll, signature, offset, deref) namespace I { inline type *name = nullptr; } \
+namespace MAKE_INTERFACE_SCOPE \
+{\
+ inline InterfaceInit_t name##InterfaceInit_t(reinterpret_cast(&I::name), dll, signature, offset, deref, true); \
+}
+#define NETVAR(_name, type, table, name) type& _name() \
+{ \
+ static int nOffset = U::NetVars.GetNetVar(table, name); \
+ return *reinterpret_cast(std::uintptr_t(this) + nOffset); \
+}
+#define NETVAR_OFF(_name, type, table, name, offset) type& _name() \
+{ \
+ static int nOffset = U::NetVars.GetNetVar(table, name) + offset; \
+ return *reinterpret_cast(std::uintptr_t(this) + nOffset); \
+}
+#define OFFSET(name, type, offset) type& name() \
+{ \
+ return *reinterpret_cast(std::uintptr_t(this) + offset); \
+}
+#define VIRTUAL(name, type, fn, base, index) type name() \
+{ \
+ return reinterpret_cast(U::Memory.GetVFunc(base, index))(base); \
+}
+#define CONDGET(name, conditions, cond) bool name() \
+{ \
+ return (conditions & cond); \
+}
+#define MAKE_SIGNATURE(name, dll, sig, offset) namespace S { inline CSignature name(dll, sig, offset, #name); }
+#define ADD_FEATURE_CUSTOM(type, name, scope) namespace scope { inline type name; }
+#define ADD_FEATURE(type, name) ADD_FEATURE_CUSTOM(type, name, F)
\ No newline at end of file
diff --git a/Amalgam/include/ImGui/TextEditor.cpp b/Amalgam/include/ImGui/TextEditor.cpp
new file mode 100644
index 0000000..18128cf
--- /dev/null
+++ b/Amalgam/include/ImGui/TextEditor.cpp
@@ -0,0 +1,3597 @@
+#include
+#include
+#include
+#include
+#include
+
+#include "TextEditor.h"
+
+#define IMGUI_DEFINE_MATH_OPERATORS
+#include "imgui.h" // for imGui::GetCurrentWindow()
+
+#pragma warning( push )
+#pragma warning( disable : 4018 )
+
+// TODO
+// - multiline comments vs single-line: latter is blocking start of a ML
+
+template
+bool equals(InputIt1 first1, InputIt1 last1,
+ InputIt2 first2, InputIt2 last2, BinaryPredicate p)
+{
+ for (; first1 != last1 && first2 != last2; ++first1, ++first2)
+ {
+ if (!p(*first1, *first2))
+ {
+ return false;
+ }
+ }
+ return first1 == last1 && first2 == last2;
+}
+
+TextEditor::TextEditor()
+ : mLineSpacing(1.0f)
+ , mUndoIndex(0)
+ , mTabSize(4)
+ , mOverwrite(false)
+ , mReadOnly(false)
+ , mWithinRender(false)
+ , mScrollToCursor(false)
+ , mScrollToTop(false)
+ , mTextChanged(false)
+ , mColorizerEnabled(true)
+ , mTextStart(20.0f)
+ , mLeftMargin(10)
+ , mCursorPositionChanged(false)
+ , mColorRangeMin(0)
+ , mColorRangeMax(0)
+ , mSelectionMode(SelectionMode::Normal)
+ , mHandleKeyboardInputs(true)
+ , mHandleMouseInputs(true)
+ , mIgnoreImGuiChild(false)
+ , mShowWhitespaces(true)
+ , mCheckComments(true)
+ , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count())
+ , mLastClick(-1.0f)
+{
+ SetPalette(GetDarkPalette());
+ SetLanguageDefinition(LanguageDefinition::HLSL());
+ mLines.push_back(Line());
+}
+
+TextEditor::~TextEditor() = default;
+
+void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef)
+{
+ mLanguageDefinition = aLanguageDef;
+ mRegexList.clear();
+
+ for (auto& r : mLanguageDefinition.mTokenRegexStrings)
+ {
+ mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second));
+ }
+
+ Colorize();
+}
+
+void TextEditor::SetPalette(const Palette & aValue)
+{
+ mPaletteBase = aValue;
+}
+
+std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const
+{
+ std::string result;
+
+ auto lstart = aStart.mLine;
+ auto lend = aEnd.mLine;
+ auto istart = GetCharacterIndex(aStart);
+ auto iend = GetCharacterIndex(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 >= static_cast(mLines.size()))
+ {
+ break;
+ }
+
+ auto& line = mLines[lstart];
+ if (istart < static_cast(line.size()))
+ {
+ result += line[istart].mChar;
+ istart++;
+ }
+ else
+ {
+ istart = 0;
+ ++lstart;
+ result += '\n';
+ }
+ }
+
+ return result;
+}
+
+TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const
+{
+ return SanitizeCoordinates(mState.mCursorPosition);
+}
+
+TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const
+{
+ auto line = aValue.mLine;
+ auto column = aValue.mColumn;
+ if (line >= static_cast(mLines.size()))
+ {
+ if (mLines.empty())
+ {
+ line = 0;
+ column = 0;
+ }
+ else
+ {
+ line = static_cast(mLines.size()) - 1;
+ column = GetLineMaxColumn(line);
+ }
+ return Coordinates(line, column);
+ }
+ column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line));
+ return Coordinates(line, column);
+}
+
+// https://en.wikipedia.org/wiki/UTF-8
+// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
+static int UTF8CharLength(TextEditor::Char c)
+{
+ if ((c & 0xFE) == 0xFC)
+ {
+ return 6;
+ }
+ if ((c & 0xFC) == 0xF8)
+ {
+ return 5;
+ }
+ if ((c & 0xF8) == 0xF0)
+ {
+ return 4;
+ }
+ if ((c & 0xF0) == 0xE0)
+ {
+ return 3;
+ }
+ if ((c & 0xE0) == 0xC0)
+ {
+ return 2;
+ }
+ return 1;
+}
+
+// "Borrowed" from ImGui source
+static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
+{
+ if (c < 0x80)
+ {
+ buf[0] = static_cast(c);
+ return 1;
+ }
+ if (c < 0x800)
+ {
+ if (buf_size < 2)
+ {
+ return 0;
+ }
+ buf[0] = static_cast(0xc0 + (c >> 6));
+ buf[1] = static_cast(0x80 + (c & 0x3f));
+ return 2;
+ }
+ if (c >= 0xdc00 && c < 0xe000)
+ {
+ return 0;
+ }
+ if (c >= 0xd800 && c < 0xdc00)
+ {
+ if (buf_size < 4)
+ {
+ return 0;
+ }
+ buf[0] = static_cast(0xf0 + (c >> 18));
+ buf[1] = static_cast(0x80 + ((c >> 12) & 0x3f));
+ buf[2] = static_cast(0x80 + ((c >> 6) & 0x3f));
+ buf[3] = static_cast(0x80 + ((c) & 0x3f));
+ return 4;
+ }
+ //else if (c < 0x10000)
+ {
+ if (buf_size < 3)
+ {
+ return 0;
+ }
+ buf[0] = static_cast(0xe0 + (c >> 12));
+ buf[1] = static_cast(0x80 + ((c >> 6) & 0x3f));
+ buf[2] = static_cast(0x80 + ((c) & 0x3f));
+ return 3;
+ }
+}
+
+void TextEditor::Advance(Coordinates & aCoordinates) const
+{
+ if (aCoordinates.mLine < static_cast(mLines.size()))
+ {
+ auto& line = mLines[aCoordinates.mLine];
+ auto cindex = GetCharacterIndex(aCoordinates);
+
+ if (cindex + 1 < static_cast(line.size()))
+ {
+ auto delta = UTF8CharLength(line[cindex].mChar);
+ cindex = std::min(cindex + delta, static_cast(line.size()) - 1);
+ }
+ else
+ {
+ ++aCoordinates.mLine;
+ cindex = 0;
+ }
+ aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex);
+ }
+}
+
+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)
+{
+ assert(!mReadOnly);
+
+ int cindex = GetCharacterIndex(aWhere);
+ int totalLines = 0;
+ while (*aValue != '\0')
+ {
+ assert(!mLines.empty());
+
+ if (*aValue == '\r')
+ {
+ // skip
+ ++aValue;
+ }
+ else if (*aValue == '\n')
+ {
+ if (cindex < static_cast(mLines[aWhere.mLine].size()))
+ {
+ 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());
+ }
+ else
+ {
+ InsertLine(aWhere.mLine + 1);
+ }
+ ++aWhere.mLine;
+ aWhere.mColumn = 0;
+ cindex = 0;
+ ++totalLines;
+ ++aValue;
+ }
+ else
+ {
+ 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;
+ }
+
+ mTextChanged = true;
+ }
+
+ return totalLines;
+}
+
+void TextEditor::AddUndo(UndoRecord & aValue)
+{
+ assert(!mReadOnly);
+ //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n",
+ // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn,
+ // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn,
+ // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn,
+ // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn
+ // );
+
+ mUndoBuffer.resize(static_cast(mUndoIndex + 1));
+ mUndoBuffer.back() = aValue;
+ ++mUndoIndex;
+}
+
+TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2 & aPosition) const
+{
+ ImVec2 origin = ImGui::GetCursorScreenPos();
+ ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y);
+
+ int lineNo = std::max(0, static_cast(floor(local.y / mCharAdvance.y)));
+
+ int columnCoord = 0;
+
+ if (lineNo >= 0 && lineNo < static_cast(mLines.size()))
+ {
+ auto& line = mLines.at(lineNo);
+
+ int columnIndex = 0;
+ float columnX = 0.0f;
+
+ while (static_cast(columnIndex) < line.size())
+ {
+ float columnWidth = 0.0f;
+
+ if (line[columnIndex].mChar == '\t')
+ {
+ float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x;
+ float oldX = columnX;
+ float newColumnX = (1.0f + std::floor((1.0f + columnX) / (static_cast(mTabSize) * spaceSize))) * (static_cast(mTabSize) * spaceSize);
+ columnWidth = newColumnX - oldX;
+ if (mTextStart + columnX + columnWidth * 0.5f > local.x)
+ {
+ break;
+ }
+ columnX = newColumnX;
+ columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize;
+ columnIndex++;
+ }
+ 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++;
+ }
+ }
+ }
+
+ return SanitizeCoordinates(Coordinates(lineNo, columnCoord));
+}
+
+TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const
+{
+ Coordinates at = aFrom;
+ if (at.mLine >= static_cast(mLines.size()))
+ {
+ return at;
+ }
+
+ auto& line = mLines[at.mLine];
+ auto cindex = GetCharacterIndex(at);
+
+ if (cindex >= static_cast(line.size()))
+ {
+ return at;
+ }
+
+ while (cindex > 0 && isspace(line[cindex].mChar))
+ {
+ --cindex;
+ }
+
+ auto cstart = static_cast(line[cindex].mColorIndex);
+ while (cindex > 0)
+ {
+ auto c = line[cindex].mChar;
+ if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx
+ {
+ if (c <= 32 && isspace(c))
+ {
+ cindex++;
+ break;
+ }
+ if (cstart != static_cast(line[static_cast(cindex - 1)].mColorIndex))
+ {
+ break;
+ }
+ }
+ --cindex;
+ }
+ return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
+}
+
+TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const
+{
+ Coordinates at = aFrom;
+ if (at.mLine >= static_cast(mLines.size()))
+ {
+ return at;
+ }
+
+ auto& line = mLines[at.mLine];
+ auto cindex = GetCharacterIndex(at);
+
+ if (cindex >= static_cast(line.size()))
+ {
+ return at;
+ }
+
+ bool prevspace = static_cast(isspace(line[cindex].mChar));
+ auto cstart = static_cast(line[cindex].mColorIndex);
+ while (cindex < static_cast(line.size()))
+ {
+ auto c = line[cindex].mChar;
+ auto d = UTF8CharLength(c);
+ if (cstart != static_cast(line[cindex].mColorIndex))
+ {
+ break;
+ }
+
+ if (prevspace != !!isspace(c))
+ {
+ if (isspace(c))
+ {
+ while (cindex < static_cast(line.size()) && isspace(line[cindex].mChar))
+ {
+ ++cindex;
+ }
+ }
+ break;
+ }
+ cindex += d;
+ }
+ return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex));
+}
+
+TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates & aFrom) const
+{
+ Coordinates at = aFrom;
+ if (at.mLine >= static_cast(mLines.size()))
+ {
+ return at;
+ }
+
+ // skip to the next non-word character
+ auto cindex = GetCharacterIndex(aFrom);
+ bool isword = false;
+ bool skip = false;
+ if (cindex < static_cast(mLines[at.mLine].size()))
+ {
+ auto& line = mLines[at.mLine];
+ isword = isalnum(line[cindex].mChar);
+ skip = isword;
+ }
+
+ while (!isword || skip)
+ {
+ if (at.mLine >= mLines.size())
+ {
+ auto l = std::max(0, static_cast(mLines.size()) - 1);
+ return Coordinates(l, GetLineMaxColumn(l));
+ }
+
+ auto& line = mLines[at.mLine];
+ if (cindex < static_cast(line.size()))
+ {
+ isword = isalnum(line[cindex].mChar);
+
+ if (isword && !skip)
+ {
+ return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
+ }
+
+ if (!isword)
+ {
+ skip = false;
+ }
+
+ cindex++;
+ }
+ else
+ {
+ cindex = 0;
+ ++at.mLine;
+ skip = false;
+ isword = false;
+ }
+ }
+
+ return at;
+}
+
+int TextEditor::GetCharacterIndex(const Coordinates & aCoordinates) const
+{
+ if (aCoordinates.mLine >= mLines.size())
+ {
+ return -1;
+ }
+ auto& line = mLines[aCoordinates.mLine];
+ int c = 0;
+ int i = 0;
+ for (; i < line.size() && c < aCoordinates.mColumn;)
+ {
+ if (line[i].mChar == '\t')
+ {
+ c = (c / mTabSize) * mTabSize + mTabSize;
+ }
+ else
+ {
+ ++c;
+ }
+ i += UTF8CharLength(line[i].mChar);
+ }
+ return i;
+}
+
+int TextEditor::GetCharacterColumn(int aLine, int aIndex) const
+{
+ if (aLine >= mLines.size())
+ {
+ return 0;
+ }
+ auto& line = mLines[aLine];
+ int col = 0;
+ int i = 0;
+ while (i < aIndex && i < static_cast(line.size()))
+ {
+ auto c = line[i].mChar;
+ i += UTF8CharLength(c);
+ if (c == '\t')
+ {
+ col = (col / mTabSize) * mTabSize + mTabSize;
+ }
+ else
+ {
+ col++;
+ }
+ }
+ return col;
+}
+
+int TextEditor::GetLineCharacterCount(int aLine) const
+{
+ if (aLine >= mLines.size())
+ {
+ return 0;
+ }
+ auto& line = mLines[aLine];
+ int c = 0;
+ for (unsigned i = 0; i < line.size(); c++)
+ {
+ i += UTF8CharLength(line[i].mChar);
+ }
+ return c;
+}
+
+int TextEditor::GetLineMaxColumn(int aLine) const
+{
+ if (aLine >= mLines.size())
+ {
+ return 0;
+ }
+ auto& line = mLines[aLine];
+ int col = 0;
+ for (unsigned i = 0; i < line.size();)
+ {
+ auto c = line[i].mChar;
+ if (c == '\t')
+ {
+ col = (col / mTabSize) * mTabSize + mTabSize;
+ }
+ else
+ {
+ col++;
+ }
+ i += UTF8CharLength(c);
+ }
+ return col;
+}
+
+bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const
+{
+ if (aAt.mLine >= static_cast(mLines.size()) || aAt.mColumn == 0)
+ {
+ return true;
+ }
+
+ auto& line = mLines[aAt.mLine];
+ auto cindex = GetCharacterIndex(aAt);
+ if (cindex >= static_cast(line.size()))
+ {
+ return true;
+ }
+
+ if (mColorizerEnabled)
+ {
+ return line[cindex].mColorIndex != line[static_cast(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() > static_cast(aEnd - aStart));
+
+ ErrorMarkers etmp;
+ for (auto& i : mErrorMarkers)
+ {
+ ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second);
+ if (e.first >= aStart && e.first <= aEnd)
+ {
+ continue;
+ }
+ etmp.insert(e);
+ }
+ mErrorMarkers = std::move(etmp);
+
+ Breakpoints btmp;
+ for (auto i : mBreakpoints)
+ {
+ if (i >= aStart && i <= aEnd)
+ {
+ continue;
+ }
+ btmp.insert(i >= aStart ? i - 1 : i);
+ }
+ 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;
+}
+
+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);
+
+ return result;
+}
+
+std::string TextEditor::GetWordUnderCursor() const
+{
+ auto c = GetCursorPosition();
+ return GetWordAt(c);
+}
+
+std::string TextEditor::GetWordAt(const Coordinates & aCoords) const
+{
+ auto start = FindWordStart(aCoords);
+ auto end = FindWordEnd(aCoords);
+
+ std::string r;
+
+ auto istart = GetCharacterIndex(start);
+ auto iend = GetCharacterIndex(end);
+
+ for (auto it = istart; it < iend; ++it)
+ {
+ r.push_back(mLines[aCoords.mLine][it].mChar);
+ }
+
+ return r;
+}
+
+ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const
+{
+ if (!mColorizerEnabled)
+ {
+ return mPalette[static_cast(PaletteIndex::Default)];
+ }
+ if (aGlyph.mComment)
+ {
+ return mPalette[static_cast(PaletteIndex::Comment)];
+ }
+ if (aGlyph.mMultiLineComment)
+ {
+ return mPalette[static_cast(PaletteIndex::MultiLineComment)];
+ }
+ const auto color = mPalette[static_cast(aGlyph.mColorIndex)];
+ if (aGlyph.mPreprocessor)
+ {
+ const auto ppcolor = mPalette[static_cast(PaletteIndex::Preprocessor)];
+ const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2;
+ const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2;
+ const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2;
+ const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2;
+ return static_cast(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24));
+ }
+ return color;
+}
+
+void TextEditor::HandleKeyboardInputs()
+{
+ 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::IsWindowHovered())
+ {
+ ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
+ }
+ //ImGui::CaptureKeyboardFromApp(true);
+
+ io.WantCaptureKeyboard = true;
+ io.WantTextInput = true;
+
+ if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z)))
+ {
+ Undo();
+ }
+ else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
+ {
+ Undo();
+ }
+ else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y)))
+ {
+ Redo();
+ }
+ else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
+ {
+ MoveUp(1, shift);
+ }
+ else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(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)))
+ {
+ MoveTop(shift);
+ }
+ else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
+ {
+ MoveBottom(shift);
+ }
+ else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
+ {
+ MoveHome(shift);
+ }
+ else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(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)))
+ {
+ Copy();
+ }
+ else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
+ {
+ Copy();
+ }
+ else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
+ {
+ Paste();
+ }
+ else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V)))
+ {
+ Paste();
+ }
+ else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X)))
+ {
+ Cut();
+ }
+ else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
+ {
+ Cut();
+ }
+ else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A)))
+ {
+ SelectAll();
+ }
+ else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)))
+ {
+ EnterCharacter('\n', false);
+ }
+ else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab)))
+ {
+ EnterCharacter('\t', shift);
+ }
+
+ if (!IsReadOnly() && !io.InputQueueCharacters.empty())
+ {
+ for (int i = 0; i < io.InputQueueCharacters.Size; i++)
+ {
+ auto c = io.InputQueueCharacters[i];
+ if (c != 0 && (c == '\n' || c >= 32))
+ {
+ EnterCharacter(c, shift);
+ }
+ }
+ io.InputQueueCharacters.resize(0);
+ }
+ }
+}
+
+void TextEditor::HandleMouseInputs()
+{
+ ImGuiIO& io = ImGui::GetIO();
+ auto shift = io.KeyShift;
+ auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
+ auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
+
+ if (ImGui::IsWindowHovered())
+ {
+ 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);
+
+ /*
+ Left mouse button triple click
+ */
+
+ if (tripleClick)
+ {
+ if (!ctrl)
+ {
+ mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
+ mSelectionMode = SelectionMode::Line;
+ SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
+ }
+
+ mLastClick = -1.0f;
+ }
+
+ /*
+ Left mouse button double click
+ */
+
+ 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);
+ }
+
+ mLastClick = static_cast(ImGui::GetTime());
+ }
+
+ /*
+ Left mouse button click
+ */
+ else if (click)
+ {
+ mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
+ if (ctrl)
+ {
+ mSelectionMode = SelectionMode::Word;
+ }
+ else
+ {
+ mSelectionMode = SelectionMode::Normal;
+ }
+ SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
+
+ mLastClick = static_cast(ImGui::GetTime());
+ }
+ // Mouse left button dragging (=> update selection)
+ else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))
+ {
+ io.WantCaptureMouse = true;
+ mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
+ SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
+ }
+ }
+ }
+}
+
+void TextEditor::Render()
+{
+ /* 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);
+
+ /* Update palette with the current alpha from style */
+ for (int i = 0; i < static_cast(PaletteIndex::Max); ++i)
+ {
+ 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);
+ }
+
+ ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
+ auto scrollX = ImGui::GetScrollX();
+ auto scrollY = ImGui::GetScrollY();
+
+ auto lineNo = static_cast(floor(scrollY / mCharAdvance.y));
+ auto globalLineMax = static_cast(mLines.size());
+ auto lineMax = std::max(0, std::min(static_cast(mLines.size()) - 1, lineNo + static_cast(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;
+
+ if (!mLines.empty())
+ {
+ float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
+
+ while (lineNo <= lineMax)
+ {
+ auto lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
+ auto textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);
+
+ auto& line = mLines[lineNo];
+ longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);
+ auto columnNo = 0;
+ Coordinates lineStartCoord(lineNo, 0);
+ Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo));
+
+ // Draw selection for the current line
+ 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)
+ {
+ ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y);
+ ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y);
+ drawList->AddRectFilled(vstart, vend, mPalette[static_cast(PaletteIndex::Selection)]);
+ }
+
+ // Draw breakpoints
+ auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);
+
+ 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[static_cast(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[static_cast(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();
+ }
+ }
+
+ // 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[static_cast(PaletteIndex::LineNumber)], buf);
+
+ if (mState.mCursorPosition.mLine == lineNo)
+ {
+ auto focused = ImGui::IsWindowFocused();
+
+ // 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[static_cast(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
+ drawList->AddRect(start, end, mPalette[static_cast(PaletteIndex::CurrentLineEdge)], 1.0f);
+ }
+
+ // Render the cursor
+ if (focused)
+ {
+ auto timeEnd = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count();
+ auto elapsed = timeEnd - mStartTime;
+ if (elapsed > 400)
+ {
+ float width = 1.0f;
+ auto cindex = GetCharacterIndex(mState.mCursorPosition);
+ float cx = TextDistanceToLineStart(mState.mCursorPosition);
+
+ if (mOverwrite && cindex < static_cast(line.size()))
+ {
+ auto c = line[cindex].mChar;
+ if (c == '\t')
+ {
+ auto x = (1.0f + std::floor((1.0f + cx) / (static_cast(mTabSize) * spaceSize))) * (static_cast(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[static_cast(PaletteIndex::Cursor)]);
+ if (elapsed > 800)
+ {
+ mStartTime = timeEnd;
+ }
+ }
+ }
+ }
+
+ // Render colorized text
+ auto prevColor = line.empty() ? mPalette[static_cast(PaletteIndex::Default)] : GetGlyphColor(line[0]);
+ ImVec2 bufferOffset;
+
+ for (int i = 0; i < line.size();)
+ {
+ auto& glyph = line[i];
+ 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;
+
+ if (glyph.mChar == '\t')
+ {
+ auto oldX = bufferOffset.x;
+ bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (static_cast(mTabSize) * spaceSize))) * (static_cast(mTabSize) * spaceSize);
+ ++i;
+
+ if (mShowWhitespaces)
+ {
+ const auto s = ImGui::GetFontSize();
+ const auto x1 = textScreenPos.x + oldX + 1.0f;
+ const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f;
+ const auto 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);
+ }
+ }
+ else if (glyph.mChar == ' ')
+ {
+ 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);
+ }
+ 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())
+ {
+ ImGui::BeginTooltip();
+ ImGui::TextUnformatted(pi->second.mDeclaration.c_str());
+ ImGui::EndTooltip();
+ }
+ }
+ }
+ }
+ }
+
+
+ ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
+
+ if (mScrollToCursor)
+ {
+ 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[static_cast(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')
+ {
+ // ignore the carriage return character
+ }
+ else if (chr == '\n')
+ {
+ mLines.emplace_back(Line());
+ }
+ else
+ {
+ mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default));
+ }
+ }
+
+ mTextChanged = true;
+ mScrollToTop = true;
+
+ mUndoBuffer.clear();
+ mUndoIndex = 0;
+
+ Colorize();
+}
+
+void TextEditor::SetTextLines(const std::vector&aLines)
+{
+ mLines.clear();
+
+ 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));
+ }
+ }
+ }
+
+ mTextChanged = true;
+ mScrollToTop = true;
+
+ mUndoBuffer.clear();
+ mUndoIndex = 0;
+
+ Colorize();
+}
+
+void TextEditor::EnterCharacter(ImWchar aChar, bool aShift)
+{
+ 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 >= static_cast(mLines.size()))
+ {
+ end.mLine = mLines.empty() ? 0 : static_cast(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', 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'
+ 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, static_cast(whitespaceSize))));
+ u.mAdded = static_cast(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 < static_cast(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 < static_cast(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 SelectionMode::Normal:
+ break;
+ case SelectionMode::Word:
+ {
+ mState.mSelectionStart = FindWordStart(mState.mSelectionStart);
+ if (!IsOnWordBoundary(mState.mSelectionEnd))
+ {
+ mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd));
+ }
+ break;
+ }
+ case SelectionMode::Line:
+ {
+ const auto lineNo = mState.mSelectionEnd.mLine;
+ const auto lineSize = static_cast(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(static_cast(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 (static_cast(mLines.size()) > line)
+ {
+ cindex = static_cast(mLines[line].size());
+ }
+ else
+ {
+ cindex = 0;
+ }
+ }
+ }
+ else
+ {
+ --cindex;
+ if (cindex > 0)
+ {
+ if (static_cast(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(static_cast(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::MoveBottom(bool aSelect)
+{
+ auto oldPos = GetCursorPosition();
+ auto newPos = Coordinates(static_cast(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 == static_cast(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 < static_cast(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(static_cast(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 < static_cast(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(static_cast(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() {}
+
+void TextEditor::Colorize(int aFromLine, int aLines)
+{
+ int toLine = aLines == -1 ? static_cast(mLines.size()) : std::min(static_cast(mLines.size()), aFromLine + aLines);
+ mColorRangeMin = std::min(mColorRangeMin, aFromLine);
+ mColorRangeMax = std::max(mColorRangeMax, toLine);
+ mColorRangeMin = std::max(0, mColorRangeMin);
+ mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax);
+ mCheckComments = true;
+}
+
+void TextEditor::ColorizeRange(int aFromLine, int aToLine)
+{
+ if (mLines.empty() || aFromLine >= aToLine)
+ {
+ return;
+ }
+
+ std::string buffer;
+ std::cmatch results;
+ std::string id;
+
+ int endLine = std::max(0, std::min(static_cast(mLines.size()), aToLine));
+ for (int i = aFromLine; i < endLine; ++i)
+ {
+ auto& line = mLines[i];
+
+ if (line.empty())
+ {
+ continue;
+ }
+
+ buffer.resize(line.size());
+ for (size_t j = 0; j < line.size(); ++j)
+ {
+ auto& col = line[j];
+ buffer[j] = col.mChar;
+ col.mColorIndex = PaletteIndex::Default;
+ }
+
+ 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;
+ auto token_color = PaletteIndex::Default;
+
+ bool hasTokenizeResult = false;
+
+ if (mLanguageDefinition.mTokenize != nullptr)
+ {
+ if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color))
+ {
+ hasTokenizeResult = true;
+ }
+ }
+
+ if (hasTokenizeResult == false)
+ {
+ // todo : remove
+ //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first);
+
+ for (auto& p : mRegexList)
+ {
+ if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous))
+ {
+ hasTokenizeResult = true;
+
+ auto& v = *results.begin();
+ token_begin = v.first;
+ token_end = v.second;
+ token_color = p.second;
+ break;
+ }
+ }
+ }
+
+ if (hasTokenizeResult == false)
+ {
+ first++;
+ }
+ else
+ {
+ const size_t token_length = token_end - token_begin;
+
+ if (token_color == PaletteIndex::Identifier)
+ {
+ id.assign(token_begin, token_end);
+
+ // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ?
+ if (!mLanguageDefinition.mCaseSensitive)
+ {
+ std::transform(id.begin(), id.end(), id.begin(), toupper);
+ }
+
+ if (!line[first - bufferBegin].mPreprocessor)
+ {
+ if (mLanguageDefinition.mKeywords.count(id) != 0)
+ {
+ token_color = PaletteIndex::Keyword;
+ }
+ else if (mLanguageDefinition.mIdentifiers.count(id) != 0)
+ {
+ token_color = PaletteIndex::KnownIdentifier;
+ }
+ else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0)
+ {
+ token_color = PaletteIndex::PreprocIdentifier;
+ }
+ }
+ else
+ {
+ if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0)
+ {
+ token_color = PaletteIndex::PreprocIdentifier;
+ }
+ }
+ }
+
+ for (size_t j = 0; j < token_length; ++j)
+ {
+ line[(token_begin - bufferBegin) + j].mColorIndex = token_color;
+ }
+
+ first = token_end;
+ }
+ }
+ }
+}
+
+void TextEditor::ColorizeInternal()
+{
+ if (mLines.empty() || !mColorizerEnabled)
+ {
+ return;
+ }
+
+ if (mCheckComments)
+ {
+ auto endLine = mLines.size();
+ auto endIndex = 0;
+ auto commentStartLine = endLine;
+ auto commentStartIndex = endIndex;
+ auto withinString = false;
+ auto withinSingleLineComment = false;
+ auto withinPreproc = false;
+ auto firstChar = true; // there is no other non-whitespace characters in the line before
+ auto concatenate = false; // '\' on the very end of the line
+ auto currentLine = 0;
+ auto currentIndex = 0;
+ while (currentLine < endLine || currentIndex < endIndex)
+ {
+ auto& line = mLines[currentLine];
+
+ if (currentIndex == 0 && !concatenate)
+ {
+ withinSingleLineComment = false;
+ withinPreproc = false;
+ firstChar = true;
+ }
+
+ concatenate = false;
+
+ if (!line.empty())
+ {
+ auto& g = line[currentIndex];
+ auto c = g.mChar;
+
+ if (c != mLanguageDefinition.mPreprocChar && !isspace(c))
+ {
+ firstChar = false;
+ }
+
+ if (currentIndex == static_cast(line.size()) - 1 && line[line.size() - 1].mChar == '\\')
+ {
+ concatenate = true;
+ }
+
+ bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
+
+ if (withinString)
+ {
+ line[currentIndex].mMultiLineComment = inComment;
+
+ if (c == '\"')
+ {
+ if (currentIndex + 1 < static_cast(line.size()) && line[currentIndex + 1].mChar == '\"')
+ {
+ currentIndex += 1;
+ if (currentIndex < static_cast(line.size()))
+ {
+ line[currentIndex].mMultiLineComment = inComment;
+ }
+ }
+ else
+ {
+ withinString = false;
+ }
+ }
+ else if (c == '\\')
+ {
+ currentIndex += 1;
+ if (currentIndex < static_cast(line.size()))
+ {
+ line[currentIndex].mMultiLineComment = inComment;
+ }
+ }
+ }
+ else
+ {
+ if (firstChar && c == mLanguageDefinition.mPreprocChar)
+ {
+ withinPreproc = true;
+ }
+
+ if (c == '\"')
+ {
+ withinString = true;
+ line[currentIndex].mMultiLineComment = inComment;
+ }
+ else
+ {
+ auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; };
+ auto from = line.begin() + currentIndex;
+ auto& startStr = mLanguageDefinition.mCommentStart;
+ auto& singleStartStr = mLanguageDefinition.mSingleLineComment;
+
+ if (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))
+ {
+ commentStartLine = currentLine;
+ commentStartIndex = currentIndex;
+ }
+
+ inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
+
+ line[currentIndex].mMultiLineComment = inComment;
+ line[currentIndex].mComment = withinSingleLineComment;
+
+ auto& endStr = mLanguageDefinition.mCommentEnd;
+ if (currentIndex + 1 >= static_cast(endStr.size()) &&
+ equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred))
+ {
+ commentStartIndex = endIndex;
+ commentStartLine = endLine;
+ }
+ }
+ }
+ line[currentIndex].mPreprocessor = withinPreproc;
+ currentIndex += UTF8CharLength(c);
+ if (currentIndex >= static_cast(line.size()))
+ {
+ currentIndex = 0;
+ ++currentLine;
+ }
+ }
+ else
+ {
+ currentIndex = 0;
+ ++currentLine;
+ }
+ }
+ mCheckComments = false;
+ }
+
+ if (mColorRangeMin < mColorRangeMax)
+ {
+ const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000;
+ const int to = std::min(mColorRangeMin + increment, mColorRangeMax);
+ ColorizeRange(mColorRangeMin, to);
+ mColorRangeMin = to;
+
+ if (mColorRangeMax == mColorRangeMin)
+ {
+ mColorRangeMin = std::numeric_limits::max();
+ mColorRangeMax = 0;
+ }
+ }
+}
+
+float TextEditor::TextDistanceToLineStart(const Coordinates & aFrom) const
+{
+ auto& line = mLines[aFrom.mLine];
+ float distance = 0.0f;
+ float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
+ int colIndex = 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) / (static_cast(mTabSize) * spaceSize))) * (static_cast(mTabSize) * spaceSize);
+ ++it;
+ }
+ else
+ {
+ auto d = UTF8CharLength(line[it].mChar);
+ char tempCString[7];
+ int i = 0;
+ for (; i < 6 && d-- > 0 && it < static_cast(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;
+}
+
+void TextEditor::EnsureCursorVisible()
+{
+ if (!mWithinRender)
+ {
+ mScrollToCursor = true;
+ return;
+ }
+
+ float scrollX = ImGui::GetScrollX();
+ float scrollY = ImGui::GetScrollY();
+
+ auto height = ImGui::GetWindowHeight();
+ auto width = ImGui::GetWindowWidth();
+
+ auto top = 1 + static_cast(ceil(scrollY / mCharAdvance.y));
+ auto bottom = static_cast(ceil((scrollY + height) / mCharAdvance.y));
+
+ auto left = static_cast(ceil(scrollX / mCharAdvance.x));
+ auto right = static_cast(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));
+ }
+}
+
+int TextEditor::GetPageSize() const
+{
+ auto height = ImGui::GetWindowHeight() - 20.0f;
+ return static_cast(floor(height / mCharAdvance.y));
+}
+
+TextEditor::UndoRecord::UndoRecord(
+ const std::string & aAdded,
+ const Coordinates aAddedStart,
+ const Coordinates aAddedEnd,
+ const std::string & aRemoved,
+ const Coordinates aRemovedStart,
+ const Coordinates aRemovedEnd,
+ EditorState & aBefore,
+ EditorState & aAfter)
+ : mAdded(aAdded)
+ , mAddedStart(aAddedStart)
+ , mAddedEnd(aAddedEnd)
+ , mRemoved(aRemoved)
+ , mRemovedStart(aRemovedStart)
+ , mRemovedEnd(aRemovedEnd)
+ , mBefore(aBefore)
+ , mAfter(aAfter)
+{
+ assert(mAddedStart <= mAddedEnd);
+ assert(mRemovedStart <= mRemovedEnd);
+}
+
+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;
+}
+
+#pragma warning( pop )
\ No newline at end of file
diff --git a/Amalgam/include/ImGui/TextEditor.h b/Amalgam/include/ImGui/TextEditor.h
new file mode 100644
index 0000000..7e2b1aa
--- /dev/null
+++ b/Amalgam/include/ImGui/TextEditor.h
@@ -0,0 +1,406 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include