From a9eb7b2d2578413761a2c85a0bd497f702d1f389 Mon Sep 17 00:00:00 2001 From: paxcut <53811119+paxcut@users.noreply.github.com> Date: Sat, 2 Aug 2025 01:29:34 -0700 Subject: [PATCH] fix: fixed auto save projects not being done after the first one. (#2366) Currently setting the time interval to auto save the project has no effect other than creating one initial save if the input file is unsaved. When a file is created and not saved it remains in a dirty state which prevented setting the state of the autosaved project to needing to be saved. Fixed by decoupling the state of the provider from the state of the autosave. When a provider is detected as being dirty it always makes the autosave as being needed once the time interval has elapsed. feat: Implemented the menus on the main menu bar that will be available when the text editor has focus. It allows you to load and save patterns using open and save and will tack changes if files on disk are modified externally. It also only opens the file chooser the first time you save a pattern file and subsequent changes save to the same file. If you want to save into another file and have the new file be tracked you can use Save As. Finally, export doesn't track the file on disk at all. this feature uses the same changes tracker class used elsewhere in imHex. fix: Changed the defaults of various shortcuts that were using Alt + a key to avoid possible problems with some keyboards. Shouldn't affect end users as their shortcuts are loaded from internal file but those who complain about the Alt key misbehaving will be asked to reset the keys to the new defaults. In addition, all globally accessible shortcuts were added the Allow while typing flag so that they can be used in any field that accepts text. New menu entries were added for debugging to make the pattern editor and the hex editor menus more like each other. Finally, the call to RegisterMainMenuEntries() when initializing views was moved to occur after the call to registerViews() so that menus are not repeated when set for different views. --- .../hex/api/events/requests_interaction.hpp | 4 +- lib/libimhex/source/api/imhex_api.cpp | 2 +- .../content/views/view_pattern_editor.hpp | 73 +-- plugins/builtin/romfs/lang/en_US.json | 34 +- .../source/content/main_menu_items.cpp | 22 +- .../source/content/settings_entries.cpp | 1 - .../source/content/views/view_hex_editor.cpp | 2 +- .../content/views/view_pattern_editor.cpp | 423 +++++++----------- plugins/builtin/source/plugin_builtin.cpp | 3 +- 9 files changed, 240 insertions(+), 324 deletions(-) diff --git a/lib/libimhex/include/hex/api/events/requests_interaction.hpp b/lib/libimhex/include/hex/api/events/requests_interaction.hpp index f68bff5c6..d818d170e 100644 --- a/lib/libimhex/include/hex/api/events/requests_interaction.hpp +++ b/lib/libimhex/include/hex/api/events/requests_interaction.hpp @@ -85,8 +85,10 @@ namespace hex { * This request should be scrapped. * * @param path the pattern file's path + * @param bool track changes to the file on disk + * */ - EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path); + EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path, bool); /** * @brief Request to save a pattern language file diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 6ad2f2fc9..dfae6fb2b 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -359,8 +359,8 @@ namespace hex { const auto provider = get(); if (!provider->isDirty()) { provider->markDirty(); - EventProviderDirtied::post(provider); } + EventProviderDirtied::post(provider); } void resetDirty() { diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index 2cc129e6b..61bbd7555 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -162,7 +162,7 @@ namespace hex::plugin::builtin { } if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) - m_view->loadPatternFile(m_view->m_possiblePatternFiles.get(provider)[m_selectedPatternFile].path, provider); + m_view->loadPatternFile(m_view->m_possiblePatternFiles.get(provider)[m_selectedPatternFile].path, provider, false); ImGuiExt::InfoTooltip(wolv::util::toUTF8String(path).c_str()); @@ -184,7 +184,7 @@ namespace hex::plugin::builtin { ImGuiExt::ConfirmButtons("hex.ui.common.yes"_lang, "hex.ui.common.no"_lang, [this, provider] { - m_view->loadPatternFile(m_view->m_possiblePatternFiles.get(provider)[m_selectedPatternFile].path, provider); + m_view->loadPatternFile(m_view->m_possiblePatternFiles.get(provider)[m_selectedPatternFile].path, provider, false); this->close(); }, [this] { @@ -313,17 +313,10 @@ namespace hex::plugin::builtin { bool m_openGotoLinePopUp = false; bool m_patternEvaluating = false; std::map m_patternNames; - - // External pattern file tracking - struct ExternalPatternFile { - std::fs::path path; - std::filesystem::file_time_type lastModified; - size_t contentHash; - std::string originalContent; - }; - PerProvider> m_externalPatternFile; - bool m_checkingExternalFile = false; - bool m_enableExternalFileTracking; + PerProvider m_changeTracker; + PerProvider m_ignoreNextChangeEvent; + PerProvider m_changeEventAcknowledgementPending; + PerProvider m_patternFileDirty; ImRect m_textEditorHoverBox; ImRect m_consoleHoverBox; @@ -355,7 +348,9 @@ namespace hex::plugin::builtin { void historyInsert(std::array &history, u32 &size, u32 &index, const std::string &value); - void loadPatternFile(const std::fs::path &path, prv::Provider *provider); + void loadPatternFile(const std::fs::path &path, prv::Provider *provider, bool trackFile = false); + bool isPatternDirty(prv::Provider *provider) { return m_patternFileDirty.get(provider); } + void markPatternFileDirty(prv::Provider *provider) { m_patternFileDirty.get(provider) = true; } void parsePattern(const std::string &code, prv::Provider *provider); void evaluatePattern(const std::string &code, prv::Provider *provider); @@ -368,16 +363,12 @@ namespace hex::plugin::builtin { void registerMenuItems(); void registerHandlers(); - // External pattern file management - void trackExternalFile(const std::fs::path &path, prv::Provider *provider); - void checkExternalFileChanges(); - bool hasExternalFileChanged(const ExternalPatternFile &fileInfo) const; - size_t calculateContentHash(const std::string &content) const; - void writeChangesToExternalFile(); - void showFileConflictPopup(const std::fs::path &path, prv::Provider *provider); + void handleFileChange(prv::Provider *provider); - std::function m_importPatternFile = [this] { + std::function m_openPatternFile = [this](bool trackFile) { auto provider = ImHexApi::Provider::get(); + if (provider == nullptr) + return; const auto basePaths = paths::Patterns.read(); std::vector paths; @@ -439,22 +430,48 @@ namespace hex::plugin::builtin { return m_patternNames[path]; }, - [this, provider](const std::fs::path &path) { - this->loadPatternFile(path, provider); + [this, provider, trackFile](const std::fs::path &path) { + this->loadPatternFile(path, provider, trackFile); AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.load_existing.name"); } ); }; - std::function m_exportPatternFile = [this] { + std::function m_savePatternFile = [this](bool trackFile) { auto provider = ImHexApi::Provider::get(); + if (provider == nullptr) + return; + auto path = m_changeTracker.get(provider).getPath(); + wolv::io::File file(path, wolv::io::File::Mode::Write); + if (file.isValid() && trackFile) { + if (isPatternDirty(provider)) { + file.writeString(wolv::util::trim(m_textEditor.get(provider).GetText())); + m_patternFileDirty.get(provider) = false; + } + return; + } + m_savePatternAsFile(trackFile); + }; + + std::function m_savePatternAsFile = [this](bool trackFile) { + auto provider = ImHexApi::Provider::get(); + if (provider == nullptr) + return; fs::openFileBrowser( - fs::DialogMode::Save, { {"Pattern", "hexpat"} }, - [this, provider](const auto &path) { + fs::DialogMode::Save, { {"Pattern File", "hexpat"}, {"Pattern Import File", "pat"} }, + [this, provider, trackFile](const auto &path) { wolv::io::File file(path, wolv::io::File::Mode::Create); file.writeString(wolv::util::trim(m_textEditor.get(provider).GetText())); + m_patternFileDirty.get(provider) = false; + auto loadedPath = m_changeTracker.get(provider).getPath(); + if ((loadedPath.empty() && loadedPath != path) || (!loadedPath.empty() && !trackFile)) + m_changeTracker.get(provider).stopTracking(); - this->trackExternalFile(path, provider); + if (trackFile) { + m_changeTracker.get(provider) = wolv::io::ChangeTracker(file); + m_changeTracker.get(provider).startTracking([this, provider]{ this->handleFileChange(provider); }); + m_ignoreNextChangeEvent.get(provider) = true; + } } ); }; diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 4a080e556..701dda342 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -492,7 +492,6 @@ "hex.builtin.setting.general.save_recent_providers": "Save recently used providers", "hex.builtin.setting.general.show_tips": "Show tips on startup", "hex.builtin.setting.general.sync_pattern_source": "Sync pattern source code between providers", - "hex.builtin.setting.general.sync_pattern_file": "Sync pattern source code with exported pattern file", "hex.builtin.setting.general.upload_crash_logs": "Upload crash reports", "hex.builtin.setting.hex_editor": "Hex Editor", "hex.builtin.setting.hex_editor.byte_padding": "Extra byte cell padding", @@ -974,13 +973,16 @@ "hex.builtin.view.pattern_editor.find_hint": "Find", "hex.builtin.view.pattern_editor.find_hint_history": " for history)", "hex.builtin.view.pattern_editor.goto_line": "Go to line: ", + "hex.builtin.view.pattern_editor.menu.edit.cut": "Cut", "hex.builtin.view.pattern_editor.menu.edit.place_pattern": "Place pattern", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin": "Built-in Type", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array": "Array", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single": "Single", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom": "Custom Type", - "hex.builtin.view.pattern_editor.menu.file.load_pattern": "Load pattern...", - "hex.builtin.view.pattern_editor.menu.file.save_pattern": "Save pattern...", + "hex.builtin.view.pattern_editor.menu.file.load_pattern": "Load Pattern...", + "hex.builtin.view.pattern_editor.menu.file.open_pattern": "Open Pattern...", + "hex.builtin.view.pattern_editor.menu.file.save_pattern": "Save Pattern", + "hex.builtin.view.pattern_editor.menu.file.save_pattern_as": "Save Pattern AS...", "hex.builtin.view.pattern_editor.menu.find": "Find...", "hex.builtin.view.pattern_editor.menu.find_next": "Find Next", "hex.builtin.view.pattern_editor.menu.find_previous": "Find Previous", @@ -1001,25 +1003,29 @@ "hex.builtin.view.pattern_editor.replace_hint_history": " for history)", "hex.builtin.view.pattern_editor.settings": "Settings", "hex.builtin.view.pattern_editor.shortcut.goto_line": "Go to line ...", - "hex.builtin.view.pattern_editor.shortcut.find": "Search ...", - "hex.builtin.view.pattern_editor.shortcut.replace": "Replace ...", - "hex.builtin.view.pattern_editor.shortcut.find_next": "Find Next", - "hex.builtin.view.pattern_editor.shortcut.find_previous": "Find Previous", + "hex.builtin.view.pattern_editor.menu.file.find": "Search ...", + "hex.builtin.view.pattern_editor.menu.file.replace": "Replace ...", + "hex.builtin.view.pattern_editor.menu.file.find_next": "Find Next", + "hex.builtin.view.pattern_editor.menu.file.find_previous": "Find Previous", "hex.builtin.view.pattern_editor.shortcut.match_case_toggle": "Toggle Case Sensitive Search", "hex.builtin.view.pattern_editor.shortcut.regex_toggle": "Toggle Regular Expression Search/Replace", "hex.builtin.view.pattern_editor.shortcut.whole_word_toggle": "Toggle Whole Word Search", - "hex.builtin.view.pattern_editor.shortcut.save_project": "Save Project", - "hex.builtin.view.pattern_editor.shortcut.open_project": "Open Project ...", - "hex.builtin.view.pattern_editor.shortcut.save_project_as": "Save Project As ...", + "hex.builtin.view.pattern_editor.menu.file.save_project": "Save Project", + "hex.builtin.view.pattern_editor.menu.file.open_project": "Open Project ...", + "hex.builtin.view.pattern_editor.menu.file.save_project_as": "Save Project As ...", + "hex.builtin.view.pattern_editor.menu.edit.copy": "Copy", "hex.builtin.view.pattern_editor.shortcut.copy": "Copy Selection to the Clipboard", "hex.builtin.view.pattern_editor.shortcut.cut": "Copy Selection to the Clipboard and Delete it", + "hex.builtin.view.pattern_editor.menu.edit.cut": "Cut", "hex.builtin.view.pattern_editor.shortcut.paste": "Paste Clipboard Contents at the Cursor Position", + "hex.builtin.view.pattern_editor.menu.edit.paste": "Paste", "hex.builtin.view.pattern_editor.menu.edit.undo": "Undo", "hex.builtin.view.pattern_editor.menu.edit.redo": "Redo", "hex.builtin.view.pattern_editor.shortcut.toggle_insert": "Toggle Write Over", "hex.builtin.view.pattern_editor.shortcut.delete": "Delete One Character at the Cursor Position", "hex.builtin.view.pattern_editor.shortcut.backspace": "Delete One Character to the Left of Cursor", "hex.builtin.view.pattern_editor.shortcut.select_all": "Select Entire File", + "hex.builtin.view.pattern_editor.menu.edit.select_all": "Select All", "hex.builtin.view.pattern_editor.shortcut.select_left": "Extend Selection One Character to the Left of the Cursor", "hex.builtin.view.pattern_editor.shortcut.select_right": "Extend Selection One Character to the Right of the Cursor", "hex.builtin.view.pattern_editor.shortcut.select_word_left": "Extend Selection One Word to the Left of the Cursor", @@ -1049,10 +1055,10 @@ "hex.builtin.view.pattern_editor.shortcut.move_bottom": "Move Cursor to the End of the File", "hex.builtin.view.pattern_editor.shortcut.delete_word_left": "Delete One Word to the Left of the Cursor", "hex.builtin.view.pattern_editor.shortcut.delete_word_right": "Delete One Word to the Right of the Cursor", - "hex.builtin.view.pattern_editor.shortcut.run_pattern": "Run Pattern", - "hex.builtin.view.pattern_editor.shortcut.step_debugger": "Step Debugger", - "hex.builtin.view.pattern_editor.shortcut.continue_debugger": "Continue Debugger", - "hex.builtin.view.pattern_editor.shortcut.add_breakpoint": "Add Breakpoint", + "hex.builtin.view.pattern_editor.menu.edit.run_pattern": "Run Pattern", + "hex.builtin.view.pattern_editor.menu.edit.step_debugger": "Step Debugger", + "hex.builtin.view.pattern_editor.menu.edit.continue_debugger": "Continue Debugger", + "hex.builtin.view.pattern_editor.menu.edit.add_breakpoint": "Add Breakpoint", "hex.builtin.view.pattern_editor.tooltip.parent_offset": "Parent offset", "hex.builtin.view.pattern_editor.virtual_files": "Virtual Filesystem", "hex.builtin.view.provider_settings.load_error": "An error occurred while trying to open this provider!", diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index bea1b7292..cdea529c2 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -368,7 +368,7 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.file", 1000); /* Create File */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.create_file" }, ICON_VS_FILE, 1050, CTRLCMD + Keys::N, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.create_file" }, ICON_VS_FILE, 1050, CTRLCMD + Keys::N + AllowWhileTyping, [] { auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true); if (newProvider != nullptr && !newProvider->open()) hex::ImHexApi::Provider::remove(newProvider); @@ -377,9 +377,9 @@ namespace hex::plugin::builtin { }, noRunningTasks); /* Open File */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, ICON_VS_FOLDER_OPENED, 1100, CTRLCMD + Keys::O, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, ICON_VS_FOLDER_OPENED, 1100, CTRLCMD + Keys::O + AllowWhileTyping, [] { RequestOpenWindow::post("Open File"); - }, noRunningTasks); + }, noRunningTasks, ContentRegistry::Views::getViewByName("hex.builtin.view.hex_editor.name")); /* Open Other */ ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_other"}, ICON_VS_TELESCOPE, 1150, [] { @@ -390,7 +390,7 @@ namespace hex::plugin::builtin { }, noRunningTasks); /* Reload Provider */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.reload_provider"}, ICON_VS_REFRESH, 1250, CTRLCMD + Keys::R, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.reload_provider"}, ICON_VS_REFRESH, 1250, CTRLCMD + Keys::R + AllowWhileTyping, [] { auto provider = ImHexApi::Provider::get(); provider->close(); @@ -405,15 +405,15 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.project" }, ICON_VS_NOTEBOOK, 1400, []{}, noRunningTasks); ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.open" }, ICON_VS_ROOT_FOLDER_OPENED, 1410, - ALT + Keys::O, + CTRL + ALT + Keys::O + AllowWhileTyping, openProject, noRunningTasks); ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save" }, ICON_VS_SAVE, 1450, - ALT + Keys::S, + CTRL + ALT + Keys::S + AllowWhileTyping, saveProject, [&] { return noRunningTaskAndValidProvider() && ProjectFile::hasPath(); }); ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save_as" }, ICON_VS_SAVE_AS, 1500, - ALT + SHIFT + Keys::S, + ALT + SHIFT + Keys::S + AllowWhileTyping, saveProjectAs, noRunningTaskAndValidProvider); @@ -488,12 +488,12 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 10000); /* Close Provider */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.close"}, ICON_VS_CHROME_CLOSE, 10050, CTRLCMD + Keys::W, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.close"}, ICON_VS_CHROME_CLOSE, 10050, CTRLCMD + Keys::W + AllowWhileTyping, [] { ImHexApi::Provider::remove(ImHexApi::Provider::get()); }, noRunningTaskAndValidProvider); /* Quit ImHex */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.quit"}, ICON_VS_CLOSE_ALL, 10100, ALT + Keys::F4, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.quit"}, ICON_VS_CLOSE_ALL, 10100, ALT + Keys::F4 + AllowWhileTyping, [] { ImHexApi::System::closeImHex(); }); } @@ -506,7 +506,7 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.view", 3000); #if !defined(OS_WEB) - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.view", "hex.builtin.menu.view.always_on_top" }, ICON_VS_PINNED, 1000, Keys::F10, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.view", "hex.builtin.menu.view.always_on_top" }, ICON_VS_PINNED, 1000, Keys::F10 + AllowWhileTyping, [] { static bool state = false; state = !state; @@ -515,7 +515,7 @@ namespace hex::plugin::builtin { #endif #if !defined(OS_MACOS) && !defined(OS_WEB) - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.view", "hex.builtin.menu.view.fullscreen" }, ICON_VS_SCREEN_FULL, 2000, Keys::F11, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.view", "hex.builtin.menu.view.fullscreen" }, ICON_VS_SCREEN_FULL, 2000, Keys::F11 + AllowWhileTyping, [] { static bool state = false; static ImVec2 position, size; diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index 05da0352c..04a9fbc82 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -754,7 +754,6 @@ namespace hex::plugin::builtin { ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.auto_load_patterns", true); ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.sync_pattern_source", false); - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.sync_pattern_file", false); ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.network_interface", false); diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index cc5ce8385..b64b83fd4 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -1279,7 +1279,7 @@ namespace hex::plugin::builtin { /* Copy As */ ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.ascii" }, ICON_VS_SYMBOL_KEY, 1200, - CurrentView + ALT + Keys::C, + CurrentView + CTRLCMD + ALT + Keys::C, [] { auto selection = ImHexApi::HexEditor::getSelection(); if (selection.has_value() && selection != Region::Invalid()) diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 3fd7ad60b..39f79a49a 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -73,7 +74,7 @@ namespace hex::plugin::builtin { paletteIndex = TextEditor::PaletteIndex::Max; while (inBegin < inEnd && isascii(*inBegin) && std::isblank(*inBegin)) - inBegin++; + ++inBegin; if (inBegin == inEnd) { outBegin = inEnd; @@ -542,9 +543,6 @@ namespace hex::plugin::builtin { } if (m_textEditor.get(provider).IsTextChanged()) { - if (m_enableExternalFileTracking) { - writeChangesToExternalFile(); - } m_textEditor.get(provider).SetTextChanged(false); if (!m_hasUnevaluatedChanges.get(provider) ) { m_hasUnevaluatedChanges.get(provider) = true; @@ -552,6 +550,7 @@ namespace hex::plugin::builtin { } m_lastEditorChangeTime = std::chrono::steady_clock::now(); ImHexApi::Provider::markDirty(); + markPatternFileDirty(provider); } if (m_hasUnevaluatedChanges.get(provider) && m_runningEvaluators == 0 && m_runningParsers == 0 && @@ -1327,8 +1326,8 @@ namespace hex::plugin::builtin { if (*m_callStack != nullptr && !(*m_callStack)->empty()) { for (const auto &frame : **m_callStack | std::views::reverse) { auto location = frame.node->getLocation(); - std::string message; if (location.source != nullptr && location.source->mainSource) { + std::string message = ""; if (m_lastEvaluationError->has_value()) message = processMessage((*m_lastEvaluationError)->message); auto key = TextEditor::Coordinates(location.line, location.column); @@ -1664,7 +1663,7 @@ namespace hex::plugin::builtin { } - void ViewPatternEditor::loadPatternFile(const std::fs::path &path, prv::Provider *provider) { + void ViewPatternEditor::loadPatternFile(const std::fs::path &path, prv::Provider *provider, bool trackFile) { wolv::io::File file(path, wolv::io::File::Mode::Read); if (file.isValid()) { auto code = wolv::util::preprocessText(file.readString()); @@ -1672,7 +1671,10 @@ namespace hex::plugin::builtin { this->evaluatePattern(code, provider); m_textEditor.get(provider).SetText(code, true); m_sourceCode.get(provider) = code; - + if (trackFile) { + m_changeTracker.get(provider) = wolv::io::ChangeTracker(file); + m_changeTracker.get(provider).startTracking([this, provider]{ this->handleFileChange(provider); }); + } m_textHighlighter.m_needsToUpdateColors = false; TaskManager::createBackgroundTask("hex.builtin.task.parsing_pattern", [this, code, provider](auto&) { this->parsePattern(code, provider); }); } @@ -1850,8 +1852,8 @@ namespace hex::plugin::builtin { m_textEditor.get(provider).SetCursorPosition(coords); }); - RequestLoadPatternLanguageFile::subscribe(this, [this](const std::fs::path &path) { - this->loadPatternFile(path, ImHexApi::Provider::get()); + RequestLoadPatternLanguageFile::subscribe(this, [this](const std::fs::path &path, bool trackFile) { + this->loadPatternFile(path, ImHexApi::Provider::get(), trackFile); }); RequestRunPatternCode::subscribe(this, [this] { @@ -1875,9 +1877,7 @@ namespace hex::plugin::builtin { ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source", [this](const ContentRegistry::Settings::SettingsValue &value) { m_sourceCode.enableSync(value.get(false)); }); - ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_file", [this](const ContentRegistry::Settings::SettingsValue &value) { - m_enableExternalFileTracking = value.get(false); - }); + ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns", [this](const ContentRegistry::Settings::SettingsValue &value) { m_autoLoadPatterns = value.get(true); }); @@ -1942,11 +1942,6 @@ namespace hex::plugin::builtin { m_virtualFiles->emplace_back(path, data, region); }); - EventWindowFocused::subscribe(this, [this](bool focused) { - if (focused && m_enableExternalFileTracking) { - checkExternalFileChanges(); - } - }); } static void createNestedMenu(const std::vector &menus, const std::function &function) { @@ -1999,57 +1994,43 @@ namespace hex::plugin::builtin { void ViewPatternEditor::registerMenuItems() { - /* Undo */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.undo" }, ICON_VS_DISCARD, 1000, AllowWhileTyping + CTRLCMD + Keys::Z, [this] { - m_textEditor->Undo(); - }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->CanUndo() && m_focusedSubWindowName.contains(textEditorView); }, + + /* Open File */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.file.open_pattern" }, ICON_VS_FOLDER_OPENED, 1100, AllowWhileTyping + CTRLCMD + Keys::O, [this] { + m_openPatternFile(true); + }, [] { return ImHexApi::Provider::isValid(); }, this); - /* Redo */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.redo" }, ICON_VS_REDO, 1100, AllowWhileTyping + CTRLCMD + Keys::Y, [this] { - m_textEditor->Redo(); - }, [this] { return ImHexApi::Provider::isValid() &&m_textEditor->CanRedo() && m_focusedSubWindowName.contains(textEditorView); }, - this); + /* Save */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.file.save_pattern" }, ICON_VS_SAVE, 1350, AllowWhileTyping + CTRLCMD + Keys::S, [this] { + m_savePatternFile(true); + },[this] { + auto provider = ImHexApi::Provider::get(); + bool providerValid = ImHexApi::Provider::isValid(); - ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1200, this); - - - /* Cut */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_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); }, - this); - - /* Copy */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy" }, ICON_VS_COPY, 1400, AllowWhileTyping + CTRLCMD + Keys::C, [this] { - if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { - editor->Copy(); - } else { - m_textEditor->Copy(); - } - }, [this] { - if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) - return ImHexApi::Provider::isValid() && editor->HasSelection(); - else - return false; + return providerValid && isPatternDirty(provider); }, this); - /* Paste */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.paste" }, ICON_VS_OUTPUT, 1500, AllowWhileTyping + CTRLCMD + Keys::V, [this] { - m_textEditor->Paste(); - }, [this] { return m_focusedSubWindowName.contains(textEditorView); }, + /* Save As */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.file.save_pattern_as" }, ICON_VS_SAVE_AS, 1375, AllowWhileTyping + CTRLCMD + SHIFT + Keys::S, [this] { + m_savePatternAsFile(true); + },[] { + return ImHexApi::Provider::isValid(); + }, this); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 1500, this); + /* Find */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.find" }, ICON_VS_SEARCH, 1700, AllowWhileTyping + CTRLCMD + Keys::F, [this] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.find" }, ICON_VS_SEARCH, 1510, AllowWhileTyping + CTRLCMD + Keys::F, [this] { m_replaceMode = false; m_openFindReplacePopUp = true; }, [] { return true; }, this); /* Find Next */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.find_next" }, 1800, AllowWhileTyping + Keys::F3, [this] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.find_next" }, 1520, AllowWhileTyping + Keys::F3, [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { TextEditor::FindReplaceHandler *findReplaceHandler = editor->GetFindReplaceHandler(); findReplaceHandler->FindMatch(editor, true); @@ -2067,7 +2048,7 @@ namespace hex::plugin::builtin { this); /* Find Previous */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.find_previous" }, 1900, AllowWhileTyping + SHIFT + Keys::F3, [this] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.find_previous" }, 1530, AllowWhileTyping + SHIFT + Keys::F3, [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { TextEditor::FindReplaceHandler *findReplaceHandler = editor->GetFindReplaceHandler(); findReplaceHandler->FindMatch(editor, false); @@ -2085,52 +2066,150 @@ namespace hex::plugin::builtin { this); /* Replace */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace" }, ICON_VS_REPLACE, 2000, AllowWhileTyping + CTRLCMD + Keys::H, [this] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace" }, ICON_VS_REPLACE, 1540, AllowWhileTyping + CTRLCMD + Keys::H, [this] { m_replaceMode = true; m_openFindReplacePopUp = true; }, [this] { return m_focusedSubWindowName.contains(textEditorView); }, this); /* Replace Next */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_next" }, 2100, Shortcut::None, [this] { + ContentRegistry::Interface::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); }, []{ return false; }, this); /* Replace Previous */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_previous" }, 2200, Shortcut::None, [this] { + ContentRegistry::Interface::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); }, []{ return false; }, this); /* Replace All */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_all" }, ICON_VS_REPLACE_ALL, 2300, Shortcut::None, [this] { + ContentRegistry::Interface::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); }, this); - ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 2400, this); /* Goto Line */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.goto_line" }, ICON_VS_DEBUG_STEP_INTO, 2500, AllowWhileTyping + CTRLCMD + Keys::G, [this] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.goto_line" }, ICON_VS_DEBUG_STEP_INTO, 1600, AllowWhileTyping + CTRLCMD + Keys::G, [this] { m_openGotoLinePopUp = true; }, [] { return true; }, this); - ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 4200, this); - /* Import Pattern */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.pattern" }, ICON_VS_FILE_CODE, 5600, Shortcut::None, - m_importPatternFile, ImHexApi::Provider::isValid); + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.pattern" }, ICON_VS_FILE_CODE, 5600, Shortcut::None, [this] { + m_openPatternFile(false); + }, ImHexApi::Provider::isValid); /* Export Pattern */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, ICON_VS_FILE_CODE, 7050, Shortcut::None, - m_exportPatternFile, [this] { - return ImHexApi::Provider::isValid() && !wolv::util::trim(m_textEditor->GetText()).empty(); - } - ); + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, ICON_VS_FILE_CODE, 7050, Shortcut::None, [this] { + m_savePatternFile(false); + }, [this] { + return ImHexApi::Provider::isValid() && !wolv::util::trim(m_textEditor->GetText()).empty(); + }); + + /* Undo */ + ContentRegistry::Interface::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); }, + this); + + /* Redo */ + ContentRegistry::Interface::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); }, + this); + + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1280, this); + + + /* Cut */ + ContentRegistry::Interface::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); }, + this); + + /* Copy */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.copy" }, ICON_VS_COPY, 1400, AllowWhileTyping + CTRLCMD + Keys::C, [this] { + if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { + editor->Copy(); + } else { + m_textEditor->Copy(); + } + }, [this] { + if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) + return ImHexApi::Provider::isValid() && editor->HasSelection(); + else + return false; + }, + this); + + /* Paste */ + ContentRegistry::Interface::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(); + }, [this] { return m_focusedSubWindowName.contains(textEditorView); }, + this); + + + /* Select All */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.select_all" }, ICON_VS_LIST_FLAT, 1650, AllowWhileTyping + CTRLCMD + Keys::A, [this] { + if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) + editor->SelectAll(); + }, [] { return ImHexApi::Provider::isValid(); }, + this); + + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1700, this); + + /* Add Breakpoint */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.add_breakpoint"}, ICON_VS_DEBUG_BREAKPOINT_DATA, 1750, Keys::F8 + AllowWhileTyping, [this] { + const auto line = m_textEditor.get(ImHexApi::Provider::get()).GetCursorPosition().mLine + 1; + const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); + + auto &evaluator = runtime.getInternals().evaluator; + m_breakpoints = m_textEditor.get(ImHexApi::Provider::get()).GetBreakpoints(); + evaluator->setBreakpoints(m_breakpoints); + + if (m_breakpoints->contains(line)) + evaluator->removeBreakpoint(line); + else + evaluator->addBreakpoint(line); + + m_breakpoints = evaluator->getBreakpoints(); + m_textEditor.get(ImHexApi::Provider::get()).SetBreakpoints(m_breakpoints); + }, [] { return ImHexApi::Provider::isValid(); }, + this); + + /* Trigger Evaluation */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit","hex.builtin.view.pattern_editor.menu.edit.run_pattern" }, ICON_VS_PLAY, 1800, Keys::F5 + AllowWhileTyping, [this] { + auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); + if (runtime.isRunning()) { + m_breakpointHit = false; + runtime.abort(); + } + m_triggerAutoEvaluate = true; + }, [] { return ImHexApi::Provider::isValid(); }, + this); + + /* Continue debugger */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit","hex.builtin.view.pattern_editor.menu.edit.continue_debugger"}, ICON_VS_DEBUG_CONTINUE, 1850, SHIFT + Keys::F9 + AllowWhileTyping, [this] { + const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); + if (runtime.isRunning()) + m_breakpointHit = false; + }, [] { return ImHexApi::Provider::isValid(); }, + this); + + /* Step debugger */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit","hex.builtin.view.pattern_editor.menu.edit.step_debugger" },ICON_VS_DEBUG_STEP_INTO, 1900, SHIFT + Keys::F7 + AllowWhileTyping, [this] { + const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); + if (runtime.isRunning()) { + runtime.getInternals().evaluator->pauseNextLine(); + m_breakpointHit = false; + } + }, [] { return ImHexApi::Provider::isValid(); }, + this); constexpr static std::array, 21> Types = {{ { "u8", 1 }, { "u16", 2 }, { "u24", 3 }, { "u32", 4 }, { "u48", 6 }, { "u64", 8 }, { "u96", 12 }, { "u128", 16 }, @@ -2313,49 +2392,32 @@ namespace hex::plugin::builtin { } }); - ShortcutManager::addShortcut(this, ALT + Keys::C + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.match_case_toggle", [this] { + ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::C + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.match_case_toggle", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { TextEditor::FindReplaceHandler *findReplaceHandler = editor->GetFindReplaceHandler(); findReplaceHandler->SetMatchCase(editor, !findReplaceHandler->GetMatchCase()); } }); - ShortcutManager::addShortcut(this, ALT + Keys::R + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.regex_toggle", [this] { + ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::R + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.regex_toggle", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { TextEditor::FindReplaceHandler *findReplaceHandler = editor->GetFindReplaceHandler(); findReplaceHandler->SetFindRegEx(editor, !findReplaceHandler->GetFindRegEx()); } }); - ShortcutManager::addShortcut(this, ALT + Keys::W + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.whole_word_toggle", [this] { + ShortcutManager::addShortcut(this, CTRL + SHIFT + Keys::W + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.whole_word_toggle", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) { TextEditor::FindReplaceHandler *findReplaceHandler = editor->GetFindReplaceHandler(); findReplaceHandler->SetWholeWord(editor, !findReplaceHandler->GetWholeWord()); } }); - ShortcutManager::addShortcut(this, ALT + Keys::S + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.save_project", [] { - hex::plugin::builtin::saveProject(); - }); - - ShortcutManager::addShortcut(this, ALT + Keys::O + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.open_project", [] { - hex::plugin::builtin::openProject(); - }); - - ShortcutManager::addShortcut(this, ALT + SHIFT + Keys::S + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.save_project_as", [] { - hex::plugin::builtin::saveProjectAs(); - }); - ShortcutManager::addShortcut(this, Keys::Delete + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.delete", [this] { if (m_focusedSubWindowName.contains(textEditorView)) m_textEditor.get(ImHexApi::Provider::get()).Delete(); }); - ShortcutManager::addShortcut(this, CTRLCMD + Keys::A + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.select_all", [this] { - if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) - editor->SelectAll(); - }); - ShortcutManager::addShortcut(this, SHIFT + Keys::Right + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.select_right", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) editor->MoveRight(1, true, false); @@ -2511,49 +2573,6 @@ namespace hex::plugin::builtin { editor->MoveToMatchedBracket(false); }); - ShortcutManager::addShortcut(this, Keys::F8 + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.add_breakpoint", [this] { - const auto line = m_textEditor.get(ImHexApi::Provider::get()).GetCursorPosition().mLine + 1; - const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); - - auto &evaluator = runtime.getInternals().evaluator; - m_breakpoints = m_textEditor.get(ImHexApi::Provider::get()).GetBreakpoints(); - evaluator->setBreakpoints(m_breakpoints); - - if (m_breakpoints->contains(line)) { - evaluator->removeBreakpoint(line); - } else { - evaluator->addBreakpoint(line); - } - m_breakpoints = evaluator->getBreakpoints(); - m_textEditor.get(ImHexApi::Provider::get()).SetBreakpoints(m_breakpoints); - }); - - /* Trigger evaluation */ - ShortcutManager::addGlobalShortcut(Keys::F5 + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.run_pattern", [this] { - auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); - if (runtime.isRunning()) { - m_breakpointHit = false; - runtime.abort(); - } - m_triggerAutoEvaluate = true; - }); - - /* Continue debugger */ - ShortcutManager::addGlobalShortcut(SHIFT + Keys::F9 + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.continue_debugger", [this] { - const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); - if (runtime.isRunning()) - m_breakpointHit = false; - }); - - /* Step debugger */ - ShortcutManager::addGlobalShortcut(SHIFT + Keys::F7 + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.step_debugger", [this] { - const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); - if (runtime.isRunning()) { - runtime.getInternals().evaluator->pauseNextLine(); - m_breakpointHit = false; - } - }); - // Generate pattern code report ContentRegistry::Reports::addReportProvider([this](prv::Provider *provider) -> std::string { const auto &patternCode = m_sourceCode.get(provider); @@ -2572,145 +2591,19 @@ namespace hex::plugin::builtin { }); } - void ViewPatternEditor::trackExternalFile(const std::fs::path &path, prv::Provider *provider) { - - if (!std::filesystem::exists(path) || provider == nullptr) { + void ViewPatternEditor::handleFileChange(prv::Provider *provider) { + if (m_ignoreNextChangeEvent.get(provider)) { + m_ignoreNextChangeEvent.get(provider) = false; return; } - try { - auto lastModified = std::filesystem::last_write_time(path); - auto content = m_textEditor.get(provider).GetText(); - auto contentHash = calculateContentHash(content); - - ExternalPatternFile fileInfo = { - .path = path, - .lastModified = lastModified, - .contentHash = contentHash, - .originalContent = content - }; - - m_externalPatternFile.get(provider) = fileInfo; - } catch (const std::filesystem::filesystem_error &) { - - m_externalPatternFile.get(provider) = std::nullopt; - } - } - - void ViewPatternEditor::checkExternalFileChanges() { - - if (m_checkingExternalFile) { - return; - } - auto provider = ImHexApi::Provider::get(); - - if (provider == nullptr ) - return; - auto &externalFile = m_externalPatternFile.get(provider); - - if (!externalFile.has_value()) { + if (m_changeEventAcknowledgementPending.get(provider)) { return; } - if (hasExternalFileChanged(*externalFile)) { - m_checkingExternalFile = true; - - try { - wolv::io::File file(externalFile->path, wolv::io::File::Mode::Read); - if (file.isValid()) { - auto newContent = wolv::util::preprocessText(file.readString()); - auto currentContent = m_textEditor.get(provider).GetText(); - bool internalModified = (calculateContentHash(currentContent) != externalFile->contentHash); - - if (internalModified) { - showFileConflictPopup(externalFile->path, provider); - - } else { - m_textEditor.get(provider).SetText(newContent, true); - m_sourceCode.get(provider) = newContent; - trackExternalFile(externalFile->path, provider); - } - } - - } catch (const std::filesystem::filesystem_error &) { - m_externalPatternFile.get(provider) = std::nullopt; - } - m_checkingExternalFile = false; - } - } - - void ViewPatternEditor::writeChangesToExternalFile() { - auto provider = ImHexApi::Provider::get(); - - if (provider == nullptr) - return; - auto &externalFile = m_externalPatternFile.get(provider); - - if (!externalFile.has_value()) { - return; - } - const auto &path = externalFile->path; - - try { - wolv::io::File file(path, wolv::io::File::Mode::Write); - auto code = m_textEditor.get(provider).GetText(); - - if (file.isValid()) - file.writeString(code); - file.flush(); - file.close(); - auto lastModified = std::filesystem::last_write_time(path); - auto content = code; - auto contentHash = calculateContentHash(content); - externalFile->contentHash = contentHash; - externalFile->lastModified = lastModified; - externalFile->originalContent = content; - - } catch (const std::filesystem::filesystem_error &) { - externalFile->contentHash = 0; - externalFile->lastModified = std::filesystem::file_time_type::min(); - externalFile->originalContent.clear(); - } - } - - bool ViewPatternEditor::hasExternalFileChanged(const ExternalPatternFile &fileInfo) const { - - try { - if (!std::filesystem::exists(fileInfo.path)) { - return false; - } - - return std::filesystem::last_write_time(fileInfo.path) != fileInfo.lastModified; - } catch (const std::filesystem::filesystem_error &) { - return false; - } - } - - size_t ViewPatternEditor::calculateContentHash(const std::string &content) const { - return std::hash{}(content); - } - - void ViewPatternEditor::showFileConflictPopup(const std::fs::path &path, prv::Provider *provider) { - ui::PopupQuestion::open(hex::format("hex.builtin.view.pattern_editor.conflict_resolution"_lang, wolv::util::toUTF8String(path.filename())), [this, path, provider] { - wolv::io::File file(path, wolv::io::File::Mode::Read); - - if (file.isValid()) { - auto newContent = wolv::util::preprocessText(file.readString()); - m_textEditor.get(provider).SetText(newContent, true); - m_sourceCode.get(provider) = newContent; - - trackExternalFile(path, provider); - } - }, [this, path, provider] { - auto &externalFile = m_externalPatternFile.get(provider); - - if (externalFile.has_value()) { - try { - externalFile->lastModified = std::filesystem::last_write_time(path); - } catch (const std::filesystem::filesystem_error &) { - externalFile->lastModified = std::filesystem::file_time_type::min(); - } - } + m_changeEventAcknowledgementPending.get(provider) = true; + hex::ui::BannerButton::open(ICON_VS_INFO, "hex.builtin.provider.file.reload_changes", ImColor(66, 104, 135), "hex.builtin.provider.file.reload_changes.reload", [this, provider] { + m_changeEventAcknowledgementPending.get(provider) = false; }); } } diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index 029639b26..983f2c438 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -108,8 +108,6 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { addInitTasks(); extractBundledFiles(); - registerMainMenuEntries(); - addFooterItems(); addTitleBarButtons(); addToolbarItems(); @@ -132,6 +130,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { registerProviders(); registerDataFormatters(); registerViews(); + registerMainMenuEntries(); registerThemeHandlers(); registerStyleHandlers(); registerBackgroundServices();