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