Fix: Very long lines in text editor could make it hang. (#2426)

Fixed by only processing text that's visible.

Also fixed the cursor jumping to breakpoint line when selected by
clicking the line number and added highlighting of the current editing
line. An optimization that caches the number of utf-8 chars in each line
was included as well.
Finally, an error that caused ImHex to crash if a pattern was saved as
itself was also fixed.
This commit is contained in:
paxcut
2025-08-30 20:01:05 -07:00
committed by GitHub
parent 76bb0e420d
commit c8caf6124e
7 changed files with 103 additions and 69 deletions

View File

@@ -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)

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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) {