diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index 51b4ad54c..3163d61b1 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -152,10 +152,10 @@ namespace hex::plugin::builtin { std::mutex m_logMutex; PerProvider m_cursorPosition; + PerProvider m_scroll; + PerProvider m_consoleScroll; PerProvider m_consoleCursorPosition; - PerProvider m_cursorNeedsUpdate; - PerProvider m_consoleCursorNeedsUpdate; PerProvider m_selection; PerProvider m_consoleSelection; PerProvider m_consoleLongestLineLength; diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index ed6c9d8f3..7b4ee4ce7 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -1031,6 +1031,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", diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 5db652450..543adfed1 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -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); @@ -1836,31 +1857,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; @@ -1959,7 +1979,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) { @@ -1977,11 +1997,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; } @@ -1998,22 +2018,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); @@ -2032,19 +2052,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); @@ -2052,8 +2072,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 */ @@ -2061,7 +2081,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) @@ -2073,7 +2093,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); diff --git a/plugins/ui/include/ui/text_editor.hpp b/plugins/ui/include/ui/text_editor.hpp index bb00c0a7a..6d401fc4c 100644 --- a/plugins/ui/include/ui/text_editor.hpp +++ b/plugins/ui/include/ui/text_editor.hpp @@ -114,23 +114,23 @@ namespace hex::ui { Coordinates m_cursorPosition; }; + class UndoRecord; + class UndoAction; + using UndoBuffer = std::vector; + using UndoRecords = std::vector; + class FindReplaceHandler { public: FindReplaceHandler(); using Matches = std::vector; 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 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 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; diff --git a/plugins/ui/source/ui/text_editor/editor.cpp b/plugins/ui/source/ui/text_editor/editor.cpp index 93283bf6b..4c1658f09 100644 --- a/plugins/ui/source/ui/text_editor/editor.cpp +++ b/plugins/ui/source/ui/text_editor/editor.cpp @@ -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(lineStart); u32 uLineEnd = static_cast(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 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 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); } } \ No newline at end of file diff --git a/plugins/ui/source/ui/text_editor/navigate.cpp b/plugins/ui/source/ui/text_editor/navigate.cpp index 3b5c103c0..fe5634f5a 100644 --- a/plugins/ui/source/ui/text_editor/navigate.cpp +++ b/plugins/ui/source/ui/text_editor/navigate.cpp @@ -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(); } } diff --git a/plugins/ui/source/ui/text_editor/render.cpp b/plugins/ui/source/ui/text_editor/render.cpp index 0ed48e25c..996df8160 100644 --- a/plugins/ui/source/ui/text_editor/render.cpp +++ b/plugins/ui/source/ui/text_editor/render.cpp @@ -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 diff --git a/plugins/ui/source/ui/text_editor/support.cpp b/plugins/ui/source/ui/text_editor/support.cpp index a7ec416da..7f5f188c3 100644 --- a/plugins/ui/source/ui/text_editor/support.cpp +++ b/plugins/ui/source/ui/text_editor/support.cpp @@ -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 = 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 & 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 TextEditor::KMPSearch(const std::string& text, const std::string& pattern) { + i32 textLength = text.length(); + i32 patternLength = pattern.length(); + std::vector result; + std::vector 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; } }