backend-infra-engineer: Release v0.3.2 snapshot

This commit is contained in:
scawful
2025-10-17 12:10:25 -04:00
parent 4371618a9b
commit 3d71417f62
857 changed files with 174954 additions and 45626 deletions

View File

@@ -1,31 +1,110 @@
#include "assembly_editor.h"
#include "app/editor/system/editor_card_registry.h"
#include <fstream>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h"
#include "app/gui/modules/text_editor.h"
#include "absl/strings/match.h"
#include "util/file_util.h"
#include "app/gui/core/icons.h"
#include "app/gui/core/ui_helpers.h"
#include "app/gui/widgets/text_editor.h"
namespace yaze::editor {
using core::FileDialogWrapper;
using util::FileDialogWrapper;
namespace {
static const char *const kKeywords[] = {
"ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", "BPL",
"BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", "CMP", "CPX",
"CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", "INY", "JMP", "JSR",
"JSL", "LDA", "LDX", "LDY", "LSR", "MVN", "NOP", "ORA", "PEA", "PER",
"PHA", "PHB", "PHD", "PHP", "PHX", "PHY", "PLA", "PLB", "PLD", "PLP",
"PLX", "PLY", "REP", "ROL", "ROR", "RTI", "RTL", "RTS", "SBC", "SEC",
"SEI", "SEP", "STA", "STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD",
"TCS", "TDC", "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA",
"TYX", "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM"};
static const char *const kIdentifiers[] = {
"abort", "abs", "acos", "asin", "atan", "atexit",
"atof", "atoi", "atol", "ceil", "clock", "cosh",
"ctime", "div", "exit", "fabs", "floor", "fmod",
"getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
"ispunct", "isspace", "isupper", "kbhit", "log10", "log2",
"log", "memcmp", "modf", "pow", "putchar", "putenv",
"puts", "rand", "remove", "rename", "sinh", "sqrt",
"srand", "strcat", "strcmp", "strerror", "time", "tolower",
"toupper"};
TextEditor::LanguageDefinition GetAssemblyLanguageDef() {
TextEditor::LanguageDefinition language_65816;
for (auto &k : kKeywords) language_65816.mKeywords.emplace(k);
for (auto &k : kIdentifiers) {
TextEditor::Identifier id;
id.mDeclaration = "Built-in function";
language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id));
}
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
"\\;\\,\\.]",
TextEditor::PaletteIndex::Punctuation));
language_65816.mCommentStart = "/*";
language_65816.mCommentEnd = "*/";
language_65816.mSingleLineComment = ";";
language_65816.mCaseSensitive = false;
language_65816.mAutoIndentation = true;
language_65816.mName = "65816";
return language_65816;
}
std::vector<std::string> RemoveIgnoredFiles(
const std::vector<std::string>& files,
const std::vector<std::string>& ignored_files) {
std::vector<std::string> filtered_files;
for (const auto& file : files) {
// Remove subdirectory files
if (file.contains('/')) {
if (absl::StrContains(file, '/')) {
continue;
}
// Make sure the file has an extension
if (!file.contains('.')) {
if (!absl::StrContains(file, '.')) {
continue;
}
if (std::ranges::find(ignored_files, file) == ignored_files.end()) {
@@ -66,11 +145,11 @@ FolderItem LoadFolder(const std::string& folder) {
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
for (const auto& files : folder_files) {
// Remove subdirectory files
if (files.contains('/')) {
if (absl::StrContains(files, '/')) {
continue;
}
// Make sure the file has an extension
if (!files.contains('.')) {
if (!absl::StrContains(files, '.')) {
continue;
}
if (std::ranges::find(ignored_files, files) != ignored_files.end()) {
@@ -95,10 +174,29 @@ FolderItem LoadFolder(const std::string& folder) {
} // namespace
void AssemblyEditor::Initialize() {
// Set the language definition
text_editor_.SetLanguageDefinition(GetAssemblyLanguageDef());
// Register cards with EditorCardManager
if (!dependencies_.card_registry) return;
auto* card_registry = dependencies_.card_registry;
card_registry->RegisterCard({.card_id = "assembly.editor", .display_name = "Assembly Editor",
.icon = ICON_MD_CODE, .category = "Assembly",
.shortcut_hint = "", .priority = 10});
card_registry->RegisterCard({.card_id = "assembly.file_browser", .display_name = "File Browser",
.icon = ICON_MD_FOLDER_OPEN, .category = "Assembly",
.shortcut_hint = "", .priority = 20});
// Don't show by default - only show when user explicitly opens Assembly Editor
}
absl::Status AssemblyEditor::Load() { return absl::OkStatus(); }
absl::Status AssemblyEditor::Load() {
// Register cards with EditorCardRegistry (dependency injection)
// Note: Assembly editor uses dynamic file tabs, so we register the main editor window
if (!dependencies_.card_registry) return absl::OkStatus();
auto* card_registry = dependencies_.card_registry;
return absl::OkStatus();
}
void AssemblyEditor::OpenFolder(const std::string& folder_path) {
current_folder_ = LoadFolder(folder_path);
@@ -113,7 +211,6 @@ void AssemblyEditor::Update(bool& is_loaded) {
}
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
@@ -127,7 +224,6 @@ void AssemblyEditor::Update(bool& is_loaded) {
void AssemblyEditor::InlineUpdate() {
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
@@ -139,41 +235,77 @@ void AssemblyEditor::InlineUpdate() {
}
void AssemblyEditor::UpdateCodeView() {
ImGui::BeginTable("##table_view", 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable);
DrawToolset();
gui::VerticalSpacing(2.0f);
// Table headers
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 256.0f);
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
// Table data
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (current_folder_.name != "") {
DrawCurrentFolder();
} else {
if (ImGui::Button("Open Folder")) {
current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
// Create session-aware card (non-static for multi-session support)
gui::EditorCard file_browser_card(MakeCardTitle("File Browser").c_str(), ICON_MD_FOLDER);
bool file_browser_open = true;
if (file_browser_card.Begin(&file_browser_open)) {
if (current_folder_.name != "") {
DrawCurrentFolder();
} else {
if (ImGui::Button("Open Folder")) {
current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
}
}
}
file_browser_card.End(); // ALWAYS call End after Begin
ImGui::TableNextColumn();
// Draw open files as individual, dockable EditorCards
for (int i = 0; i < active_files_.Size; i++) {
int file_id = active_files_[i];
bool open = true;
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
// Ensure we have a TextEditor instance for this file
if (file_id >= open_files_.size()) {
open_files_.resize(file_id + 1);
}
if (file_id >= files_.size()) {
// This can happen if a file was closed and its ID is being reused.
// For now, we just skip it.
continue;
}
text_editor_.Render("##asm_editor");
// Create session-aware card title for each file
std::string card_name = MakeCardTitle(files_[file_id]);
gui::EditorCard file_card(card_name.c_str(), ICON_MD_DESCRIPTION, &open);
if (file_card.Begin()) {
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
active_file_id_ = file_id;
}
open_files_[file_id].Render(absl::StrCat("##", card_name).c_str());
}
file_card.End(); // ALWAYS call End after Begin
ImGui::EndTable();
if (!open) {
active_files_.erase(active_files_.Data + i);
i--;
}
}
}
absl::Status AssemblyEditor::Save() {
if (active_file_id_ != -1 && active_file_id_ < open_files_.size()) {
std::string content = open_files_[active_file_id_].GetText();
util::SaveFile(files_[active_file_id_], content);
return absl::OkStatus();
}
return absl::FailedPreconditionError("No active file to save.");
}
void AssemblyEditor::DrawToolset() {
static gui::Toolset toolbar;
toolbar.Begin();
if (toolbar.AddAction(ICON_MD_FOLDER_OPEN, "Open Folder")) {
current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
}
if (toolbar.AddAction(ICON_MD_SAVE, "Save File")) {
Save();
}
toolbar.End();
}
void AssemblyEditor::DrawCurrentFolder() {
@@ -226,63 +358,11 @@ void AssemblyEditor::DrawCurrentFolder() {
}
}
void AssemblyEditor::DrawFileTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) {
if (std::ranges::find(active_files_, current_file_id_) !=
active_files_.end()) {
// Room is already open
next_tab_id++;
}
active_files_.push_back(next_tab_id++); // Add new tab
}
// Submit our regular tabs
for (int n = 0; n < active_files_.Size;) {
bool open = true;
if (ImGui::BeginTabItem(files_[active_files_[n]].data(), &open,
ImGuiTabItemFlags_None)) {
auto cpos = text_editor_.GetCursorPosition();
{
std::ifstream t(current_file_);
if (t.good()) {
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error opening file: %s\n", current_file_.c_str());
}
}
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
open_files_[active_files_[n]].Render("##asm_editor");
ImGui::EndTabItem();
}
if (!open)
active_files_.erase(active_files_.Data + n);
else
n++;
}
ImGui::EndTabBar();
}
ImGui::Separator();
}
void AssemblyEditor::DrawFileMenu() {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) {
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
ChangeActiveFile(filename);
}
if (ImGui::MenuItem("Save", "Ctrl+S")) {
@@ -318,19 +398,36 @@ void AssemblyEditor::DrawEditMenu() {
}
}
void AssemblyEditor::SetEditorText() {
if (!file_is_loaded_) {
std::ifstream t(current_file_);
if (t.good()) {
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
current_file_.c_str());
void AssemblyEditor::ChangeActiveFile(const std::string_view &filename) {
// Check if file is already open
for (int i = 0; i < active_files_.Size; ++i) {
int file_id = active_files_[i];
if (files_[file_id] == filename) {
// Optional: Focus window
return;
}
}
// Add new file
int new_file_id = files_.size();
files_.push_back(std::string(filename));
active_files_.push_back(new_file_id);
// Resize open_files_ if needed
if (new_file_id >= open_files_.size()) {
open_files_.resize(new_file_id + 1);
}
// Load file content using utility
std::string content = util::LoadFile(std::string(filename));
if (!content.empty()) {
open_files_[new_file_id].SetText(content);
open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef());
open_files_[new_file_id].SetPalette(TextEditor::GetDarkPalette());
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
std::string(filename).c_str());
}
file_is_loaded_ = true;
}
}
absl::Status AssemblyEditor::Cut() {

View File

@@ -2,10 +2,13 @@
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "app/editor/editor.h"
#include "app/gui/modules/text_editor.h"
#include "app/gui/style.h"
#include "app/gui/widgets/text_editor.h"
#include "app/gui/app/editor_layout.h"
#include "app/gui/core/style.h"
#include "app/rom.h"
namespace yaze {
@@ -24,15 +27,11 @@ struct FolderItem {
class AssemblyEditor : public Editor {
public:
explicit AssemblyEditor(Rom* rom = nullptr) : rom_(rom) {
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
text_editor_.SetPalette(TextEditor::GetDarkPalette());
text_editor_.SetShowWhitespaces(false);
type_ = EditorType::kAssembly;
}
void ChangeActiveFile(const std::string_view &filename) {
current_file_ = filename;
file_is_loaded_ = false;
}
void ChangeActiveFile(const std::string_view &filename);
void Initialize() override;
absl::Status Load() override;
@@ -51,7 +50,7 @@ class AssemblyEditor : public Editor {
absl::Status Update() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
absl::Status Save() override;
void OpenFolder(const std::string &folder_path);
@@ -61,12 +60,13 @@ class AssemblyEditor : public Editor {
private:
void DrawFileMenu();
void DrawEditMenu();
void SetEditorText();
void DrawCurrentFolder();
void DrawFileTabView();
void DrawToolset();
bool file_is_loaded_ = false;
int current_file_id_ = 0;
int active_file_id_ = -1;
std::vector<std::string> files_;
std::vector<TextEditor> open_files_;

View File

@@ -0,0 +1,222 @@
#include "app/editor/code/memory_editor.h"
#include "absl/strings/str_format.h"
#include "app/gui/core/icons.h"
#include "app/gui/core/style.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
void MemoryEditorWithDiffChecker::DrawToolbar() {
// Modern compact toolbar with icon-only buttons
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 4));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
if (ImGui::Button(ICON_MD_LOCATION_SEARCHING " Jump")) {
ImGui::OpenPopup("JumpToAddress");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Jump to specific address");
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_SEARCH " Search")) {
ImGui::OpenPopup("SearchPattern");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Search for hex pattern");
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_BOOKMARK " Bookmarks")) {
ImGui::OpenPopup("Bookmarks");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Manage address bookmarks");
}
ImGui::SameLine();
ImGui::Text(ICON_MD_MORE_VERT);
ImGui::SameLine();
// Show current address
if (current_address_ != 0) {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
ICON_MD_LOCATION_ON " 0x%06X", current_address_);
}
ImGui::PopStyleVar(2);
ImGui::Separator();
DrawJumpToAddressPopup();
DrawSearchPopup();
DrawBookmarksPopup();
}
void MemoryEditorWithDiffChecker::DrawJumpToAddressPopup() {
if (ImGui::BeginPopupModal("JumpToAddress", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
ICON_MD_LOCATION_SEARCHING " Jump to Address");
ImGui::Separator();
ImGui::Spacing();
ImGui::SetNextItemWidth(200);
if (ImGui::InputText("##jump_addr", jump_address_, IM_ARRAYSIZE(jump_address_),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) {
// Parse and jump on Enter key
unsigned int addr;
if (sscanf(jump_address_, "%X", &addr) == 1) {
current_address_ = addr;
ImGui::CloseCurrentPopup();
}
}
ImGui::TextDisabled("Format: 0x1C800 or 1C800");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button(ICON_MD_CHECK " Go", ImVec2(120, 0))) {
unsigned int addr;
if (sscanf(jump_address_, "%X", &addr) == 1) {
current_address_ = addr;
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CANCEL " Cancel", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void MemoryEditorWithDiffChecker::DrawSearchPopup() {
if (ImGui::BeginPopupModal("SearchPattern", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f),
ICON_MD_SEARCH " Search Hex Pattern");
ImGui::Separator();
ImGui::Spacing();
ImGui::SetNextItemWidth(300);
if (ImGui::InputText("##search_pattern", search_pattern_, IM_ARRAYSIZE(search_pattern_),
ImGuiInputTextFlags_EnterReturnsTrue)) {
// TODO: Implement search
ImGui::CloseCurrentPopup();
}
ImGui::TextDisabled("Use ?? for wildcard (e.g. FF 00 ?? 12)");
ImGui::Spacing();
// Quick preset patterns
ImGui::Text(ICON_MD_LIST " Quick Patterns:");
if (ImGui::SmallButton("LDA")) {
snprintf(search_pattern_, sizeof(search_pattern_), "A9 ??");
}
ImGui::SameLine();
if (ImGui::SmallButton("STA")) {
snprintf(search_pattern_, sizeof(search_pattern_), "8D ?? ??");
}
ImGui::SameLine();
if (ImGui::SmallButton("JSR")) {
snprintf(search_pattern_, sizeof(search_pattern_), "20 ?? ??");
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button(ICON_MD_SEARCH " Search", ImVec2(120, 0))) {
// TODO: Implement search using hex-search handler
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CANCEL " Cancel", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void MemoryEditorWithDiffChecker::DrawBookmarksPopup() {
if (ImGui::BeginPopupModal("Bookmarks", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
ICON_MD_BOOKMARK " Memory Bookmarks");
ImGui::Separator();
ImGui::Spacing();
if (bookmarks_.empty()) {
ImGui::TextDisabled(ICON_MD_INFO " No bookmarks yet");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button(ICON_MD_ADD " Add Current Address", ImVec2(250, 0))) {
Bookmark new_bookmark;
new_bookmark.address = current_address_;
new_bookmark.name = absl::StrFormat("Bookmark %zu", bookmarks_.size() + 1);
new_bookmark.description = "User-defined bookmark";
bookmarks_.push_back(new_bookmark);
}
} else {
// Bookmarks table
ImGui::BeginChild("##bookmarks_list", ImVec2(500, 300), true);
if (ImGui::BeginTable("##bookmarks_table", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 150);
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (size_t i = 0; i < bookmarks_.size(); ++i) {
const auto& bm = bookmarks_[i];
ImGui::PushID(static_cast<int>(i));
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(bm.name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
current_address_ = bm.address;
ImGui::CloseCurrentPopup();
}
ImGui::TableNextColumn();
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "0x%06X", bm.address);
ImGui::TableNextColumn();
ImGui::TextDisabled("%s", bm.description.c_str());
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::EndChild();
ImGui::Spacing();
if (ImGui::Button(ICON_MD_ADD " Add Bookmark", ImVec2(150, 0))) {
Bookmark new_bookmark;
new_bookmark.address = current_address_;
new_bookmark.name = absl::StrFormat("Bookmark %zu", bookmarks_.size() + 1);
new_bookmark.description = "User-defined bookmark";
bookmarks_.push_back(new_bookmark);
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear All", ImVec2(150, 0))) {
bookmarks_.clear();
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button(ICON_MD_CLOSE " Close", ImVec2(250, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
} // namespace editor
} // namespace yaze

View File

@@ -1,8 +1,10 @@
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#include "app/core/platform/file_dialog.h"
#include "app/gui/input.h"
#include "util/file_util.h"
#include "absl/container/flat_hash_map.h"
#include "app/editor/editor.h"
#include "app/gui/core/input.h"
#include "app/rom.h"
#include "app/snes.h"
#include "imgui/imgui.h"
@@ -19,13 +21,15 @@ struct MemoryEditorWithDiffChecker {
explicit MemoryEditorWithDiffChecker(Rom* rom = nullptr) : rom_(rom) {}
void Update(bool &show_memory_editor) {
DrawToolbar();
ImGui::Separator();
static MemoryEditor mem_edit;
static MemoryEditor comp_edit;
static bool show_compare_rom = false;
static Rom comparison_rom;
ImGui::Begin("Hex Editor", &show_memory_editor);
if (ImGui::Button("Compare Rom")) {
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
auto file_name = util::FileDialogWrapper::ShowOpenFileDialog();
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
show_compare_rom = true;
}
@@ -66,7 +70,24 @@ struct MemoryEditorWithDiffChecker {
Rom* rom() const { return rom_; }
private:
void DrawToolbar();
void DrawJumpToAddressPopup();
void DrawSearchPopup();
void DrawBookmarksPopup();
Rom* rom_;
// Toolbar state
char jump_address_[16] = "0x000000";
char search_pattern_[256] = "";
uint32_t current_address_ = 0;
struct Bookmark {
uint32_t address;
std::string name;
std::string description;
};
std::vector<Bookmark> bookmarks_;
};
} // namespace editor

View File

@@ -0,0 +1,289 @@
#include "app/editor/code/project_file_editor.h"
#include <fstream>
#include <sstream>
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "core/project.h"
#include "util/file_util.h"
#include "app/editor/system/toast_manager.h"
#include "app/gui/core/icons.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
ProjectFileEditor::ProjectFileEditor() {
text_editor_.SetLanguageDefinition(TextEditor::LanguageDefinition::C());
text_editor_.SetTabSize(2);
text_editor_.SetShowWhitespaces(false);
}
void ProjectFileEditor::Draw() {
if (!active_) return;
ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(absl::StrFormat("%s Project Editor###ProjectFileEditor",
ICON_MD_DESCRIPTION).c_str(),
&active_)) {
ImGui::End();
return;
}
// Toolbar
if (ImGui::BeginTable("ProjectEditorToolbar", 8, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumn();
if (ImGui::Button(absl::StrFormat("%s New", ICON_MD_NOTE_ADD).c_str())) {
NewFile();
}
ImGui::TableNextColumn();
if (ImGui::Button(absl::StrFormat("%s Open", ICON_MD_FOLDER_OPEN).c_str())) {
auto file = util::FileDialogWrapper::ShowOpenFileDialog();
if (!file.empty()) {
auto status = LoadFile(file);
if (!status.ok() && toast_manager_) {
toast_manager_->Show(std::string(status.message()),
ToastType::kError);
}
}
}
ImGui::TableNextColumn();
bool can_save = !filepath_.empty() && IsModified();
if (!can_save) ImGui::BeginDisabled();
if (ImGui::Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str())) {
auto status = SaveFile();
if (status.ok() && toast_manager_) {
toast_manager_->Show("Project file saved", ToastType::kSuccess);
} else if (!status.ok() && toast_manager_) {
toast_manager_->Show(std::string(status.message()), ToastType::kError);
}
}
if (!can_save) ImGui::EndDisabled();
ImGui::TableNextColumn();
if (ImGui::Button(absl::StrFormat("%s Save As", ICON_MD_SAVE_AS).c_str())) {
auto file = util::FileDialogWrapper::ShowSaveFileDialog(
filepath_.empty() ? "project" : filepath_, "yaze");
if (!file.empty()) {
auto status = SaveFileAs(file);
if (status.ok() && toast_manager_) {
toast_manager_->Show("Project file saved", ToastType::kSuccess);
} else if (!status.ok() && toast_manager_) {
toast_manager_->Show(std::string(status.message()), ToastType::kError);
}
}
}
ImGui::TableNextColumn();
ImGui::Text("|");
ImGui::TableNextColumn();
if (ImGui::Button(absl::StrFormat("%s Validate", ICON_MD_CHECK_CIRCLE).c_str())) {
ValidateContent();
show_validation_ = true;
}
ImGui::TableNextColumn();
ImGui::Checkbox("Show Validation", &show_validation_);
ImGui::TableNextColumn();
if (!filepath_.empty()) {
ImGui::TextDisabled("%s", filepath_.c_str());
} else {
ImGui::TextDisabled("No file loaded");
}
ImGui::EndTable();
}
ImGui::Separator();
// Validation errors panel
if (show_validation_ && !validation_errors_.empty()) {
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.3f, 0.2f, 0.2f, 0.5f));
if (ImGui::BeginChild("ValidationErrors", ImVec2(0, 100), true)) {
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
"%s Validation Errors:", ICON_MD_ERROR);
for (const auto& error : validation_errors_) {
ImGui::BulletText("%s", error.c_str());
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
// Main editor
ImVec2 editor_size = ImGui::GetContentRegionAvail();
text_editor_.Render("##ProjectEditor", editor_size);
ImGui::End();
}
absl::Status ProjectFileEditor::LoadFile(const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot open file: %s", filepath));
}
std::stringstream buffer;
buffer << file.rdbuf();
file.close();
text_editor_.SetText(buffer.str());
filepath_ = filepath;
modified_ = false;
ValidateContent();
return absl::OkStatus();
}
absl::Status ProjectFileEditor::SaveFile() {
if (filepath_.empty()) {
return absl::InvalidArgumentError("No file path specified");
}
return SaveFileAs(filepath_);
}
absl::Status ProjectFileEditor::SaveFileAs(const std::string& filepath) {
// Ensure .yaze extension
std::string final_path = filepath;
if (!absl::EndsWith(final_path, ".yaze")) {
final_path += ".yaze";
}
std::ofstream file(final_path);
if (!file.is_open()) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot create file: %s", final_path));
}
file << text_editor_.GetText();
file.close();
filepath_ = final_path;
modified_ = false;
// Add to recent files
auto& recent_mgr = project::RecentFilesManager::GetInstance();
recent_mgr.AddFile(filepath_);
recent_mgr.Save();
return absl::OkStatus();
}
void ProjectFileEditor::NewFile() {
// Create a template project file
const char* template_content = R"(# yaze Project File
# Format Version: 2.0
[project]
name=New Project
description=
author=
license=
version=1.0
created_date=
last_modified=
yaze_version=0.4.0
tags=
[files]
rom_filename=
rom_backup_folder=backups
code_folder=asm
assets_folder=assets
patches_folder=patches
labels_filename=labels.txt
symbols_filename=symbols.txt
output_folder=build
additional_roms=
[feature_flags]
# REMOVED: kLogInstructions - DisassemblyViewer is always active
kSaveDungeonMaps=true
kSaveGraphicsSheet=true
kLoadCustomOverworld=false
[workspace_settings]
font_global_scale=1.0
autosave_enabled=true
autosave_interval_secs=300
theme=dark
)";
text_editor_.SetText(template_content);
filepath_.clear();
modified_ = true;
validation_errors_.clear();
}
void ProjectFileEditor::ApplySyntaxHighlighting() {
// TODO: Implement custom syntax highlighting for INI format
// For now, use C language definition which provides some basic highlighting
}
void ProjectFileEditor::ValidateContent() {
validation_errors_.clear();
std::string content = text_editor_.GetText();
std::vector<std::string> lines = absl::StrSplit(content, '\n');
std::string current_section;
int line_num = 0;
for (const auto& line : lines) {
line_num++;
std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
// Skip empty lines and comments
if (trimmed.empty() || trimmed[0] == '#') continue;
// Check for section headers
if (trimmed[0] == '[' && trimmed[trimmed.size() - 1] == ']') {
current_section = trimmed.substr(1, trimmed.size() - 2);
// Validate known sections
if (current_section != "project" &&
current_section != "files" &&
current_section != "feature_flags" &&
current_section != "workspace_settings" &&
current_section != "build_settings") {
validation_errors_.push_back(
absl::StrFormat("Line %d: Unknown section [%s]",
line_num, current_section));
}
continue;
}
// Check for key=value pairs
size_t equals_pos = trimmed.find('=');
if (equals_pos == std::string::npos) {
validation_errors_.push_back(
absl::StrFormat("Line %d: Invalid format, expected key=value", line_num));
continue;
}
}
if (validation_errors_.empty() && show_validation_ && toast_manager_) {
toast_manager_->Show("Project file validation passed", ToastType::kSuccess);
}
}
void ProjectFileEditor::ShowValidationErrors() {
if (validation_errors_.empty()) return;
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Validation Errors:");
for (const auto& error : validation_errors_) {
ImGui::BulletText("%s", error.c_str());
}
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,95 @@
#ifndef YAZE_APP_EDITOR_CODE_PROJECT_FILE_EDITOR_H_
#define YAZE_APP_EDITOR_CODE_PROJECT_FILE_EDITOR_H_
#include <string>
#include "absl/status/status.h"
#include "core/project.h"
#include "app/gui/widgets/text_editor.h"
namespace yaze {
namespace editor {
class ToastManager;
/**
* @class ProjectFileEditor
* @brief Editor for .yaze project files with syntax highlighting and validation
*
* Provides a rich text editing experience for yaze project files with:
* - Syntax highlighting for INI-style format
* - Real-time validation
* - Auto-save capability
* - Integration with project::YazeProject
*/
class ProjectFileEditor {
public:
ProjectFileEditor();
void Draw();
/**
* @brief Load a project file into the editor
*/
absl::Status LoadFile(const std::string& filepath);
/**
* @brief Save the current editor contents to disk
*/
absl::Status SaveFile();
/**
* @brief Save to a new file path
*/
absl::Status SaveFileAs(const std::string& filepath);
/**
* @brief Get whether the file has unsaved changes
*/
bool IsModified() const { return text_editor_.IsTextChanged() || modified_; }
/**
* @brief Get the current filepath
*/
const std::string& filepath() const { return filepath_; }
/**
* @brief Set whether the editor window is active
*/
void set_active(bool active) { active_ = active; }
/**
* @brief Get pointer to active state for ImGui
*/
bool* active() { return &active_; }
/**
* @brief Set toast manager for notifications
*/
void SetToastManager(ToastManager* toast_manager) {
toast_manager_ = toast_manager;
}
/**
* @brief Create a new empty project file
*/
void NewFile();
private:
void ApplySyntaxHighlighting();
void ValidateContent();
void ShowValidationErrors();
TextEditor text_editor_;
std::string filepath_;
bool active_ = false;
bool modified_ = false;
bool show_validation_ = true;
std::vector<std::string> validation_errors_;
ToastManager* toast_manager_ = nullptr;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_CODE_PROJECT_FILE_EDITOR_H_