Files
yaze/src/app/gui/modules/text_editor.cc

3767 lines
129 KiB
C++

#include "text_editor.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <regex>
#include <string>
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h" // for imGui::GetCurrentWindow()
// 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))
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]);
if (elapsed > 800) mStartTime = timeEnd;
}
}
}
// 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,
ImGuiWindowFlags_HorizontalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar |
ImGuiWindowFlags_NoMove);
if (mHandleKeyboardInputs) {
HandleKeyboardInputs();
ImGui::PushAllowKeyboardFocus(true);
}
if (mHandleMouseInputs) HandleMouseInputs();
ColorizeInternal();
Render();
if (mHandleKeyboardInputs) ImGui::PopAllowKeyboardFocus();
if (!mIgnoreImGuiChild) ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
mWithinRender = false;
}
void TextEditor::SetText(const std::string& aText) {
mLines.clear();
mLines.emplace_back(Line());
for (auto chr : aText) {
if (chr == '\r') {
// ignore the carriage return character
} else if (chr == '\n')
mLines.emplace_back(Line());
else {
mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default));
}
}
mTextChanged = true;
mScrollToTop = true;
mUndoBuffer.clear();
mUndoIndex = 0;
Colorize();
}
void TextEditor::SetTextLines(const std::vector<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::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;
}