yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
enhanced_tui.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <ctime>
5#include <iomanip>
6#include <iostream>
7#include <sstream>
8
9#ifdef _WIN32
10#include <conio.h>
11#include <windows.h>
12#else
13#include <sys/ioctl.h>
14#include <termios.h>
15#include <unistd.h>
16#endif
17
18#include "absl/strings/ascii.h"
19#include "absl/strings/str_format.h"
20#include "absl/strings/str_split.h"
21#include "util/macro.h"
22
23namespace yaze {
24namespace cli {
25namespace agent {
26
27namespace {
28
29// ANSI color codes
30constexpr const char* RESET = "\033[0m";
31constexpr const char* BOLD = "\033[1m";
32constexpr const char* DIM = "\033[2m";
33constexpr const char* ITALIC = "\033[3m";
34constexpr const char* UNDERLINE = "\033[4m";
35
36// Foreground colors
37constexpr const char* FG_BLACK = "\033[30m";
38constexpr const char* FG_RED = "\033[31m";
39constexpr const char* FG_GREEN = "\033[32m";
40constexpr const char* FG_YELLOW = "\033[33m";
41constexpr const char* FG_BLUE = "\033[34m";
42constexpr const char* FG_MAGENTA = "\033[35m";
43constexpr const char* FG_CYAN = "\033[36m";
44constexpr const char* FG_WHITE = "\033[37m";
45constexpr const char* FG_BRIGHT_BLACK = "\033[90m";
46constexpr const char* FG_BRIGHT_RED = "\033[91m";
47constexpr const char* FG_BRIGHT_GREEN = "\033[92m";
48constexpr const char* FG_BRIGHT_YELLOW = "\033[93m";
49constexpr const char* FG_BRIGHT_BLUE = "\033[94m";
50constexpr const char* FG_BRIGHT_MAGENTA = "\033[95m";
51constexpr const char* FG_BRIGHT_CYAN = "\033[96m";
52constexpr const char* FG_BRIGHT_WHITE = "\033[97m";
53
54// Background colors
55constexpr const char* BG_BLACK = "\033[40m";
56constexpr const char* BG_RED = "\033[41m";
57constexpr const char* BG_GREEN = "\033[42m";
58constexpr const char* BG_YELLOW = "\033[43m";
59constexpr const char* BG_BLUE = "\033[44m";
60constexpr const char* BG_MAGENTA = "\033[45m";
61constexpr const char* BG_CYAN = "\033[46m";
62constexpr const char* BG_WHITE = "\033[47m";
63constexpr const char* BG_BRIGHT_BLACK = "\033[100m";
64constexpr const char* BG_BRIGHT_RED = "\033[101m";
65constexpr const char* BG_BRIGHT_GREEN = "\033[102m";
66constexpr const char* BG_BRIGHT_YELLOW = "\033[103m";
67constexpr const char* BG_BRIGHT_BLUE = "\033[104m";
68constexpr const char* BG_BRIGHT_MAGENTA = "\033[105m";
69constexpr const char* BG_BRIGHT_CYAN = "\033[106m";
70constexpr const char* BG_BRIGHT_WHITE = "\033[107m";
71
72// Key codes
73constexpr int KEY_ESC = 27;
74constexpr int KEY_ENTER = 10;
75constexpr int KEY_BACKSPACE = 127;
76constexpr int KEY_TAB = 9;
77constexpr int KEY_UP = 1000;
78constexpr int KEY_DOWN = 1001;
79constexpr int KEY_LEFT = 1002;
80constexpr int KEY_RIGHT = 1003;
81constexpr int KEY_HOME = 1004;
82constexpr int KEY_END = 1005;
83constexpr int KEY_DELETE = 1006;
84constexpr int KEY_PAGE_UP = 1007;
85constexpr int KEY_PAGE_DOWN = 1008;
86
87// Terminal control sequences
88constexpr const char* CLEAR_SCREEN = "\033[2J";
89constexpr const char* CLEAR_LINE = "\033[2K";
90constexpr const char* CURSOR_HOME = "\033[H";
91constexpr const char* SAVE_CURSOR = "\033[s";
92constexpr const char* RESTORE_CURSOR = "\033[u";
93constexpr const char* HIDE_CURSOR = "\033[?25l";
94constexpr const char* SHOW_CURSOR = "\033[?25h";
95
96// Get terminal size
97std::pair<int, int> GetTerminalSize() {
98#ifdef _WIN32
99 CONSOLE_SCREEN_BUFFER_INFO csbi;
100 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
101 return {csbi.srWindow.Right - csbi.srWindow.Left + 1,
102 csbi.srWindow.Bottom - csbi.srWindow.Top + 1};
103#else
104 struct winsize w;
105 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
106 return {w.ws_col, w.ws_row};
107#endif
108}
109
110// Read a single character from stdin
111int ReadChar() {
112#ifdef _WIN32
113 return _getch();
114#else
115 return getchar();
116#endif
117}
118
119// Check if a key is a special key
120bool IsSpecialKey(int key) {
121 return key >= 1000;
122}
123
124} // namespace
125
126// Helper function to convert TUITheme to string
127std::string TUIThemeToString(TUITheme theme) {
128 switch (theme) {
130 return "Default";
131 case TUITheme::kDark:
132 return "Dark";
133 case TUITheme::kLight:
134 return "Light";
135 case TUITheme::kZelda:
136 return "Zelda";
138 return "Cyberpunk";
139 default:
140 return "Unknown";
141 }
142}
143
144EnhancedTUI::EnhancedTUI(const TUIConfig& config) : config_(config) {
146}
147
151
154 return absl::OkStatus();
155 }
156
158
159 auto [width, height] = GetTerminalSize();
160 terminal_width_ = width;
161 terminal_height_ = height;
162
164
166
167 return absl::OkStatus();
168}
169
172 return;
173 }
174
176 terminal_initialized_ = false;
177}
178
180 rom_context_ = rom;
181}
182
183absl::Status EnhancedTUI::Run() {
185
186 ClearScreen();
188
189 while (true) {
192 }
193
194 return absl::OkStatus();
195}
196
197void EnhancedTUI::DisplayMessage(const std::string& message,
198 const std::string& sender, bool is_error) {
199 std::string timestamp = FormatTimestamp();
200 std::string formatted_message;
201
202 if (is_error) {
203 formatted_message = absl::StrFormat("%s %sERROR%s %s: %s", timestamp,
204 FG_RED, RESET, sender, message);
205 } else {
206 formatted_message =
207 absl::StrFormat("%s %s: %s", timestamp, sender, message);
208 }
209
210 output_history_.push_back(formatted_message);
211
212 // Keep history size manageable
214 output_history_.erase(output_history_.begin(),
215 output_history_.begin() + 100);
216 }
217}
218
219void EnhancedTUI::DisplayToolOutput(const std::string& output,
220 const std::string& tool_name) {
221 std::string timestamp = FormatTimestamp();
222 std::string formatted_output = absl::StrFormat(
223 "%s %sTOOL%s %s:\n%s", timestamp, FG_CYAN, RESET, tool_name, output);
224
225 output_history_.push_back(formatted_output);
226}
227
229 const std::vector<std::string>& suggestions) {
230 if (suggestions.empty()) {
231 return;
232 }
233
234 std::string suggestion_text =
235 absl::StrFormat("%sSuggestions:%s\n", FG_YELLOW, RESET);
236
237 for (size_t i = 0; i < suggestions.size() && i < 5; ++i) {
238 suggestion_text += absl::StrFormat(" %s%d.%s %s\n", FG_BRIGHT_BLACK, i + 1,
239 RESET, suggestions[i]);
240 }
241
242 output_history_.push_back(suggestion_text);
243}
244
245void EnhancedTUI::UpdateStatusBar(const std::string& status) {
246 // Status updates are handled in the status bar drawing
247 // This method can be extended to store status history
248}
249
250void EnhancedTUI::ShowHelp(const std::string& command) {
251 std::string help_text;
252
253 if (commands_.find(command) != commands_.end()) {
254 help_text = absl::StrFormat("%sHelp for '%s':%s\n%s\n", FG_GREEN, command,
255 RESET, command_descriptions_[command]);
256 } else {
257 help_text =
258 absl::StrFormat("%sUnknown command: %s%s\n", FG_RED, command, RESET);
259 }
260
261 output_history_.push_back(help_text);
262}
263
265 const std::string& name,
266 std::function<absl::Status(const std::vector<std::string>&)> handler,
267 const std::string& description) {
268 commands_[name] = handler;
270}
271
273 config_ = config;
276}
277
279 std::cout << HIDE_CURSOR;
280 std::cout.flush();
281}
282
284 std::cout << SHOW_CURSOR;
285 std::cout.flush();
286}
287
289 std::cout << CLEAR_SCREEN << CURSOR_HOME;
290 std::cout.flush();
291}
292
294 std::cout << CURSOR_HOME;
295
296 DrawHeader();
298 DrawChatArea();
301 DrawSidebar();
302
303 std::cout.flush();
304}
305
315
318 std::string header_text = absl::StrFormat(
319 "%sZ3ED Enhanced TUI v2.0%s | ROM: %s | Theme: %s",
320 ApplyStyle("", style).c_str(), RESET, rom_context_ ? "Loaded" : "None",
322
323 std::cout << CURSOR_HOME;
324 std::cout << header_text;
325
326 // Fill remaining width with spaces
327 int remaining = terminal_width_ - header_text.length();
328 for (int i = 0; i < remaining; ++i) {
329 std::cout << " ";
330 }
331
332 std::cout << "\n";
333 std::cout << std::string(terminal_width_, '-') << "\n";
334}
335
337 if (!in_command_palette_) {
338 return;
339 }
340
342 std::cout << ApplyStyle("Command Palette: ", style);
343 std::cout << palette_filter_;
344
345 if (!palette_matches_.empty()) {
346 std::cout << "\n";
347 for (size_t i = 0; i < palette_matches_.size() && i < 5; ++i) {
348 if (i == palette_selection_) {
349 std::cout << ApplyStyle("> " + palette_matches_[i], style);
350 } else {
351 std::cout << " " << palette_matches_[i];
352 }
353 std::cout << "\n";
354 }
355 }
356}
357
360
361 // Draw chat history
362 int start_line = layout_.header_height + 1;
363 int end_line = start_line + layout_.chat_height;
364
365 for (int line = start_line;
366 line < end_line &&
367 line - start_line < static_cast<int>(output_history_.size());
368 ++line) {
369 std::cout << "\033[" << line << ";1H";
370
371 int history_index = output_history_.size() - (end_line - line);
372 if (history_index >= 0 &&
373 history_index < static_cast<int>(output_history_.size())) {
374 std::string history_line = output_history_[history_index];
375 std::string truncated =
377 std::cout << truncated;
378 }
379 }
380
381 // Draw input prompt
382 std::cout << "\033[" << end_line << ";1H";
383 std::cout << ApplyStyle(config_.prompt_style, style);
384 std::cout << current_input_;
385
386 // Position cursor
387 std::cout << "\033[" << end_line << ";"
388 << (config_.prompt_style.length() + current_input_.length() + 1)
389 << "H";
390}
391
393 // Tool output is integrated into the chat area
394 // This method can be extended for a dedicated tool output panel
395}
396
399
400 std::cout << "\033[" << terminal_height_ - 1 << ";1H";
401
402 std::string status_text =
403 absl::StrFormat("Commands: %d | History: %d | Mode: %s",
404 static_cast<int>(commands_.size()),
405 static_cast<int>(command_history_.size()),
406 in_command_palette_ ? "Palette" : "Normal");
407
408 std::cout << ApplyStyle(status_text, style);
409
410 // Fill remaining width
411 int remaining = terminal_width_ - status_text.length();
412 for (int i = 0; i < remaining; ++i) {
413 std::cout << " ";
414 }
415}
416
419
420 int sidebar_start = terminal_width_ - layout_.sidebar_width + 1;
421
422 std::cout << "\033[" << layout_.header_height + 1 << ";" << sidebar_start
423 << "H";
424 std::cout << ApplyStyle("ROM Info", style) << "\n";
425
426 if (rom_context_) {
427 std::cout << "\033[" << layout_.header_height + 2 << ";" << sidebar_start
428 << "H";
429 std::cout << "Size: " << rom_context_->size() << " bytes\n";
430 std::cout << "\033[" << layout_.header_height + 3 << ";" << sidebar_start
431 << "H";
432 std::cout << "Loaded: Yes\n";
433 } else {
434 std::cout << "\033[" << layout_.header_height + 2 << ";" << sidebar_start
435 << "H";
436 std::cout << "No ROM loaded\n";
437 }
438
439 // Draw shortcuts
440 std::cout << "\033[" << layout_.header_height + 5 << ";" << sidebar_start
441 << "H";
442 std::cout << ApplyStyle("Shortcuts", style) << "\n";
443 std::cout << "\033[" << layout_.header_height + 6 << ";" << sidebar_start
444 << "H";
445 std::cout << "Ctrl+P: Command Palette\n";
446 std::cout << "\033[" << layout_.header_height + 7 << ";" << sidebar_start
447 << "H";
448 std::cout << "Tab: Autocomplete\n";
449 std::cout << "\033[" << layout_.header_height + 8 << ";" << sidebar_start
450 << "H";
451 std::cout << "Esc: Exit Palette\n";
452}
453
455 int key = ReadChar();
456
457 HandleKeyPress(key);
458
459 return absl::OkStatus();
460}
461
465 } else {
466 HandleNormalKey(key);
467 }
468}
469
471 switch (key) {
472 case KEY_ENTER:
473 if (!current_input_.empty()) {
476 current_input_.clear();
477 }
478 break;
479
480 case KEY_BACKSPACE:
481 if (!current_input_.empty()) {
482 current_input_.pop_back();
483 }
484 break;
485
486 case KEY_TAB:
488 auto suggestions = GetCommandSuggestions(current_input_);
489 if (!suggestions.empty()) {
490 current_input_ = suggestions[0];
491 }
492 }
493 break;
494
495 case KEY_ESC:
496 // Could be used for command palette
497 break;
498
499 default:
500 if (key >= 32 && key <= 126) { // Printable characters
501 current_input_ += static_cast<char>(key);
502 }
503 break;
504 }
505}
506
508 switch (key) {
509 case KEY_ENTER:
510 if (!palette_matches_.empty() &&
511 palette_selection_ < static_cast<int>(palette_matches_.size())) {
513 in_command_palette_ = false;
514 }
515 break;
516
517 case KEY_ESC:
518 in_command_palette_ = false;
519 break;
520
521 case KEY_UP:
522 if (palette_selection_ > 0) {
524 }
525 break;
526
527 case KEY_DOWN:
528 if (palette_selection_ < static_cast<int>(palette_matches_.size()) - 1) {
530 }
531 break;
532
533 case KEY_BACKSPACE:
534 if (!palette_filter_.empty()) {
535 palette_filter_.pop_back();
537 }
538 break;
539
540 default:
541 if (key >= 32 && key <= 126) {
542 palette_filter_ += static_cast<char>(key);
544 }
545 break;
546 }
547}
548
550 palette_matches_.clear();
552
553 for (const auto& [command, _] : commands_) {
554 if (absl::AsciiStrToLower(command).find(
555 absl::AsciiStrToLower(palette_filter_)) != std::string::npos) {
556 palette_matches_.push_back(command);
557 }
558 }
559
560 std::sort(palette_matches_.begin(), palette_matches_.end());
561}
562
563absl::Status EnhancedTUI::ProcessCommand(const std::string& input) {
564 std::vector<std::string> parts =
565 absl::StrSplit(input, ' ', absl::SkipEmpty());
566 if (parts.empty()) {
567 return absl::OkStatus();
568 }
569
570 std::string command = parts[0];
571 std::vector<std::string> args(parts.begin() + 1, parts.end());
572
573 if (commands_.find(command) != commands_.end()) {
574 return commands_[command](args);
575 } else {
576 DisplayMessage("Unknown command: " + command, "System", true);
577 return absl::NotFoundError("Unknown command: " + command);
578 }
579}
580
581std::vector<std::string> EnhancedTUI::GetCommandSuggestions(
582 const std::string& partial) {
583 std::vector<std::string> suggestions;
584
585 for (const auto& [command, _] : commands_) {
586 if (absl::AsciiStrToLower(command).find(absl::AsciiStrToLower(partial)) ==
587 0) {
588 suggestions.push_back(command);
589 }
590 }
591
592 std::sort(suggestions.begin(), suggestions.end());
593 return suggestions;
594}
595
597 auto it = styles_.find(component);
598 if (it != styles_.end()) {
599 return it->second;
600 }
601
602 // Default style
603 return TUIStyle{FG_WHITE, BG_BLACK, FG_CYAN, FG_BRIGHT_BLACK};
604}
605
606std::string EnhancedTUI::ApplyStyle(const std::string& text,
607 const TUIStyle& style) const {
608 std::string result;
609
610 if (!style.foreground_color.empty()) {
611 result += style.foreground_color;
612 }
613
614 if (!style.background_color.empty()) {
615 result += style.background_color;
616 }
617
618 if (style.bold) {
619 result += BOLD;
620 }
621
622 if (style.italic) {
623 result += ITALIC;
624 }
625
626 if (style.underline) {
627 result += UNDERLINE;
628 }
629
630 result += text;
631 result += RESET;
632
633 return result;
634}
635
637 switch (theme) {
639 styles_[TUIComponent::kHeader] = {FG_BRIGHT_WHITE, BG_BLUE, FG_CYAN,
640 FG_BRIGHT_BLACK, true};
641 styles_[TUIComponent::kChatArea] = {FG_WHITE, BG_BLACK, FG_GREEN,
642 FG_BRIGHT_BLACK};
643 styles_[TUIComponent::kStatusBar] = {FG_BRIGHT_BLACK, BG_BLACK, FG_CYAN,
644 FG_BRIGHT_BLACK};
645 styles_[TUIComponent::kSidebar] = {FG_BRIGHT_WHITE, BG_BLACK, FG_YELLOW,
646 FG_BRIGHT_BLACK};
647 break;
648
649 case TUITheme::kDark:
650 styles_[TUIComponent::kHeader] = {FG_BRIGHT_WHITE, BG_BLACK, FG_CYAN,
651 FG_BRIGHT_BLACK, true};
652 styles_[TUIComponent::kChatArea] = {FG_BRIGHT_WHITE, BG_BLACK, FG_GREEN,
653 FG_BRIGHT_BLACK};
654 styles_[TUIComponent::kStatusBar] = {FG_BRIGHT_BLACK, BG_BLACK, FG_CYAN,
655 FG_BRIGHT_BLACK};
656 styles_[TUIComponent::kSidebar] = {FG_BRIGHT_WHITE, BG_BRIGHT_BLACK,
657 FG_YELLOW, FG_BRIGHT_BLACK};
658 break;
659
660 case TUITheme::kZelda:
661 styles_[TUIComponent::kHeader] = {FG_BRIGHT_GREEN, BG_BLACK, FG_YELLOW,
662 FG_GREEN, true};
663 styles_[TUIComponent::kChatArea] = {FG_WHITE, BG_BLACK, FG_GREEN,
664 FG_BRIGHT_BLACK};
665 styles_[TUIComponent::kStatusBar] = {FG_GREEN, BG_BLACK, FG_YELLOW,
666 FG_BRIGHT_BLACK};
667 styles_[TUIComponent::kSidebar] = {FG_BRIGHT_GREEN, BG_BLACK, FG_YELLOW,
668 FG_BRIGHT_BLACK};
669 break;
670
671 default:
673 break;
674 }
675}
676
677std::string EnhancedTUI::FormatTimestamp() const {
679 return "";
680 }
681
682 auto now = std::time(nullptr);
683 auto tm = *std::localtime(&now);
684
685 std::ostringstream oss;
686 oss << std::put_time(&tm, "%H:%M:%S");
687 return "[" + oss.str() + "]";
688}
689
690std::string EnhancedTUI::TruncateText(const std::string& text,
691 int max_width) const {
692 if (static_cast<int>(text.length()) <= max_width) {
693 return text;
694 }
695
696 return text.substr(0, max_width - 3) + "...";
697}
698
699std::vector<std::string> EnhancedTUI::WrapText(const std::string& text,
700 int width) const {
701 std::vector<std::string> lines;
702 std::vector<std::string> words = absl::StrSplit(text, ' ', absl::SkipEmpty());
703
704 std::string current_line;
705 for (const auto& word : words) {
706 if (static_cast<int>(current_line.length() + word.length() + 1) <= width) {
707 if (!current_line.empty()) {
708 current_line += " ";
709 }
710 current_line += word;
711 } else {
712 if (!current_line.empty()) {
713 lines.push_back(current_line);
714 current_line = word;
715 } else {
716 lines.push_back(word);
717 }
718 }
719 }
720
721 if (!current_line.empty()) {
722 lines.push_back(current_line);
723 }
724
725 return lines;
726}
727
728} // namespace agent
729} // namespace cli
730} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
auto size() const
Definition rom.h:134
void UpdateStatusBar(const std::string &status)
struct yaze::cli::agent::EnhancedTUI::Layout layout_
std::map< std::string, std::function< absl::Status(const std::vector< std::string > &)> commands_)
EnhancedTUI(const TUIConfig &config=TUIConfig{})
std::vector< std::string > output_history_
std::vector< std::string > GetCommandSuggestions(const std::string &partial)
void DisplayToolOutput(const std::string &output, const std::string &tool_name)
void LoadTheme(TUITheme theme)
void RegisterCommand(const std::string &name, std::function< absl::Status(const std::vector< std::string > &)> handler, const std::string &description="")
absl::Status ProcessCommand(const std::string &input)
std::map< TUIComponent, TUIStyle > styles_
std::string TruncateText(const std::string &text, int max_width) const
void DisplaySuggestions(const std::vector< std::string > &suggestions)
std::vector< std::string > palette_matches_
void DisplayMessage(const std::string &message, const std::string &sender="User", bool is_error=false)
void ShowHelp(const std::string &command)
std::string FormatTimestamp() const
std::string ApplyStyle(const std::string &text, const TUIStyle &style) const
std::vector< std::string > WrapText(const std::string &text, int width) const
void SetConfig(const TUIConfig &config)
TUIStyle GetStyle(TUIComponent component) const
std::map< std::string, std::string > command_descriptions_
std::vector< std::string > command_history_
TUITheme
Visual themes for the enhanced TUI.
TUIComponent
Different UI components in the enhanced TUI.
std::string TUIThemeToString(TUITheme theme)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Configuration for the enhanced TUI.
Visual styling configuration for TUI components.