Various fixes for pattern editor (#2561)

- Fix for vertical scroll bar being too far to the left.
- Fix constructor not initializing from const char pointer properly
- maxcolumn not being set for console text lines causing crashes on
empty pattern evaluation
- A replacement using replace all is now undone in one step.
- Find/replace no longer need to have enter or return key to accept
text. You can use arrows or shortcuts.
- More efficient search replace implementation with plans to add even
faster.
- Tooltips added to find/replace window
- Providers now save both horizontal and vertical scroll positions when
switching to another one and restore them when switching back. This is
independent to the cursor position which is also saved.
- Pattern editor no longer takes focus when changing providers via a tab
click. This has the effect that menus won't change by just clicking on a
tab.
- Small fixes and code refactoring.

(cherry picked from commit 1676342e28)
This commit is contained in:
paxcut
2025-12-13 05:23:16 -07:00
committed by WerWolv
parent febd46ec58
commit 60e2c32ae0
8 changed files with 352 additions and 186 deletions

View File

@@ -80,9 +80,14 @@ namespace hex::ui {
m_lines[0].m_chars = text;
m_lines[0].m_colors = std::string(text.size(), 0);
m_lines[0].m_flags = std::string(text.size(), 0);
} else
m_lines[0].m_lineMaxColumn = -1;
m_lines[0].m_lineMaxColumn = m_lines[0].maxColumn();
} else {
m_lines.push_back(Line(text));
auto &line = m_lines.back();
line.m_lineMaxColumn = -1;
line.m_lineMaxColumn = line.maxColumn();
}
setCursorPosition(setCoordinates((i32) m_lines.size() - 1, 0));
m_lines.back().m_colorized = false;
ensureCursorVisible();
@@ -129,23 +134,26 @@ namespace hex::ui {
}
void TextEditor::removeLine(i32 lineStart, i32 lineEnd) {
ErrorMarkers errorMarkers;
for (auto &errorMarker : m_errorMarkers) {
if (errorMarker.first.m_line <= lineStart || errorMarker.first.m_line > lineEnd + 1) {
if (errorMarker.first.m_line >= lineEnd + 1) {
auto newRow = errorMarker.first.m_line - (lineEnd - lineStart + 1);
auto newCoord = setCoordinates(newRow, errorMarker.first.m_column);
errorMarkers.insert(ErrorMarkers::value_type(newCoord, errorMarker.second));
} else
errorMarkers.insert(errorMarker);
}
}
m_errorMarkers = std::move(errorMarkers);
ErrorMarkers errorMarker;
u32 uLineStart = static_cast<u32>(lineStart);
u32 uLineEnd = static_cast<u32>(lineEnd);
for (auto &i: m_errorMarkers) {
ErrorMarkers::value_type e(i.first.m_line >= lineStart ? setCoordinates(i.first.m_line - 1, i.first.m_column) : i.first, i.second);
if (e.first.m_line >= lineStart && e.first.m_line <= lineEnd)
continue;
errorMarker.insert(e);
}
m_errorMarkers = std::move(errorMarker);
Breakpoints breakpoints;
for (auto breakpoint: m_breakpoints) {
if (breakpoint <= uLineStart || breakpoint >= uLineEnd) {
if (breakpoint >= uLineEnd) {
breakpoints.insert(breakpoint - 1);
for (auto breakpoint : m_breakpoints) {
if (breakpoint <= uLineStart || breakpoint > uLineEnd + 1) {
if (breakpoint > uLineEnd + 1) {
breakpoints.insert(breakpoint - (uLineEnd - uLineStart + 1));
m_breakPointsChanged = true;
} else
breakpoints.insert(breakpoint);
@@ -190,10 +198,18 @@ namespace hex::ui {
TextEditor::Line &result = *m_lines.insert(m_lines.begin() + index, newLine);
result.m_colorized = false;
ErrorMarkers errorMarker;
for (auto &i: m_errorMarkers)
errorMarker.insert(ErrorMarkers::value_type(i.first.m_line >= index ? setCoordinates(i.first.m_line + 1, i.first.m_column) : i.first, i.second));
m_errorMarkers = std::move(errorMarker);
ErrorMarkers errorMarkers;
bool errorMarkerChanged = false;
for (auto &errorMarker : m_errorMarkers) {
if (errorMarker.first.m_line > index) {
auto newCoord = setCoordinates(errorMarker.first.m_line + 1, errorMarker.first.m_column);
errorMarkers.insert(ErrorMarkers::value_type(newCoord, errorMarker.second));
errorMarkerChanged = true;
} else
errorMarkers.insert(errorMarker);
}
if (errorMarkerChanged)
m_errorMarkers = std::move(errorMarkers);
Breakpoints breakpoints;
for (auto breakpoint: m_breakpoints) {
@@ -231,6 +247,8 @@ namespace hex::ui {
for (auto line: vectorString) {
m_lines[i].setLine(line);
m_lines[i].m_colorized = false;
m_lines[i].m_lineMaxColumn = -1;
m_lines[i].m_lineMaxColumn = m_lines[i].maxColumn();
i++;
}
}
@@ -245,8 +263,9 @@ namespace hex::ui {
m_scrollToTop = true;
if (!m_readOnly && undo) {
u.m_after = m_state;
addUndo(u);
UndoRecords v;
v.push_back(u);
addUndo(v);
}
colorize();
@@ -325,7 +344,9 @@ namespace hex::ui {
u.m_after = m_state;
m_state.m_selection = Range(start, end);
addUndo(u);
std::vector<UndoRecord> v;
v.push_back(u);
addUndo(v);
m_textChanged = true;
@@ -464,7 +485,9 @@ namespace hex::ui {
u.m_after = m_state;
m_textChanged = true;
addUndo(u);
UndoRecords v;
v.push_back(u);
addUndo(v);
colorize();
refreshSearchResults();
ensureCursorVisible();
@@ -558,7 +581,9 @@ namespace hex::ui {
}
u.m_after = m_state;
addUndo(u);
UndoRecords v;
v.push_back(u);
addUndo(v);
refreshSearchResults();
}
@@ -641,7 +666,9 @@ namespace hex::ui {
}
u.m_after = m_state;
addUndo(u);
UndoRecords v;
v.push_back(u);
addUndo(v);
refreshSearchResults();
}
@@ -677,7 +704,9 @@ namespace hex::ui {
deleteSelection();
u.m_after = m_state;
addUndo(u);
std::vector<UndoRecord> v;
v.push_back(u);
addUndo(v);
}
refreshSearchResults();
}
@@ -701,7 +730,9 @@ namespace hex::ui {
u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition);
u.m_after = m_state;
addUndo(u);
UndoRecords v;
v.push_back(u);
addUndo(v);
}
refreshSearchResults();
}
@@ -731,21 +762,26 @@ namespace hex::ui {
return !m_readOnly && m_undoIndex < (i32) m_undoBuffer.size();
}
void TextEditor::undo(i32 steps) {
while (canUndo() && steps-- > 0)
m_undoBuffer[--m_undoIndex].undo(this);
void TextEditor::undo() {
if (canUndo()) {
m_undoIndex--;
m_undoBuffer[m_undoIndex].undo(this);
}
refreshSearchResults();
}
void TextEditor::redo(i32 steps) {
while (canRedo() && steps-- > 0)
m_undoBuffer[m_undoIndex++].redo(this);
void TextEditor::redo() {
if (canRedo()) {
m_undoBuffer[m_undoIndex].redo(this);
m_undoIndex++;
}
refreshSearchResults();
}
std::string TextEditor::getText() {
auto start = setCoordinates(0, 0);
auto end = setCoordinates(-1, -1);
auto size = m_lines.size();
auto end = setCoordinates(-1, m_lines[size - 1].m_lineMaxColumn);
if (start == Invalid || end == Invalid)
return "";
return getText(Range(start, end));
@@ -778,11 +814,11 @@ namespace hex::ui {
TextEditor::UndoRecord::UndoRecord(
const std::string &added,
const TextEditor::Range addedSelection,
const TextEditor::Range addedRange,
const std::string &removed,
const TextEditor::Range removedSelection,
const TextEditor::Range removedRange,
TextEditor::EditorState &before,
TextEditor::EditorState &after) : m_added(added), m_addedRange(addedSelection), m_removed(removed), m_removedRange(removedSelection), m_before(before), m_after(after) {}
TextEditor::EditorState &after) : m_added(added), m_addedRange(addedRange), m_removed(removed), m_removedRange(removedRange), m_before(before), m_after(after) {}
void TextEditor::UndoRecord::undo(TextEditor *editor) {
if (!m_added.empty()) {
@@ -814,7 +850,16 @@ namespace hex::ui {
editor->m_state = m_after;
editor->ensureCursorVisible();
}
void TextEditor::UndoAction::undo(TextEditor *editor) {
for (i32 i = (i32) m_records.size() - 1; i >= 0; i--)
m_records.at(i).undo(editor);
}
void TextEditor::UndoAction::redo(TextEditor *editor) {
for (i32 i = 0; i < (i32) m_records.size(); i++)
m_records.at(i).redo(editor);
}
}

View File

@@ -19,7 +19,7 @@ namespace hex::ui {
void TextEditor::jumpToCoords(const Coordinates &coords) {
setSelection(Range(coords, coords));
setCursorPosition(coords);
setCursorPosition(coords, true);
ensureCursorVisible();
setFocusAtCoords(coords, true);
@@ -203,7 +203,7 @@ namespace hex::ui {
void TextEditor::moveTop(bool select) {
resetCursorBlinkTime();
auto oldPos = m_state.m_cursorPosition;
setCursorPosition(setCoordinates(0, 0));
setCursorPosition(setCoordinates(0, 0), false);
if (m_state.m_cursorPosition != oldPos) {
if (select) {
@@ -218,7 +218,7 @@ namespace hex::ui {
resetCursorBlinkTime();
auto oldPos = getCursorPosition();
auto newPos = setCoordinates(-1, -1);
setCursorPosition(newPos);
setCursorPosition(newPos, false);
if (select) {
m_interactiveSelection = Range(oldPos, newPos);
} else
@@ -315,10 +315,33 @@ namespace hex::ui {
}
}
void TextEditor::setCursorPosition(const Coordinates &position) {
void TextEditor::setScroll(ImVec2 scroll) {
if (!m_withinRender) {
m_scroll = scroll;
m_setScroll = true;
return;
} else {
m_setScroll = false;
ImGui::SetScrollX(scroll.x);
ImGui::SetScrollY(scroll.y);
//m_updateFocus = true;
}
}
void TextEditor::setFocusAtCoords(const Coordinates &coords, bool scrollToCursor) {
m_focusAtCoords = coords;
m_state.m_cursorPosition = coords;
m_updateFocus = true;
m_scrollToCursor = scrollToCursor;
}
void TextEditor::setCursorPosition(const Coordinates &position, bool scrollToCursor) {
if (m_state.m_cursorPosition != position) {
m_state.m_cursorPosition = position;
ensureCursorVisible();
m_scrollToCursor = scrollToCursor;
if (scrollToCursor)
ensureCursorVisible();
}
}

View File

@@ -19,12 +19,6 @@ namespace hex::ui {
m_topMarginChanged = true;
}
void TextEditor::setFocusAtCoords(const Coordinates &coords, bool scrollToCursor) {
m_focusAtCoords = coords;
m_updateFocus = true;
m_scrollToCursor = scrollToCursor;
}
void TextEditor::clearErrorMarkers() {
m_errorMarkers.clear();
m_errorHoverBoxes.clear();
@@ -140,8 +134,6 @@ namespace hex::ui {
bool scroll_x = m_longestLineLength * m_charAdvance.x >= textEditorSize.x;
bool scroll_y = m_lines.size() > 1;
if (!border)
textEditorSize.x -= scrollBarSize;
ImGui::SetCursorScreenPos(ImVec2(position.x + m_lineNumberFieldWidth, position.y));
ImGuiChildFlags childFlags = border ? ImGuiChildFlags_Borders : ImGuiChildFlags_None;
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove;
@@ -262,9 +254,16 @@ namespace hex::ui {
auto drawList = ImGui::GetWindowDrawList();
s_cursorScreenPosition = ImGui::GetCursorScreenPos();
ImVec2 position = lineNumbersStartPos;
if (m_setScrollY)
setScrollY();
auto scrollY = ImGui::GetScrollY();
float scrollY;
if (m_setScroll) {
setScroll(m_scroll);
scrollY = m_scroll.y;
} else {
scrollY = ImGui::GetScrollY();
float scrollX = ImGui::GetScrollX();
m_scroll = ImVec2(scrollX, scrollY);
}
if (m_setTopLine)
setTopLine();
else

View File

@@ -537,13 +537,13 @@ namespace hex::ui {
return !isEmpty() && m_state.m_selection.m_end > m_state.m_selection.m_start;
}
void TextEditor::addUndo(UndoRecord &value) {
void TextEditor::addUndo(UndoRecords &value) {
if (m_readOnly)
return;
m_undoBuffer.resize((u64) (m_undoIndex + 1));
m_undoBuffer.back() = value;
++m_undoIndex;
m_undoBuffer.back() = UndoAction(value);
m_undoIndex++;
}
TextEditor::PaletteIndex TextEditor::getColorIndexFromFlags(Line::Flags flags) {
@@ -711,7 +711,7 @@ namespace hex::ui {
i32 count = m_matches.size();
if (count == 0) {
editor->setCursorPosition(targetPos);
editor->setCursorPosition(targetPos, true);
return 0;
}
@@ -723,7 +723,7 @@ namespace hex::ui {
break;
}
}
if (matchIndex >= 0 && matchIndex <count) {
if (matchIndex >= 0 && matchIndex < count) {
while (matchIndex + index < 0)
index += count;
auto rem = (matchIndex + index) % count;
@@ -804,17 +804,98 @@ namespace hex::ui {
return out;
}
void FindReplaceHandler::setFindWord(TextEditor *editor, const std::string &findWord) {
if (findWord != m_findWord) {
findAllMatches(editor, findWord);
m_findWord = findWord;
}
}
void FindReplaceHandler::setMatchCase(TextEditor *editor, bool matchCase) {
if (matchCase != m_matchCase) {
m_matchCase = matchCase;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void FindReplaceHandler::setWholeWord(TextEditor *editor, bool wholeWord) {
if (wholeWord != m_wholeWord) {
m_wholeWord = wholeWord;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void FindReplaceHandler::setFindRegEx(TextEditor *editor, bool findRegEx) {
if (findRegEx != m_findRegEx) {
m_findRegEx = findRegEx;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void FindReplaceHandler::resetMatches() {
m_matches.clear();
m_findWord = "";
}
/*
void TextEditor::computeLPSArray(const std::string &pattern, std::vector<i32> & lps) {
i32 length = 0; // length of the previous longest prefix suffix
i32 i = 1;
lps[0] = 0; // lps[0] is always 0
i32 patternLength = pattern.length();
while (i < patternLength) {
if (pattern[i] == pattern[length]) {
length++;
lps[i] = length;
i++;
} else {
if (length != 0)
length = lps[length - 1];
else {
lps[i] = 0;
i++;
}
}
}
}
std::vector<i32> TextEditor::KMPSearch(const std::string& text, const std::string& pattern) {
i32 textLength = text.length();
i32 patternLength = pattern.length();
std::vector<i32> result;
std::vector<i32> lps(patternLength);
computeLPSArray(pattern, lps);
i32 textIndex = 0;
i32 patternIndex = 0;
while (textIndex < textLength) {
if (pattern[patternIndex] == text[textIndex]) {
textIndex++;
patternIndex++;
}
if (patternIndex == patternLength) {
result.push_back(textIndex - patternIndex);
patternIndex = lps[patternIndex - 1];
} else if (textIndex < textLength && pattern[patternIndex] != text[textIndex]) {
if (patternIndex != 0)
patternIndex = lps[patternIndex - 1];
else
textIndex++;
}
}
return result;
}*/
// Performs actual search to fill mMatches
bool FindReplaceHandler::findNext(TextEditor *editor) {
Coordinates curPos = m_matches.empty() ? editor->m_state.m_cursorPosition : editor->lineCoordsToIndexCoords(m_matches.back().m_cursorPosition);
bool FindReplaceHandler::findNext(TextEditor *editor, u64 &byteIndex) {
u64 matchLength = stringCharacterCount(m_findWord);
u64 matchBytes = m_findWord.size();
u64 byteIndex = 0;
for (i64 ln = 0; ln < curPos.m_line; ln++)
byteIndex += editor->getLineByteCount(ln) + 1;
byteIndex += curPos.m_column;
std::string wordLower = m_findWord;
if (!getMatchCase())
@@ -854,16 +935,14 @@ namespace hex::ui {
if (!iter->ready())
return false;
u64 firstLoc = iter->position();
u64 firstLength = iter->length();
if (firstLoc > byteIndex) {
pos = firstLoc;
matchLength = firstLength;
} else {
while (iter != end) {
iter++;
if (((pos = iter->position()) > byteIndex) && ((matchLength = iter->length()) > 0))
if (((pos = iter->position()) > byteIndex))
break;
}
}
@@ -875,15 +954,16 @@ namespace hex::ui {
} else {
// non regex search
textLoc = textSrc.find(wordLower, byteIndex);
if (textLoc == std::string::npos)
return false;
}
if (textLoc == std::string::npos)
return false;
TextEditor::EditorState state;
state.m_selection = Range(TextEditor::stringIndexToCoordinates(textLoc, textSrc), TextEditor::stringIndexToCoordinates(textLoc + matchBytes, textSrc));
state.m_cursorPosition = state.m_selection.m_end;
if (!m_matches.empty() && state == m_matches.back())
return false;
m_matches.push_back(state);
byteIndex = textLoc + 1;
return true;
}
@@ -902,6 +982,7 @@ namespace hex::ui {
if (m_optionsChanged)
m_optionsChanged = false;
u64 byteIndex = 0;
m_matches.clear();
m_findWord = findWord;
auto startingPos = editor->m_state.m_cursorPosition;
@@ -909,7 +990,7 @@ namespace hex::ui {
Coordinates begin = editor->setCoordinates(0, 0);
editor->m_state.m_cursorPosition = begin;
if (!findNext(editor)) {
if (!findNext(editor, byteIndex)) {
editor->m_state = saveState;
editor->ensureCursorVisible();
return;
@@ -917,7 +998,7 @@ namespace hex::ui {
TextEditor::EditorState state = m_matches.back();
while (state.m_cursorPosition < startingPos) {
if (!findNext(editor)) {
if (!findNext(editor, byteIndex)) {
editor->m_state = saveState;
editor->ensureCursorVisible();
return;
@@ -925,7 +1006,7 @@ namespace hex::ui {
state = m_matches.back();
}
while (findNext(editor));
while (findNext(editor, byteIndex));
editor->m_state = saveState;
editor->ensureCursorVisible();
@@ -972,7 +1053,7 @@ namespace hex::ui {
ImGui::SetKeyboardFocusHere(0);
u.m_after = editor->m_state;
editor->addUndo(u);
m_undoBuffer.push_back(u);
editor->m_textChanged = true;
return true;
@@ -983,10 +1064,11 @@ namespace hex::ui {
bool FindReplaceHandler::replaceAll(TextEditor *editor) {
u32 count = m_matches.size();
m_undoBuffer.clear();
for (u32 i = 0; i < count; i++)
replace(editor, true);
editor->addUndo(m_undoBuffer);
return true;
}
}