diff --git a/imguicolortextedit/TextEditor.cpp b/imguicolortextedit/TextEditor.cpp index 581b5164..52fb1740 100644 --- a/imguicolortextedit/TextEditor.cpp +++ b/imguicolortextedit/TextEditor.cpp @@ -9,11 +9,8 @@ namespace tracy { -static const int cTextStart = 7; - // TODO // - multiline comments vs single-line: latter is blocking start of a ML -// - handle non-monospace fonts // - handle unicode/utf // - testing @@ -30,7 +27,7 @@ bool equals(InputIt1 first1, InputIt1 last1, } TextEditor::TextEditor() - : mLineSpacing(0.0f) + : mLineSpacing(1.0f) , mUndoIndex(0) , mTabSize(4) , mOverwrite(false) @@ -38,6 +35,8 @@ TextEditor::TextEditor() , mWithinRender(false) , mScrollToCursor(false) , mTextChanged(false) + , mTextStart(20.0f) + , mLeftMargin(10) , mColorRangeMin(0) , mColorRangeMax(0) , mSelectionMode(SelectionMode::Normal) @@ -76,10 +75,11 @@ int TextEditor::AppendBuffer(std::string& aBuffer, char chr, int aIndex) } else { - auto num = mTabSize - aIndex % mTabSize; - for (int j = num; j > 0; --j) - aBuffer.push_back(' '); - return aIndex + num; + //auto num = mTabSize - aIndex % mTabSize; + //for (int j = num; j > 0; --j) + // aBuffer.push_back(' '); + //return aIndex + num; + return aIndex; } } @@ -117,8 +117,16 @@ TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aVal if (line >= (int)mLines.size()) { - line = (int)mLines.size() - 1; - column = mLines.empty() ? 0 : (int)mLines[line].size(); + if (mLines.empty()) + { + line = 0; + column = 0; + } + else + { + line = (int)mLines.size() - 1; + column = (int)mLines[line].size(); + } } else { @@ -186,8 +194,7 @@ int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValu auto chr = *aValue; while (chr != '\0') { - if (mLines.empty()) - mLines.push_back(Line()); + assert(!mLines.empty()); if (chr == '\r') { @@ -239,23 +246,37 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPositi ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); - int columnCoord = std::max(0, (int)floor(local.x / mCharAdvance.x) - cTextStart); - int column = 0; + /* + Compute columnCoord according to text size + */ + + int columnCoord = 0; + float columnWidth = 0.0f; + std::string cumulatedString = ""; + float cumulatedStringWidth[2] = {0.0f, 0.0f}; //( [0] is the lastest, [1] is the previous. I use that trick to check where cursor is exactly (important for tabs) + if (lineNo >= 0 && lineNo < (int)mLines.size()) { - auto& line = mLines[lineNo]; - auto distance = 0; - while (distance < columnCoord && column < (int)line.size()) - { - if (line[column].mChar == '\t') - distance = (distance / mTabSize) * mTabSize + mTabSize; - else - ++distance; - ++column; + auto& line = mLines.at(lineNo); + + // First we find the hovered column coord. + while ( mTextStart + cumulatedStringWidth[0] < local.x && + columnCoord < line.size()) + { + cumulatedStringWidth[1] = cumulatedStringWidth[0]; + cumulatedString += line[columnCoord].mChar; + cumulatedStringWidth[0] = ImGui::CalcTextSize(cumulatedString.c_str()).x ; + columnWidth = (cumulatedStringWidth[0] - cumulatedStringWidth[1]); + columnCoord++; } + + // Then we reduce by 1 column coord if cursor is on the left side of the hovered column. + if( mTextStart + cumulatedStringWidth[0] - columnWidth / 2.0f > local.x) + columnCoord = std::max(0, --columnCoord); } - return Coordinates(lineNo, column); + + return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); } TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const @@ -315,7 +336,9 @@ bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const void TextEditor::RemoveLine(int aStart, int aEnd) { assert(!mReadOnly); - + assert(aEnd >= aStart); + assert(mLines.size() > aEnd - aStart); + ErrorMarkers etmp; for (auto& i : mErrorMarkers) { @@ -336,6 +359,7 @@ void TextEditor::RemoveLine(int aStart, int aEnd) mBreakpoints = std::move(btmp); mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); + assert(!mLines.empty()); mTextChanged = true; } @@ -343,12 +367,13 @@ void TextEditor::RemoveLine(int aStart, int aEnd) 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 == aIndex) + ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); + if (e.first - 1 == aIndex) continue; etmp.insert(e); } @@ -364,6 +389,7 @@ void TextEditor::RemoveLine(int aIndex) mBreakpoints = std::move(btmp); mLines.erase(mLines.begin() + aIndex); + assert(!mLines.empty()); mTextChanged = true; } @@ -410,39 +436,42 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) { mWithinRender = true; mTextChanged = false; + mCursorPositionChanged = false; - ImGuiIO& io = ImGui::GetIO(); - auto font = ImGui::GetFont(); - auto xadv = (font->IndexAdvanceX['X']); - mCharAdvance = ImVec2(io.FontGlobalScale * xadv, io.FontGlobalScale * font->FontSize + mLineSpacing); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); + ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove); + ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); + + /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ + const float fontSize = ImGui::CalcTextSize("#").x; + mCharAdvance = ImVec2(fontSize , ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); + + /* + Keyboard inputs + */ ImGui::PushAllowKeyboardFocus(true); - + ImGuiIO& io = ImGui::GetIO(); auto shift = io.KeyShift; - auto ctrl = io.KeyCtrl; - auto alt = io.KeyAlt; + 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() && ImGui::IsKeyPressed('Z')) - if (ctrl && !shift && !alt) - Undo(); - if (!IsReadOnly() && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) - if (!ctrl && !shift && alt) - Undo(); - if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('Y')) + if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('Z')) + Undo(); + else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Undo(); + else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('Y')) Redo(); - - if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) MoveUp(1, shift); else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) MoveDown(1, shift); @@ -482,8 +511,9 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) Cut(); else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) SelectAll(); - - if (!IsReadOnly()) + else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) + EnterCharacter('\n', false); + else if (!IsReadOnly() && !ctrl && !alt) { for (size_t i = 0; i < sizeof(io.InputCharacters) / sizeof(io.InputCharacters[0]); i++) { @@ -492,27 +522,33 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) { if (isprint(c) || isspace(c)) { - if (c == '\r') - c = '\n'; - EnterCharacter((char)c); + EnterCharacter((char)c, shift); } } } } } + /* + Mouse inputs + */ + if (ImGui::IsWindowHovered()) { static float lastClick = -1.0f; if (!shift && !alt) { - auto click = ImGui::IsMouseClicked(0); + auto click = ImGui::IsMouseClicked(0); auto doubleClick = ImGui::IsMouseDoubleClicked(0); - auto t = ImGui::GetTime(); + auto t = ImGui::GetTime(); auto tripleClick = click && !doubleClick && t - lastClick < io.MouseDoubleClickTime; + + /* + Left mouse button triple click + */ + if (tripleClick) { - printf("triple\n"); if (!ctrl) { mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos())); @@ -522,9 +558,13 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) lastClick = -1.0f; } + + /* + Left mouse button double click + */ + else if (doubleClick) { - printf("double\n"); if (!ctrl) { mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos())); @@ -535,11 +575,15 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); } - lastClick = ImGui::GetTime(); + lastClick = (float)ImGui::GetTime(); } + + /* + Left mouse button click + */ + else if (click) { - printf("single\n"); mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos())); if (ctrl) mSelectionMode = SelectionMode::Word; @@ -547,21 +591,16 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) mSelectionMode = SelectionMode::Normal; SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); - lastClick = ImGui::GetTime(); + lastClick = (float)ImGui::GetTime(); } + // Mouse left button dragging (=> update selection) else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) { io.WantCaptureMouse = true; mState.mCursorPosition = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos())); SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); } - else - { - } } - - //if (!ImGui::IsMouseDown(0)) - // mWordSelectionMode = false; } ColorizeInternal(); @@ -569,56 +608,76 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) static std::string buffer; auto contentSize = ImGui::GetWindowContentRegionMax(); auto drawList = ImGui::GetWindowDrawList(); - int appendIndex = 0; - int longest = cTextStart; + float longest(mTextStart); 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::CalcTextSize(buf).x + mLeftMargin; + if (!mLines.empty()) { + auto fontScale = ImGui::GetFontSize() / ImGui::GetFont()->FontSize; + float spaceSize = ImGui::CalcTextSize(" ").x + 1.0f * fontScale; + while (lineNo <= lineMax) { ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y); - ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mCharAdvance.x * cTextStart, lineStartScreenPos.y); + ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); auto& line = mLines[lineNo]; - longest = std::max(cTextStart + TextDistanceToLineStart(Coordinates(lineNo, (int) line.size())), longest); + longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, (int) line.size())), longest); auto columnNo = 0; Coordinates lineStartCoord(lineNo, 0); Coordinates lineEndCoord(lineNo, (int)line.size()); - int sstart = -1; - int ssend = -1; + /* + Draw Selected area + */ + + 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; + 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; + ssend += mCharAdvance.x; if (sstart != -1 && ssend != -1 && sstart < ssend) { - ImVec2 vstart(lineStartScreenPos.x + (mCharAdvance.x) * (sstart + cTextStart), lineStartScreenPos.y); - ImVec2 vend(lineStartScreenPos.x + (mCharAdvance.x) * (ssend + cTextStart), lineStartScreenPos.y + mCharAdvance.y); + 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]); } - static char buf[16]; + /* + Draw break point + */ + auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); - if (mBreakpoints.find(lineNo + 1) != mBreakpoints.end()) + 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 marker + */ + auto errorIt = mErrorMarkers.find(lineNo + 1); if (errorIt != mErrorMarkers.end()) { @@ -639,9 +698,17 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) } } - auto chars = snprintf(buf, 16, "%6d", lineNo + 1); - assert(chars >= 0 && chars < 16); - drawList->AddText(ImVec2(lineStartScreenPos.x /*+ mCharAdvance.x * 1*/, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); + /* + Draw line number (right aligned) + */ + + snprintf(buf, 16, "%d ", lineNo + 1); + auto lineNoWidth = ImGui::CalcTextSize(buf).x; + drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); + + /* + Highlight the current line (where the cursor is). + */ if (mState.mCursorPosition.mLine == lineNo) { @@ -654,7 +721,7 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f); } - int cx = TextDistanceToLineStart(mState.mCursorPosition); + float cx = TextDistanceToLineStart(mState.mCursorPosition); if (focused) { @@ -664,8 +731,8 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) auto elapsed = std::chrono::duration_cast(diff).count(); if (elapsed > 400) { - ImVec2 cstart(lineStartScreenPos.x + mCharAdvance.x * (cx + cTextStart), lineStartScreenPos.y); - ImVec2 cend(lineStartScreenPos.x + mCharAdvance.x * (cx + cTextStart) + (mOverwrite ? mCharAdvance.x : 1.0f), lineStartScreenPos.y + mCharAdvance.y); + ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); + ImVec2 cend(textScreenPos.x + cx + (mOverwrite ? mCharAdvance.x : 1.0f), lineStartScreenPos.y + mCharAdvance.y); drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); if (elapsed > 800) timeStart = timeEnd; @@ -673,61 +740,72 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) } } - appendIndex = 0; + /* + Draw Text + */ + auto prevColor = line.empty() ? PaletteIndex::Default : (line[0].mMultiLineComment ? PaletteIndex::MultiLineComment : line[0].mColorIndex); + ImVec2 bufferOffset; for (auto& glyph : line) { auto color = glyph.mMultiLineComment ? PaletteIndex::MultiLineComment : glyph.mColorIndex; - if (color != prevColor && !buffer.empty()) + if ((color != prevColor || glyph.mChar == '\t') && !buffer.empty()) { - drawList->AddText(textScreenPos, mPalette[(uint8_t)prevColor], buffer.c_str()); - textScreenPos.x += mCharAdvance.x * buffer.length(); + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, mPalette[(uint8_t)prevColor], buffer.c_str()); + auto textSize = ImGui::CalcTextSize(buffer.c_str()); + bufferOffset.x += textSize.x + 1.0f * fontScale; buffer.clear(); - prevColor = color; } - appendIndex = AppendBuffer(buffer, glyph.mChar, appendIndex); + prevColor = color; + + if (glyph.mChar == '\t') + bufferOffset.x = (1.0f * fontScale + std::floor((1.0f + bufferOffset.x)) / (float(mTabSize) * spaceSize)) * (float(mTabSize) * spaceSize); + else + AppendBuffer(buffer, glyph.mChar, 0); ++columnNo; } if (!buffer.empty()) { - drawList->AddText(textScreenPos, mPalette[(uint8_t)prevColor], buffer.c_str()); + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, mPalette[(uint8_t)prevColor], buffer.c_str()); buffer.clear(); } - appendIndex = 0; - lineStartScreenPos.y += mCharAdvance.y; - textScreenPos.x = lineStartScreenPos.x + mCharAdvance.x * cTextStart; - textScreenPos.y = lineStartScreenPos.y; + ++lineNo; } - - auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); - if (!id.empty()) + + if (ImGui::IsMousePosValid()) { - auto it = mLanguageDefinition.mIdentifiers.find(id); - if (it != mLanguageDefinition.mIdentifiers.end()) + auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); + if (!id.empty()) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(it->second.mDeclaration.c_str()); - ImGui::EndTooltip(); - } - else - { - auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); - if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) + auto it = mLanguageDefinition.mIdentifiers.find(id); + if (it != mLanguageDefinition.mIdentifiers.end()) { ImGui::BeginTooltip(); - ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::TextUnformatted(it->second.mDeclaration.c_str()); ImGui::EndTooltip(); } + else + { + auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); + if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) + { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } + } } } } - ImGui::Dummy(ImVec2((longest + 2) * mCharAdvance.x, mLines.size() * mCharAdvance.y)); + ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); if (mScrollToCursor) { @@ -744,22 +822,24 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) mWithinRender = false; } -void TextEditor::SetText(const char* aText) +void TextEditor::SetText(const std::string & aText) { mLines.clear(); - while( *aText ) + mLines.push_back(Line()); + for (auto chr : aText) { - if (mLines.empty()) - mLines.push_back(Line()); - if (*aText == '\n') + if (chr == '\r') + { + // ignore the carriage return character + } + else if (chr == '\n') mLines.push_back(Line()); else { - mLines.back().push_back(Glyph(*aText, PaletteIndex::Default)); + mLines.back().push_back(Glyph(chr, PaletteIndex::Default)); } mTextChanged = true; - aText++; } mUndoBuffer.clear(); @@ -767,7 +847,7 @@ void TextEditor::SetText(const char* aText) Colorize(); } -void TextEditor::EnterCharacter(Char aChar) +void TextEditor::EnterCharacter(Char aChar, bool aShift) { assert(!mReadOnly); @@ -777,26 +857,100 @@ void TextEditor::EnterCharacter(Char aChar) if (HasSelection()) { - u.mRemoved = GetSelectedText(); - u.mRemovedStart = mState.mSelectionStart; - u.mRemovedEnd = mState.mSelectionEnd; - DeleteSelection(); + if (aChar == '\t') + { + auto start = mState.mSelectionStart; + auto end = mState.mSelectionEnd; + + 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; + end.mColumn = mLines[end.mLine].size(); + } + + 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[0].mChar == '\t') + { + line.erase(line.begin()); + if (i == end.mLine && end.mColumn > 0) + end.mColumn--; + modified = true; + } + else for (int j = 0; j < mTabSize && line[0].mChar == ' '; j++) + { + line.erase(line.begin()); + if (i == end.mLine && end.mColumn > 0) + end.mColumn--; + modified = true; + } + } + else + { + line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background)); + if (i == end.mLine) + ++end.mColumn; + modified = true; + } + } + + if (modified) + { + u.mAddedStart = start; + u.mAddedEnd = end; + u.mAdded = GetText(start, end); + + mTextChanged = true; + + AddUndo(u); + EnsureCursorVisible(); + } + + return; + } + else + { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } } auto coord = GetActualCursorCoordinates(); u.mAddedStart = coord; - if (mLines.empty()) - mLines.push_back(Line()); + assert(!mLines.empty()); if (aChar == '\n') { InsertLine(coord.mLine + 1); auto& line = mLines[coord.mLine]; auto& newLine = mLines[coord.mLine + 1]; - newLine.insert(newLine.begin(), line.begin() + coord.mColumn, line.end()); + + if (mLanguageDefinition.mAutoIndentation) + { + for (size_t it = 0; it < line.size() && isblank(line[it].mChar); ++it) + newLine.push_back(line[it]); + } + + const size_t whitespaceSize = newLine.size(); + newLine.insert(newLine.end(), line.begin() + coord.mColumn, line.end()); line.erase(line.begin() + coord.mColumn, line.begin() + line.size()); - mState.mCursorPosition = Coordinates(coord.mLine + 1, 0); + SetCursorPosition(Coordinates(coord.mLine + 1, (int)whitespaceSize)); } else { @@ -805,8 +959,7 @@ void TextEditor::EnterCharacter(Char aChar) line[coord.mColumn] = Glyph(aChar, PaletteIndex::Default); else line.insert(line.begin() + coord.mColumn, Glyph(aChar, PaletteIndex::Default)); - mState.mCursorPosition = coord; - ++mState.mCursorPosition.mColumn; + SetCursorPosition(Coordinates(coord.mLine, coord.mColumn + 1)); } mTextChanged = true; @@ -828,9 +981,10 @@ void TextEditor::SetReadOnly(bool aValue) void TextEditor::SetCursorPosition(const Coordinates & aPosition) { - //if (mState.mCursorPosition != aPosition) + if (mState.mCursorPosition != aPosition) { mState.mCursorPosition = aPosition; + mCursorPositionChanged = true; EnsureCursorVisible(); } } @@ -851,6 +1005,9 @@ void TextEditor::SetSelectionEnd(const Coordinates & aPosition) 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 (aStart > aEnd) @@ -878,6 +1035,10 @@ void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aE default: break; } + + if (mState.mSelectionStart != oldSelStart || + mState.mSelectionEnd != oldSelEnd) + mCursorPositionChanged = true; } void TextEditor::InsertText(const std::string & aValue) @@ -1205,7 +1366,6 @@ void TextEditor::BackSpace() if (mLines.empty()) return; - UndoRecord u; u.mBefore = mState; @@ -1228,13 +1388,19 @@ void TextEditor::BackSpace() return; u.mRemoved = '\n'; - u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, (int)mLines[pos.mLine - 1].size()); Advance(u.mRemovedEnd); auto& line = mLines[mState.mCursorPosition.mLine]; auto& prevLine = mLines[mState.mCursorPosition.mLine - 1]; auto prevSize = (int)prevLine.size(); 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; @@ -1466,6 +1632,12 @@ std::string TextEditor::GetSelectedText() const return GetText(mState.mSelectionStart, mState.mSelectionEnd); } +std::string TextEditor::GetCurrentLineText()const +{ + auto lineLength = (int) mLines[mState.mCursorPosition.mLine].size(); + return GetText(Coordinates(mState.mCursorPosition.mLine, 0), Coordinates(mState.mCursorPosition.mLine, lineLength)); +} + void TextEditor::ProcessInputs() { } @@ -1486,63 +1658,108 @@ void TextEditor::ColorizeRange(int aFromLine, int 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) { bool preproc = false; auto& line = mLines[i]; - buffer.clear(); - for (auto g : mLines[i]) + + if (line.empty()) + continue; + + buffer.resize(line.size()); + for (size_t j = 0; j < line.size(); ++j) { - buffer.push_back(g.mChar); - g.mColorIndex = PaletteIndex::Default; + auto& col = line[j]; + buffer[j] = col.mChar; + col.mColorIndex = PaletteIndex::Default; } - - std::match_results results; - auto last = buffer.cend(); - for (auto first = buffer.cbegin(); first != last; ++first) + + const char * bufferBegin = &buffer.front(); + const char * bufferEnd = bufferBegin + buffer.size(); + + auto last = bufferEnd; + + for (auto first = bufferBegin; first != last; ) { - for (auto& p : mRegexList) + const char * token_begin = nullptr; + const char * token_end = nullptr; + PaletteIndex token_color = PaletteIndex::Default; + + bool hasTokenizeResult = false; + + if (mLanguageDefinition.mTokenize != nullptr) { - if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous)) + 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) { - auto v = *results.begin(); - auto start = v.first - buffer.begin(); - auto end = v.second - buffer.begin(); - auto id = buffer.substr(start, end - start); - auto color = p.second; - if (color == PaletteIndex::Identifier) + if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous)) { - if (!mLanguageDefinition.mCaseSensitive) - std::transform(id.begin(), id.end(), id.begin(), ::toupper); - - if (!preproc) - { - if (mLanguageDefinition.mKeywords.find(id) != mLanguageDefinition.mKeywords.end()) - color = PaletteIndex::Keyword; - else if (mLanguageDefinition.mIdentifiers.find(id) != mLanguageDefinition.mIdentifiers.end()) - color = PaletteIndex::KnownIdentifier; - else if (mLanguageDefinition.mPreprocIdentifiers.find(id) != mLanguageDefinition.mPreprocIdentifiers.end()) - color = PaletteIndex::PreprocIdentifier; - } - else - { - if (mLanguageDefinition.mPreprocIdentifiers.find(id) != mLanguageDefinition.mPreprocIdentifiers.end()) - color = PaletteIndex::PreprocIdentifier; - else - color = PaletteIndex::Identifier; - } + hasTokenizeResult = true; + + auto& v = *results.begin(); + token_begin = v.first; + token_end = v.second; + token_color = p.second; + break; } - else if (color == PaletteIndex::Preprocessor) - { - preproc = true; - } - for (int j = (int)start; j < (int)end; ++j) - line[j].mColorIndex = color; - first += end - start - 1; - 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 (!preproc) + { + 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; + else + token_color = PaletteIndex::Identifier; + } + } + else if (token_color == PaletteIndex::Preprocessor) + { + preproc = true; + } + + for (int j = 0; j < token_length; ++j) + line[(token_begin - bufferBegin) + j].mColorIndex = token_color; + + first = token_end; + } } } } @@ -1562,7 +1779,7 @@ void TextEditor::ColorizeInternal() auto& line = mLines[i.mLine]; if (!line.empty()) { - auto g = line[i.mColumn]; + auto& g = line[i.mColumn]; auto c = g.mChar; bool inComment = commentStart <= i; @@ -1623,7 +1840,8 @@ void TextEditor::ColorizeInternal() if (mColorRangeMin < mColorRangeMax) { - int to = std::min(mColorRangeMin + 10, mColorRangeMax); + const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000; + const int to = std::min(mColorRangeMin + increment, mColorRangeMax); ColorizeRange(mColorRangeMin, to); mColorRangeMin = to; @@ -1636,13 +1854,28 @@ void TextEditor::ColorizeInternal() } } -int TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const +float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const { auto& line = mLines[aFrom.mLine]; - auto len = 0; + float distance = 0.0f; + auto fontScale = ImGui::GetFontSize() / ImGui::GetFont()->FontSize; + float spaceSize = ImGui::CalcTextSize(" ").x + 1.0f * fontScale; for (size_t it = 0u; it < line.size() && it < (unsigned)aFrom.mColumn; ++it) - len = line[it].mChar == '\t' ? (len / mTabSize) * mTabSize + mTabSize : len + 1; - return len; + { + if (line[it].mChar == '\t') + { + distance = (1.0f * fontScale + std::floor((1.0f + distance)) / (float(mTabSize) * spaceSize)) * (float(mTabSize) * spaceSize); + } + else + { + char tempCString[2]; + tempCString[0] = line[it].mChar; + tempCString[1] = '\0'; + distance += ImGui::CalcTextSize(tempCString).x + 1.0f * fontScale; + } + } + + return distance; } void TextEditor::EnsureCursorVisible() @@ -1672,10 +1905,10 @@ void TextEditor::EnsureCursorVisible() 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 + cTextStart < left + 4) - ImGui::SetScrollX(std::max(0.0f, (len + cTextStart - 4) * mCharAdvance.x)); - if (len + cTextStart > right - 4) - ImGui::SetScrollX(std::max(0.0f, (len + cTextStart + 4) * mCharAdvance.x - width)); + 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 @@ -1745,7 +1978,265 @@ void TextEditor::UndoRecord::Redo(TextEditor * aEditor) aEditor->EnsureCursorVisible(); } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::CPlusPlus() +static bool TokenizeCStyleComment(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) +{ + if (*in_begin != '/') + return false; + + if (in_begin + 1 < in_end && in_begin[1] == '/') + { + out_begin = in_begin; + out_end = in_end; + return true; + } + + return false; +} + +static bool TokenizeCStylePreprocessorDirective(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) +{ + if (*in_begin != '#') + return false; + + const char * p = in_begin + 1; + + while (p < in_end && isblank(*p)) + p++; + + bool hasText = false; + + while (p < in_end && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')) + { + hasText = true; + p++; + } + + if (hasText) + { + out_begin = in_begin; + out_end = p; + return true; + } + + return false; +} + +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) +{ + 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; @@ -1772,22 +2263,43 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::CPlusPlus() id.mDeclaration = "Built-in function"; langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); } - - langDef.mTokenRegexStrings.push_back(std::make_pair("//.*", PaletteIndex::Comment)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - + + langDef.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 && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleComment(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Comment; + else if (TokenizeCStylePreprocessorDirective(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Preprocessor; + 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.mCaseSensitive = true; + langDef.mAutoIndentation = true; langDef.mName = "C++"; @@ -1796,7 +2308,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::CPlusPlus() return langDef; } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::HLSL() +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() { static bool inited = false; static LanguageDefinition langDef; @@ -1859,6 +2371,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::HLSL() langDef.mCommentEnd = "*/"; langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; langDef.mName = "HLSL"; @@ -1867,7 +2380,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::HLSL() return langDef; } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::GLSL() +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() { static bool inited = false; static LanguageDefinition langDef; @@ -1907,6 +2420,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::GLSL() langDef.mCommentEnd = "*/"; langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; langDef.mName = "GLSL"; @@ -1915,7 +2429,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::GLSL() return langDef; } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::C() +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() { static bool inited = false; static LanguageDefinition langDef; @@ -1940,21 +2454,42 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::C() langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); } - langDef.mTokenRegexStrings.push_back(std::make_pair("//.*", PaletteIndex::Comment)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - + langDef.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 && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleComment(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Comment; + else if (TokenizeCStylePreprocessorDirective(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Preprocessor; + 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.mCaseSensitive = true; + langDef.mAutoIndentation = true; langDef.mName = "C"; @@ -1963,7 +2498,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::C() return langDef; } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::SQL() +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() { static bool inited = false; static LanguageDefinition langDef; @@ -2018,6 +2553,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::SQL() langDef.mCommentEnd = "*/"; langDef.mCaseSensitive = false; + langDef.mAutoIndentation = false; langDef.mName = "SQL"; @@ -2026,7 +2562,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::SQL() return langDef; } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::AngelScript() +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() { static bool inited = false; static LanguageDefinition langDef; @@ -2067,6 +2603,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::AngelScript() langDef.mCommentEnd = "*/"; langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; langDef.mName = "AngelScript"; @@ -2075,7 +2612,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::AngelScript() return langDef; } -TextEditor::LanguageDefinition TextEditor::LanguageDefinition::Lua() +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() { static bool inited = false; static LanguageDefinition langDef; @@ -2120,6 +2657,7 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::Lua() langDef.mCommentEnd = "\\]\\]"; langDef.mCaseSensitive = true; + langDef.mAutoIndentation = false; langDef.mName = "Lua"; @@ -2128,4 +2666,4 @@ TextEditor::LanguageDefinition TextEditor::LanguageDefinition::Lua() return langDef; } -} +} \ No newline at end of file diff --git a/imguicolortextedit/TextEditor.h b/imguicolortextedit/TextEditor.h index d1a2c92f..d55e27e1 100644 --- a/imguicolortextedit/TextEditor.h +++ b/imguicolortextedit/TextEditor.h @@ -133,7 +133,7 @@ public: struct Glyph { Char mChar; - PaletteIndex mColorIndex : 7; + PaletteIndex mColorIndex = PaletteIndex::Default; bool mMultiLineComment : 1; Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), mMultiLineComment(false) {} @@ -146,24 +146,33 @@ public: { typedef std::pair TokenRegexString; typedef std::vector TokenRegexStrings; + typedef bool (*TokenizeCallback)(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex); std::string mName; Keywords mKeywords; Identifiers mIdentifiers; Identifiers mPreprocIdentifiers; std::string mCommentStart, mCommentEnd; + bool mAutoIndentation; + + TokenizeCallback mTokenize; TokenRegexStrings mTokenRegexStrings; bool mCaseSensitive; - - static LanguageDefinition CPlusPlus(); - static LanguageDefinition HLSL(); - static LanguageDefinition GLSL(); - static LanguageDefinition C(); - static LanguageDefinition SQL(); - static LanguageDefinition AngelScript(); - static LanguageDefinition Lua(); + + LanguageDefinition() + : mTokenize(nullptr) + { + } + + static const LanguageDefinition& CPlusPlus(); + static const LanguageDefinition& HLSL(); + static const LanguageDefinition& GLSL(); + static const LanguageDefinition& C(); + static const LanguageDefinition& SQL(); + static const LanguageDefinition& AngelScript(); + static const LanguageDefinition& Lua(); }; TextEditor(); @@ -179,16 +188,18 @@ public: void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); - void SetText(const char* aText); + void SetText(const std::string& aText); std::string GetText() const; std::string GetSelectedText() const; - + std::string GetCurrentLineText()const; + int GetTotalLines() const { return (int)mLines.size(); } bool IsOverwrite() const { return mOverwrite; } void SetReadOnly(bool aValue); bool IsReadOnly() const { return mReadOnly; } bool IsTextChanged() const { return mTextChanged; } + bool IsCursorPositionChanged() const { return mCursorPositionChanged; } Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } void SetCursorPosition(const Coordinates& aPosition); @@ -275,7 +286,7 @@ private: void Colorize(int aFromLine = 0, int aCount = -1); void ColorizeRange(int aFromLine = 0, int aToLine = 0); void ColorizeInternal(); - int TextDistanceToLineStart(const Coordinates& aFrom) const; + float TextDistanceToLineStart(const Coordinates& aFrom) const; void EnsureCursorVisible(); int GetPageSize() const; int AppendBuffer(std::string& aBuffer, char chr, int aIndex); @@ -293,7 +304,7 @@ private: void RemoveLine(int aStart, int aEnd); void RemoveLine(int aIndex); Line& InsertLine(int aIndex); - void EnterCharacter(Char aChar); + void EnterCharacter(Char aChar, bool aShift); void BackSpace(); void DeleteSelection(); std::string GetWordUnderCursor() const; @@ -311,6 +322,9 @@ private: bool mWithinRender; bool mScrollToCursor; bool mTextChanged; + float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor. + int mLeftMargin; + bool mCursorPositionChanged; int mColorRangeMin, mColorRangeMax; SelectionMode mSelectionMode;