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"
3
4#include <fstream>
5#include <string>
6#include <vector>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/match.h"
10#include "util/file_util.h"
11#include "app/gui/core/icons.h"
14
15namespace yaze::editor {
16
17using util::FileDialogWrapper;
18
19namespace {
20
21static const char *const kKeywords[] = {
22 "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", "BPL",
23 "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", "CMP", "CPX",
24 "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", "INY", "JMP", "JSR",
25 "JSL", "LDA", "LDX", "LDY", "LSR", "MVN", "NOP", "ORA", "PEA", "PER",
26 "PHA", "PHB", "PHD", "PHP", "PHX", "PHY", "PLA", "PLB", "PLD", "PLP",
27 "PLX", "PLY", "REP", "ROL", "ROR", "RTI", "RTL", "RTS", "SBC", "SEC",
28 "SEI", "SEP", "STA", "STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD",
29 "TCS", "TDC", "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA",
30 "TYX", "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM"};
31
32static const char *const kIdentifiers[] = {
33 "abort", "abs", "acos", "asin", "atan", "atexit",
34 "atof", "atoi", "atol", "ceil", "clock", "cosh",
35 "ctime", "div", "exit", "fabs", "floor", "fmod",
36 "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
37 "ispunct", "isspace", "isupper", "kbhit", "log10", "log2",
38 "log", "memcmp", "modf", "pow", "putchar", "putenv",
39 "puts", "rand", "remove", "rename", "sinh", "sqrt",
40 "srand", "strcat", "strcmp", "strerror", "time", "tolower",
41 "toupper"};
42
44 TextEditor::LanguageDefinition language_65816;
45 for (auto &k : kKeywords) language_65816.mKeywords.emplace(k);
46
47 for (auto &k : kIdentifiers) {
49 id.mDeclaration = "Built-in function";
50 language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id));
51 }
52
53 language_65816.mTokenRegexStrings.push_back(
54 std::make_pair<std::string, TextEditor::PaletteIndex>(
55 "[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor));
56 language_65816.mTokenRegexStrings.push_back(
57 std::make_pair<std::string, TextEditor::PaletteIndex>(
58 "L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String));
59 language_65816.mTokenRegexStrings.push_back(
60 std::make_pair<std::string, TextEditor::PaletteIndex>(
61 "\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral));
62 language_65816.mTokenRegexStrings.push_back(
63 std::make_pair<std::string, TextEditor::PaletteIndex>(
64 "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
66 language_65816.mTokenRegexStrings.push_back(
67 std::make_pair<std::string, TextEditor::PaletteIndex>(
68 "[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
69 language_65816.mTokenRegexStrings.push_back(
70 std::make_pair<std::string, TextEditor::PaletteIndex>(
71 "0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
72 language_65816.mTokenRegexStrings.push_back(
73 std::make_pair<std::string, TextEditor::PaletteIndex>(
74 "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
76 language_65816.mTokenRegexStrings.push_back(
77 std::make_pair<std::string, TextEditor::PaletteIndex>(
78 "[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier));
79 language_65816.mTokenRegexStrings.push_back(
80 std::make_pair<std::string, TextEditor::PaletteIndex>(
81 "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
82 "\\;\\,\\.]",
84
85 language_65816.mCommentStart = "/*";
86 language_65816.mCommentEnd = "*/";
87 language_65816.mSingleLineComment = ";";
88
89 language_65816.mCaseSensitive = false;
90 language_65816.mAutoIndentation = true;
91
92 language_65816.mName = "65816";
93
94 return language_65816;
95}
96
97std::vector<std::string> RemoveIgnoredFiles(
98 const std::vector<std::string>& files,
99 const std::vector<std::string>& ignored_files) {
100 std::vector<std::string> filtered_files;
101 for (const auto& file : files) {
102 // Remove subdirectory files
103 if (absl::StrContains(file, '/')) {
104 continue;
105 }
106 // Make sure the file has an extension
107 if (!absl::StrContains(file, '.')) {
108 continue;
109 }
110 if (std::ranges::find(ignored_files, file) == ignored_files.end()) {
111 filtered_files.push_back(file);
112 }
113 }
114 return filtered_files;
115}
116
117FolderItem LoadFolder(const std::string& folder) {
118 // Check if .gitignore exists in the folder
119 std::ifstream gitignore(folder + "/.gitignore");
120 std::vector<std::string> ignored_files;
121 if (gitignore.good()) {
122 std::string line;
123 while (std::getline(gitignore, line)) {
124 if (line[0] == '#') {
125 continue;
126 }
127 if (line[0] == '!') {
128 // Ignore the file
129 continue;
130 }
131 ignored_files.push_back(line);
132 }
133 }
134
135 FolderItem current_folder;
136 current_folder.name = folder;
137 auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
138 current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
139
140 for (const auto& subfolder :
142 FolderItem folder_item;
143 folder_item.name = subfolder;
144 std::string full_folder = current_folder.name + "/" + subfolder;
145 auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
146 for (const auto& files : folder_files) {
147 // Remove subdirectory files
148 if (absl::StrContains(files, '/')) {
149 continue;
150 }
151 // Make sure the file has an extension
152 if (!absl::StrContains(files, '.')) {
153 continue;
154 }
155 if (std::ranges::find(ignored_files, files) != ignored_files.end()) {
156 continue;
157 }
158 folder_item.files.push_back(files);
159 }
160
161 for (const auto& subdir :
163 FolderItem subfolder_item;
164 subfolder_item.name = subdir;
165 subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
166 folder_item.subfolders.push_back(subfolder_item);
167 }
168 current_folder.subfolders.push_back(folder_item);
169 }
170
171 return current_folder;
172}
173
174} // namespace
175
178
179 // Register cards with EditorCardManager
180 if (!dependencies_.card_registry) return;
181 auto* card_registry = dependencies_.card_registry;
182 card_registry->RegisterCard({.card_id = "assembly.editor", .display_name = "Assembly Editor",
183 .icon = ICON_MD_CODE, .category = "Assembly",
184 .shortcut_hint = "", .priority = 10});
185 card_registry->RegisterCard({.card_id = "assembly.file_browser", .display_name = "File Browser",
186 .icon = ICON_MD_FOLDER_OPEN, .category = "Assembly",
187 .shortcut_hint = "", .priority = 20});
188
189 // Don't show by default - only show when user explicitly opens Assembly Editor
190}
191
192absl::Status AssemblyEditor::Load() {
193 // Register cards with EditorCardRegistry (dependency injection)
194 // Note: Assembly editor uses dynamic file tabs, so we register the main editor window
195 if (!dependencies_.card_registry) return absl::OkStatus();
196 auto* card_registry = dependencies_.card_registry;
197
198 return absl::OkStatus();
199}
200
201void AssemblyEditor::OpenFolder(const std::string& folder_path) {
202 current_folder_ = LoadFolder(folder_path);
203}
204
205void AssemblyEditor::Update(bool& is_loaded) {
206 ImGui::Begin("Assembly Editor", &is_loaded);
207 if (ImGui::BeginMenuBar()) {
208 DrawFileMenu();
209 DrawEditMenu();
210 ImGui::EndMenuBar();
211 }
212
213 auto cpos = text_editor_.GetCursorPosition();
214 ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
215 cpos.mColumn + 1, text_editor_.GetTotalLines(),
216 text_editor_.IsOverwrite() ? "Ovr" : "Ins",
217 text_editor_.CanUndo() ? "*" : " ",
219 current_file_.c_str());
220
221 text_editor_.Render("##asm_editor");
222 ImGui::End();
223}
224
226 auto cpos = text_editor_.GetCursorPosition();
227 ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
228 cpos.mColumn + 1, text_editor_.GetTotalLines(),
229 text_editor_.IsOverwrite() ? "Ovr" : "Ins",
230 text_editor_.CanUndo() ? "*" : " ",
232 current_file_.c_str());
233
234 text_editor_.Render("##asm_editor", ImVec2(0, 0));
235}
236
238 DrawToolset();
240
241 // Create session-aware card (non-static for multi-session support)
242 gui::EditorCard file_browser_card(MakeCardTitle("File Browser").c_str(), ICON_MD_FOLDER);
243 bool file_browser_open = true;
244 if (file_browser_card.Begin(&file_browser_open)) {
245 if (current_folder_.name != "") {
247 } else {
248 if (ImGui::Button("Open Folder")) {
250 }
251 }
252 }
253 file_browser_card.End(); // ALWAYS call End after Begin
254
255 // Draw open files as individual, dockable EditorCards
256 for (int i = 0; i < active_files_.Size; i++) {
257 int file_id = active_files_[i];
258 bool open = true;
259
260 // Ensure we have a TextEditor instance for this file
261 if (file_id >= open_files_.size()) {
262 open_files_.resize(file_id + 1);
263 }
264 if (file_id >= files_.size()) {
265 // This can happen if a file was closed and its ID is being reused.
266 // For now, we just skip it.
267 continue;
268 }
269
270 // Create session-aware card title for each file
271 std::string card_name = MakeCardTitle(files_[file_id]);
272 gui::EditorCard file_card(card_name.c_str(), ICON_MD_DESCRIPTION, &open);
273 if (file_card.Begin()) {
274 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
275 active_file_id_ = file_id;
276 }
277 open_files_[file_id].Render(absl::StrCat("##", card_name).c_str());
278 }
279 file_card.End(); // ALWAYS call End after Begin
280
281 if (!open) {
282 active_files_.erase(active_files_.Data + i);
283 i--;
284 }
285 }
286}
287
288absl::Status AssemblyEditor::Save() {
289 if (active_file_id_ != -1 && active_file_id_ < open_files_.size()) {
290 std::string content = open_files_[active_file_id_].GetText();
292 return absl::OkStatus();
293 }
294 return absl::FailedPreconditionError("No active file to save.");
295}
296
298 static gui::Toolset toolbar;
299 toolbar.Begin();
300
301 if (toolbar.AddAction(ICON_MD_FOLDER_OPEN, "Open Folder")) {
303 }
304 if (toolbar.AddAction(ICON_MD_SAVE, "Save File")) {
305 Save();
306 }
307
308 toolbar.End();
309}
310
312 if (ImGui::BeginChild("##current_folder", ImVec2(0, 0), true,
313 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
314 if (ImGui::BeginTable("##file_table", 2,
315 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
316 ImGuiTableFlags_Resizable |
317 ImGuiTableFlags_Sortable)) {
318 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
319 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch);
320
321 ImGui::TableHeadersRow();
322
323 for (const auto& file : current_folder_.files) {
324 ImGui::TableNextRow();
325 ImGui::TableNextColumn();
326 if (ImGui::Selectable(file.c_str())) {
327 ChangeActiveFile(absl::StrCat(current_folder_.name, "/", file));
328 }
329 ImGui::TableNextColumn();
330 ImGui::Text("File");
331 }
332
333 for (const auto& subfolder : current_folder_.subfolders) {
334 ImGui::TableNextRow();
335 ImGui::TableNextColumn();
336 if (ImGui::TreeNode(subfolder.name.c_str())) {
337 for (const auto& file : subfolder.files) {
338 ImGui::TableNextRow();
339 ImGui::TableNextColumn();
340 if (ImGui::Selectable(file.c_str())) {
341 ChangeActiveFile(absl::StrCat(current_folder_.name, "/",
342 subfolder.name, "/", file));
343 }
344 ImGui::TableNextColumn();
345 ImGui::Text("File");
346 }
347 ImGui::TreePop();
348 } else {
349 ImGui::TableNextColumn();
350 ImGui::Text("Folder");
351 }
352 }
353
354 ImGui::EndTable();
355 }
356
357 ImGui::EndChild();
358 }
359}
360
361
363 if (ImGui::BeginMenu("File")) {
364 if (ImGui::MenuItem("Open", "Ctrl+O")) {
366 ChangeActiveFile(filename);
367 }
368 if (ImGui::MenuItem("Save", "Ctrl+S")) {
369 // TODO: Implement this
370 }
371 ImGui::EndMenu();
372 }
373}
374
376 if (ImGui::BeginMenu("Edit")) {
377 if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
379 }
380 if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
382 }
383 ImGui::Separator();
384 if (ImGui::MenuItem("Cut", "Ctrl+X")) {
386 }
387 if (ImGui::MenuItem("Copy", "Ctrl+C")) {
389 }
390 if (ImGui::MenuItem("Paste", "Ctrl+V")) {
392 }
393 ImGui::Separator();
394 if (ImGui::MenuItem("Find", "Ctrl+F")) {
395 // TODO: Implement this.
396 }
397 ImGui::EndMenu();
398 }
399}
400
401void AssemblyEditor::ChangeActiveFile(const std::string_view &filename) {
402 // Check if file is already open
403 for (int i = 0; i < active_files_.Size; ++i) {
404 int file_id = active_files_[i];
405 if (files_[file_id] == filename) {
406 // Optional: Focus window
407 return;
408 }
409 }
410
411 // Add new file
412 int new_file_id = files_.size();
413 files_.push_back(std::string(filename));
414 active_files_.push_back(new_file_id);
415
416 // Resize open_files_ if needed
417 if (new_file_id >= open_files_.size()) {
418 open_files_.resize(new_file_id + 1);
419 }
420
421 // Load file content using utility
422 std::string content = util::LoadFile(std::string(filename));
423 if (!content.empty()) {
424 open_files_[new_file_id].SetText(content);
425 open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef());
426 open_files_[new_file_id].SetPalette(TextEditor::GetDarkPalette());
427 } else {
428 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
429 std::string(filename).c_str());
430 }
431}
432
433absl::Status AssemblyEditor::Cut() {
435 return absl::OkStatus();
436}
437
438absl::Status AssemblyEditor::Copy() {
440 return absl::OkStatus();
441}
442
443absl::Status AssemblyEditor::Paste() {
445 return absl::OkStatus();
446}
447
448absl::Status AssemblyEditor::Undo() {
450 return absl::OkStatus();
451}
452
453absl::Status AssemblyEditor::Redo() {
455 return absl::OkStatus();
456}
457
458absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
459
460} // 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)
void Redo(int aSteps=1)
const LanguageDefinition & GetLanguageDefinition() const
bool CanUndo() const
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
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 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
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
std::string MakeCardTitle(const std::string &base_title) const
Definition editor.h:168
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
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:811
#define ICON_MD_CODE
Definition icons.h:432
#define ICON_MD_DESCRIPTION
Definition icons.h:537
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_FOLDER
Definition icons.h:807
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.
void VerticalSpacing(float pixels)
TextEditor::LanguageDefinition GetAssemblyLanguageDef()
Definition style.cc:192
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
EditorCardRegistry * card_registry
Definition editor.h:80
std::vector< FolderItem > subfolders
std::vector< std::string > files