1
0
mirror of synced 2024-12-02 19:27:21 +01:00
ImHex/source/views/view_pattern_editor.cpp

368 lines
15 KiB
C++
Raw Normal View History

#include "views/view_pattern_editor.hpp"
2020-11-10 21:31:04 +01:00
2020-11-30 00:03:12 +01:00
#include "helpers/project_file_handler.hpp"
#include <hex/pattern_language/preprocessor.hpp>
#include <hex/pattern_language/pattern_data.hpp>
#include <hex/helpers/paths.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/file.hpp>
2020-11-10 21:31:04 +01:00
2021-09-06 16:15:05 +02:00
#include <hex/helpers/magic.hpp>
#include <hex/helpers/literals.hpp>
#include <imgui_imhex_extensions.h>
#include <nlohmann/json.hpp>
2020-11-10 21:31:04 +01:00
namespace hex {
2021-09-06 16:15:05 +02:00
using namespace hex::literals;
static const TextEditor::LanguageDefinition& PatternLanguage() {
static bool initialized = false;
static TextEditor::LanguageDefinition langDef;
if (!initialized) {
static const char* const keywords[] = {
2021-08-25 17:07:01 +02:00
"using", "struct", "union", "enum", "bitfield", "be", "le", "if", "else", "false", "true", "parent", "addressof", "sizeof", "$", "while", "fn", "return", "namespace"
};
for (auto& k : keywords)
langDef.mKeywords.insert(k);
static std::pair<const char* const, size_t> builtInTypes[] = {
{ "u8", 1 }, { "u16", 2 }, { "u32", 4 }, { "u64", 8 }, { "u128", 16 },
{ "s8", 1 }, { "s16", 2 }, { "s32", 4 }, { "s64", 8 }, { "s128", 16 },
{ "float", 4 }, { "double", 8 }, { "char", 1 }, { "char16", 2 }, { "bool", 1 }, { "padding", 1 }
};
for (const auto &[name, size] : builtInTypes) {
TextEditor::Identifier id;
id.mDeclaration = std::to_string(size);
id.mDeclaration += size == 1 ? " byte" : " bytes";
langDef.mIdentifiers.insert(std::make_pair(std::string(name), id));
}
langDef.mTokenize = [](const char * inBegin, const char * inEnd, const char *& outBegin, const char *& outEnd, TextEditor::PaletteIndex & paletteIndex) -> bool {
paletteIndex = TextEditor::PaletteIndex::Max;
while (inBegin < inEnd && isascii(*inBegin) && isblank(*inBegin))
inBegin++;
if (inBegin == inEnd) {
outBegin = inEnd;
outEnd = inEnd;
paletteIndex = TextEditor::PaletteIndex::Default;
}
else if (TokenizeCStyleIdentifier(inBegin, inEnd, outBegin, outEnd)) {
paletteIndex = TextEditor::PaletteIndex::Identifier;
}
else if (TokenizeCStyleNumber(inBegin, inEnd, outBegin, outEnd))
paletteIndex = TextEditor::PaletteIndex::Number;
else if (TokenizeCStyleCharacterLiteral(inBegin, inEnd, outBegin, outEnd))
paletteIndex = TextEditor::PaletteIndex::CharLiteral;
else if (TokenizeCStyleString(inBegin, inEnd, outBegin, outEnd))
paletteIndex = TextEditor::PaletteIndex::String;
return paletteIndex != TextEditor::PaletteIndex::Max;
};
langDef.mCommentStart = "/*";
langDef.mCommentEnd = "*/";
langDef.mSingleLineComment = "//";
langDef.mCaseSensitive = true;
langDef.mAutoIndentation = true;
langDef.mPreprocChar = '#';
langDef.mName = "Pattern Language";
initialized = true;
}
return langDef;
}
ViewPatternEditor::ViewPatternEditor() : View("hex.view.pattern.name") {
this->m_patternLanguageRuntime = new pl::PatternLanguage();
this->m_textEditor.SetLanguageDefinition(PatternLanguage());
this->m_textEditor.SetShowWhitespaces(false);
EventManager::subscribe<EventProjectFileStore>(this, [this]() {
2020-11-30 00:03:12 +01:00
ProjectFile::setPattern(this->m_textEditor.GetText());
});
EventManager::subscribe<EventProjectFileLoad>(this, [this]() {
2020-11-30 00:03:12 +01:00
this->m_textEditor.SetText(ProjectFile::getPattern());
this->parsePattern(this->m_textEditor.GetText().data());
});
EventManager::subscribe<RequestAppendPatternLanguageCode>(this, [this](std::string code) {
this->m_textEditor.InsertText("\n");
this->m_textEditor.InsertText(code);
});
EventManager::subscribe<EventFileLoaded>(this, [this](const std::string &path) {
if (this->m_textEditor.GetText().find_first_not_of(" \f\n\r\t\v") != std::string::npos)
2020-11-30 00:03:12 +01:00
return;
pl::Preprocessor preprocessor;
auto provider = SharedData::currentProvider;
if (provider == nullptr)
return;
2021-09-06 16:15:05 +02:00
std::string mimeType = magic::getMIMEType(provider);
bool foundCorrectType = false;
preprocessor.addPragmaHandler("MIME", [&mimeType, &foundCorrectType](std::string value) {
if (value == mimeType) {
foundCorrectType = true;
return true;
}
return !std::all_of(value.begin(), value.end(), isspace) && !value.ends_with('\n') && !value.ends_with('\r');
});
preprocessor.addDefaultPragmaHandlers();
this->m_possiblePatternFiles.clear();
std::error_code errorCode;
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
for (const auto &dir : hex::getPath(ImHexPath::Patterns)) {
for (auto &entry : std::filesystem::directory_iterator(dir, errorCode)) {
if (!entry.is_regular_file())
continue;
File file(entry.path().string(), File::Mode::Read);
if (!file.isValid())
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
continue;
preprocessor.preprocess(file.readString());
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
if (foundCorrectType)
this->m_possiblePatternFiles.push_back(entry.path().string());
}
}
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
if (!this->m_possiblePatternFiles.empty()) {
this->m_selectedPatternFile = 0;
View::doLater([] { ImGui::OpenPopup("hex.view.pattern.accept_pattern"_lang); });
}
});
/* Settings */
{
EventManager::subscribe<EventSettingsChanged>(this, [this]() {
auto theme = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.color");
if (theme.is_number()) {
switch (static_cast<int>(theme)) {
default:
case 0: /* Dark theme */
this->m_textEditor.SetPalette(TextEditor::GetDarkPalette());
break;
case 1: /* Light theme */
this->m_textEditor.SetPalette(TextEditor::GetLightPalette());
break;
case 2: /* Classic theme */
this->m_textEditor.SetPalette(TextEditor::GetRetroBluePalette());
break;
}
}
});
}
2020-11-10 21:31:04 +01:00
}
ViewPatternEditor::~ViewPatternEditor() {
2021-02-01 20:07:57 +01:00
delete this->m_patternLanguageRuntime;
EventManager::unsubscribe<EventProjectFileStore>(this);
EventManager::unsubscribe<EventProjectFileLoad>(this);
EventManager::unsubscribe<RequestAppendPatternLanguageCode>(this);
EventManager::unsubscribe<EventFileLoaded>(this);
EventManager::unsubscribe<EventSettingsChanged>(this);
2020-11-10 21:31:04 +01:00
}
void ViewPatternEditor::drawMenu() {
if (ImGui::BeginMenu("hex.menu.file"_lang)) {
if (ImGui::MenuItem("hex.view.pattern.menu.file.load_pattern"_lang)) {
2021-08-28 00:45:59 +02:00
hex::openFileBrowser("hex.view.pattern.open_pattern"_lang, DialogMode::Open, { { "Pattern File", "hexpat" } }, [this](auto path) {
2021-01-27 00:44:10 +01:00
this->loadPatternFile(path);
});
2020-11-10 21:31:04 +01:00
}
ImGui::EndMenu();
}
}
void ViewPatternEditor::drawContent() {
if (ImGui::Begin(View::toWindowName("hex.view.pattern.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_None | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
auto provider = SharedData::currentProvider;
if (provider != nullptr && provider->isAvailable()) {
auto textEditorSize = ImGui::GetContentRegionAvail();
textEditorSize.y *= 4.0/5.0;
textEditorSize.y -= ImGui::GetTextLineHeightWithSpacing();
this->m_textEditor.Render("hex.view.pattern.name"_lang, textEditorSize, true);
auto consoleSize = ImGui::GetContentRegionAvail();
consoleSize.y -= ImGui::GetTextLineHeightWithSpacing();
ImGui::PushStyleColor(ImGuiCol_ChildBg, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Background)]);
if (ImGui::BeginChild("##console", consoleSize, true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (auto &[level, message] : this->m_console) {
switch (level) {
case pl::LogConsole::Level::Debug:
ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Comment)]);
break;
case pl::LogConsole::Level::Info:
ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Default)]);
break;
case pl::LogConsole::Level::Warning:
ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Preprocessor)]);
break;
case pl::LogConsole::Level::Error:
ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::ErrorMarker)]);
break;
default: continue;
}
ImGui::TextUnformatted(message.c_str());
ImGui::PopStyleColor();
}
}
ImGui::EndChild();
ImGui::PopStyleColor(1);
ImGui::Disabled([this] {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(ImColor(0x20, 0x85, 0x20)));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1);
2021-02-22 11:56:33 +01:00
if (ImGui::ArrowButton("evaluate", ImGuiDir_Right))
this->parsePattern(this->m_textEditor.GetText().data());
ImGui::PopStyleVar();
ImGui::PopStyleColor();
2021-02-22 11:56:33 +01:00
}, this->m_evaluatorRunning);
ImGui::SameLine();
2021-02-22 11:56:33 +01:00
if (this->m_evaluatorRunning)
ImGui::TextSpinner("hex.view.pattern.evaluating"_lang);
else
2021-02-20 22:41:17 +01:00
ImGui::Checkbox("hex.view.pattern.auto"_lang, &this->m_runAutomatically);
if (this->m_textEditor.IsTextChanged()) {
if (this->m_runAutomatically)
this->parsePattern(this->m_textEditor.GetText().data());
ProjectFile::markDirty();
2020-11-21 00:12:58 +01:00
}
}
View::discardNavigationRequests();
}
2020-11-10 21:31:04 +01:00
ImGui::End();
}
void ViewPatternEditor::drawAlwaysVisible() {
if (ImGui::BeginPopupModal("hex.view.pattern.accept_pattern"_lang, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextWrapped("%s", static_cast<const char *>("hex.view.pattern.accept_pattern.desc"_lang));
std::vector<std::string> entries;
entries.resize(this->m_possiblePatternFiles.size());
for (u32 i = 0; i < entries.size(); i++) {
entries[i] = std::filesystem::path(this->m_possiblePatternFiles[i]).filename().string();
}
ImGui::ListBox("hex.view.pattern.accept_pattern.pattern_language"_lang, &this->m_selectedPatternFile, [](void *data, int id, const char** outText) -> bool {
auto &entries = *static_cast<std::vector<std::string>*>(data);
*outText = entries[id].c_str();
return true;
}, &entries, entries.size(), 4);
ImGui::NewLine();
ImGui::Text("%s", static_cast<const char *>("hex.view.pattern.accept_pattern.question"_lang));
confirmButtons("hex.common.yes"_lang, "hex.common.no"_lang, [this]{
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
this->loadPatternFile(this->m_possiblePatternFiles[this->m_selectedPatternFile]);
ImGui::CloseCurrentPopup();
}, []{
ImGui::CloseCurrentPopup();
});
if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
void ViewPatternEditor::loadPatternFile(const std::string &path) {
FILE *file = fopen(path.c_str(), "rb");
if (file != nullptr) {
char *buffer;
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
rewind(file);
buffer = new char[size + 1];
fread(buffer, size, 1, file);
buffer[size] = 0x00;
fclose(file);
this->parsePattern(buffer);
this->m_textEditor.SetText(buffer);
delete[] buffer;
}
2020-11-10 21:31:04 +01:00
}
void ViewPatternEditor::clearPatternData() {
for (auto &data : SharedData::patternData)
delete data;
SharedData::patternData.clear();
pl::PatternData::resetPalette();
2020-11-10 21:31:04 +01:00
}
void ViewPatternEditor::parsePattern(char *buffer) {
2021-02-22 11:56:33 +01:00
this->m_evaluatorRunning = true;
this->clearPatternData();
this->m_textEditor.SetErrorMarkers({ });
this->m_console.clear();
EventManager::post<EventPatternChanged>();
2020-11-10 21:31:04 +01:00
std::thread([this, buffer = std::string(buffer)] {
auto result = this->m_patternLanguageRuntime->executeString(SharedData::currentProvider, buffer);
2020-11-10 21:31:04 +01:00
auto error = this->m_patternLanguageRuntime->getError();
if (error.has_value()) {
this->m_textEditor.SetErrorMarkers({ error.value() });
}
2020-11-10 21:31:04 +01:00
this->m_console = this->m_patternLanguageRuntime->getConsoleLog();
if (result.has_value()) {
SharedData::patternData = std::move(result.value());
View::doLater([]{
EventManager::post<EventPatternChanged>();
});
}
2021-02-22 11:56:33 +01:00
this->m_evaluatorRunning = false;
}).detach();
2020-11-10 21:31:04 +01:00
}
}