code:
/*
MIT License

Copyright (c) 2017 BalazsJako

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <algorithm>
#include <chrono>
#include <cmath>
#include <regex>
#include <string>

#include "Core/CommonUI/TextEditor.hpp"

#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif

#include "imgui.h" // for imGui::GetCurrentWindow()
#include "imgui_internal.h"

// TODO
// - multiline comments vs single-line: latter is blocking start of a ML

template <class InputIt1, class InputIt2, class BinaryPredicate>
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)
, mCheckComments(true)
, mLastClick(-1.0f)
, mHandleKeyboardInputs(true)
, mHandleMouseInputs(true)
, mIgnoreImGuiChild(false)
, mShowWhitespaces(true)
, mStartTime(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()) {
  SetPalette(GetDarkPalette());
  SetLanguageDefinition(LanguageDefinition::HLSL());
  mLines.push_back(Line());
}

TextEditor::~TextEditor() {}

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 >= (int)mLines.size())
      break;

    auto& line = mLines[lstart];
    if (istart < (int)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 >= (int)mLines.size()) {
    if (mLines.empty()) {
      line = 0;
      column = 0;
    } else {
      line = (int)mLines.size() - 1;
      column = GetLineMaxColumn(line);
    }
    return Coordinates(line, column);
  } else {
    column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line));
    return Coordinates(line, column);
  }
}

// https://en.wikipedia.org/wiki/UTF-8
// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
static int UTF8CharLength(TextEditor::Char c) {
  if ((c & 0xFE) == 0xFC)
    return 6;
  if ((c & 0xFC) == 0xF8)
    return 5;
  if ((c & 0xF8) == 0xF0)
    return 4;
  else if ((c & 0xF0) == 0xE0)
    return 3;
  else if ((c & 0xE0) == 0xC0)
    return 2;
  return 1;
}

// "Borrowed" from ImGui source
static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) {
  if (c < 0x80) {
    buf[0] = (char)c;
    return 1;
  }
  if (c < 0x800) {
    if (buf_size < 2)
      return 0;
    buf[0] = (char)(0xc0 + (c >> 6));
    buf[1] = (char)(0x80 + (c & 0x3f));
    return 2;
  }
  if (c >= 0xdc00 && c < 0xe000) {
    return 0;
  }
  if (c >= 0xd800 && c < 0xdc00) {
    if (buf_size < 4)
      return 0;
    buf[0] = (char)(0xf0 + (c >> 18));
    buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
    buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
    buf[3] = (char)(0x80 + ((c) & 0x3f));
    return 4;
  }
  // else if (c < 0x10000)
  {
    if (buf_size < 3)
      return 0;
    buf[0] = (char)(0xe0 + (c >> 12));
    buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
    buf[2] = (char)(0x80 + ((c) & 0x3f));
    return 3;
  }
}

void TextEditor::Advance(Coordinates& aCoordinates) const {
  if (aCoordinates.mLine < (int)mLines.size()) {
    auto& line = mLines[aCoordinates.mLine];
    auto cindex = GetCharacterIndex(aCoordinates);

    if (cindex + 1 < (int)line.size()) {
      auto delta = UTF8CharLength(line[cindex].mChar);
      cindex = std::min(cindex + delta, (int)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 < (int)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((size_t)(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, (int)floor(local.y / mCharAdvance.y));

  int columnCoord = 0;

  if (lineNo >= 0 && lineNo < (int)mLines.size()) {
    auto& line = mLines.at(lineNo);

    int columnIndex = 0;
    float columnX = 0.0f;

    while ((size_t)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) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
        columnWidth = newColumnX - oldX;
        if (mTextStart + columnX + columnWidth * 0.5f > local.x)
          break;
        columnX = newColumnX;
        columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize;
        columnIndex++;
      } 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 >= (int)mLines.size())
    return at;

  auto& line = mLines[at.mLine];
  auto cindex = GetCharacterIndex(at);

  if (cindex >= (int)line.size())
    return at;

  while (cindex > 0 && isspace(line[cindex].mChar))
    --cindex;

  auto cstart = (PaletteIndex)line[cindex].mColorIndex;
  while (cindex > 0) {
    auto c = line[cindex].mChar;
    if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx
    {
      if (c <= 32 && isspace(c)) {
        cindex++;
        break;
      }
      if (cstart != (PaletteIndex)line[size_t(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 >= (int)mLines.size())
    return at;

  auto& line = mLines[at.mLine];
  auto cindex = GetCharacterIndex(at);

  if (cindex >= (int)line.size())
    return at;

  bool prevspace = (bool)isspace(line[cindex].mChar);
  auto cstart = (PaletteIndex)line[cindex].mColorIndex;
  while (cindex < (int)line.size()) {
    auto c = line[cindex].mChar;
    auto d = UTF8CharLength(c);
    if (cstart != (PaletteIndex)line[cindex].mColorIndex)
      break;

    if (prevspace != !!isspace(c)) {
      if (isspace(c))
        while (cindex < (int)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 >= (int)mLines.size())
    return at;

  // skip to the next non-word character
  auto cindex = GetCharacterIndex(aFrom);
  bool isword = false;
  bool skip = false;
  if (cindex < (int)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, (int)mLines.size() - 1);
      return Coordinates(l, GetLineMaxColumn(l));
    }

    auto& line = mLines[at.mLine];
    if (cindex < (int)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 < (int)line.size()) {
    auto c = line[i].mChar;
    i += UTF8CharLength(c);
    if (c == '\t')
      col = (col / mTabSize) * mTabSize + mTabSize;
    else
      col++;
  }
  return col;
}

int TextEditor::GetLineCharacterCount(int aLine) const {
  if (aLine >= mLines.size())
    return 0;
  auto& line = mLines[aLine];
  int c = 0;
  for (unsigned i = 0; i < line.size(); c++)
    i += UTF8CharLength(line[i].mChar);
  return c;
}

int TextEditor::GetLineMaxColumn(int aLine) const {
  if (aLine >= mLines.size())
    return 0;
  auto& line = mLines[aLine];
  int col = 0;
  for (unsigned i = 0; i < line.size();) {
    auto c = line[i].mChar;
    if (c == '\t')
      col = (col / mTabSize) * mTabSize + mTabSize;
    else
      col++;
    i += UTF8CharLength(c);
  }
  return col;
}

bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const {
  if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0)
    return true;

  auto& line = mLines[aAt.mLine];
  auto cindex = GetCharacterIndex(aAt);
  if (cindex >= (int)line.size())
    return true;

  if (mColorizerEnabled)
    return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex;

  return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar);
}

void TextEditor::RemoveLine(int aStart, int aEnd) {
  assert(!mReadOnly);
  assert(aEnd >= aStart);
  assert(mLines.size() > (size_t)(aEnd - aStart));

  ErrorMarkers etmp;
  for (auto& i : mErrorMarkers) {
    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[(int)PaletteIndex::Default];
  if (aGlyph.mComment)
    return mPalette[(int)PaletteIndex::Comment];
  if (aGlyph.mMultiLineComment)
    return mPalette[(int)PaletteIndex::MultiLineComment];
  auto const color = mPalette[(int)aGlyph.mColorIndex];
  if (aGlyph.mPreprocessor) {
    const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor];
    const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2;
    const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2;
    const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2;
    const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2;
    return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24));
  }
  return color;
}

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(ImGuiKey_Z))
      Undo();
    else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGuiKey_Backspace))
      Undo();
    else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Y))
      Redo();
    else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_UpArrow))
      MoveUp(1, shift);
    else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_DownArrow))
      MoveDown(1, shift);
    else if (!alt && ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
      MoveLeft(1, shift, ctrl);
    else if (!alt && ImGui::IsKeyPressed(ImGuiKey_RightArrow))
      MoveRight(1, shift, ctrl);
    else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageUp))
      MoveUp(GetPageSize() - 4, shift);
    else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageDown))
      MoveDown(GetPageSize() - 4, shift);
    else if (!alt && ctrl && ImGui::IsKeyPressed(ImGuiKey_Home))
      MoveTop(shift);
    else if (ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End))
      MoveBottom(shift);
    else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Home))
      MoveHome(shift);
    else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End))
      MoveEnd(shift);
    else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete))
      Delete();
    else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Backspace))
      Backspace();
    else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))
      mOverwrite ^= true;
    else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))
      Copy();
    else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_C))
      Copy();
    else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert))
      Paste();
    else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_V))
      Paste();
    else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_X))
      Cut();
    else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete))
      Cut();
    else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_A))
      SelectAll();
    else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Enter))
      EnterCharacter('\n', false);
    else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab)) {
      if (mTabsAsSpaces) {
        for (int i = 0; i < mTabSpaceCount; ++i) {
          EnterCharacter(' ', shift);
        }
      } else {
        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 = (float)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 = (float)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 < (int)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 = (int)floor(scrollY / mCharAdvance.y);
  auto globalLineMax = (int)mLines.size();
  auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y)));

  // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
  char buf[16];
  snprintf(buf, 16, " %d ", globalLineMax);
  mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin;

  if (!mLines.empty()) {
    float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;

    while (lineNo <= lineMax) {
      ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
      ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);

      auto& line = mLines[lineNo];
      longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);
      auto columnNo = 0;
      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[(int)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[(int)PaletteIndex::Breakpoint]);
      }

      // Draw error markers
      auto errorIt = mErrorMarkers.find(lineNo + 1);
      if (errorIt != mErrorMarkers.end()) {
        auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
        drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]);

        if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) {
          ImGui::BeginTooltip();
          ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
          ImGui::Text("Error at line %d:", errorIt->first);
          ImGui::PopStyleColor();
          ImGui::Separator();
          ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f));
          ImGui::Text("%s", errorIt->second.c_str());
          ImGui::PopStyleColor();
          ImGui::EndTooltip();
        }
      }

      // Draw line number (right aligned)
      snprintf(buf, 16, "%d  ", lineNo + 1);

      auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x;
      drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf);

      if (mState.mCursorPosition.mLine == lineNo) {
        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[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
          drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f);
        }

        // Render the cursor
        if (focused) {
          auto timeEnd = std::chrono::duration_cast<std::chrono::milliseconds>(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 < (int)line.size()) {
              auto c = line[cindex].mChar;
              if (c == '\t') {
                auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
                width = x - cx;
              } else {
                char buf2[2];
                buf2[0] = line[cindex].mChar;
                buf2[1] = '\0';
                width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x;
              }
            }
            ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y);
            ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y);
            drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]);
            { // see imgui_widgets.cpp
              ImGuiContext& g = *ImGui::GetCurrentContext();
              ImGuiWindow* window = ImGui::GetCurrentWindow();
              g.PlatformImeData.WantVisible = true;
              g.PlatformImeData.InputPos = ImVec2(textScreenPos.x - 1.0f, textScreenPos.y - fontSize);
              g.PlatformImeData.InputLineHeight = fontSize;
              g.PlatformImeViewport = window->Viewport->ID;
            }
          }
        }
      }

      // Render colorized text
      auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]);
      ImVec2 bufferOffset;

      for (int i = 0; i < line.size();) {
        auto& glyph = line[i];
        auto color = GetGlyphColor(glyph);

        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) / (float(mTabSize) * spaceSize))) * (float(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[(int)PaletteIndex::Background]));
  ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
  if (!mIgnoreImGuiChild)
    ImGui::BeginChild(aTitle, aSize, aBorder ? ImGuiChildFlags_Borders : 0, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove);

  if (mHandleKeyboardInputs) {
    ImGui::PushAllowKeyboardFocus(true);
    HandleKeyboardInputs();
  }

  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<std::string>& 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 >= (int)mLines.size())
        end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1;
      end.mColumn = GetLineMaxColumn(end.mLine);

      // if (end.mColumn >= GetLineMaxColumn(end.mLine))
      //	end.mColumn = GetLineMaxColumn(end.mLine) - 1;

      u.mRemovedStart = start;
      u.mRemovedEnd = end;
      u.mRemoved = GetText(start, end);

      bool modified = false;

      for (int i = start.mLine; i <= end.mLine; i++) {
        auto& line = mLines[i];
        if (aShift) {
          if (!line.empty()) {
            if (line.front().mChar == '\t') {
              line.erase(line.begin());
              modified = true;
            } else {
              for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) {
                line.erase(line.begin());
                modified = true;
              }
            }
          }
        } else {
          line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background));
          modified = true;
        }
      }

      if (modified) {
        start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0));
        Coordinates rangeEnd;
        if (originalEnd.mColumn != 0) {
          end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine));
          rangeEnd = end;
          u.mAdded = GetText(start, end);
        } else {
          end = Coordinates(originalEnd.mLine, 0);
          rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1));
          u.mAdded = GetText(start, rangeEnd);
        }

        u.mAddedStart = start;
        u.mAddedEnd = rangeEnd;
        u.mAfter = mState;

        mState.mSelectionStart = start;
        mState.mSelectionEnd = end;
        AddUndo(u);

        mTextChanged = true;

        EnsureCursorVisible();
      }

      return;
    } // c == '\t'
    else {
      u.mRemoved = GetSelectedText();
      u.mRemovedStart = mState.mSelectionStart;
      u.mRemovedEnd = mState.mSelectionEnd;
      DeleteSelection();
    }
  } // HasSelection

  auto coord = GetActualCursorCoordinates();
  u.mAddedStart = coord;

  assert(!mLines.empty());

  if (aChar == '\n') {
    InsertLine(coord.mLine + 1);
    auto& line = mLines[coord.mLine];
    auto& newLine = mLines[coord.mLine + 1];

    if (mLanguageDefinition.mAutoIndentation)
      for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it)
        newLine.push_back(line[it]);

    const size_t whitespaceSize = newLine.size();
    auto cindex = GetCharacterIndex(coord);
    newLine.insert(newLine.end(), line.begin() + cindex, line.end());
    line.erase(line.begin() + cindex, line.begin() + line.size());
    SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize)));
    u.mAdded = (char)aChar;
  } else {
    char buf[7];
    int e = ImTextCharToUtf8(buf, 7, aChar);
    if (e > 0) {
      buf[e] = '\0';
      auto& line = mLines[coord.mLine];
      auto cindex = GetCharacterIndex(coord);

      if (mOverwrite && cindex < (int)line.size()) {
        auto d = UTF8CharLength(line[cindex].mChar);

        u.mRemovedStart = mState.mCursorPosition;
        u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d));

        while (d-- > 0 && cindex < (int)line.size()) {
          u.mRemoved += line[cindex].mChar;
          line.erase(line.begin() + cindex);
        }
      }

      for (auto p = buf; *p != '\0'; p++, ++cindex)
        line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default));
      u.mAdded = buf;

      SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex)));
    } else
      return;
  }

  mTextChanged = true;

  u.mAddedEnd = GetActualCursorCoordinates();
  u.mAfter = mState;

  AddUndo(u);

  Colorize(coord.mLine - 1, 3);
  EnsureCursorVisible();
}

void TextEditor::SetReadOnly(bool aValue) { mReadOnly = aValue; }

void TextEditor::SetColorizerEnable(bool aValue) { mColorizerEnabled = aValue; }

void TextEditor::SetCursorPosition(const Coordinates& aPosition) {
  if (mState.mCursorPosition != aPosition) {
    mState.mCursorPosition = aPosition;
    mCursorPositionChanged = true;
    EnsureCursorVisible();
  }
}

void TextEditor::SetSelectionStart(const Coordinates& aPosition) {
  mState.mSelectionStart = SanitizeCoordinates(aPosition);
  if (mState.mSelectionStart > mState.mSelectionEnd)
    std::swap(mState.mSelectionStart, mState.mSelectionEnd);
}

void TextEditor::SetSelectionEnd(const Coordinates& aPosition) {
  mState.mSelectionEnd = SanitizeCoordinates(aPosition);
  if (mState.mSelectionStart > mState.mSelectionEnd)
    std::swap(mState.mSelectionStart, mState.mSelectionEnd);
}

void TextEditor::SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode) {
  auto oldSelStart = mState.mSelectionStart;
  auto oldSelEnd = mState.mSelectionEnd;

  mState.mSelectionStart = SanitizeCoordinates(aStart);
  mState.mSelectionEnd = SanitizeCoordinates(aEnd);
  if (mState.mSelectionStart > mState.mSelectionEnd)
    std::swap(mState.mSelectionStart, mState.mSelectionEnd);

  switch (aMode) {
  case TextEditor::SelectionMode::Normal:
    break;
  case TextEditor::SelectionMode::Word: {
    mState.mSelectionStart = FindWordStart(mState.mSelectionStart);
    if (!IsOnWordBoundary(mState.mSelectionEnd))
      mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd));
    break;
  }
  case TextEditor::SelectionMode::Line: {
    const auto lineNo = mState.mSelectionEnd.mLine;
    const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0;
    mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0);
    mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo));
    break;
  }
  default:
    break;
  }

  if (mState.mSelectionStart != oldSelStart || mState.mSelectionEnd != oldSelEnd)
    mCursorPositionChanged = true;
}

void TextEditor::SetTabSize(int aValue) { mTabSize = std::max(0, std::min(32, aValue)); }

void TextEditor::InsertText(const std::string& aValue) { InsertText(aValue.c_str()); }

void TextEditor::InsertText(const char* aValue) {
  if (aValue == nullptr)
    return;

  auto pos = GetActualCursorCoordinates();
  auto start = std::min(pos, mState.mSelectionStart);
  int totalLines = pos.mLine - start.mLine;

  totalLines += InsertTextAt(pos, aValue);

  SetSelection(pos, pos);
  SetCursorPosition(pos);
  Colorize(start.mLine - 1, totalLines + 2);
}

void TextEditor::DeleteSelection() {
  assert(mState.mSelectionEnd >= mState.mSelectionStart);

  if (mState.mSelectionEnd == mState.mSelectionStart)
    return;

  DeleteRange(mState.mSelectionStart, mState.mSelectionEnd);

  SetSelection(mState.mSelectionStart, mState.mSelectionStart);
  SetCursorPosition(mState.mSelectionStart);
  Colorize(mState.mSelectionStart.mLine, 1);
}

void TextEditor::MoveUp(int aAmount, bool aSelect) {
  auto oldPos = mState.mCursorPosition;
  mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount);
  if (oldPos != mState.mCursorPosition) {
    if (aSelect) {
      if (oldPos == mInteractiveStart)
        mInteractiveStart = mState.mCursorPosition;
      else if (oldPos == mInteractiveEnd)
        mInteractiveEnd = mState.mCursorPosition;
      else {
        mInteractiveStart = mState.mCursorPosition;
        mInteractiveEnd = oldPos;
      }
    } else
      mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
    SetSelection(mInteractiveStart, mInteractiveEnd);

    EnsureCursorVisible();
  }
}

void TextEditor::MoveDown(int aAmount, bool aSelect) {
  assert(mState.mCursorPosition.mColumn >= 0);
  auto oldPos = mState.mCursorPosition;
  mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount));

  if (mState.mCursorPosition != oldPos) {
    if (aSelect) {
      if (oldPos == mInteractiveEnd)
        mInteractiveEnd = mState.mCursorPosition;
      else if (oldPos == mInteractiveStart)
        mInteractiveStart = mState.mCursorPosition;
      else {
        mInteractiveStart = oldPos;
        mInteractiveEnd = mState.mCursorPosition;
      }
    } else
      mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
    SetSelection(mInteractiveStart, mInteractiveEnd);

    EnsureCursorVisible();
  }
}

static bool IsUTFSequence(char c) { return (c & 0xC0) == 0x80; }

void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) {
  if (mLines.empty())
    return;

  auto oldPos = mState.mCursorPosition;
  mState.mCursorPosition = GetActualCursorCoordinates();
  auto line = mState.mCursorPosition.mLine;
  auto cindex = GetCharacterIndex(mState.mCursorPosition);

  while (aAmount-- > 0) {
    if (cindex == 0) {
      if (line > 0) {
        --line;
        if ((int)mLines.size() > line)
          cindex = (int)mLines[line].size();
        else
          cindex = 0;
      }
    } else {
      --cindex;
      if (cindex > 0) {
        if ((int)mLines.size() > line) {
          while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar))
            --cindex;
        }
      }
    }

    mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));
    if (aWordMode) {
      mState.mCursorPosition = FindWordStart(mState.mCursorPosition);
      cindex = GetCharacterIndex(mState.mCursorPosition);
    }
  }

  mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex));

  assert(mState.mCursorPosition.mColumn >= 0);
  if (aSelect) {
    if (oldPos == mInteractiveStart)
      mInteractiveStart = mState.mCursorPosition;
    else if (oldPos == mInteractiveEnd)
      mInteractiveEnd = mState.mCursorPosition;
    else {
      mInteractiveStart = mState.mCursorPosition;
      mInteractiveEnd = oldPos;
    }
  } else
    mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
  SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);

  EnsureCursorVisible();
}

void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) {
  auto oldPos = mState.mCursorPosition;

  if (mLines.empty() || oldPos.mLine >= mLines.size())
    return;

  auto cindex = GetCharacterIndex(mState.mCursorPosition);
  while (aAmount-- > 0) {
    auto lindex = mState.mCursorPosition.mLine;
    auto& line = mLines[lindex];

    if (cindex >= line.size()) {
      if (mState.mCursorPosition.mLine < mLines.size() - 1) {
        mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1));
        mState.mCursorPosition.mColumn = 0;
      } else
        return;
    } else {
      cindex += UTF8CharLength(line[cindex].mChar);
      mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex));
      if (aWordMode)
        mState.mCursorPosition = FindNextWord(mState.mCursorPosition);
    }
  }

  if (aSelect) {
    if (oldPos == mInteractiveEnd)
      mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition);
    else if (oldPos == mInteractiveStart)
      mInteractiveStart = mState.mCursorPosition;
    else {
      mInteractiveStart = oldPos;
      mInteractiveEnd = mState.mCursorPosition;
    }
  } else
    mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
  SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);

  EnsureCursorVisible();
}

void TextEditor::MoveTop(bool aSelect) {
  auto oldPos = mState.mCursorPosition;
  SetCursorPosition(Coordinates(0, 0));

  if (mState.mCursorPosition != oldPos) {
    if (aSelect) {
      mInteractiveEnd = oldPos;
      mInteractiveStart = mState.mCursorPosition;
    } else
      mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
    SetSelection(mInteractiveStart, mInteractiveEnd);
  }
}

void TextEditor::TextEditor::MoveBottom(bool aSelect) {
  auto oldPos = GetCursorPosition();
  auto newPos = Coordinates((int)mLines.size() - 1, 0);
  SetCursorPosition(newPos);
  if (aSelect) {
    mInteractiveStart = oldPos;
    mInteractiveEnd = newPos;
  } else
    mInteractiveStart = mInteractiveEnd = newPos;
  SetSelection(mInteractiveStart, mInteractiveEnd);
}

void TextEditor::MoveHome(bool aSelect) {
  auto oldPos = mState.mCursorPosition;
  SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0));

  if (mState.mCursorPosition != oldPos) {
    if (aSelect) {
      if (oldPos == mInteractiveStart)
        mInteractiveStart = mState.mCursorPosition;
      else if (oldPos == mInteractiveEnd)
        mInteractiveEnd = mState.mCursorPosition;
      else {
        mInteractiveStart = mState.mCursorPosition;
        mInteractiveEnd = oldPos;
      }
    } else
      mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
    SetSelection(mInteractiveStart, mInteractiveEnd);
  }
}

void TextEditor::MoveEnd(bool aSelect) {
  auto oldPos = mState.mCursorPosition;
  SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine)));

  if (mState.mCursorPosition != oldPos) {
    if (aSelect) {
      if (oldPos == mInteractiveEnd)
        mInteractiveEnd = mState.mCursorPosition;
      else if (oldPos == mInteractiveStart)
        mInteractiveStart = mState.mCursorPosition;
      else {
        mInteractiveStart = oldPos;
        mInteractiveEnd = mState.mCursorPosition;
      }
    } else
      mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
    SetSelection(mInteractiveStart, mInteractiveEnd);
  }
}

void TextEditor::Delete() {
  assert(!mReadOnly);

  if (mLines.empty())
    return;

  UndoRecord u;
  u.mBefore = mState;

  if (HasSelection()) {
    u.mRemoved = GetSelectedText();
    u.mRemovedStart = mState.mSelectionStart;
    u.mRemovedEnd = mState.mSelectionEnd;

    DeleteSelection();
  } else {
    auto pos = GetActualCursorCoordinates();
    SetCursorPosition(pos);
    auto& line = mLines[pos.mLine];

    if (pos.mColumn == GetLineMaxColumn(pos.mLine)) {
      if (pos.mLine == (int)mLines.size() - 1)
        return;

      u.mRemoved = '\n';
      u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
      Advance(u.mRemovedEnd);

      auto& nextLine = mLines[pos.mLine + 1];
      line.insert(line.end(), nextLine.begin(), nextLine.end());
      RemoveLine(pos.mLine + 1);
    } else {
      auto cindex = GetCharacterIndex(pos);
      u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
      u.mRemovedEnd.mColumn++;
      u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd);

      auto d = UTF8CharLength(line[cindex].mChar);
      while (d-- > 0 && cindex < (int)line.size())
        line.erase(line.begin() + cindex);
    }

    mTextChanged = true;

    Colorize(pos.mLine, 1);
  }

  u.mAfter = mState;
  AddUndo(u);
}

void TextEditor::Backspace() {
  assert(!mReadOnly);

  if (mLines.empty())
    return;

  UndoRecord u;
  u.mBefore = mState;

  if (HasSelection()) {
    u.mRemoved = GetSelectedText();
    u.mRemovedStart = mState.mSelectionStart;
    u.mRemovedEnd = mState.mSelectionEnd;

    DeleteSelection();
  } else {
    auto pos = GetActualCursorCoordinates();
    SetCursorPosition(pos);

    if (mState.mCursorPosition.mColumn == 0) {
      if (mState.mCursorPosition.mLine == 0)
        return;

      u.mRemoved = '\n';
      u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1));
      Advance(u.mRemovedEnd);

      auto& line = mLines[mState.mCursorPosition.mLine];
      auto& prevLine = mLines[mState.mCursorPosition.mLine - 1];
      auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1);
      prevLine.insert(prevLine.end(), line.begin(), line.end());

      ErrorMarkers etmp;
      for (auto& i : mErrorMarkers)
        etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second));
      mErrorMarkers = std::move(etmp);

      RemoveLine(mState.mCursorPosition.mLine);
      --mState.mCursorPosition.mLine;
      mState.mCursorPosition.mColumn = prevSize;
    } else {
      auto& line = mLines[mState.mCursorPosition.mLine];
      auto cindex = GetCharacterIndex(pos) - 1;
      auto cend = cindex + 1;
      while (cindex > 0 && IsUTFSequence(line[cindex].mChar))
        --cindex;

      // if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1)
      //	--cindex;

      u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
      --u.mRemovedStart.mColumn;
      --mState.mCursorPosition.mColumn;

      while (cindex < line.size() && cend-- > cindex) {
        u.mRemoved += line[cindex].mChar;
        line.erase(line.begin() + cindex);
      }
    }

    mTextChanged = true;

    EnsureCursorVisible();
    Colorize(mState.mCursorPosition.mLine, 1);
  }

  u.mAfter = mState;
  AddUndo(u);
}

void TextEditor::SelectWordUnderCursor() {
  auto c = GetCursorPosition();
  SetSelection(FindWordStart(c), FindWordEnd(c));
}

void TextEditor::SelectAll() { SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0)); }

bool TextEditor::HasSelection() const { return mState.mSelectionEnd > mState.mSelectionStart; }

void TextEditor::Copy() {
  if (HasSelection()) {
    ImGui::SetClipboardText(GetSelectedText().c_str());
  } else {
    if (!mLines.empty()) {
      std::string str;
      auto& line = mLines[GetActualCursorCoordinates().mLine];
      for (auto& g : line)
        str.push_back(g.mChar);
      ImGui::SetClipboardText(str.c_str());
    }
  }
}

void TextEditor::Cut() {
  if (IsReadOnly()) {
    Copy();
  } else {
    if (HasSelection()) {
      UndoRecord u;
      u.mBefore = mState;
      u.mRemoved = GetSelectedText();
      u.mRemovedStart = mState.mSelectionStart;
      u.mRemovedEnd = mState.mSelectionEnd;

      Copy();
      DeleteSelection();

      u.mAfter = mState;
      AddUndo(u);
    }
  }
}

void TextEditor::Paste() {
  if (IsReadOnly())
    return;

  auto clipText = ImGui::GetClipboardText();
  if (clipText != nullptr && strlen(clipText) > 0) {
    UndoRecord u;
    u.mBefore = mState;

    if (HasSelection()) {
      u.mRemoved = GetSelectedText();
      u.mRemovedStart = mState.mSelectionStart;
      u.mRemovedEnd = mState.mSelectionEnd;
      DeleteSelection();
    }

    u.mAdded = clipText;
    u.mAddedStart = GetActualCursorCoordinates();

    InsertText(clipText);

    u.mAddedEnd = GetActualCursorCoordinates();
    u.mAfter = mState;
    AddUndo(u);
  }
}

bool TextEditor::CanUndo() const { return !mReadOnly && mUndoIndex > 0; }

bool TextEditor::CanRedo() const { return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); }

void TextEditor::Undo(int aSteps) {
  while (CanUndo() && aSteps-- > 0)
    mUndoBuffer[--mUndoIndex].Undo(this);
}

void TextEditor::Redo(int aSteps) {
  while (CanRedo() && aSteps-- > 0)
    mUndoBuffer[mUndoIndex++].Redo(this);
}

const TextEditor::Palette& TextEditor::GetDarkPalette() {
  const static Palette p = {{
      0xff7f7f7f, // Default
      0xffd69c56, // Keyword
      0xff00ff00, // Number
      0xff7070e0, // String
      0xff70a0e0, // Char literal
      0xffffffff, // Punctuation
      0xff408080, // Preprocessor
      0xffaaaaaa, // Identifier
      0xff9bc64d, // Known identifier
      0xffc040a0, // Preproc identifier
      0xff206020, // Comment (single line)
      0xff406020, // Comment (multi line)
      0xff101010, // Background
      0xffe0e0e0, // Cursor
      0x80a06020, // Selection
      0x800020ff, // ErrorMarker
      0x40f08000, // Breakpoint
      0xff707000, // Line number
      0x40000000, // Current line fill
      0x40808080, // Current line fill (inactive)
      0x40a0a0a0, // Current line edge
  }};
  return p;
}

const TextEditor::Palette& TextEditor::GetLightPalette() {
  const static Palette p = {{
      0xff7f7f7f, // None
      0xffff0c06, // Keyword
      0xff008000, // Number
      0xff2020a0, // String
      0xff304070, // Char literal
      0xff000000, // Punctuation
      0xff406060, // Preprocessor
      0xff404040, // Identifier
      0xff606010, // Known identifier
      0xffc040a0, // Preproc identifier
      0xff205020, // Comment (single line)
      0xff405020, // Comment (multi line)
      0xffffffff, // Background
      0xff000000, // Cursor
      0x80600000, // Selection
      0xa00010ff, // ErrorMarker
      0x80f08000, // Breakpoint
      0xff505000, // Line number
      0x40000000, // Current line fill
      0x40808080, // Current line fill (inactive)
      0x40000000, // Current line edge
  }};
  return p;
}

const TextEditor::Palette& TextEditor::GetRetroBluePalette() {
  const static Palette p = {{
      0xff00ffff, // None
      0xffffff00, // Keyword
      0xff00ff00, // Number
      0xff808000, // String
      0xff808000, // Char literal
      0xffffffff, // Punctuation
      0xff008000, // Preprocessor
      0xff00ffff, // Identifier
      0xffffffff, // Known identifier
      0xffff00ff, // Preproc identifier
      0xff808080, // Comment (single line)
      0xff404040, // Comment (multi line)
      0xff800000, // Background
      0xff0080ff, // Cursor
      0x80ffff00, // Selection
      0xa00000ff, // ErrorMarker
      0x80ff8000, // Breakpoint
      0xff808000, // Line number
      0x40000000, // Current line fill
      0x40808080, // Current line fill (inactive)
      0x40000000, // Current line edge
  }};
  return p;
}

std::string TextEditor::GetText() const { return GetText(Coordinates(), Coordinates((int)mLines.size(), 0)); }

std::vector<std::string> TextEditor::GetTextLines() const {
  std::vector<std::string> 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 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines);
  mColorRangeMin = std::min(mColorRangeMin, aFromLine);
  mColorRangeMax = std::max(mColorRangeMax, toLine);
  mColorRangeMin = std::max(0, mColorRangeMin);
  mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax);
  mCheckComments = true;
}

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((int)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;
      PaletteIndex 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 == (int)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 < (int)line.size() && line[currentIndex + 1].mChar == '\"') {
              currentIndex += 1;
              if (currentIndex < (int)line.size())
                line[currentIndex].mMultiLineComment = inComment;
            } else
              withinString = false;
          } else if (c == '\\') {
            currentIndex += 1;
            if (currentIndex < (int)line.size())
              line[currentIndex].mMultiLineComment = inComment;
          }
        } else {
          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 >= (int)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 >= (int)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<int>::max();
      mColorRangeMax = 0;
    }
    return;
  }
}

float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const {
  auto& line = mLines[aFrom.mLine];
  float distance = 0.0f;
  float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
  int colIndex = GetCharacterIndex(aFrom);
  for (size_t it = 0u; it < line.size() && it < colIndex;) {
    if (line[it].mChar == '\t') {
      distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
      ++it;
    } else {
      auto d = UTF8CharLength(line[it].mChar);
      char tempCString[7];
      int i = 0;
      for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++)
        tempCString[i] = line[it].mChar;

      tempCString[i] = '\0';
      distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x;
    }
  }

  return distance;
}

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 + (int)ceil(scrollY / mCharAdvance.y);
  auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y);

  auto left = (int)ceil(scrollX / mCharAdvance.x);
  auto right = (int)ceil((scrollX + width) / mCharAdvance.x);

  auto pos = GetActualCursorCoordinates();
  auto len = TextDistanceToLineStart(pos);

  if (pos.mLine < top)
    ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y));
  if (pos.mLine > bottom - 4)
    ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height));
  if (len + mTextStart < left + 4)
    ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4));
  if (len + mTextStart > right - 4)
    ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width));
}

int TextEditor::GetPageSize() const {
  auto height = ImGui::GetWindowHeight() - 20.0f;
  return (int)floor(height / mCharAdvance.y);
}

TextEditor::UndoRecord::UndoRecord(const std::string& aAdded, const TextEditor::Coordinates aAddedStart, const TextEditor::Coordinates aAddedEnd, const std::string& aRemoved,
                                   const TextEditor::Coordinates aRemovedStart, const TextEditor::Coordinates aRemovedEnd, TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter)
: mAdded(aAdded)
, mAddedStart(aAddedStart)
, mAddedEnd(aAddedEnd)
, mRemoved(aRemoved)
, mRemovedStart(aRemovedStart)
, mRemovedEnd(aRemovedEnd)
, mBefore(aBefore)
, mAfter(aAfter) {
  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::Javascript() {
  static bool inited = false;
  static LanguageDefinition langDef;
  if (!inited) {
    static constexpr std::string_view jsKeywords[] = {"await",     "break",  "case",    "catch", "class",   "const",   "continue",  "debugger",  "default",    "delete", "do",    "else",
                                                      "enum",      "export", "extends", "false", "finally", "for",     "function",  "if",        "implements", "import", "in",    "instanceof",
                                                      "interface", "let",    "new",     "null",  "package", "private", "protected", "prototype", "public",     "return", "super", "switch",
                                                      "static",    "this",   "throw",   "try",   "true",    "typeof",  "var",       "void",      "while",      "with",   "yield", "console"};
    for (auto& k : jsKeywords)
      langDef.mKeywords.insert(k.data());

    static constexpr std::string_view identifiers[] = {
        "Math",      "Array",       "string",  "Object", "prototype", "keys",     "values",     "entries", "call",   "apply",   "bind",   "toUpperCase", "toLowerCase", "split",
        "substring", "lastIndexOf", "indexOf", "charAt", "length",    "splice",   "slice",      "join",    "concat", "unshift", "shift",  "pop",         "push",        "abs",
        "ceil",      "floor",       "round",   "max",    "min",       "parseInt", "parseFloat", "Number",  "String", "Boolean", "random", "log",
    };
    for (auto& k : identifiers) {
      Identifier id;
      id.mDeclaration = "Built-in function";
      langDef.mIdentifiers.insert(std::make_pair(k.data(), 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.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[$_][a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
    langDef.mCommentStart = "/*";
    langDef.mCommentEnd = "*/";
    langDef.mSingleLineComment = "//";

    langDef.mCaseSensitive = true;
    langDef.mAutoIndentation = true;

    langDef.mName = "Javascript";
    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<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[$_][a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
    langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));

    langDef.mCommentStart = "--[[";
    langDef.mCommentEnd = "]]";
    langDef.mSingleLineComment = "--";

    langDef.mCaseSensitive = true;
    langDef.mAutoIndentation = false;

    langDef.mName = "Lua";

    inited = true;
  }
  return langDef;
}