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