Prechádzať zdrojové kódy

fix: resolve build errors and restore TUI functionality

- GtkRenderer: Fix undeclared identifier 'main_drawing_area_' and unused variable errors.
- TuiEditor: Restore missing method implementations (init, run, handle_editor_event) caused by previous file corruption. Re-implement hardware cursor support.
- Docs: Update PLAN.md with regression fixes.
Bernardo Magri 1 mesiac pred
rodič
commit
b7c3ec9e5b

+ 1 - 0
documentation/PLAN.md

@@ -171,6 +171,7 @@ Lumacs/
 - ✅ **Testing Infrastructure (Framework Integration)**: Integrated Google Test and removed custom test framework.
 - ✅ **Testing Infrastructure (Migrate Existing Tests)**: Converted `test_buffer.cpp` and `test_editor_core.cpp` to Google Test format.
 - ✅ **Build Fixes**: Resolved circular dependencies, missing definitions, and GTK4 incompatibilities across the codebase.
+- ✅ **Regression Fixes**: Resolved build failures in `GtkRenderer` (undeclared identifier, unused variable) and `TuiEditor` (missing implementation due to file corruption). Restored TUI hardware cursor support.
 - ✅ **LuaApi Test Coverage**: Expanded, and `sol2` binding issues for C++ managed objects (non-copyable) resolved via manual Lua C functions for core interactions.
 
 ## Technical Debt/Notes

+ 1 - 1
include/lumacs/gtk_renderer.hpp

@@ -33,7 +33,7 @@ public:
                  std::shared_ptr<Window> active_window_cache, bool cursor_visible_state);
 
     void draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
-                     std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
+                     std::shared_ptr<Window> window, Gtk::Widget* widget,
                      std::shared_ptr<Window> active_window_cache, bool cursor_visible_state);
 
     void render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,

+ 3 - 3
src/gtk_renderer.cpp

@@ -130,7 +130,7 @@ void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, in
     if (!active_window) return;
 
     // Render the active window (main editor area)
-    draw_window(cr, width, height, active_window, &main_drawing_area_, active_window_cache, cursor_visible_state);
+    draw_window(cr, width, height, active_window, &context_widget_, active_window_cache, cursor_visible_state);
 
     // Use a temporary layout for modeline/minibuffer as they are dynamic
     auto temp_layout = Pango::Layout::create(context_widget_.get_pango_context());
@@ -144,7 +144,7 @@ void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, in
 }
 
 void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
-                                std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
+                                std::shared_ptr<Window> window, Gtk::Widget* widget,
                                 std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
     if (!core_.theme_manager().active_theme() || !window || !widget) return;
 
@@ -504,7 +504,7 @@ void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int
     }
     
     // Render minibuffer cursor if active and visible (assuming cursor_visible is managed by GtkEditor)
-    bool cursor_visible_ = true; // Placeholder - in real usage passed or managed
+    // bool cursor_visible_ = true; // Placeholder - in real usage passed or managed
     if (core_.minibuffer_manager().is_active()) { // Assuming always visible or managed externally
         // Calculate cursor position in minibuffer
         // text is prompt + input. Cursor is relative to input start.

+ 362 - 2
src/tui_editor.cpp

@@ -60,7 +60,329 @@ private:
 };
 
 // --- TuiEditor Public Method Definitions ---
-// ... (init, run, handle_editor_event, set_core methods unchanged) ...
+
+void TuiEditor::init() {
+    // Initialize ncurses
+    initscr();
+    cbreak();           // Disable line buffering
+    noecho();           // Don't echo pressed keys
+    keypad(stdscr, TRUE); // Enable special keys
+    raw();              // Enable all control characters
+    timeout(50);        // Set 50ms timeout for getch() to avoid blocking forever
+    
+    // Color support
+    if (has_colors()) {
+        start_color();
+        use_default_colors();
+    }
+    
+    // Get screen dimensions
+    getmaxyx(stdscr, height_, width_);
+    
+    // Initialize theme colors for ncurses
+    if (has_colors() && core_->theme_manager().active_theme()) {
+        core_->theme_manager().active_theme()->initialize_ncurses_colors();
+    }
+    
+    // Set initial viewport size (leave room for minibuffer/message line)
+    int minibuffer_lines = 1; // Always reserve 1 line for minibuffer/message
+    int content_height = height_ - minibuffer_lines;
+    bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
+    int line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
+    int content_width = width_ - line_number_width;
+    core_->set_viewport_size(content_width, content_height);
+    
+    debug_log << "ncurses editor initialized: " << width_ << "x" << height_ << std::endl;
+}
+
+void TuiEditor::run() {
+    should_quit_ = false;
+    
+    // Initial render
+    render();
+    
+    std::chrono::steady_clock::time_point last_blink_toggle_time = std::chrono::steady_clock::now();
+
+    while (!should_quit_) {
+        // Handle screen resize
+        int new_height, new_width;
+        getmaxyx(stdscr, new_height, new_width);
+        if (new_height != height_ || new_width != width_) {
+            height_ = new_height;
+            width_ = new_width;
+            int minibuffer_lines = 1; // Always reserve 1 line for minibuffer/message
+            int content_height = height_ - minibuffer_lines;
+            bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
+            int line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
+            int content_width = width_ - line_number_width;
+            core_->set_viewport_size(content_width, content_height);
+            debug_log << "Screen resized to: " << width_ << "x" << height_ << std::endl;
+            debug_log << "Content area: " << content_width << "x" << content_height << std::endl;
+            // Force cursor to be visible after resize, as it implies movement.
+            last_cursor_move_time_ = std::chrono::steady_clock::now();
+            cursor_visible_ = true;
+            render();  // Re-render after resize
+        }
+        
+        // Get input (with timeout)
+        int ch = getch();
+        
+        // Only process input and render if we got actual input (not timeout)
+        if (ch != ERR) {
+            last_cursor_move_time_ = std::chrono::steady_clock::now();
+            cursor_visible_ = true; // Ensure cursor is visible on input
+            handle_input(ch);
+            render();
+            last_blink_toggle_time = std::chrono::steady_clock::now(); // Reset blink timer after input
+        } else { // No input (timeout occurred)
+            auto now = std::chrono::steady_clock::now();
+            if (now - last_cursor_move_time_ > BLINK_STATIONARY_THRESHOLD) {
+                // If stationary for long enough, start/continue blinking
+                if (now - last_blink_toggle_time > BLINK_INTERVAL) {
+                    cursor_visible_ = !cursor_visible_;
+                    core_->check_and_clear_message(); // Check and clear messages (like for GtkEditor's blink)
+                    render(); // Re-render to show/hide cursor
+                    last_blink_toggle_time = now;
+                }
+            } else {
+                // Still "moving" recently, keep cursor visible.
+                // If it was just hidden by a blink, make it visible again.
+                if (!cursor_visible_) {
+                    cursor_visible_ = true;
+                    render(); // Make sure it's visible
+                }
+            }
+        }
+    }
+}
+
+void TuiEditor::handle_editor_event(EditorEvent event) {
+    if (event == EditorEvent::Quit) {
+        should_quit_ = true;
+    } else if (event == EditorEvent::Message) {
+        message_line_ = core_->last_message(); // Still update local message_line_ for rendering
+    } else if (event == EditorEvent::CursorMoved) {
+        last_cursor_move_time_ = std::chrono::steady_clock::now();
+        cursor_visible_ = true;
+        render(); // Ensure immediate redraw to show cursor at new position
+    } else if (event == EditorEvent::TransientMessageCleared) {
+        // Redraw to clear the message from the screen
+        render();
+    } else if (event == EditorEvent::CommandMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::Command, ":",
+            [this](const std::string& input) {
+                if (input == "q" || input == "quit") {
+                    should_quit_ = true;
+                } else {
+                    auto result = core_->minibuffer_manager().parse_and_execute_command_string(input);
+                    core_->set_message(result.message);
+                }
+            }, // Added comma here
+            [this]() { core_->set_message("Cancelled"); }
+        );
+    } else if (event == EditorEvent::BufferSwitchMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::BufferName, "Switch to buffer: ",
+            [this](const std::string& input) {
+                if (core_->switch_buffer_in_window(input)) {
+                    core_->set_message("Switched to: " + input);
+                    core_->lua_api()->execute("auto_activate_major_mode()");
+                } else {
+                    core_->set_message("Buffer not found: " + input);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
+    } else if (event == EditorEvent::KillBufferMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::BufferName, "Kill buffer: ",
+            [this](const std::string& input) {
+                // MinibufferManager should handle confirmation for modified buffers
+                if (core_->close_buffer(input)) {
+                    core_->set_message("Closed buffer: " + input);
+                } else {
+                    core_->set_message("Failed to close buffer: " + input);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
+    } else if (event == EditorEvent::FindFileMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::FilePath, "Find file: ",
+            [this](const std::string& input) {
+                if (core_->load_file(input)) {
+                    core_->set_message("Loaded: " + input);
+                    core_->lua_api()->execute("auto_activate_major_mode()");
+                } else {
+                    core_->set_message("Failed to load: " + input);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
+    } else if (event == EditorEvent::ThemeSelectionMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ThemeName, "Set theme: ",
+            [this](const std::string& input) {
+                auto theme_names = core_->theme_manager().theme_names();
+                auto it = std::find(theme_names.begin(), theme_names.end(), input);
+                if (it != theme_names.end()) {
+                    core_->theme_manager().set_active_theme(input);
+                    core_->set_message("Switched to theme: " + input);
+                } else {
+                    core_->set_message("Theme not found: " + input);
+                }
+            }, // Added comma here
+            [this]() { core_->set_message("Cancelled"); }
+        );
+    } else if (event == EditorEvent::ISearchMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ISearch, "I-search: ",
+            [](const std::string&) { },
+            [this]() { core_->set_message("Cancelled I-search"); }
+        );
+        core_->minibuffer_manager().start_isearch(true);
+    } else if (event == EditorEvent::ISearchBackwardMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ISearch, "I-search backward: ",
+            [](const std::string&) { },
+            [this]() { core_->set_message("Cancelled I-search backward"); }
+        );
+        core_->minibuffer_manager().start_isearch(false);
+    } else if (event == EditorEvent::TransientMessageCleared) {
+        // Redraw to clear the message from the screen
+        render();
+    }
+}
+
+void TuiEditor::set_core(EditorCore* core) {
+    core_ = core;
+}
+
+// --- TuiEditor Private Helper Method Definitions ---
+
+int TuiEditor::get_attributes_for_face(const std::string& face_name) {
+    auto theme = core_->theme_manager().active_theme();
+    if (theme) {
+        return theme->get_face_attributes_ncurses(face_name);
+    }
+    return 0; // A_NORMAL
+}
+
+/// Convert ncurses key code to our key name format
+std::string TuiEditor::resolve_key(int ch) {
+    debug_log << "=== NCURSES INPUT DEBUG ===" << std::endl;
+    debug_log << "Raw key code: " << ch << " (0x" << std::hex << ch << std::dec << ")" << std::endl;
+    
+    if (ch == 27) return "Escape";
+    if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) return "Return";
+    if (ch == '\t') return "Tab";
+    if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') return "Backspace";
+    if (ch == KEY_UP) return "ArrowUp";
+    if (ch == KEY_DOWN) return "ArrowDown";
+    if (ch == KEY_LEFT) return "ArrowLeft";
+    if (ch == KEY_RIGHT) return "ArrowRight";
+    if (ch == KEY_HOME) return "Home";
+    if (ch == KEY_END) return "End";
+    if (ch == KEY_PPAGE) return "PageUp";
+    if (ch == KEY_NPAGE) return "PageDown";
+    if (ch == ' ') return "Space";
+    
+    // Control keys
+    if (ch > 0 && ch < 32) {
+        return "C-" + std::string(1, (char)('a' + ch - 1));
+    }
+
+    // Regular characters
+    if (ch >= 32 && ch < 127) {
+        return std::string(1, (char)ch);
+    }
+
+    return "";
+}
+
+bool TuiEditor::handle_input(int ch) {
+    std::string key_name;
+
+    // Check for Meta key sequence (Escape + Key)
+    if (ch == 27) {
+        // Set non-blocking read to check for immediate next key
+        timeout(0);
+        int next_ch = getch();
+        timeout(50); // Restore timeout
+
+        if (next_ch != ERR) {
+            // Ensure next_ch is a valid printable char or special key we can map
+            // For now, handle simple M-char sequences
+            std::string next_key = resolve_key(next_ch);
+            if (!next_key.empty() && next_key.length() == 1) { // Simple char
+                 key_name = "M-" + next_key;
+            } else if (!next_key.empty()) {
+                 // Special key with Meta, e.g. M-ArrowUp? 
+                 // resolve_key returns "ArrowUp", so we get "M-ArrowUp". This is valid.
+                 key_name = "M-" + next_key;
+            } else {
+                // Couldn't resolve next key, just treat as Escape then ignore next?
+                // Or treat as Escape sequence.
+                key_name = "Escape";
+                // We effectively consumed next_ch and ignored it. 
+                // Better might be to ungetch, but ncurses ungetch is tricky.
+                // Let's assume if resolve_key fails it was garbage.
+            }
+        } else {
+            key_name = "Escape";
+        }
+    } else {
+        key_name = resolve_key(ch);
+    }
+    
+    if (key_name.empty()) {
+        debug_log << "Empty key name, ignoring input" << std::endl;
+        return false;
+    }
+
+    debug_log << "Resolved key: " << key_name << std::endl;
+
+    // Handle Minibuffer Input Logic first
+    if (core_->minibuffer_manager().is_active()) {
+        return core_->minibuffer_manager().handle_key_event(key_name);
+    }
+
+    // Normal mode processing (pass to keybinding system)
+    KeyProcessingResult result = core_->keybinding_manager().process_key(Key::parse(key_name));
+
+    if (result.command_result.has_value()) {
+        core_->set_message(result.command_result->message);
+    }
+    
+    if (result.type == KeyResult::Unbound) {
+        // Fallback: Self-insert for printable characters
+        // Check if key is a single character and not a control sequence
+        // The resolve_key function returns "C-x", "M-x", "Esc", "Return", etc.
+        // Printable characters are returned as single chars "a", "1", etc.
+        bool has_ctrl = key_name.find("C-") != std::string::npos;
+        bool has_meta = key_name.find("M-") != std::string::npos;
+
+        if (!has_ctrl && !has_meta && key_name.length() == 1) {
+            // We can assume it's printable if length is 1 and it's not a special key (which resolve_key handles)
+            core_->command_system().execute("self-insert-command", {key_name});
+            
+            // --- Macro Recording Logic for Self-Insert ---
+            if (core_->is_recording_macro()) {
+                core_->record_key_sequence(key_name);
+            }
+            // --------------------------------------------
+
+            return true;
+        }
+    }
+
+    return result.type != KeyResult::Unbound;
+}
+
+// process_key is removed as keybinding_manager handles it
+
+
 
 void TuiEditor::render() {
     // Reset hardware cursor position
@@ -110,7 +432,45 @@ void TuiEditor::render() {
     refresh();
 }
 
-// ... (render_layout_node unchanged) ...
+void TuiEditor::render_layout_node(std::shared_ptr<LayoutNode> node, int x, int y, int width, int height) {
+    if (!node) return;
+    
+    if (node->type == LayoutNode::Type::Leaf) {
+        // Render a single window
+        render_window(node->window, x, y, width, height);
+    } else if (node->type == LayoutNode::Type::HorizontalSplit) {
+        // Split horizontally: top and bottom windows
+        int top_height = height / 2;
+        int bottom_height = height - top_height;
+        
+        render_layout_node(node->child1, x, y, width, top_height);
+        render_layout_node(node->child2, x, y + top_height, width, bottom_height);
+    } else if (node->type == LayoutNode::Type::VerticalSplit) {
+        // Split vertically: left and right windows  
+        int separator_width = (width > 2) ? 1 : 0;
+        int available_width = width - separator_width;
+
+        int left_width = available_width / 2;
+        int right_width = available_width - left_width;
+        
+        render_layout_node(node->child1, x, y, left_width, height);
+
+        // Draw separator if enabled
+        if (separator_width > 0) {
+            int attrs = get_attributes_for_face("window-divider");
+            attron(attrs);
+            
+            int sep_x = x + left_width;
+            for (int i = 0; i < height; ++i) {
+                mvaddch(y + i, sep_x, ACS_VLINE);
+            }
+            
+            attroff(attrs);
+        }
+
+        render_layout_node(node->child2, x + left_width + separator_width, y, right_width, height);
+    }
+}
 
 void TuiEditor::render_window(std::shared_ptr<Window> window, int x, int y, int width, int height) {
     if (!window) return;