yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
assembly_editor.cc
Go to the documentation of this file.
1#include "assembly_editor.h"
2
3#include <fstream>
4#include <string>
5#include <vector>
6
7#include "absl/strings/match.h"
8#include "absl/strings/str_cat.h"
12#include "app/gui/core/icons.h"
15#include "core/project.h"
17#include "util/file_util.h"
18
19namespace yaze::editor {
20
21using util::FileDialogWrapper;
22
23namespace {
24
25static const char* const kKeywords[] = {
26 "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", "BPL",
27 "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", "CMP", "CPX",
28 "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", "INY", "JMP", "JSR",
29 "JSL", "LDA", "LDX", "LDY", "LSR", "MVN", "NOP", "ORA", "PEA", "PER",
30 "PHA", "PHB", "PHD", "PHP", "PHX", "PHY", "PLA", "PLB", "PLD", "PLP",
31 "PLX", "PLY", "REP", "ROL", "ROR", "RTI", "RTL", "RTS", "SBC", "SEC",
32 "SEI", "SEP", "STA", "STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD",
33 "TCS", "TDC", "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA",
34 "TYX", "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM"};
35
36static const char* const kIdentifiers[] = {
37 "abort", "abs", "acos", "asin", "atan", "atexit",
38 "atof", "atoi", "atol", "ceil", "clock", "cosh",
39 "ctime", "div", "exit", "fabs", "floor", "fmod",
40 "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
41 "ispunct", "isspace", "isupper", "kbhit", "log10", "log2",
42 "log", "memcmp", "modf", "pow", "putchar", "putenv",
43 "puts", "rand", "remove", "rename", "sinh", "sqrt",
44 "srand", "strcat", "strcmp", "strerror", "time", "tolower",
45 "toupper"};
46
48 TextEditor::LanguageDefinition language_65816;
49 for (auto& k : kKeywords)
50 language_65816.mKeywords.emplace(k);
51
52 for (auto& k : kIdentifiers) {
54 id.mDeclaration = "Built-in function";
55 language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id));
56 }
57
58 language_65816.mTokenRegexStrings.push_back(
59 std::make_pair<std::string, TextEditor::PaletteIndex>(
60 "[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor));
61 language_65816.mTokenRegexStrings.push_back(
62 std::make_pair<std::string, TextEditor::PaletteIndex>(
63 "L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String));
64 language_65816.mTokenRegexStrings.push_back(
65 std::make_pair<std::string, TextEditor::PaletteIndex>(
66 "\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral));
67 language_65816.mTokenRegexStrings.push_back(
68 std::make_pair<std::string, TextEditor::PaletteIndex>(
69 "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
71 language_65816.mTokenRegexStrings.push_back(
72 std::make_pair<std::string, TextEditor::PaletteIndex>(
73 "[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
74 language_65816.mTokenRegexStrings.push_back(
75 std::make_pair<std::string, TextEditor::PaletteIndex>(
76 "0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
77 language_65816.mTokenRegexStrings.push_back(
78 std::make_pair<std::string, TextEditor::PaletteIndex>(
79 "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
81 language_65816.mTokenRegexStrings.push_back(
82 std::make_pair<std::string, TextEditor::PaletteIndex>(
83 "[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier));
84 language_65816.mTokenRegexStrings.push_back(
85 std::make_pair<std::string, TextEditor::PaletteIndex>(
86 "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
87 "\\;\\,\\.]",
89
90 language_65816.mCommentStart = "/*";
91 language_65816.mCommentEnd = "*/";
92 language_65816.mSingleLineComment = ";";
93
94 language_65816.mCaseSensitive = false;
95 language_65816.mAutoIndentation = true;
96
97 language_65816.mName = "65816";
98
99 return language_65816;
100}
101
102std::vector<std::string> RemoveIgnoredFiles(
103 const std::vector<std::string>& files,
104 const std::vector<std::string>& ignored_files) {
105 std::vector<std::string> filtered_files;
106 for (const auto& file : files) {
107 // Remove subdirectory files
108 if (absl::StrContains(file, '/')) {
109 continue;
110 }
111 // Make sure the file has an extension
112 if (!absl::StrContains(file, '.')) {
113 continue;
114 }
115 if (std::ranges::find(ignored_files, file) == ignored_files.end()) {
116 filtered_files.push_back(file);
117 }
118 }
119 return filtered_files;
120}
121
122FolderItem LoadFolder(const std::string& folder) {
123 // Check if .gitignore exists in the folder
124 std::ifstream gitignore(folder + "/.gitignore");
125 std::vector<std::string> ignored_files;
126 if (gitignore.good()) {
127 std::string line;
128 while (std::getline(gitignore, line)) {
129 if (line[0] == '#') {
130 continue;
131 }
132 if (line[0] == '!') {
133 // Ignore the file
134 continue;
135 }
136 ignored_files.push_back(line);
137 }
138 }
139
140 FolderItem current_folder;
141 current_folder.name = folder;
142 auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
143 current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
144
145 for (const auto& subfolder :
147 FolderItem folder_item;
148 folder_item.name = subfolder;
149 std::string full_folder = current_folder.name + "/" + subfolder;
150 auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
151 for (const auto& files : folder_files) {
152 // Remove subdirectory files
153 if (absl::StrContains(files, '/')) {
154 continue;
155 }
156 // Make sure the file has an extension
157 if (!absl::StrContains(files, '.')) {
158 continue;
159 }
160 if (std::ranges::find(ignored_files, files) != ignored_files.end()) {
161 continue;
162 }
163 folder_item.files.push_back(files);
164 }
165
166 for (const auto& subdir :
168 FolderItem subfolder_item;
169 subfolder_item.name = subdir;
170 subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
171 folder_item.subfolders.push_back(subfolder_item);
172 }
173 current_folder.subfolders.push_back(folder_item);
174 }
175
176 return current_folder;
177}
178
179} // namespace
180
183
184 // Register panels with PanelManager using EditorPanel instances
186 return;
187 auto* panel_manager = dependencies_.panel_manager;
188
189 // Register Code Editor panel - main text editing
190 panel_manager->RegisterEditorPanel(
191 std::make_unique<AssemblyCodeEditorPanel>([this]() { DrawCodeEditor(); }));
192
193 // Register File Browser panel - project file navigation
194 panel_manager->RegisterEditorPanel(
195 std::make_unique<AssemblyFileBrowserPanel>([this]() { DrawFileBrowser(); }));
196
197 // Register Symbols panel - symbol table viewer
198 panel_manager->RegisterEditorPanel(
199 std::make_unique<AssemblySymbolsPanel>([this]() { DrawSymbolsContent(); }));
200
201 // Register Build Output panel - errors/warnings
202 panel_manager->RegisterEditorPanel(
203 std::make_unique<AssemblyBuildOutputPanel>([this]() { DrawBuildOutput(); }));
204
205 // Register Toolbar panel - quick actions
206 panel_manager->RegisterEditorPanel(
207 std::make_unique<AssemblyToolbarPanel>([this]() { DrawToolbarContent(); }));
208}
209
210absl::Status AssemblyEditor::Load() {
211 // Assembly editor doesn't require ROM data - files are loaded independently
212 return absl::OkStatus();
213}
214
215void AssemblyEditor::OpenFolder(const std::string& folder_path) {
216 current_folder_ = LoadFolder(folder_path);
217}
218
219// =============================================================================
220// Panel Content Drawing (EditorPanel System)
221// =============================================================================
222
224 // Menu bar for file operations
225 if (ImGui::BeginMenuBar()) {
226 DrawFileMenu();
227 DrawEditMenu();
229 ImGui::EndMenuBar();
230 }
231
232 // Status line
233 auto cpos = text_editor_.GetCursorPosition();
234 ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
235 cpos.mColumn + 1, text_editor_.GetTotalLines(),
236 text_editor_.IsOverwrite() ? "Ovr" : "Ins",
237 text_editor_.CanUndo() ? "*" : " ",
239 current_file_.c_str());
240
241 // Main text editor
242 text_editor_.Render("##asm_editor", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()));
243
244 // Draw open file tabs at bottom
246}
247
249 // Lazy load project folder if not already loaded
250 if (current_folder_.name.empty() && dependencies_.project &&
254 }
255
256 // Open folder button if no folder loaded
257 if (current_folder_.name.empty()) {
258 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Open Folder",
259 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
261 }
262 ImGui::Spacing();
263 ImGui::TextDisabled("No folder opened");
264 return;
265 }
266
267 // Folder path display
268 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s",
269 current_folder_.name.c_str());
270 ImGui::Separator();
271
272 // File tree
274}
275
277 if (symbols_.empty()) {
278 ImGui::TextDisabled("No symbols loaded.");
279 ImGui::Spacing();
280 ImGui::TextWrapped("Apply a patch or load external symbols to populate this list.");
281 return;
282 }
283
284 // Search filter
285 static char filter[256] = "";
286 ImGui::SetNextItemWidth(-1);
287 ImGui::InputTextWithHint("##symbol_filter", ICON_MD_SEARCH " Filter symbols...",
288 filter, sizeof(filter));
289 ImGui::Separator();
290
291 // Symbol list
292 if (ImGui::BeginChild("##symbol_list", ImVec2(0, 0), false)) {
293 for (const auto& [name, symbol] : symbols_) {
294 // Apply filter
295 if (filter[0] != '\0' && name.find(filter) == std::string::npos) {
296 continue;
297 }
298
299 ImGui::PushID(name.c_str());
300 if (ImGui::Selectable(name.c_str())) {
301 // Could jump to symbol definition if line info is available
302 }
303 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
304 ImGui::TextDisabled("$%06X", symbol.address);
305 ImGui::PopID();
306 }
307 }
308 ImGui::EndChild();
309}
310
312 // Error/warning counts
313 ImGui::Text("Errors: %zu Warnings: %zu", last_errors_.size(),
314 last_warnings_.size());
315 ImGui::Separator();
316
317 // Build buttons
318 bool has_active_file =
319 (active_file_id_ != -1 && active_file_id_ < open_files_.size());
320 bool has_rom = (rom_ && rom_->is_loaded());
321
322 if (ImGui::Button(ICON_MD_CHECK_CIRCLE " Validate", ImVec2(120, 0))) {
323 if (has_active_file) {
324 auto status = ValidateCurrentFile();
325 if (status.ok() && dependencies_.toast_manager) {
326 dependencies_.toast_manager->Show("Validation passed!", ToastType::kSuccess);
327 }
328 }
329 }
330 ImGui::SameLine();
331 ImGui::BeginDisabled(!has_rom || !has_active_file);
332 if (ImGui::Button(ICON_MD_BUILD " Apply to ROM", ImVec2(140, 0))) {
333 auto status = ApplyPatchToRom();
334 if (status.ok() && dependencies_.toast_manager) {
336 }
337 }
338 ImGui::EndDisabled();
339
340 ImGui::Separator();
341
342 // Output log
343 if (ImGui::BeginChild("##build_log", ImVec2(0, 0), true)) {
344 // Show errors in red
345 for (const auto& error : last_errors_) {
346 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
347 ImGui::TextWrapped("%s %s", ICON_MD_ERROR, error.c_str());
348 ImGui::PopStyleColor();
349 }
350 // Show warnings in yellow
351 for (const auto& warning : last_warnings_) {
352 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
353 ImGui::TextWrapped("%s %s", ICON_MD_WARNING, warning.c_str());
354 ImGui::PopStyleColor();
355 }
356 if (last_errors_.empty() && last_warnings_.empty()) {
357 ImGui::TextDisabled("No build output");
358 }
359 }
360 ImGui::EndChild();
361}
362
364 float button_size = 32.0f;
365
366 if (ImGui::Button(ICON_MD_FOLDER_OPEN, ImVec2(button_size, button_size))) {
368 }
369 if (ImGui::IsItemHovered()) ImGui::SetTooltip("Open Folder");
370
371 ImGui::SameLine();
372 if (ImGui::Button(ICON_MD_FILE_OPEN, ImVec2(button_size, button_size))) {
374 if (!filename.empty()) {
375 ChangeActiveFile(filename);
376 }
377 }
378 if (ImGui::IsItemHovered()) ImGui::SetTooltip("Open File");
379
380 ImGui::SameLine();
381 bool can_save = (active_file_id_ != -1 && active_file_id_ < open_files_.size());
382 ImGui::BeginDisabled(!can_save);
383 if (ImGui::Button(ICON_MD_SAVE, ImVec2(button_size, button_size))) {
384 Save();
385 }
386 ImGui::EndDisabled();
387 if (ImGui::IsItemHovered()) ImGui::SetTooltip("Save File");
388
389 ImGui::SameLine();
390 ImGui::Text("|"); // Visual separator
391 ImGui::SameLine();
392
393 // Build actions
394 ImGui::BeginDisabled(!can_save);
395 if (ImGui::Button(ICON_MD_CHECK_CIRCLE, ImVec2(button_size, button_size))) {
397 }
398 ImGui::EndDisabled();
399 if (ImGui::IsItemHovered()) ImGui::SetTooltip("Validate (Ctrl+B)");
400
401 ImGui::SameLine();
402 bool can_apply = can_save && rom_ && rom_->is_loaded();
403 ImGui::BeginDisabled(!can_apply);
404 if (ImGui::Button(ICON_MD_BUILD, ImVec2(button_size, button_size))) {
406 }
407 ImGui::EndDisabled();
408 if (ImGui::IsItemHovered()) ImGui::SetTooltip("Apply to ROM (Ctrl+Shift+B)");
409}
410
412 if (active_files_.empty()) {
413 return;
414 }
415
416 if (ImGui::BeginTabBar("##OpenFileTabs", ImGuiTabBarFlags_Reorderable |
417 ImGuiTabBarFlags_AutoSelectNewTabs |
418 ImGuiTabBarFlags_FittingPolicyScroll)) {
419 for (int i = 0; i < active_files_.Size; i++) {
420 int file_id = active_files_[i];
421 if (file_id >= files_.size()) continue;
422
423 // Extract just the filename from the path
424 std::string filename = files_[file_id];
425 size_t pos = filename.find_last_of("/\\");
426 if (pos != std::string::npos) {
427 filename = filename.substr(pos + 1);
428 }
429
430 bool is_active = (active_file_id_ == file_id);
431 ImGuiTabItemFlags flags = is_active ? ImGuiTabItemFlags_SetSelected : 0;
432 bool tab_open = true;
433
434 if (ImGui::BeginTabItem(filename.c_str(), &tab_open, flags)) {
435 // When tab is selected, update active file
436 if (!is_active) {
437 active_file_id_ = file_id;
438 text_editor_ = open_files_[file_id];
439 }
440 ImGui::EndTabItem();
441 }
442
443 // Handle tab close
444 if (!tab_open) {
445 active_files_.erase(active_files_.Data + i);
446 if (active_file_id_ == file_id) {
447 active_file_id_ = active_files_.empty() ? -1 : active_files_[0];
448 if (active_file_id_ >= 0 && active_file_id_ < open_files_.size()) {
450 }
451 }
452 i--;
453 }
454 }
455 ImGui::EndTabBar();
456 }
457}
458
459// =============================================================================
460// Legacy Update Methods (kept for backward compatibility)
461// =============================================================================
462
463void AssemblyEditor::Update(bool& is_loaded) {
464 // Legacy window-based update - kept for backward compatibility
465 // New code should use the panel system via DrawCodeEditor()
466 ImGui::Begin("Assembly Editor", &is_loaded, ImGuiWindowFlags_MenuBar);
468 ImGui::End();
469
470 // Draw symbol panel as separate window if visible (legacy)
472}
473
475 auto cpos = text_editor_.GetCursorPosition();
476 ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
477 cpos.mColumn + 1, text_editor_.GetTotalLines(),
478 text_editor_.IsOverwrite() ? "Ovr" : "Ins",
479 text_editor_.CanUndo() ? "*" : " ",
481 current_file_.c_str());
482
483 text_editor_.Render("##asm_editor", ImVec2(0, 0));
484}
485
487 // Deprecated: Use the EditorPanel system instead
488 // This method is kept for backward compatibility during transition
490 ImGui::Separator();
492}
493
494absl::Status AssemblyEditor::Save() {
495 if (active_file_id_ != -1 && active_file_id_ < open_files_.size()) {
496 std::string content = open_files_[active_file_id_].GetText();
498 return absl::OkStatus();
499 }
500 return absl::FailedPreconditionError("No active file to save.");
501}
502
504 static gui::Toolset toolbar;
505 toolbar.Begin();
506
507 if (toolbar.AddAction(ICON_MD_FOLDER_OPEN, "Open Folder")) {
509 }
510 if (toolbar.AddAction(ICON_MD_SAVE, "Save File")) {
511 Save();
512 }
513
514 toolbar.End();
515}
516
518 // Lazy load project folder if not already loaded
521 }
522
523 if (ImGui::BeginChild("##current_folder", ImVec2(0, 0), true,
524 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
525 if (ImGui::BeginTable("##file_table", 2,
526 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
527 ImGuiTableFlags_Resizable |
528 ImGuiTableFlags_Sortable)) {
529 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
530 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch);
531
532 ImGui::TableHeadersRow();
533
534 for (const auto& file : current_folder_.files) {
535 ImGui::TableNextRow();
536 ImGui::TableNextColumn();
537 if (ImGui::Selectable(file.c_str())) {
538 ChangeActiveFile(absl::StrCat(current_folder_.name, "/", file));
539 }
540 ImGui::TableNextColumn();
541 ImGui::Text("File");
542 }
543
544 for (const auto& subfolder : current_folder_.subfolders) {
545 ImGui::TableNextRow();
546 ImGui::TableNextColumn();
547 if (ImGui::TreeNode(subfolder.name.c_str())) {
548 for (const auto& file : subfolder.files) {
549 ImGui::TableNextRow();
550 ImGui::TableNextColumn();
551 if (ImGui::Selectable(file.c_str())) {
552 ChangeActiveFile(absl::StrCat(current_folder_.name, "/",
553 subfolder.name, "/", file));
554 }
555 ImGui::TableNextColumn();
556 ImGui::Text("File");
557 }
558 ImGui::TreePop();
559 } else {
560 ImGui::TableNextColumn();
561 ImGui::Text("Folder");
562 }
563 }
564
565 ImGui::EndTable();
566 }
567 }
568 ImGui::EndChild();
569}
570
572 if (ImGui::BeginMenu("File")) {
573 if (ImGui::MenuItem(ICON_MD_FILE_OPEN " Open", "Ctrl+O")) {
575 ChangeActiveFile(filename);
576 }
577 if (ImGui::MenuItem(ICON_MD_SAVE " Save", "Ctrl+S")) {
578 // TODO: Implement this
579 }
580 ImGui::EndMenu();
581 }
582}
583
585 if (ImGui::BeginMenu("Edit")) {
586 if (ImGui::MenuItem(ICON_MD_UNDO " Undo", "Ctrl+Z")) {
588 }
589 if (ImGui::MenuItem(ICON_MD_REDO " Redo", "Ctrl+Y")) {
591 }
592 ImGui::Separator();
593 if (ImGui::MenuItem(ICON_MD_CONTENT_CUT " Cut", "Ctrl+X")) {
595 }
596 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy", "Ctrl+C")) {
598 }
599 if (ImGui::MenuItem(ICON_MD_CONTENT_PASTE " Paste", "Ctrl+V")) {
601 }
602 ImGui::Separator();
603 if (ImGui::MenuItem(ICON_MD_SEARCH " Find", "Ctrl+F")) {
604 // TODO: Implement this.
605 }
606 ImGui::EndMenu();
607 }
608}
609
610void AssemblyEditor::ChangeActiveFile(const std::string_view& filename) {
611 // Check if file is already open
612 for (int i = 0; i < active_files_.Size; ++i) {
613 int file_id = active_files_[i];
614 if (files_[file_id] == filename) {
615 // Optional: Focus window
616 return;
617 }
618 }
619
620 // Add new file
621 int new_file_id = files_.size();
622 files_.push_back(std::string(filename));
623 active_files_.push_back(new_file_id);
624
625 // Resize open_files_ if needed
626 if (new_file_id >= open_files_.size()) {
627 open_files_.resize(new_file_id + 1);
628 }
629
630 // Load file content using utility
631 std::string content = util::LoadFile(std::string(filename));
632 if (!content.empty()) {
633 open_files_[new_file_id].SetText(content);
634 open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef());
635 open_files_[new_file_id].SetPalette(TextEditor::GetDarkPalette());
636 } else {
637 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
638 std::string(filename).c_str());
639 }
640}
641
642absl::Status AssemblyEditor::Cut() {
644 return absl::OkStatus();
645}
646
647absl::Status AssemblyEditor::Copy() {
649 return absl::OkStatus();
650}
651
652absl::Status AssemblyEditor::Paste() {
654 return absl::OkStatus();
655}
656
657absl::Status AssemblyEditor::Undo() {
659 return absl::OkStatus();
660}
661
662absl::Status AssemblyEditor::Redo() {
664 return absl::OkStatus();
665}
666
668 return absl::OkStatus();
669}
670
671// ============================================================================
672// Asar Integration Implementation
673// ============================================================================
674
676 if (active_file_id_ == -1 || active_file_id_ >= open_files_.size()) {
677 return absl::FailedPreconditionError("No file is currently active");
678 }
679
680 // Initialize Asar if not already done
681 if (!asar_initialized_) {
682 auto status = asar_.Initialize();
683 if (!status.ok()) {
684 return status;
685 }
686 asar_initialized_ = true;
687 }
688
689 // Get the file path
690 const std::string& file_path = files_[active_file_id_];
691
692 // Validate the assembly
693 auto status = asar_.ValidateAssembly(file_path);
694
695 // Update error markers based on result
696 if (!status.ok()) {
697 // Get the error messages and show them
698 last_errors_.clear();
699 last_errors_.push_back(std::string(status.message()));
700 // Parse and update error markers
702 // Asar errors typically contain line numbers we can parse
703 for (const auto& error : last_errors_) {
704 // Simple heuristic: look for "line X" or ":X:" pattern
705 size_t line_pos = error.find(':');
706 if (line_pos != std::string::npos) {
707 size_t num_start = line_pos + 1;
708 size_t num_end = error.find(':', num_start);
709 if (num_end != std::string::npos) {
710 std::string line_str = error.substr(num_start, num_end - num_start);
711 try {
712 int line = std::stoi(line_str);
713 markers[line] = error;
714 } catch (...) {
715 // Not a line number, skip
716 }
717 }
718 }
719 }
720 open_files_[active_file_id_].SetErrorMarkers(markers);
721 return status;
722 }
723
724 // Clear any previous error markers
726 return absl::OkStatus();
727}
728
730 if (!rom_ || !rom_->is_loaded()) {
731 return absl::FailedPreconditionError("No ROM is loaded");
732 }
733
734 if (active_file_id_ == -1 || active_file_id_ >= open_files_.size()) {
735 return absl::FailedPreconditionError("No file is currently active");
736 }
737
738 // Initialize Asar if not already done
739 if (!asar_initialized_) {
740 auto status = asar_.Initialize();
741 if (!status.ok()) {
742 return status;
743 }
744 asar_initialized_ = true;
745 }
746
747 // Get the file path
748 const std::string& file_path = files_[active_file_id_];
749
750 // Get ROM data as vector for patching
751 std::vector<uint8_t> rom_data = rom_->vector();
752
753 // Apply the patch
754 auto result = asar_.ApplyPatch(file_path, rom_data);
755
756 if (!result.ok()) {
757 UpdateErrorMarkers(*result);
758 return result.status();
759 }
760
761 if (result->success) {
762 // Update the ROM with the patched data
763 rom_->LoadFromData(rom_data);
764
765 // Store symbols for lookup
767 last_errors_.clear();
768 last_warnings_ = result->warnings;
769
770 // Clear error markers
772
773 return absl::OkStatus();
774 } else {
775 UpdateErrorMarkers(*result);
776 return absl::InternalError("Patch application failed");
777 }
778}
779
781 last_errors_ = result.errors;
782 last_warnings_ = result.warnings;
783
784 if (active_file_id_ == -1 || active_file_id_ >= open_files_.size()) {
785 return;
786 }
787
789
790 // Parse error messages to extract line numbers
791 // Example Asar output: "asm/main.asm:42: error: Unknown command."
792 for (const auto& error : result.errors) {
793 try {
794 // Simple parsing: look for first two colons numbers
795 size_t first_colon = error.find(':');
796 if (first_colon != std::string::npos) {
797 size_t second_colon = error.find(':', first_colon + 1);
798 if (second_colon != std::string::npos) {
799 std::string line_str = error.substr(first_colon + 1, second_colon - (first_colon + 1));
800 int line = std::stoi(line_str);
801
802 // Adjust for 1-based line numbers if necessary (ImGuiColorTextEdit usually uses 1-based in UI but 0-based internally? Or vice versa?)
803 // Assuming standard compiler output 1-based, editor usually takes 1-based for markers key.
804 markers[line] = error;
805 }
806 }
807 } catch (...) {
808 // Ignore parsing errors
809 }
810 }
811
812 open_files_[active_file_id_].SetErrorMarkers(markers);
813}
814
816 last_errors_.clear();
817
818 if (active_file_id_ == -1 || active_file_id_ >= open_files_.size()) {
819 return;
820 }
821
822 TextEditor::ErrorMarkers empty_markers;
823 open_files_[active_file_id_].SetErrorMarkers(empty_markers);
824}
825
827 if (ImGui::BeginMenu("Assemble")) {
828 bool has_active_file =
829 (active_file_id_ != -1 && active_file_id_ < open_files_.size());
830 bool has_rom = (rom_ && rom_->is_loaded());
831
832 if (ImGui::MenuItem(ICON_MD_CHECK_CIRCLE " Validate", "Ctrl+B", false, has_active_file)) {
833 auto status = ValidateCurrentFile();
834 if (status.ok()) {
835 // Show success notification (could add toast notification here)
836 }
837 }
838
839 if (ImGui::MenuItem(ICON_MD_BUILD " Apply to ROM", "Ctrl+Shift+B", false,
840 has_active_file && has_rom)) {
841 auto status = ApplyPatchToRom();
842 if (status.ok()) {
843 // Show success notification
844 }
845 }
846
847 if (ImGui::MenuItem(ICON_MD_FILE_UPLOAD " Load External Symbols", nullptr, false)) {
849 std::string sym_file = dependencies_.project->symbols_filename;
850 if (!sym_file.empty()) {
851 std::string abs_path = dependencies_.project->GetAbsolutePath(sym_file);
852 auto status = asar_.LoadSymbolsFromFile(abs_path);
853 if (status.ok()) {
854 // Copy symbols to local map for display
857 dependencies_.toast_manager->Show("Successfully loaded external symbols from " + sym_file, ToastType::kSuccess);
858 }
859 } else {
861 dependencies_.toast_manager->Show("Failed to load symbols: " + std::string(status.message()), ToastType::kError);
862 }
863 }
864 } else {
866 dependencies_.toast_manager->Show("Project does not specify a symbols file.", ToastType::kWarning);
867 }
868 }
869 }
870 }
871
872 ImGui::Separator();
873
874 if (ImGui::MenuItem(ICON_MD_LIST " Show Symbols", nullptr, show_symbol_panel_)) {
876 }
877
878 ImGui::Separator();
879
880 // Show last error/warning count
881 ImGui::TextDisabled("Errors: %zu, Warnings: %zu", last_errors_.size(),
882 last_warnings_.size());
883
884 ImGui::EndMenu();
885 }
886
887 if (ImGui::BeginMenu("Version")) {
888 bool has_version_manager = (dependencies_.version_manager != nullptr);
889 if (ImGui::MenuItem(ICON_MD_CAMERA_ALT " Create Snapshot", nullptr, false, has_version_manager)) {
890 if (has_version_manager) {
891 ImGui::OpenPopup("Create Snapshot");
892 }
893 }
894
895 // Snapshot Dialog
896 if (ImGui::BeginPopupModal("Create Snapshot", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
897 static char message[256] = "";
898 ImGui::InputText("Message", message, sizeof(message));
899
900 if (ImGui::Button("Create", ImVec2(120, 0))) {
901 auto result = dependencies_.version_manager->CreateSnapshot(message);
902 if (result.ok() && result->success) {
904 dependencies_.toast_manager->Show("Snapshot Created: " + result->commit_hash, ToastType::kSuccess);
905 }
906 } else {
908 std::string err = result.ok() ? result->message : std::string(result.status().message());
909 dependencies_.toast_manager->Show("Snapshot Failed: " + err, ToastType::kError);
910 }
911 }
912 ImGui::CloseCurrentPopup();
913 message[0] = '\0'; // Reset
914 }
915 ImGui::SameLine();
916 if (ImGui::Button("Cancel", ImVec2(120, 0))) {
917 ImGui::CloseCurrentPopup();
918 }
919 ImGui::EndPopup();
920 }
921
922 ImGui::EndMenu();
923 }
924}
925
927 if (!show_symbol_panel_) {
928 return;
929 }
930
931 ImGui::SetNextWindowSize(ImVec2(350, 400), ImGuiCond_FirstUseEver);
932 if (ImGui::Begin("Symbols", &show_symbol_panel_)) {
933 if (symbols_.empty()) {
934 ImGui::TextDisabled("No symbols loaded.");
935 ImGui::TextDisabled("Apply a patch to load symbols.");
936 } else {
937 // Search filter
938 static char filter[256] = "";
939 ImGui::InputTextWithHint("##symbol_filter", "Filter symbols...", filter,
940 sizeof(filter));
941
942 ImGui::Separator();
943
944 if (ImGui::BeginChild("##symbol_list", ImVec2(0, 0), true)) {
945 for (const auto& [name, symbol] : symbols_) {
946 // Apply filter
947 if (filter[0] != '\0' &&
948 name.find(filter) == std::string::npos) {
949 continue;
950 }
951
952 ImGui::PushID(name.c_str());
953 if (ImGui::Selectable(name.c_str())) {
954 // Could jump to symbol definition if line info is available
955 // For now, just select it
956 }
957 ImGui::SameLine(200);
958 ImGui::TextDisabled("$%06X", symbol.address);
959 ImGui::PopID();
960 }
961 }
962 ImGui::EndChild();
963 }
964 }
965 ImGui::End();
966}
967
968} // namespace yaze::editor
static const Palette & GetDarkPalette()
Coordinates GetCursorPosition() const
int GetTotalLines() const
bool IsOverwrite() const
void Render(const char *aTitle, const ImVec2 &aSize=ImVec2(), bool aBorder=false)
void Undo(int aSteps=1)
std::map< int, std::string > ErrorMarkers
void Redo(int aSteps=1)
const LanguageDefinition & GetLanguageDefinition() const
bool CanUndo() const
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
const auto & vector() const
Definition rom.h:139
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:147
bool is_loaded() const
Definition rom.h:128
absl::StatusOr< AsarPatchResult > ApplyPatch(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths={})
absl::Status ValidateAssembly(const std::string &asm_path)
absl::Status Initialize()
absl::Status LoadSymbolsFromFile(const std::string &symbol_path)
std::map< std::string, AsarSymbol > GetSymbolTable() const
absl::StatusOr< SnapshotResult > CreateSnapshot(const std::string &message)
std::vector< std::string > last_warnings_
std::map< std::string, core::AsarSymbol > symbols_
absl::Status Save() override
std::vector< TextEditor > open_files_
absl::Status Load() override
void ChangeActiveFile(const std::string_view &filename)
absl::Status Copy() override
void UpdateErrorMarkers(const core::AsarPatchResult &result)
void OpenFolder(const std::string &folder_path)
absl::Status Paste() override
absl::Status Update() override
std::vector< std::string > files_
absl::Status Redo() override
absl::Status Undo() override
absl::Status Cut() override
std::vector< std::string > last_errors_
EditorDependencies dependencies_
Definition editor.h:237
void RegisterEditorPanel(std::unique_ptr< EditorPanel > panel)
Register an EditorPanel instance for central drawing.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
Ultra-compact toolbar that merges mode buttons with settings.
bool AddAction(const char *icon, const char *tooltip)
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
static std::vector< std::string > GetFilesInFolder(const std::string &folder_path)
static std::vector< std::string > GetSubdirectoriesInFolder(const std::string &folder_path)
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_CONTENT_CUT
Definition icons.h:466
#define ICON_MD_FILE_OPEN
Definition icons.h:747
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_CAMERA_ALT
Definition icons.h:355
#define ICON_MD_REDO
Definition icons.h:1570
#define ICON_MD_FILE_UPLOAD
Definition icons.h:749
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_CONTENT_PASTE
Definition icons.h:467
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_UNDO
Definition icons.h:2039
FolderItem LoadFolder(const std::string &folder)
std::vector< std::string > RemoveIgnoredFiles(const std::vector< std::string > &files, const std::vector< std::string > &ignored_files)
Editors are the view controllers for the application.
Definition agent_chat.cc:23
TextEditor::LanguageDefinition GetAssemblyLanguageDef()
Definition style.cc:193
void SaveFile(const std::string &filename, const std::string &contents)
Definition file_util.cc:56
std::string LoadFile(const std::string &filename)
Loads the entire contents of a file into a string.
Definition file_util.cc:23
TokenRegexStrings mTokenRegexStrings
Asar patch result information.
std::vector< std::string > errors
std::vector< std::string > warnings
project::YazeProject * project
Definition editor.h:134
core::VersionManager * version_manager
Definition editor.h:135
std::vector< FolderItem > subfolders
std::vector< std::string > files
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:853
std::string code_folder
Definition project.h:97
std::string symbols_filename
Definition project.h:101