Various fixes for pattern editor

- 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.
This commit is contained in:
paxcut 2025-12-13 04:48:18 -07:00
parent 62732de227
commit 9c96e66252
9 changed files with 353 additions and 187 deletions

2
dist/ImHex.run.xml vendored
View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImHex" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS=".ninja_log" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$CMakeCurrentBuildDir$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="ImHex" TARGET_NAME="imhex_all" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="ImHex" RUN_TARGET_NAME="main">
<configuration default="false" name="ImHex" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS=".ninja_log" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$CMakeCurrentBuildDir$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="ImHex" TARGET_NAME="imhex_all" CONFIG_NAME="Debug-MinGW" RUN_TARGET_PROJECT_NAME="ImHex" RUN_TARGET_NAME="main">
<envs>
<env name="NO_DEBUG_BANNER" value="1" />
</envs>

View File

@ -153,10 +153,10 @@ namespace hex::plugin::builtin {
std::mutex m_logMutex;
PerProvider<ui::TextEditor::Coordinates> m_cursorPosition;
PerProvider<ImVec2> m_scroll;
PerProvider<ImVec2> m_consoleScroll;
PerProvider<ui::TextEditor::Coordinates> m_consoleCursorPosition;
PerProvider<bool> m_cursorNeedsUpdate;
PerProvider<bool> m_consoleCursorNeedsUpdate;
PerProvider<ui::TextEditor::Range> m_selection;
PerProvider<ui::TextEditor::Range> m_consoleSelection;
PerProvider<size_t> m_consoleLongestLineLength;

View File

@ -1048,6 +1048,9 @@
"hex.builtin.view.pattern_data.virtual_files.no_virtual_files": "Visualize regions of data as a virtual folder structure by adding them using the hex::core::add_virtual_file function.",
"hex.builtin.view.pattern_editor.no_env_vars": "The content of Environment Variables created here can be accessed from Pattern scripts using the std::env function.",
"hex.builtin.view.pattern_editor.no_results": "no results",
"hex.builtin.view.pattern_editor.match_case_tooltip": "Match Case Alt-C",
"hex.builtin.view.pattern_editor.whole_word_tooltip": "Whole Word Alt-W",
"hex.builtin.view.pattern_editor.regex_tooltip": "Regex Alt-R",
"hex.builtin.view.pattern_editor.of": "of",
"hex.builtin.view.pattern_editor.open_pattern": "Open pattern",
"hex.builtin.view.pattern_editor.replace_hint": "Replace",

View File

@ -477,11 +477,6 @@ namespace hex::plugin::builtin {
m_textEditor.get(provider).selectWordUnderCursor();
}
if (m_cursorNeedsUpdate.get(provider)) {
m_textEditor.get(provider).setFocusAtCoords(m_cursorPosition.get(provider));
m_cursorNeedsUpdate.get(provider) = false;
}
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) {
setupFindReplace(editor);
setupGotoLine(editor);
@ -716,7 +711,7 @@ namespace hex::plugin::builtin {
ImGui::TableNextColumn();
static int findFlags = ImGuiInputTextFlags_EnterReturnsTrue;
static int findFlags = ImGuiInputTextFlags_None;
std::string hint = "hex.builtin.view.pattern_editor.find_hint"_lang.operator std::string();
if (m_findHistorySize > 0) {
@ -724,6 +719,8 @@ namespace hex::plugin::builtin {
hint += ICON_BI_DATA_TRANSFER_BOTH;
hint += "hex.builtin.view.pattern_editor.find_hint_history"_lang.operator std::string();
}
static bool enterPressedReplace = false;
static bool enterPressedFind = false;
ImGui::PushItemWidth(ImGui::GetFontSize() * 12);
if (ImGui::InputTextWithHint("###findInputTextWidget", hint.c_str(), findWord, findFlags) || enter ) {
@ -775,6 +772,12 @@ namespace hex::plugin::builtin {
updateCount = true;
requestFocusFind = true;
}
if (ImGui::IsItemHovered()) {
if (ImGui::BeginTooltip()) {
ImGui::TextUnformatted("hex.builtin.view.pattern_editor.match_case_tooltip"_lang);
ImGui::EndTooltip();
}
}
ImGui::SameLine();
@ -791,7 +794,12 @@ namespace hex::plugin::builtin {
updateCount = true;
requestFocusFind = true;
}
if (ImGui::IsItemHovered()) {
if (ImGui::BeginTooltip()) {
ImGui::TextUnformatted("hex.builtin.view.pattern_editor.whole_word_tooltip"_lang);
ImGui::EndTooltip();
}
}
ImGui::SameLine();
bool useRegex = findReplaceHandler->getFindRegEx();
@ -807,6 +815,13 @@ namespace hex::plugin::builtin {
updateCount = true;
requestFocusFind = true;
}
if (ImGui::IsItemHovered()) {
if (ImGui::BeginTooltip()) {
ImGui::TextUnformatted("hex.builtin.view.pattern_editor.regex_tooltip"_lang);
ImGui::EndTooltip();
}
}
static std::string counterString;
@ -852,12 +867,15 @@ namespace hex::plugin::builtin {
if (ImGuiExt::IconButton(ICON_VS_ARROW_UP, ImVec4(1, 1, 1, 1)))
upArrowFind = true;
static bool downArrowReplace = false;
static bool upArrowReplace = false;
static std::string replaceWord;
if (m_replaceMode) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
static int replaceFlags = ImGuiInputTextFlags_EnterReturnsTrue;
static int replaceFlags = ImGuiInputTextFlags_None;
hint = "hex.builtin.view.pattern_editor.replace_hint"_lang.operator std::string();
if (m_replaceHistorySize > 0) {
@ -867,31 +885,13 @@ namespace hex::plugin::builtin {
}
ImGui::PushItemWidth(ImGui::GetFontSize() * 12);
static std::string replaceWord;
static bool downArrowReplace = false;
static bool upArrowReplace = false;
if (ImGui::InputTextWithHint("###replaceInputTextWidget", hint.c_str(), replaceWord, replaceFlags) || downArrowReplace || upArrowReplace) {
findReplaceHandler->setReplaceWord(replaceWord);
historyInsert(m_replaceHistory, m_replaceHistorySize, m_replaceHistoryIndex, replaceWord);
if (ImGui::InputTextWithHint("###replaceInputTextWidget", hint.c_str(), replaceWord, replaceFlags) || enter) {
if (enter)
enterPressedReplace = true;
bool textReplaced = findReplaceHandler->replace(textEditor, !shift && !upArrowReplace);
if (textReplaced) {
if (count > 0) {
if (position == count)
position -= 1;
count -= 1;
}
updateCount = true;
}
downArrowReplace = false;
upArrowReplace = false;
if (enterPressedFind) {
enterPressedFind = false;
requestFocusFind = false;
}
updateCount = true;
requestFocusReplace = true;
findReplaceHandler->setReplaceWord(replaceWord);
}
if (requestFocus || requestFocusReplace) {
@ -947,6 +947,31 @@ namespace hex::plugin::builtin {
requestFocusFind = true;
enterPressedFind = false;
}
if (downArrowReplace || upArrowReplace || enterPressedReplace) {
historyInsert(m_replaceHistory, m_replaceHistorySize, m_replaceHistoryIndex, replaceWord);
findReplaceHandler->m_undoBuffer.clear();
bool textReplaced = findReplaceHandler->replace(textEditor, !shift && !upArrowReplace);
textEditor->addUndo(findReplaceHandler->m_undoBuffer);
if (textReplaced) {
if (count > 0) {
if (position == count)
position -= 1;
count -= 1;
}
updateCount = true;
}
downArrowReplace = false;
upArrowReplace = false;
if (enterPressedFind) {
enterPressedFind = false;
requestFocusFind = false;
}
requestFocusReplace = true;
enterPressedReplace = false;
}
}
// Escape key to close the popup
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
@ -1045,10 +1070,6 @@ namespace hex::plugin::builtin {
m_consoleEditor.get(provider).clearRaiseContextMenu();
}
if (m_consoleCursorNeedsUpdate.get(provider)) {
m_consoleEditor.get(provider).setFocusAtCoords(m_consoleCursorPosition.get(provider));
m_consoleCursorNeedsUpdate.get(provider) = false;
}
if (m_consoleNeedsUpdate) {
std::scoped_lock lock(m_logMutex);
@ -1847,31 +1868,30 @@ namespace hex::plugin::builtin {
EventProviderChanged::subscribe(this, [this](prv::Provider *oldProvider, prv::Provider *newProvider) {
if (oldProvider != nullptr) {
m_sourceCode.get(oldProvider) = m_textEditor.get(oldProvider).getText();
m_scroll.get(oldProvider) = m_textEditor.get(oldProvider).getScroll();
m_cursorPosition.get(oldProvider) = m_textEditor.get(oldProvider).getCursorPosition();
m_selection.get(oldProvider) = m_textEditor.get(oldProvider).getSelection();
m_breakpoints.get(oldProvider) = m_textEditor.get(oldProvider).getBreakpoints();
m_consoleCursorPosition.get(oldProvider) = m_consoleEditor.get(oldProvider).getCursorPosition();
m_consoleSelection.get(oldProvider) = m_consoleEditor.get(oldProvider).getSelection();
m_consoleLongestLineLength.get(oldProvider) = m_consoleEditor.get(oldProvider).getLongestLineLength();
m_breakpoints.get(oldProvider) = m_textEditor.get(oldProvider).getBreakpoints();
m_cursorNeedsUpdate.get(oldProvider) = false;
m_consoleCursorNeedsUpdate.get(oldProvider) = false;
m_consoleScroll.get(oldProvider) = m_consoleEditor.get(oldProvider).getScroll();
}
if (newProvider != nullptr) {
m_textEditor.get(newProvider).setText(wolv::util::preprocessText(m_sourceCode.get(newProvider)));
m_textEditor.get(newProvider).setCursorPosition(m_cursorPosition.get(newProvider));
ui::TextEditor::Range selection = m_selection.get(newProvider);
m_textEditor.get(newProvider).setSelection(selection);
m_textEditor.get(newProvider).setCursorPosition(m_cursorPosition.get(newProvider),false);
m_textEditor.get(newProvider).setScroll(m_scroll.get(newProvider));
m_textEditor.get(newProvider).setSelection(m_selection.get(newProvider));
m_textEditor.get(newProvider).setBreakpoints(m_breakpoints.get(newProvider));
m_textEditor.get(newProvider).setTextChanged(false);
m_hasUnevaluatedChanges.get(newProvider) = true;
m_consoleEditor.get(newProvider).setText(wolv::util::combineStrings(m_console.get(newProvider), "\n"));
m_consoleEditor.get(newProvider).setCursorPosition(m_consoleCursorPosition.get(newProvider));
m_consoleEditor.get(newProvider).setLongestLineLength(m_consoleLongestLineLength.get(newProvider));
selection = m_consoleSelection.get(newProvider);
m_consoleEditor.get(newProvider).setSelection(selection);
m_cursorNeedsUpdate.get(newProvider) = true;
m_consoleCursorNeedsUpdate.get(newProvider) = true;
m_textEditor.get(newProvider).setTextChanged(false);
m_hasUnevaluatedChanges.get(newProvider) = true;
m_consoleEditor.get(newProvider).setSelection(m_consoleSelection.get(newProvider));
m_consoleEditor.get(newProvider).setScroll(m_consoleScroll.get(newProvider));
}
m_textHighlighter.m_needsToUpdateColors = false;
@ -1970,7 +1990,7 @@ namespace hex::plugin::builtin {
ui::TextEditor::FindReplaceHandler *findReplaceHandler = editor->getFindReplaceHandler();
findReplaceHandler->findMatch(editor, 1);
} else {
m_textEditor->getFindReplaceHandler()->findMatch(&*m_textEditor, 1);
m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->findMatch(&m_textEditor.get(ImHexApi::Provider::get()), 1);
}
}, [this] {
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) {
@ -1988,11 +2008,11 @@ namespace hex::plugin::builtin {
ui::TextEditor::FindReplaceHandler *findReplaceHandler = editor->getFindReplaceHandler();
findReplaceHandler->findMatch(editor, -1);
} else {
m_textEditor->getFindReplaceHandler()->findMatch(&*m_textEditor, -1);
m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->findMatch(&m_textEditor.get(ImHexApi::Provider::get()), -1);
}
}, [this] {
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) {
return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getFindWord().empty();
return ImHexApi::Provider::isValid() && !m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->getFindWord().empty();
} else {
return false;
}
@ -2009,22 +2029,22 @@ namespace hex::plugin::builtin {
/* Replace Next */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_next" }, 1550, Shortcut::None, [this] {
m_textEditor->getFindReplaceHandler()->replace(&*m_textEditor, true);
}, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); },
m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->replace(&m_textEditor.get(ImHexApi::Provider::get()), true);
}, [this] { return ImHexApi::Provider::isValid() && !m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); },
[]{ return false; },
this);
/* Replace Previous */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_previous" }, 1560, Shortcut::None, [this] {
m_textEditor->getFindReplaceHandler()->replace(&*m_textEditor, false);
}, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); },
m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->replace(&m_textEditor.get(ImHexApi::Provider::get()), false);
}, [this] { return ImHexApi::Provider::isValid() && !m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); },
[]{ return false; },
this);
/* Replace All */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_all" }, ICON_VS_REPLACE_ALL, 1570, Shortcut::None, [this] {
m_textEditor->getFindReplaceHandler()->replaceAll(&*m_textEditor);
}, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); },
m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->replaceAll(&m_textEditor.get(ImHexApi::Provider::get()));
}, [this] { return ImHexApi::Provider::isValid() && !m_textEditor.get(ImHexApi::Provider::get()).getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); },
this);
@ -2043,19 +2063,19 @@ namespace hex::plugin::builtin {
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, ICON_VS_FILE_CODE, 7050, Shortcut::None, [this] {
savePatternAsNewFile(false);
}, [this] {
return ImHexApi::Provider::isValid() && !wolv::util::trim(m_textEditor->getText()).empty();
return ImHexApi::Provider::isValid() && !wolv::util::trim(m_textEditor.get(ImHexApi::Provider::get()).getText()).empty();
});
/* Undo */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.undo" }, ICON_VS_DISCARD, 1250, AllowWhileTyping + CTRLCMD + Keys::Z, [this] {
m_textEditor->undo();
}, [this] { return ImHexApi::Provider::isValid() && m_textEditor->canUndo() && m_focusedSubWindowName.contains(TextEditorView); },
m_textEditor.get(ImHexApi::Provider::get()).undo();
}, [this] { return ImHexApi::Provider::isValid() && m_textEditor.get(ImHexApi::Provider::get()).canUndo() && m_focusedSubWindowName.contains(TextEditorView); },
this);
/* Redo */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.redo" }, ICON_VS_REDO, 1275, AllowWhileTyping + CTRLCMD + Keys::Y, [this] {
m_textEditor->redo();
}, [this] { return ImHexApi::Provider::isValid() && m_textEditor->canRedo() && m_focusedSubWindowName.contains(TextEditorView); },
m_textEditor.get(ImHexApi::Provider::get()).redo();
}, [this] { return ImHexApi::Provider::isValid() && m_textEditor.get(ImHexApi::Provider::get()).canRedo() && m_focusedSubWindowName.contains(TextEditorView); },
this);
ContentRegistry::UserInterface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1280, this);
@ -2063,8 +2083,8 @@ namespace hex::plugin::builtin {
/* Cut */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.cut" }, ICON_VS_COMBINE, 1300, AllowWhileTyping + CTRLCMD + Keys::X, [this] {
m_textEditor->cut();
}, [this] { return ImHexApi::Provider::isValid() && m_textEditor->hasSelection() && m_focusedSubWindowName.contains(TextEditorView); },
m_textEditor.get(ImHexApi::Provider::get()).cut();
}, [this] { return ImHexApi::Provider::isValid() && m_textEditor.get(ImHexApi::Provider::get()).hasSelection() && m_focusedSubWindowName.contains(TextEditorView); },
this);
/* Copy */
@ -2072,7 +2092,7 @@ namespace hex::plugin::builtin {
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) {
editor->copy();
} else {
m_textEditor->copy();
m_textEditor.get(ImHexApi::Provider::get()).copy();
}
}, [this] {
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr)
@ -2084,7 +2104,7 @@ namespace hex::plugin::builtin {
/* Paste */
ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.paste" }, ICON_VS_OUTPUT, 1500, AllowWhileTyping + CTRLCMD + Keys::V, [this] {
m_textEditor->paste();
m_textEditor.get(ImHexApi::Provider::get()).paste();
}, [this] { return m_focusedSubWindowName.contains(TextEditorView); },
this);

View File

@ -114,23 +114,23 @@ namespace hex::ui {
Coordinates m_cursorPosition;
};
class UndoRecord;
class UndoAction;
using UndoBuffer = std::vector<UndoAction>;
using UndoRecords = std::vector<UndoRecord>;
class FindReplaceHandler {
public:
FindReplaceHandler();
using Matches = std::vector<EditorState>;
Matches &getMatches() { return m_matches; }
bool findNext(TextEditor *editor);
bool findNext(TextEditor *editor, u64 &byteIndex);
u32 findMatch(TextEditor *editor, i32 index);
bool replace(TextEditor *editor, bool right);
bool replaceAll(TextEditor *editor);
std::string &getFindWord() { return m_findWord; }
void setFindWord(TextEditor *editor, const std::string &findWord) {
if (findWord != m_findWord) {
findAllMatches(editor, findWord);
m_findWord = findWord;
}
}
void setFindWord(TextEditor *editor, const std::string &findWord);
std::string &getReplaceWord() { return m_replaceWord; }
void setReplaceWord(const std::string &replaceWord) { m_replaceWord = replaceWord; }
@ -139,37 +139,16 @@ namespace hex::ui {
u32 findPosition(TextEditor *editor, Coordinates pos, bool isNext);
bool getMatchCase() const { return m_matchCase; }
void setMatchCase(TextEditor *editor, bool matchCase) {
if (matchCase != m_matchCase) {
m_matchCase = matchCase;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void setMatchCase(TextEditor *editor, bool matchCase);
bool getWholeWord() const { return m_wholeWord; }
void setWholeWord(TextEditor *editor, bool wholeWord) {
if (wholeWord != m_wholeWord) {
m_wholeWord = wholeWord;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void setWholeWord(TextEditor *editor, bool wholeWord);
bool getFindRegEx() const { return m_findRegEx; }
void setFindRegEx(TextEditor *editor, bool findRegEx) {
if (findRegEx != m_findRegEx) {
m_findRegEx = findRegEx;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void resetMatches() {
m_matches.clear();
m_findWord = "";
}
void setFindRegEx(TextEditor *editor, bool findRegEx);
void resetMatches();
UndoRecords m_undoBuffer;
private:
std::string m_findWord;
std::string m_replaceWord;
@ -299,7 +278,7 @@ namespace hex::ui {
enum class LinePart { Chars, Utf8, Colors, Flags };
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 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), m_lineMaxColumn(maxColumn()) {}
Line(const Line &line) : m_chars(std::string(line.m_chars)), m_colors(std::string(line.m_colors)), m_flags(std::string(line.m_flags)), m_colorized(line.m_colorized), m_lineMaxColumn(line.m_lineMaxColumn) {}
Line(Line &&line) noexcept : m_chars(std::move(line.m_chars)), m_colors(std::move(line.m_colors)), m_flags(std::move(line.m_flags)), m_colorized(line.m_colorized), m_lineMaxColumn(line.m_lineMaxColumn) {}
@ -400,11 +379,11 @@ namespace hex::ui {
UndoRecord() {}
~UndoRecord() {}
UndoRecord( const std::string &added,
const TextEditor::Range addedRange,
const Range addedRange,
const std::string &removed,
const TextEditor::Range removedRange,
TextEditor::EditorState &before,
TextEditor::EditorState &after);
const Range removedRange,
EditorState &before,
EditorState &after);
void undo(TextEditor *editor);
void redo(TextEditor *editor);
@ -417,7 +396,18 @@ namespace hex::ui {
EditorState m_after;
};
typedef std::vector<UndoRecord> UndoBuffer;
class UndoAction {
public:
UndoAction() {}
~UndoAction() {}
explicit UndoAction(const UndoRecords &records) : m_records(records) {}
void undo(TextEditor *editor);
void redo(TextEditor *editor);
private:
UndoRecords m_records;
};
struct MatchedBracket {
bool m_active = false;
@ -511,8 +501,8 @@ namespace hex::ui {
void backspace();
bool canUndo();
bool canRedo() const;
void undo(i32 steps = 1);
void redo(i32 steps = 1);
void undo();
void redo();
void copy();
void cut();
void paste();
@ -559,8 +549,10 @@ namespace hex::ui {
void moveEnd(bool select = false);
void moveToMatchedBracket(bool select = false);
void setScrollY();
void setScroll(ImVec2 scroll);
ImVec2 getScroll() const { return m_scroll; }
Coordinates getCursorPosition() { return setCoordinates(m_state.m_cursorPosition); }
void setCursorPosition(const Coordinates &position);
void setCursorPosition(const Coordinates &position, bool scrollToCursor = true);
void setCursorPosition();
private:
Coordinates setCoordinates(const Coordinates &value);
@ -591,8 +583,8 @@ namespace hex::ui {
void clearRaiseContextMenu() { m_raiseContextMenu = false; }
TextEditor *getSourceCodeEditor();
bool isEmpty() const;
void addUndo(UndoRecords &value);
private:
void addUndo(UndoRecord &value);
TextEditor::PaletteIndex getColorIndexFromFlags(Line::Flags flags);
void handleKeyboardInputs();
void handleMouseInputs();
@ -662,7 +654,9 @@ namespace hex::ui {
std::vector<std::string> m_defines;
TextEditor *m_sourceCodeEditor = nullptr;
float m_shiftedScrollY = 0;
ImVec2 m_scroll=ImVec2(0, 0);
float m_scrollYIncrement = 0.0F;
bool m_setScroll = false;
bool m_setScrollY = false;
float m_numberOfLinesDisplayed = 0;
float m_lastClick = -1.0F;

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