diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 043e88cd5..fbcf613bf 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -2665,7 +2665,7 @@ namespace hex::plugin::builtin { file.writeString(wolv::util::trim(m_textEditor.get(provider).getText())); m_patternFileDirty.get(provider) = false; auto loadedPath = m_changeTracker.get(provider).getPath(); - if ((loadedPath.empty() && loadedPath != path) || (!loadedPath.empty() && !trackFile)) + if ((loadedPath.empty() && loadedPath != path) || (!loadedPath.empty() && !trackFile) || loadedPath == path) m_changeTracker.get(provider).stopTracking(); if (trackFile) { diff --git a/plugins/ui/include/ui/text_editor.hpp b/plugins/ui/include/ui/text_editor.hpp index f81b89a51..66081e279 100644 --- a/plugins/ui/include/ui/text_editor.hpp +++ b/plugins/ui/include/ui/text_editor.hpp @@ -324,13 +324,16 @@ namespace hex::ui { std::string m_colors; std::string m_flags; bool m_colorized = false; + i32 m_lineMaxColumn; - Line() : m_chars(), m_colors(), m_flags(), m_colorized(false) {} + Line() : m_chars(), m_colors(), m_flags(), m_colorized(false), m_lineMaxColumn(-1) {} explicit Line(const char *line) { Line(std::string(line)); } - explicit Line(const std::string &line) : m_chars(line), m_colors(std::string(line.size(), 0x00)), m_flags(std::string(line.size(), 0x00)), m_colorized(false) {} - Line(const Line &line) : m_chars(line.m_chars), m_colors(line.m_colors), m_flags(line.m_flags), m_colorized(line.m_colorized) {} + explicit Line(const std::string &line) : + m_chars(line), m_colors(std::string(line.size(), 0x00)), m_flags(std::string(line.size(), 0x00)), m_colorized(false), m_lineMaxColumn(getMaxCharColumn()) {} + Line(const Line &line) : m_chars(line.m_chars), m_colors(line.m_colors), m_flags(line.m_flags), m_colorized(line.m_colorized), m_lineMaxColumn(line.m_lineMaxColumn) {} i32 getCharacterColumn(i32 index) const; + i32 getMaxCharColumn() const; LineIterator begin(); LineIterator end(); Line &operator=(const Line &line); @@ -481,7 +484,7 @@ namespace hex::ui { void setLongestLineLength(u64 line) { m_longestLineLength = line; } u64 getLongestLineLength() const { return m_longestLineLength; } void setTopMarginChanged(i32 newMargin); - void setFocusAtCoords(const Coordinates &coords); + void setFocusAtCoords(const Coordinates &coords, bool ensureVisible = false); void clearErrorMarkers(); void clearActionables(); private: @@ -533,10 +536,10 @@ namespace hex::ui { void setOverwrite(bool value) { m_overwrite = value; } bool isOverwrite() const { return m_overwrite; } void setText(const std::string &text, bool undo = false); - std::string getText() const; + std::string getText(); std::vector getTextLines() const; - std::string getSelectedText() const; - std::string getLineText(i32 line) const; + std::string getSelectedText(); + std::string getLineText(i32 line); inline void setTextChanged(bool value) { m_textChanged = value; } inline bool isTextChanged() { return m_textChanged; } inline void setReadOnly(bool value) { m_readOnly = value; } @@ -545,7 +548,7 @@ namespace hex::ui { inline void setHandleKeyboardInputs(bool value) { m_handleKeyboardInputs = value; } inline bool isHandleKeyboardInputsEnabled() const { return m_handleKeyboardInputs; } private: - std::string getText(const Selection &selection) const; + std::string getText(const Selection &selection); void deleteRange(const Selection &selection); i32 insertTextAt(Coordinates &where, const std::string &value); void removeLine(i32 start, i32 end); @@ -568,18 +571,18 @@ namespace hex::ui { void moveEnd(bool select = false); void moveToMatchedBracket(bool select = false); void setScrollY(); - Coordinates getCursorPosition() const { return setCoordinates(m_state.m_cursorPosition); } + Coordinates getCursorPosition() { return setCoordinates(m_state.m_cursorPosition); } void setCursorPosition(const Coordinates &position); void setCursorPosition(); private: - Coordinates setCoordinates(const Coordinates &value) const; - Coordinates setCoordinates(i32 line, i32 column) const; - Selection setCoordinates(const Selection &value) const; + Coordinates setCoordinates(const Coordinates &value); + Coordinates setCoordinates(i32 line, i32 column); + Selection setCoordinates(const Selection &value); void advance(Coordinates &coordinates) const; - Coordinates findWordStart(const Coordinates &from) const; - Coordinates findWordEnd(const Coordinates &from) const; - Coordinates findPreviousWord(const Coordinates &from) const; - Coordinates findNextWord(const Coordinates &from) const; + Coordinates findWordStart(const Coordinates &from); + Coordinates findWordEnd(const Coordinates &from); + Coordinates findPreviousWord(const Coordinates &from); + Coordinates findNextWord(const Coordinates &from); u32 skipSpaces(const Coordinates &from); //Support public: @@ -614,13 +617,13 @@ namespace hex::ui { static TextEditor::Coordinates stringIndexToCoordinates(i32 strIndex, const std::string &input); private: - Coordinates screenPosToCoordinates(const ImVec2 &position) const; + Coordinates screenPosToCoordinates(const ImVec2 &position); Coordinates lineCoordsToIndexCoords(const Coordinates &coordinates) const; i32 lineCoordinatesToIndex(const Coordinates &coordinates) const; - Coordinates getCharacterCoordinates(i32 line, i32 index) const; - i32 getLineCharacterCount(i32 line) const; + Coordinates getCharacterCoordinates(i32 line, i32 index); + i32 getLineCharacterCount(i32 line); u64 getLineByteCount(i32 line) const; - i32 getLineMaxColumn(i32 line) const; + i32 getLineMaxColumn(i32 line); public: FindReplaceHandler m_findReplaceHandler; @@ -680,6 +683,7 @@ namespace hex::ui { bool m_raiseContextMenu = false; Coordinates m_focusAtCoords = {}; bool m_updateFocus = false; + bool m_ensureCursorVisible = false; std::vector m_clickableText; diff --git a/plugins/ui/source/ui/text_editor/editor.cpp b/plugins/ui/source/ui/text_editor/editor.cpp index dbbe95c35..e20dc39f6 100644 --- a/plugins/ui/source/ui/text_editor/editor.cpp +++ b/plugins/ui/source/ui/text_editor/editor.cpp @@ -29,7 +29,7 @@ namespace hex::ui { TextEditor::~TextEditor() { } - std::string TextEditor::getText(const Selection &from) const { + std::string TextEditor::getText(const Selection &from) { std::string result; auto selection = setCoordinates(from); auto columns = selection.getSelectedColumns(); @@ -734,7 +734,7 @@ namespace hex::ui { refreshSearchResults(); } - std::string TextEditor::getText() const { + std::string TextEditor::getText() { auto start = setCoordinates(0, 0); auto end = setCoordinates(-1, -1); if (start == Invalid || end == Invalid) @@ -755,11 +755,11 @@ namespace hex::ui { return result; } - std::string TextEditor::getSelectedText() const { + std::string TextEditor::getSelectedText() { return getText(m_state.m_selection); } - std::string TextEditor::getLineText(i32 line) const { + std::string TextEditor::getLineText(i32 line) { auto sanitizedLine = setCoordinates(line, 0); auto endLine = setCoordinates(line, -1); if (sanitizedLine == Invalid || endLine == Invalid) diff --git a/plugins/ui/source/ui/text_editor/navigate.cpp b/plugins/ui/source/ui/text_editor/navigate.cpp index edd0102cf..15b8bf3d7 100644 --- a/plugins/ui/source/ui/text_editor/navigate.cpp +++ b/plugins/ui/source/ui/text_editor/navigate.cpp @@ -328,7 +328,7 @@ namespace hex::ui { setCursorPosition(m_state.m_selection.m_end); } - TextEditor::Coordinates TextEditor::setCoordinates(i32 line, i32 column) const { + TextEditor::Coordinates TextEditor::setCoordinates(i32 line, i32 column) { if (isEmpty()) return Coordinates(0, 0); @@ -348,12 +348,12 @@ namespace hex::ui { return result; } - TextEditor::Coordinates TextEditor::setCoordinates(const Coordinates &value) const { + TextEditor::Coordinates TextEditor::setCoordinates(const Coordinates &value) { auto sanitized_value = setCoordinates(value.m_line, value.m_column); return sanitized_value; } - TextEditor::Selection TextEditor::setCoordinates(const Selection &value) const { + TextEditor::Selection TextEditor::setCoordinates(const Selection &value) { auto start = setCoordinates(value.m_start); auto end = setCoordinates(value.m_end); if (start == Invalid || end == Invalid) @@ -379,7 +379,7 @@ namespace hex::ui { coordinates.m_column += incr; } - TextEditor::Coordinates TextEditor::findWordStart(const Coordinates &from) const { + TextEditor::Coordinates TextEditor::findWordStart(const Coordinates &from) { Coordinates at = setCoordinates(from); if (at.m_line >= (i32) m_lines.size()) return at; @@ -400,7 +400,7 @@ namespace hex::ui { return getCharacterCoordinates(at.m_line, charIndex); } - TextEditor::Coordinates TextEditor::findWordEnd(const Coordinates &from) const { + TextEditor::Coordinates TextEditor::findWordEnd(const Coordinates &from) { Coordinates at = from; if (at.m_line >= (i32) m_lines.size()) return at; @@ -421,7 +421,7 @@ namespace hex::ui { return getCharacterCoordinates(at.m_line, charIndex); } - TextEditor::Coordinates TextEditor::findNextWord(const Coordinates &from) const { + TextEditor::Coordinates TextEditor::findNextWord(const Coordinates &from) { Coordinates at = from; if (at.m_line >= (i32) m_lines.size()) return at; @@ -443,7 +443,7 @@ namespace hex::ui { return getCharacterCoordinates(at.m_line, charIndex); } - TextEditor::Coordinates TextEditor::findPreviousWord(const Coordinates &from) const { + TextEditor::Coordinates TextEditor::findPreviousWord(const Coordinates &from) { Coordinates at = from; if (at.m_line >= (i32) m_lines.size()) return at; diff --git a/plugins/ui/source/ui/text_editor/render.cpp b/plugins/ui/source/ui/text_editor/render.cpp index c09b6bf50..fbe55738c 100644 --- a/plugins/ui/source/ui/text_editor/render.cpp +++ b/plugins/ui/source/ui/text_editor/render.cpp @@ -18,9 +18,10 @@ namespace hex::ui { m_topMarginChanged = true; } - void TextEditor::setFocusAtCoords(const Coordinates &coords) { + void TextEditor::setFocusAtCoords(const Coordinates &coords, bool ensureVisible) { m_focusAtCoords = coords; m_updateFocus = true; + m_ensureCursorVisible = ensureVisible; } void TextEditor::clearErrorMarkers() { @@ -286,11 +287,15 @@ namespace hex::ui { continue; } auto colors = m_lines[lineNo].m_colors; - u64 colorsSize = colors.size(); - u64 i = skipSpaces(setCoordinates(lineNo, 0)); - while (i < colorsSize) { + u64 colorsSize = std::min((u64)std::floor(textEditorSize.x / m_charAdvance.x), (u64) colors.size()); + u64 i = ImGui::GetScrollX() / m_charAdvance.x; + u64 maxI = i + colorsSize; + while (i < maxI) { char color = std::clamp(colors[i], (char) PaletteIndex::Default, (char) ((u8) PaletteIndex::Max - 1)); - u32 tokenLength = std::clamp((u64) (colors.find_first_not_of(color, i) - i),(u64) 1, colorsSize - i); + auto index = colors.find_first_not_of(color, i); + index -= i; + + u32 tokenLength = std::clamp((u64) index,(u64) 1, maxI - i); if (m_updateFocus) setFocus(); auto lineStart = setCoordinates(lineNo, i); @@ -314,7 +319,9 @@ namespace hex::ui { void TextEditor::setFocus() { m_state.m_cursorPosition = m_focusAtCoords; resetCursorBlinkTime(); - ensureCursorVisible(); + if (m_ensureCursorVisible) + ensureCursorVisible(); + if (!this->m_readOnly) ImGui::SetKeyboardFocusHere(0); m_updateFocus = false; @@ -364,7 +371,7 @@ namespace hex::ui { void TextEditor::drawLineNumbers(ImVec2 position, float lineNo, const ImVec2 &contentSize, bool focused, ImDrawList *drawList) { ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y); ImVec2 lineNoStartScreenPos = ImVec2(position.x, m_topMargin + s_cursorScreenPosition.y + std::floor(lineNo) * m_charAdvance.y); - auto start = ImVec2(lineNoStartScreenPos.x + m_lineNumberFieldWidth, lineStartScreenPos.y); + auto start = ImVec2(lineNoStartScreenPos.x + m_lineNumberFieldWidth - m_charAdvance.x / 2, lineStartScreenPos.y); i32 totalDigitCount = std::floor(std::log10(m_lines.size())) + 1; ImGui::SetCursorScreenPos(position); if (!m_ignoreImGuiChild) @@ -380,13 +387,18 @@ namespace hex::ui { else m_breakpoints.insert(lineNo + 1); m_breakPointsChanged = true; - auto cursorPosition = setCoordinates(lineNo, 0); - if (cursorPosition == Invalid) - return; + setFocusAtCoords(m_state.m_cursorPosition, false); + } + auto color = m_palette[(i32) PaletteIndex::LineNumber]; - m_state.m_cursorPosition = cursorPosition; - - jumpToCoords(m_state.m_cursorPosition); + if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor) { + color = m_palette[(i32) PaletteIndex::Cursor]; + // Highlight the current line (where the cursor is) + if (!hasSelection()) { + auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + m_lineNumberFieldWidth, lineStartScreenPos.y + m_charAdvance.y); + drawList->AddRectFilled(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) (focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); + drawList->AddRect(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) PaletteIndex::CurrentLineEdge], 1.0f); + } } // Draw breakpoints if (m_breakpoints.count(lineNo + 1) != 0) { @@ -395,20 +407,9 @@ namespace hex::ui { drawList->AddCircleFilled(start + ImVec2(0, m_charAdvance.y) / 2, m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Breakpoint]); drawList->AddCircle(start + ImVec2(0, m_charAdvance.y) / 2, m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Default]); - drawList->AddText(ImVec2(lineNoStartScreenPos.x + m_leftMargin, lineStartScreenPos.y), m_palette[(i32) PaletteIndex::LineNumber], lineNoStr.c_str()); + drawList->AddText(ImVec2(lineNoStartScreenPos.x + m_leftMargin, lineStartScreenPos.y), color, lineNoStr.c_str()); } - - if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor) { - - // Highlight the current line (where the cursor is) - if (!hasSelection()) { - auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + m_lineNumberFieldWidth, lineStartScreenPos.y + m_charAdvance.y); - drawList->AddRectFilled(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) (focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); - drawList->AddRect(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) PaletteIndex::CurrentLineEdge], 1.0f); - } - } - - TextUnformattedColoredAt(ImVec2(m_leftMargin + lineNoStartScreenPos.x, lineStartScreenPos.y), m_palette[(i32) PaletteIndex::LineNumber], lineNoStr.c_str()); + TextUnformattedColoredAt(ImVec2(m_leftMargin + lineNoStartScreenPos.x, lineStartScreenPos.y), color, lineNoStr.c_str()); if (!m_ignoreImGuiChild) ImGui::EndChild(); diff --git a/plugins/ui/source/ui/text_editor/support.cpp b/plugins/ui/source/ui/text_editor/support.cpp index 8f26e4cb0..551bb454c 100644 --- a/plugins/ui/source/ui/text_editor/support.cpp +++ b/plugins/ui/source/ui/text_editor/support.cpp @@ -156,6 +156,7 @@ namespace hex::ui { m_colors = line.m_colors; m_flags = line.m_flags; m_colorized = line.m_colorized; + m_lineMaxColumn = line.m_lineMaxColumn; return *this; } @@ -164,6 +165,7 @@ namespace hex::ui { m_colors = std::move(line.m_colors); m_flags = std::move(line.m_flags); m_colorized = line.m_colorized; + m_lineMaxColumn = line.m_lineMaxColumn; return *this; } @@ -196,6 +198,7 @@ namespace hex::ui { m_colors.push_back(0x00); m_flags.push_back(0x00); m_colorized = false; + m_lineMaxColumn = -1; } bool TextEditor::Line::empty() const { @@ -273,8 +276,11 @@ namespace hex::ui { } void TextEditor::Line::append(LineIterator begin, LineIterator end) { - if (begin.m_charsIter < end.m_charsIter) + if (begin.m_charsIter < end.m_charsIter) { m_chars.append(begin.m_charsIter, end.m_charsIter); + std::string charsAppended(begin.m_charsIter, end.m_charsIter); + m_lineMaxColumn += TextEditor::getStringCharacterCount(charsAppended); + } if (begin.m_colorsIter < end.m_colorsIter) m_colors.append(begin.m_colorsIter, end.m_colorsIter); if (begin.m_flagsIter < end.m_flagsIter) @@ -307,6 +313,8 @@ namespace hex::ui { m_colors.insert(iter.m_colorsIter, beginLine.m_colorsIter, endLine.m_colorsIter); m_flags.insert(iter.m_flagsIter, beginLine.m_flagsIter, endLine.m_flagsIter); m_colorized = false; + std::string charsInserted(beginLine.m_charsIter, endLine.m_charsIter); + m_lineMaxColumn += TextEditor::getStringCharacterCount(charsInserted); } } @@ -315,6 +323,8 @@ namespace hex::ui { m_colors.erase(begin.m_colorsIter); m_flags.erase(begin.m_flagsIter); m_colorized = false; + std::string charsErased(begin.m_charsIter, end().m_charsIter); + m_lineMaxColumn -= TextEditor::getStringCharacterCount(charsErased); } void TextEditor::Line::erase(LineIterator begin, u64 count) { @@ -324,6 +334,8 @@ namespace hex::ui { m_colors.erase(begin.m_colorsIter, begin.m_colorsIter + count); m_flags.erase(begin.m_flagsIter, begin.m_flagsIter + count); m_colorized = false; + std::string charsErased(begin.m_charsIter, begin.m_charsIter + count); + m_lineMaxColumn -= TextEditor::getStringCharacterCount(charsErased); } void TextEditor::Line::erase(u64 start, u64 length) { @@ -346,6 +358,7 @@ namespace hex::ui { m_colors.clear(); m_flags.clear(); m_colorized = false; + m_lineMaxColumn = 0; } void TextEditor::Line::setLine(const std::string &text) { @@ -353,6 +366,7 @@ namespace hex::ui { m_colors = std::string(text.size(), 0x00); m_flags = std::string(text.size(), 0x00); m_colorized = false; + m_lineMaxColumn = -1; } void TextEditor::Line::setLine(const Line &text) { @@ -360,6 +374,7 @@ namespace hex::ui { m_colors = text.m_colors; m_flags = text.m_flags; m_colorized = text.m_colorized; + m_lineMaxColumn = text.m_lineMaxColumn; } bool TextEditor::Line::needsUpdate() const { diff --git a/plugins/ui/source/ui/text_editor/utf8.cpp b/plugins/ui/source/ui/text_editor/utf8.cpp index 4aee97e5b..7235fb83a 100644 --- a/plugins/ui/source/ui/text_editor/utf8.cpp +++ b/plugins/ui/source/ui/text_editor/utf8.cpp @@ -27,6 +27,22 @@ namespace hex::ui { return count; } + i32 TextEditor::getLineCharacterCount(i32 lineIndex) { + if (lineIndex >= (i64) m_lines.size() || lineIndex < 0) + return 0; + Line &line = m_lines[lineIndex]; + if (line.m_lineMaxColumn != -1) + return line.m_lineMaxColumn; + else { + auto str = line.m_chars; + i32 count = 0; + for (u32 idx = 0; idx < str.size(); count++) + idx += TextEditor::utf8CharLength(str[idx]); + line.m_lineMaxColumn = count; + return count; + } + } + // "Borrowed" from ImGui source void TextEditor::imTextCharToUtf8(std::string &buffer, u32 c) { if (c < 0x80) { @@ -78,7 +94,7 @@ namespace hex::ui { return index; } - TextEditor::Coordinates TextEditor::screenPosToCoordinates(const ImVec2 &position) const { + TextEditor::Coordinates TextEditor::screenPosToCoordinates(const ImVec2 &position) { ImVec2 local = position - ImGui::GetCursorScreenPos(); i32 lineNo = std::max(0, (i32) floor(local.y / m_charAdvance.y)); if (local.x < (m_leftMargin - 2) || lineNo >= (i32) m_lines.size() || m_lines[lineNo].empty()) @@ -129,7 +145,11 @@ namespace hex::ui { return col; } - TextEditor::Coordinates TextEditor::getCharacterCoordinates(i32 lineIndex, i32 strIndex) const { + i32 TextEditor::Line::getMaxCharColumn() const { + return getCharacterColumn(size()); + } + + TextEditor::Coordinates TextEditor::getCharacterCoordinates(i32 lineIndex, i32 strIndex) { if (lineIndex < 0 || lineIndex >= (i32) m_lines.size()) return Coordinates(0, 0); auto &line = m_lines[lineIndex]; @@ -144,14 +164,8 @@ namespace hex::ui { return line.size(); } - i32 TextEditor::getLineCharacterCount(i32 line) const { - return getLineMaxColumn(line); - } - - i32 TextEditor::getLineMaxColumn(i32 line) const { - if (line >= (i64) m_lines.size() || line < 0) - return 0; - return getStringCharacterCount(m_lines[line].m_chars); + i32 TextEditor::getLineMaxColumn(i32 lineIndex) { + return getLineCharacterCount(lineIndex); } TextEditor::Coordinates TextEditor::stringIndexToCoordinates(i32 strIndex, const std::string &input) {