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