From d02e147dc35061c7b7cf02216914e51e1ff82101 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sun, 24 Aug 2025 10:34:07 +0200 Subject: [PATCH] impr: Move most of the implementations of the pattern editor view to the cpp file --- .../content/views/view_pattern_editor.hpp | 253 +------------- .../content/views/view_pattern_editor.cpp | 312 +++++++++++++++--- 2 files changed, 286 insertions(+), 279 deletions(-) diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index f8c6ace40..b44ad884e 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -1,61 +1,27 @@ #pragma once -#include #include -#include #include -#include +#include #include #include -#include - -#include -#include -#include - -#include -#include #include -#include #include +#include -namespace pl::ptrn { class Pattern; } + namespace pl::ptrn { class Pattern; } namespace hex::plugin::builtin { - - constexpr static auto textEditorView = "/##pattern_editor_"; - constexpr static auto consoleView = "/##console_"; - constexpr static auto variablesView = "/##env_vars_"; - constexpr static auto settingsView = "/##settings_"; - constexpr static auto virtualFilesView = "/##Virtual_File_Tree_"; - constexpr static auto debuggerView = "/##debugger_"; - class PatternSourceCode { public: - const std::string& get(prv::Provider *provider) const { - if (m_synced) - return m_sharedSource; + const std::string& get(prv::Provider *provider) const; + std::string& get(prv::Provider *provider); - return m_perProviderSource.get(provider); - } - - std::string& get(prv::Provider *provider) { - if (m_synced) - return m_sharedSource; - - return m_perProviderSource.get(provider); - } - - bool isSynced() const { - return m_synced; - } - - void enableSync(bool enabled) { - m_synced = enabled; - } + bool isSynced() const; + void enableSync(bool enabled); private: bool m_synced = false; @@ -114,98 +80,8 @@ namespace hex::plugin::builtin { }; private: - class PopupAcceptPattern : public Popup { - public: - explicit PopupAcceptPattern(ViewPatternEditor *view) : Popup("hex.builtin.view.pattern_editor.accept_pattern"), m_view(view) {} + class PopupAcceptPattern; - void drawContent() override { - std::scoped_lock lock(m_view->m_possiblePatternFilesMutex); - - auto* provider = ImHexApi::Provider::get(); - - ImGuiExt::TextFormattedWrapped("{}", static_cast("hex.builtin.view.pattern_editor.accept_pattern.desc"_lang)); - - if (ImGui::BeginListBox("##patterns_accept", ImVec2(400_scaled, 0))) { - u32 index = 0; - for (const auto &[path, author, description] : m_view->m_possiblePatternFiles.get(provider)) { - ImGui::PushID(index + 1); - auto fileName = wolv::util::toUTF8String(path.filename()); - - std::string displayValue; - if (!description.empty()) { - displayValue = fmt::format("{} ({})", description, fileName); - } else { - displayValue = fileName; - } - - if (ImGui::Selectable(displayValue.c_str(), index == m_selectedPatternFile, ImGuiSelectableFlags_NoAutoClosePopups)) - m_selectedPatternFile = index; - - if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal)) { - if (ImGui::BeginTooltip()) { - ImGui::TextUnformatted(fileName.c_str()); - - if (!author.empty()) { - ImGui::SameLine(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - ImGui::SameLine(); - ImGui::TextUnformatted(author.c_str()); - } - - if (!description.empty()) { - ImGui::Separator(); - ImGui::TextUnformatted(description.c_str()); - } - - ImGui::EndTooltip(); - } - } - - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) - m_view->loadPatternFile(m_view->m_possiblePatternFiles.get(provider)[m_selectedPatternFile].path, provider, false); - - ImGuiExt::InfoTooltip(wolv::util::toUTF8String(path).c_str()); - - index++; - - ImGui::PopID(); - } - - // Close the popup if there aren't any patterns available - if (index == 0) - this->close(); - - ImGui::EndListBox(); - } - - ImGui::NewLine(); - ImGui::TextUnformatted("hex.builtin.view.pattern_editor.accept_pattern.question"_lang); - ImGui::NewLine(); - - 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, false); - this->close(); - }, - [this] { - this->close(); - } - ); - - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) - this->close(); - } - - [[nodiscard]] ImGuiWindowFlags getFlags() const override { - return ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; - } - - private: - ViewPatternEditor *m_view; - u32 m_selectedPatternFile = 0; - }; - - private: struct PatternVariable { bool inVariable; bool outVariable; @@ -365,116 +241,9 @@ namespace hex::plugin::builtin { void handleFileChange(prv::Provider *provider); - 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; - - for (const auto &imhexPath : basePaths) { - if (!wolv::io::fs::exists(imhexPath)) continue; - - std::error_code error; - for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) { - if (entry.is_regular_file() && entry.path().extension() == ".hexpat") - paths.push_back(entry.path()); - } - } - - auto createRuntime = [provider] { - auto runtime = std::make_shared(); - ContentRegistry::PatternLanguage::configureRuntime(*runtime, provider); - - return runtime; - }; - - ui::PopupNamedFileChooser::open( - basePaths, paths, std::vector{ { "Pattern File", "hexpat" } }, false, - [this, runtime = createRuntime()](const std::fs::path &path, const std::fs::path &adjustedPath) mutable -> std::string { - if (auto it = m_patternNames.find(path); it != m_patternNames.end()) { - return it->second; - } - - const auto fileName = wolv::util::toUTF8String(adjustedPath.filename()); - m_patternNames[path] = fileName; - - wolv::io::File file(path, wolv::io::File::Mode::Read); - pl::api::Source source(file.readString()); - - // Only run the lexer on the source file and manually extract the #pragma description to make this - // process as fast as possible. Running the preprocessor directly takes too much time - auto result = runtime->getInternals().lexer->lex(&source); - if (result.isOk()) { - const auto tokens = result.unwrap(); - for (auto it = tokens.begin(); it != tokens.end(); ++it) { - if (it->type == pl::core::Token::Type::Directive && std::get(it->value) == pl::core::Token::Directive::Pragma) { - ++it; - if (it != tokens.end() && it->type == pl::core::Token::Type::String) { - auto literal = std::get(it->value); - auto string = std::get_if(&literal); - if (string != nullptr && *string == "description") { - ++it; - if (it != tokens.end() && it->type == pl::core::Token::Type::String) { - literal = std::get(it->value); - string = std::get_if(&literal); - if (string != nullptr) { - m_patternNames[path] = fmt::format("{} ({})", *string, fileName); - } - } - } - } - } - } - } - - return m_patternNames[path]; - }, - [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_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 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(); - - 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; - } - } - ); - }; + void openPatternFile(bool trackFile); + void savePatternToCurrentFile(bool trackFile); + void savePatternAsNewFile(bool trackFile); void appendEditorText(const std::string &text); void appendVariable(const std::string &type); diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 8b5d21d2a..3333797f7 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -1,15 +1,17 @@ #include "content/views/view_pattern_editor.hpp" #include +#include #include #include #include #include #include +#include #include #include -#include +#include #include #include @@ -30,6 +32,7 @@ #include #include +#include #include #include @@ -39,7 +42,6 @@ #include #include -#include #include #include #include @@ -48,6 +50,128 @@ namespace hex::plugin::builtin { using namespace hex::literals; + constexpr static auto TextEditorView = "/##pattern_editor_"; + constexpr static auto ConsoleView = "/##console_"; + constexpr static auto VariablesView = "/##env_vars_"; + constexpr static auto SettingsView = "/##settings_"; + constexpr static auto VirtualFilesView = "/##virtual_file_tree_"; + constexpr static auto DebuggerView = "/##debugger_"; + + class ViewPatternEditor::PopupAcceptPattern : public Popup { + public: + explicit PopupAcceptPattern(ViewPatternEditor *view) : Popup("hex.builtin.view.pattern_editor.accept_pattern"), m_view(view) {} + + void drawContent() override { + std::scoped_lock lock(m_view->m_possiblePatternFilesMutex); + + auto* provider = ImHexApi::Provider::get(); + + ImGuiExt::TextFormattedWrapped("{}", static_cast("hex.builtin.view.pattern_editor.accept_pattern.desc"_lang)); + + if (ImGui::BeginListBox("##patterns_accept", ImVec2(400_scaled, 0))) { + u32 index = 0; + for (const auto &[path, author, description] : m_view->m_possiblePatternFiles.get(provider)) { + ImGui::PushID(index + 1); + auto fileName = wolv::util::toUTF8String(path.filename()); + + std::string displayValue; + if (!description.empty()) { + displayValue = fmt::format("{} ({})", description, fileName); + } else { + displayValue = fileName; + } + + if (ImGui::Selectable(displayValue.c_str(), index == m_selectedPatternFile, ImGuiSelectableFlags_NoAutoClosePopups)) + m_selectedPatternFile = index; + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal)) { + if (ImGui::BeginTooltip()) { + ImGui::TextUnformatted(fileName.c_str()); + + if (!author.empty()) { + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + ImGui::TextUnformatted(author.c_str()); + } + + if (!description.empty()) { + ImGui::Separator(); + ImGui::TextUnformatted(description.c_str()); + } + + ImGui::EndTooltip(); + } + } + + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) + m_view->loadPatternFile(m_view->m_possiblePatternFiles.get(provider)[m_selectedPatternFile].path, provider, false); + + ImGuiExt::InfoTooltip(wolv::util::toUTF8String(path).c_str()); + + index++; + + ImGui::PopID(); + } + + // Close the popup if there aren't any patterns available + if (index == 0) + this->close(); + + ImGui::EndListBox(); + } + + ImGui::NewLine(); + ImGui::TextUnformatted("hex.builtin.view.pattern_editor.accept_pattern.question"_lang); + ImGui::NewLine(); + + 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, false); + this->close(); + }, + [this] { + this->close(); + } + ); + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) + this->close(); + } + + [[nodiscard]] ImGuiWindowFlags getFlags() const override { + return ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; + } + + private: + ViewPatternEditor *m_view; + u32 m_selectedPatternFile = 0; + }; + + + std::string& PatternSourceCode::get(prv::Provider* provider) { + if (m_synced) + return m_sharedSource; + + return m_perProviderSource.get(provider); + } + + const std::string& PatternSourceCode::get(prv::Provider* provider) const { + if (m_synced) + return m_sharedSource; + + return m_perProviderSource.get(provider); + } + + void PatternSourceCode::enableSync(bool enabled) { + m_synced = enabled; + } + + bool PatternSourceCode::isSynced() const { + return m_synced; + } + + static const ui::TextEditor::LanguageDefinition &PatternLanguage() { static bool initialized = false; static ui::TextEditor::LanguageDefinition langDef; @@ -271,9 +395,9 @@ namespace hex::plugin::builtin { ImVec2 windowPosForPopup; windowPosForPopup.x = m_textEditorHoverBox.Max.x - style.ScrollbarSize - popupSizeX; - if (m_focusedSubWindowName.contains(consoleView)) + if (m_focusedSubWindowName.contains(ConsoleView)) windowPosForPopup.y = m_consoleHoverBox.Min.y; - else if (m_focusedSubWindowName.contains(textEditorView)) + else if (m_focusedSubWindowName.contains(TextEditorView)) windowPosForPopup.y = m_textEditorHoverBox.Min.y; else return; @@ -309,9 +433,9 @@ namespace hex::plugin::builtin { // Move to the right so as not to overlap the scrollbar windowPosForPopup.x = m_textEditorHoverBox.Max.x - style.ScrollbarSize - popupSizeX; - if (m_focusedSubWindowName.contains(consoleView)) { + if (m_focusedSubWindowName.contains(ConsoleView)) { windowPosForPopup.y = m_consoleHoverBox.Min.y; - } else if (m_focusedSubWindowName.contains(textEditorView)) { + } else if (m_focusedSubWindowName.contains(TextEditorView)) { windowPosForPopup.y = m_textEditorHoverBox.Min.y; } else { return; @@ -355,7 +479,7 @@ namespace hex::plugin::builtin { oldHeight = height; if (g.NavWindow != nullptr) { std::string name = g.NavWindow->Name; - if (name.contains(textEditorView) || name.contains(consoleView) || name.contains(variablesView) || name.contains(settingsView) || name.contains(virtualFilesView) || name.contains(debuggerView)) + if (name.contains(TextEditorView) || name.contains(ConsoleView) || name.contains(VariablesView) || name.contains(SettingsView) || name.contains(VirtualFilesView) || name.contains(DebuggerView)) m_focusedSubWindowName = name; } @@ -645,9 +769,9 @@ namespace hex::plugin::builtin { findReplaceHandler->setFindWord(textEditor, findWord); requestFocus = true; updateCount = true; - if (m_focusedSubWindowName.contains(consoleView)) + if (m_focusedSubWindowName.contains(ConsoleView)) canReplace = false; - else if (m_focusedSubWindowName.contains(textEditorView)) + else if (m_focusedSubWindowName.contains(TextEditorView)) canReplace = true; } bool enter = ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false); @@ -656,9 +780,9 @@ namespace hex::plugin::builtin { bool shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); bool alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt); std::string childName; - if (m_focusedSubWindowName.contains(consoleView)) + if (m_focusedSubWindowName.contains(ConsoleView)) childName = "##console_find_replace"; - else if (m_focusedSubWindowName.contains(textEditorView)) + else if (m_focusedSubWindowName.contains(TextEditorView)) childName = "##text_editor_find_replace"; else return; @@ -923,7 +1047,7 @@ namespace hex::plugin::builtin { } ImGui::EndChild(); - if (m_focusedSubWindowName.contains(textEditorView)) { + if (m_focusedSubWindowName.contains(TextEditorView)) { if (auto window = ImGui::GetCurrentWindow(); window != nullptr) { auto height = window->Size.y; auto heightChange = height - m_popupWindowHeight; @@ -949,9 +1073,9 @@ namespace hex::plugin::builtin { ImGuiWindowFlags popupFlags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar; if (ImGui::BeginPopup("##text_editor_view_goto_line_popup", popupFlags)) { std::string childName; - if (m_focusedSubWindowName.contains(consoleView)) + if (m_focusedSubWindowName.contains(ConsoleView)) childName = "##console_goto_line"; - else if (m_focusedSubWindowName.contains(textEditorView)) + else if (m_focusedSubWindowName.contains(TextEditorView)) childName = "##text_editor_goto_line"; else return; @@ -983,7 +1107,7 @@ namespace hex::plugin::builtin { } ImGui::EndChild(); - if (m_focusedSubWindowName.contains(textEditorView)) { + if (m_focusedSubWindowName.contains(TextEditorView)) { if (auto window = ImGui::GetCurrentWindow(); window != nullptr) { auto height = window->Size.y; auto heightChange = height - m_popupWindowHeight; @@ -1220,7 +1344,7 @@ namespace hex::plugin::builtin { for (const auto &file : virtualFiles) virtualFilePointers.emplace_back(&file); - if (ImGui::BeginTable("Virtual File Tree", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, size)) { + if (ImGui::BeginTable("##virtual_file_tree", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, size)) { if (virtualFiles.empty()) { ImGuiExt::TextOverlay("hex.builtin.view.pattern_editor.no_virtual_files"_lang, ImGui::GetWindowPos() + ImGui::GetWindowSize() / 2, ImGui::GetWindowWidth() * 0.7); } @@ -1973,10 +2097,10 @@ namespace hex::plugin::builtin { ui::TextEditor *ViewPatternEditor::getEditorFromFocusedWindow() { auto provider = ImHexApi::Provider::get(); if (provider != nullptr) { - if (m_focusedSubWindowName.contains(consoleView)) { + if (m_focusedSubWindowName.contains(ConsoleView)) { return &m_consoleEditor.get(provider); } - if (m_focusedSubWindowName.contains(textEditorView)) { + if (m_focusedSubWindowName.contains(TextEditorView)) { return &m_textEditor.get(provider); } } @@ -1989,13 +2113,13 @@ namespace hex::plugin::builtin { /* Open File */ ContentRegistry::UserInterface::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); + openPatternFile(true); }, [] { return ImHexApi::Provider::isValid(); }, this); /* Save */ ContentRegistry::UserInterface::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); + savePatternToCurrentFile(true); },[this] { auto provider = ImHexApi::Provider::get(); bool providerValid = ImHexApi::Provider::isValid(); @@ -2006,7 +2130,7 @@ namespace hex::plugin::builtin { /* Save As */ ContentRegistry::UserInterface::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); + savePatternAsNewFile(true); },[] { return ImHexApi::Provider::isValid(); }, @@ -2061,27 +2185,27 @@ namespace hex::plugin::builtin { ContentRegistry::UserInterface::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] { return m_focusedSubWindowName.contains(TextEditorView); }, this); /* Replace Next */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_next" }, 1550, Shortcut::None, [this] { m_textEditor->getFindReplaceHandler()->replace(&*m_textEditor, true); - }, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); }, []{ return false; }, this); /* Replace Previous */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_previous" }, 1560, Shortcut::None, [this] { m_textEditor->getFindReplaceHandler()->replace(&*m_textEditor, false); - }, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); }, []{ return false; }, this); /* Replace All */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.pattern_editor.menu.replace_all" }, ICON_VS_REPLACE_ALL, 1570, Shortcut::None, [this] { m_textEditor->getFindReplaceHandler()->replaceAll(&*m_textEditor); - }, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return ImHexApi::Provider::isValid() && !m_textEditor->getFindReplaceHandler()->getReplaceWord().empty() && m_focusedSubWindowName.contains(TextEditorView); }, this); @@ -2093,12 +2217,12 @@ namespace hex::plugin::builtin { /* Import Pattern */ ContentRegistry::UserInterface::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); + openPatternFile(false); }, ImHexApi::Provider::isValid); /* Export Pattern */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, ICON_VS_FILE_CODE, 7050, Shortcut::None, [this] { - m_savePatternFile(false); + savePatternAsNewFile(false); }, [this] { return ImHexApi::Provider::isValid() && !wolv::util::trim(m_textEditor->getText()).empty(); }); @@ -2106,13 +2230,13 @@ namespace hex::plugin::builtin { /* Undo */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.undo" }, ICON_VS_DISCARD, 1250, AllowWhileTyping + CTRLCMD + Keys::Z, [this] { m_textEditor->undo(); - }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->canUndo() && m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->canUndo() && m_focusedSubWindowName.contains(TextEditorView); }, this); /* Redo */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.redo" }, ICON_VS_REDO, 1275, AllowWhileTyping + CTRLCMD + Keys::Y, [this] { m_textEditor->redo(); - }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->canRedo() && m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->canRedo() && m_focusedSubWindowName.contains(TextEditorView); }, this); ContentRegistry::UserInterface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1280, this); @@ -2121,7 +2245,7 @@ namespace hex::plugin::builtin { /* Cut */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.cut" }, ICON_VS_COMBINE, 1300, AllowWhileTyping + CTRLCMD + Keys::X, [this] { m_textEditor->cut(); - }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->hasSelection() && m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return ImHexApi::Provider::isValid() && m_textEditor->hasSelection() && m_focusedSubWindowName.contains(TextEditorView); }, this); /* Copy */ @@ -2142,7 +2266,7 @@ namespace hex::plugin::builtin { /* Paste */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.paste" }, ICON_VS_OUTPUT, 1500, AllowWhileTyping + CTRLCMD + Keys::V, [this] { m_textEditor->paste(); - }, [this] { return m_focusedSubWindowName.contains(textEditorView); }, + }, [this] { return m_focusedSubWindowName.contains(TextEditorView); }, this); @@ -2406,7 +2530,7 @@ namespace hex::plugin::builtin { }); ShortcutManager::addShortcut(this, Keys::Delete + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.delete", [this] { - if (m_focusedSubWindowName.contains(textEditorView)) + if (m_focusedSubWindowName.contains(TextEditorView)) m_textEditor.get(ImHexApi::Provider::get()).deleteChar(); }); @@ -2471,27 +2595,27 @@ namespace hex::plugin::builtin { }); ShortcutManager::addShortcut(this, CTRLCMD + Keys::Delete + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.delete_word_right", [this] { - if (m_focusedSubWindowName.contains(textEditorView)) + if (m_focusedSubWindowName.contains(TextEditorView)) m_textEditor.get(ImHexApi::Provider::get()).deleteWordRight(); }); ShortcutManager::addShortcut(this, CTRLCMD + Keys::Backspace + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.delete_word_left", [this] { - if (m_focusedSubWindowName.contains(textEditorView)) + if (m_focusedSubWindowName.contains(TextEditorView)) m_textEditor.get(ImHexApi::Provider::get()).deleteWordLeft(); }); ShortcutManager::addShortcut(this, Keys::Backspace + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.backspace", [this] { - if (m_focusedSubWindowName.contains(textEditorView)) + if (m_focusedSubWindowName.contains(TextEditorView)) m_textEditor.get(ImHexApi::Provider::get()).backspace(); }); ShortcutManager::addShortcut(this, SHIFT + Keys::Backspace + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.backspace_shifted", [this] { - if (m_focusedSubWindowName.contains(textEditorView)) + if (m_focusedSubWindowName.contains(TextEditorView)) m_textEditor.get(ImHexApi::Provider::get()).backspace(); }); ShortcutManager::addShortcut(this, Keys::Insert + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.toggle_insert", [this] { - if (m_focusedSubWindowName.contains(textEditorView)) + if (m_focusedSubWindowName.contains(TextEditorView)) m_textEditor.get(ImHexApi::Provider::get()).setOverwrite(!m_textEditor.get(ImHexApi::Provider::get()).isOverwrite()); }); @@ -2603,4 +2727,118 @@ namespace hex::plugin::builtin { m_changeEventAcknowledgementPending.get(provider) = false; }); } + + void ViewPatternEditor::openPatternFile(bool trackFile) { + auto provider = ImHexApi::Provider::get(); + if (provider == nullptr) + return; + const auto basePaths = paths::Patterns.read(); + std::vector paths; + + for (const auto &imhexPath : basePaths) { + if (!wolv::io::fs::exists(imhexPath)) continue; + + std::error_code error; + for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) { + if (entry.is_regular_file() && entry.path().extension() == ".hexpat") + paths.push_back(entry.path()); + } + } + + auto createRuntime = [provider] { + auto runtime = std::make_shared(); + ContentRegistry::PatternLanguage::configureRuntime(*runtime, provider); + + return runtime; + }; + + ui::PopupNamedFileChooser::open( + basePaths, paths, std::vector{ { "Pattern File", "hexpat" } }, false, + [this, runtime = createRuntime()](const std::fs::path &path, const std::fs::path &adjustedPath) mutable -> std::string { + if (auto it = m_patternNames.find(path); it != m_patternNames.end()) { + return it->second; + } + + const auto fileName = wolv::util::toUTF8String(adjustedPath.filename()); + m_patternNames[path] = fileName; + + wolv::io::File file(path, wolv::io::File::Mode::Read); + pl::api::Source source(file.readString()); + + // Only run the lexer on the source file and manually extract the #pragma description to make this + // process as fast as possible. Running the preprocessor directly takes too much time + auto result = runtime->getInternals().lexer->lex(&source); + if (result.isOk()) { + const auto tokens = result.unwrap(); + for (auto it = tokens.begin(); it != tokens.end(); ++it) { + if (it->type == pl::core::Token::Type::Directive && std::get(it->value) == pl::core::Token::Directive::Pragma) { + ++it; + if (it != tokens.end() && it->type == pl::core::Token::Type::String) { + auto literal = std::get(it->value); + auto string = std::get_if(&literal); + if (string != nullptr && *string == "description") { + ++it; + if (it != tokens.end() && it->type == pl::core::Token::Type::String) { + literal = std::get(it->value); + string = std::get_if(&literal); + if (string != nullptr) { + m_patternNames[path] = fmt::format("{} ({})", *string, fileName); + } + } + } + } + } + } + } + + return m_patternNames[path]; + }, + [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"); + } + ); + } + + void ViewPatternEditor::savePatternAsNewFile(bool trackFile) { + auto provider = ImHexApi::Provider::get(); + if (provider == nullptr) + return; + fs::openFileBrowser( + 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(); + + 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; + } + } + ); + } + + void ViewPatternEditor::savePatternToCurrentFile(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; + } + savePatternAsNewFile(trackFile); + } + + + }