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.
This commit is contained in:
paxcut
2025-08-02 01:29:34 -07:00
committed by GitHub
parent 05a06fe7a3
commit a9eb7b2d25
9 changed files with 240 additions and 324 deletions

View File

@@ -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

View File

@@ -359,8 +359,8 @@ namespace hex {
const auto provider = get();
if (!provider->isDirty()) {
provider->markDirty();
EventProviderDirtied::post(provider);
}
EventProviderDirtied::post(provider);
}
void resetDirty() {

View File

@@ -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<std::fs::path, std::string> 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<std::optional<ExternalPatternFile>> m_externalPatternFile;
bool m_checkingExternalFile = false;
bool m_enableExternalFileTracking;
PerProvider<wolv::io::ChangeTracker> m_changeTracker;
PerProvider<bool> m_ignoreNextChangeEvent;
PerProvider<bool> m_changeEventAcknowledgementPending;
PerProvider<bool> m_patternFileDirty;
ImRect m_textEditorHoverBox;
ImRect m_consoleHoverBox;
@@ -355,7 +348,9 @@ namespace hex::plugin::builtin {
void historyInsert(std::array<std::string, 256> &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<void()> m_importPatternFile = [this] {
std::function<void(bool)> m_openPatternFile = [this](bool trackFile) {
auto provider = ImHexApi::Provider::get();
if (provider == nullptr)
return;
const auto basePaths = paths::Patterns.read();
std::vector<std::fs::path> 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<void()> m_exportPatternFile = [this] {
std::function<void(bool)> 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<void(bool)> 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;
}
}
);
};

View File

@@ -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!",

View File

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

View File

@@ -754,7 +754,6 @@ namespace hex::plugin::builtin {
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.auto_load_patterns", true);
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.sync_pattern_source", false);
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.sync_pattern_file", false);
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.network_interface", false);

View File

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

View File

@@ -18,6 +18,7 @@
#include <hex/helpers/magic.hpp>
#include <hex/helpers/binary_pattern.hpp>
#include <hex/helpers/default_paths.hpp>
#include <banners/banner_button.hpp>
#include <hex/providers/memory_provider.hpp>
@@ -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<bool>(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<bool>(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<bool>(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<std::string> &menus, const std::function<void()> &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<std::pair<const char *, size_t>, 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<std::string>{}(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;
});
}
}

View File

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