Parcourir la source

adding window split

Bernardo Magri il y a 1 mois
Parent
commit
6cabe583b0

BIN
build/tests/CMakeFiles/run_tests.dir/__/src/buffer.cpp.o


BIN
build/tests/CMakeFiles/run_tests.dir/test_buffer.cpp.o


+ 0 - 8
build/tests/CTestTestfile.cmake

@@ -1,8 +0,0 @@
-# CMake generated Testfile for 
-# Source directory: /Users/user/Projects/lumacs/tests
-# Build directory: /Users/user/Projects/lumacs/build/tests
-# 
-# This file includes the relevant testing commands required for 
-# testing this directory and lists subdirectories to be tested as well.
-add_test([=[buffer_tests]=] "/Users/user/Projects/lumacs/build/tests/run_tests")
-set_tests_properties([=[buffer_tests]=] PROPERTIES  _BACKTRACE_TRIPLES "/Users/user/Projects/lumacs/tests/CMakeLists.txt;16;add_test;/Users/user/Projects/lumacs/tests/CMakeLists.txt;0;")

+ 0 - 45
build/tests/cmake_install.cmake

@@ -1,45 +0,0 @@
-# Install script for directory: /Users/user/Projects/lumacs/tests
-
-# Set the install prefix
-if(NOT DEFINED CMAKE_INSTALL_PREFIX)
-  set(CMAKE_INSTALL_PREFIX "/usr/local")
-endif()
-string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
-
-# Set the install configuration name.
-if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
-  if(BUILD_TYPE)
-    string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
-           CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
-  else()
-    set(CMAKE_INSTALL_CONFIG_NAME "Release")
-  endif()
-  message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
-endif()
-
-# Set the component getting installed.
-if(NOT CMAKE_INSTALL_COMPONENT)
-  if(COMPONENT)
-    message(STATUS "Install component: \"${COMPONENT}\"")
-    set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
-  else()
-    set(CMAKE_INSTALL_COMPONENT)
-  endif()
-endif()
-
-# Is this installation the result of a crosscompile?
-if(NOT DEFINED CMAKE_CROSSCOMPILING)
-  set(CMAKE_CROSSCOMPILING "FALSE")
-endif()
-
-# Set path to fallback-tool for dependency-resolution.
-if(NOT DEFINED CMAKE_OBJDUMP)
-  set(CMAKE_OBJDUMP "/nix/store/l58v977vq4qy58m1bw60bzrpkywizz2b-clang-wrapper-21.1.1/bin/objdump")
-endif()
-
-string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
-       "${CMAKE_INSTALL_MANIFEST_FILES}")
-if(CMAKE_INSTALL_LOCAL_ONLY)
-  file(WRITE "/Users/user/Projects/lumacs/build/tests/install_local_manifest.txt"
-     "${CMAKE_INSTALL_MANIFEST_CONTENT}")
-endif()

BIN
build/tests/run_tests


+ 1 - 0
include/lumacs/buffer.hpp

@@ -2,6 +2,7 @@
 
 #include <string>
 #include <vector>
+#include <functional>
 #include <optional>
 #include <filesystem>
 #include <memory>

+ 5 - 4
include/lumacs/editor_core.hpp

@@ -13,10 +13,11 @@ namespace lumacs {
 enum class EditorEvent {
     BufferModified,
     CursorMoved,
-    ViewportChanged,
-    WindowLayoutChanged, // New event
-    Message, // New event
-    CommandMode, // Trigger command mode (minibuffer)
+        ViewportChanged,
+        WindowLayoutChanged, // New event
+        WindowFocused,       // New event: a different window gained focus
+        Message,             // New event
+        CommandMode,         // Trigger command mode (minibuffer)
     Quit
 };
 

+ 56 - 0
include/lumacs/window.hpp

@@ -0,0 +1,56 @@
+#pragma once
+
+#include "lumacs/buffer.hpp"
+#include <memory>
+#include <utility>
+#include <algorithm>
+
+namespace lumacs {
+
+struct Viewport {
+    int width = 0;
+    int height = 0;
+    int scroll_offset = 0;
+};
+
+class Window {
+public:
+    Window(std::shared_ptr<Buffer> buffer);
+
+    void set_buffer(std::shared_ptr<Buffer> buffer);
+    
+    // Accessors
+    std::shared_ptr<Buffer> buffer_ptr() const { return buffer_; }
+    Buffer& buffer() { return *buffer_; }
+    const Buffer& buffer() const { return *buffer_; }
+
+    void set_cursor(Position pos);
+    Position cursor() const { return cursor_; }
+
+    // Movement
+    void move_up();
+    void move_down();
+    void move_left();
+    void move_right();
+    void move_to_line_start();
+    void move_to_line_end();
+
+    // Viewport
+    void set_viewport_size(int width, int height);
+    const Viewport& viewport() const { return viewport_; }
+    
+    void adjust_scroll();
+    
+    std::pair<size_t, size_t> visible_line_range() const;
+
+private:
+    void clamp_cursor();
+
+    std::shared_ptr<Buffer> buffer_;
+    Position cursor_;
+    Viewport viewport_;
+    
+    static constexpr int SCROLL_MARGIN = 3;
+};
+
+} // namespace lumacs

+ 17 - 4
lumacs_debug.log

@@ -1,5 +1,18 @@
-Render Frame. Term Size: 172x26
-update_layout_sizes: node=0 w=172 h=25
-  Leaf: setting viewport to 170x22
-render_window: 172x25
+Render Frame. Term Size: 88x47
+update_layout_sizes: node=0 w=88 h=46
+  Leaf: setting viewport to 86x43
+render_window: 88x46
+  Mode: Normal
+Input Event: is_char=0 size=1 bytes=[1b ]
+Render Frame. Term Size: 88x47
+update_layout_sizes: node=0 w=88 h=46
+  Leaf: setting viewport to 86x43
+render_window: 88x46
+  Mode: Normal
+Input Event: is_char=0 size=1 bytes=[1b ]
+Resolved Key: Escape
+Render Frame. Term Size: 88x47
+update_layout_sizes: node=0 w=88 h=46
+  Leaf: setting viewport to 86x43
+render_window: 88x46
   Mode: Normal

+ 15 - 3
src/editor_core.cpp

@@ -25,7 +25,20 @@ Buffer& EditorCore::buffer() noexcept {
 }
 
 bool EditorCore::load_file(const std::filesystem::path& path) {
-    auto new_buffer_opt = Buffer::from_file(path);
+    std::filesystem::path abs_path = std::filesystem::absolute(path);
+
+    // Check if already open
+    for (const auto& buf : buffers_) {
+        if (buf->file_path() && std::filesystem::equivalent(*buf->file_path(), abs_path)) {
+            active_window_->set_buffer(buf);
+            emit_event(EditorEvent::BufferModified);
+            emit_event(EditorEvent::CursorMoved);
+            emit_event(EditorEvent::ViewportChanged);
+            return true;
+        }
+    }
+
+    auto new_buffer_opt = Buffer::from_file(abs_path);
     if (!new_buffer_opt) {
         return false;
     }
@@ -233,8 +246,7 @@ void EditorCore::next_window() {
         } else {
             active_window_ = *next;
         }
-        emit_event(EditorEvent::CursorMoved); // Focus change is like cursor move
-        emit_event(EditorEvent::ViewportChanged);
+        emit_event(EditorEvent::WindowFocused); // Emit new event
     }
 }
 

+ 173 - 144
src/main.cpp

@@ -103,7 +103,14 @@ public:
 private:
     enum class Mode {
         Normal,
-        Command // Minibuffer entry
+        Command,   // Minibuffer entry (:)
+        FindFile   // Find file prompt (C-x C-f)
+    };
+
+    enum class Prefix {
+        None,
+        Meta,  // Esc pressed
+        CtrlX  // C-x pressed
     };
 
     std::unique_ptr<EditorCore> core_;
@@ -112,10 +119,10 @@ private:
     bool should_quit_ = false;
     std::string message_line_;  // For displaying messages in the UI
     
-    // Command mode state
+    // Command/Input state
     Mode mode_ = Mode::Normal;
     std::string command_buffer_;
-    bool meta_pending_ = false; // Emacs-style ESC prefix support
+    Prefix prefix_ = Prefix::None;
 
     void handle_editor_event(EditorEvent event) {
         if (event == EditorEvent::Quit) {
@@ -125,11 +132,12 @@ private:
             }
         } else if (event == EditorEvent::Message) {
             message_line_ = core_->last_message();
-            // Trigger re-render? The main loop usually handles it if it came from input.
-            // But if it came from elsewhere, we might need screen_->Post(Event::Custom)
         } else if (event == EditorEvent::CommandMode) {
             mode_ = Mode::Command;
             command_buffer_.clear();
+        } else if (event == EditorEvent::WindowFocused) {
+            // No specific action needed here, just re-render handled by main loop.
+            // But it's good to have a dedicated event for this.
         }
     }
     
@@ -229,8 +237,8 @@ private:
         for(unsigned char c : input_debug) debug_log << std::hex << (int)c << " ";
         debug_log << std::dec << "]" << std::endl;
 
-        // Handle Command Mode Input
-        if (mode_ == Mode::Command) {
+        // Handle Minibuffer Inputs (Command / FindFile)
+        if (mode_ == Mode::Command || mode_ == Mode::FindFile) {
             if (event == Event::Escape) {
                 mode_ = Mode::Normal;
                 command_buffer_.clear();
@@ -238,7 +246,15 @@ private:
                 return true;
             }
             if (event == Event::Return) {
-                execute_command(command_buffer_);
+                if (mode_ == Mode::Command) {
+                    execute_command(command_buffer_);
+                } else if (mode_ == Mode::FindFile) {
+                    if (core_->load_file(command_buffer_)) {
+                        message_line_ = "Loaded: " + command_buffer_;
+                    } else {
+                        message_line_ = "Failed to load: " + command_buffer_;
+                    }
+                }
                 mode_ = Mode::Normal;
                 command_buffer_.clear();
                 return true;
@@ -247,7 +263,6 @@ private:
                 if (!command_buffer_.empty()) {
                     command_buffer_.pop_back();
                 } else {
-                    // Backspace on empty buffer exits command mode
                     mode_ = Mode::Normal;
                 }
                 return true;
@@ -256,202 +271,183 @@ private:
                 command_buffer_ += event.input();
                 return true;
             }
-            // Allow arrows to fall through to default navigation or specific handling if we added history?
-            // For now, just consume
             return true; 
         }
 
-        // Convert FTXUI event to string key for Lua bindings
+        // === Key Resolution ===
         std::string key_name;
         std::string input_str = event.input();
 
-        // 1. Handle Meta Pending state (previous key was ESC)
-        if (meta_pending_) {
-            meta_pending_ = false; // One-shot prefix
-            
-            if (event == Event::Escape) {
-                // ESC ESC -> treat as single Escape (Cancel/Quit)
-                key_name = "Escape";
-            } else if (event.is_character()) {
-                key_name = "M-" + event.input();
-            } else if (event == Event::ArrowUp) key_name = "M-ArrowUp";
-            else if (event == Event::ArrowDown) key_name = "M-ArrowDown";
-            else if (event == Event::ArrowLeft) key_name = "M-ArrowLeft";
-            else if (event == Event::ArrowRight) key_name = "M-ArrowRight";
-            else {
-                // Unknown special key with meta... ignore or try best effort?
-                // Fallback to normal processing effectively ignoring the previous ESC
-            }
-        }
-        
-        // 2. Check for Raw Meta sequences (Alt+Key sending \x1b + char in one event)
-        if (key_name.empty()) {
-            if (input_str.size() >= 2 && input_str[0] == 27 && input_str[1] >= 32 && input_str[1] < 127) {
-                 // Atomic Alt+Char
-                 key_name = "M-" + std::string(1, input_str[1]);
-            } else if (input_str.size() >= 6 && input_str.substr(0,5) == "\x1b[1;3") {
-                 // Atomic Alt+Arrow (simplified check)
-                 if (event == Event::ArrowUp) key_name = "M-ArrowUp";
-                 if (event == Event::ArrowDown) key_name = "M-ArrowDown";
-                 if (event == Event::ArrowRight) key_name = "M-ArrowRight";
-                 if (event == Event::ArrowLeft) key_name = "M-ArrowLeft";
+        // 1. Basic Key Mapping
+        if (event == Event::Escape) key_name = "Escape";
+        else if (event == Event::ArrowUp) key_name = "ArrowUp";
+        else if (event == Event::ArrowDown) key_name = "ArrowDown";
+        else if (event == Event::ArrowLeft) key_name = "ArrowLeft";
+        else if (event == Event::ArrowRight) key_name = "ArrowRight";
+        else if (event == Event::Home) key_name = "Home";
+        else if (event == Event::End) key_name = "End";
+        else if (event == Event::Return) key_name = "Return";
+        else if (event == Event::Tab) key_name = "Tab";
+        else if (event == Event::Backspace) key_name = "Backspace";
+        else if (event == Event::Delete) key_name = "Delete";
+        else {
+            // Check based on input content (handles Chars, Control keys, Meta)
+            if (!input_str.empty()) {
+                int code = static_cast<int>(static_cast<unsigned char>(input_str[0]));
+                if (code > 0 && code < 27 && code != 9 && code != 10 && code != 13) {
+                    char letter = 'a' + (code - 1);
+                    key_name = "C-" + std::string(1, letter);
+                } else if (input_str.size() >= 2 && input_str[0] == 27) {
+                    // Alt+Char (Meta) - often sent as ESC + char
+                    // Avoid parsing arrow keys (ESC [ ...) as Meta
+                    if (input_str[1] != '[') {
+                        key_name = "M-" + std::string(1, input_str[1]);
+                    }
+                } else if (code >= 32 || code < 0) { // Printable (including extended ASCII/UTF-8 starts)
+                    key_name = input_str; 
+                }
             }
         }
 
-        // 3. Handle standard Control/Special keys if not yet resolved
-        if (key_name.empty()) {
-            if (event == Event::Escape) {
-                // Don't act immediately, set pending
-                meta_pending_ = true;
-                message_line_ = "M- (Press key)";
-                return true; 
-            } else if (event == Event::ArrowUp) {
-                key_name = "ArrowUp";
-            } else if (event == Event::ArrowDown) {
-                key_name = "ArrowDown";
-            }
-            else if (event == Event::ArrowLeft) {
-                key_name = "ArrowLeft";
-            } else if (event == Event::ArrowRight) {
-                key_name = "ArrowRight";
-            } else if (event == Event::Home) {
-                key_name = "Home";
-            } else if (event == Event::End) {
-                key_name = "End";
-            } else if (event == Event::Return) {
-                key_name = "Return";
-            } else if (event == Event::Tab) {
-                key_name = "Tab";
-            } else if (event.is_character()) {
-                // Check control chars
-                if (!input_str.empty()) {
-                    int code = static_cast<int>(static_cast<unsigned char>(input_str[0]));
-                    if (code > 0 && code < 27 && code != 9 && code != 10 && code != 13) {
-                        char letter = 'a' + (code - 1);
-                        key_name = "C-" + std::string(1, letter);
-                    } else {
-                        key_name = input_str;
-                    }
-                }
+        // 2. Apply Prefixes
+        if (prefix_ == Prefix::Meta) {
+            prefix_ = Prefix::None;
+            if (key_name.size() == 1) { // Single char
+                 key_name = "M-" + key_name;
+            } else if (key_name == "ArrowUp") key_name = "M-ArrowUp";
+            // ... map other meta combos if needed
+        } 
+        else if (prefix_ == Prefix::CtrlX) {
+            prefix_ = Prefix::None;
+            if (key_name == "C-f") { // Handle C-f specially if needed or let it be C-x C-f
+                // C-x C-f is standard
             }
+            key_name = "C-x " + key_name;
         }
 
-        // Debug: show what key was pressed
+        // Debug: show what key was resolved
         if (!key_name.empty()) {
             message_line_ = "Key: " + key_name;
             debug_log << "Resolved Key: " << key_name << std::endl;
+        } else {
+            return false;
         }
 
-        // Try Lua key binding first
-        if (!key_name.empty() && lua_api_->execute_key_binding(key_name)) {
-            message_line_ = "Executed: " + key_name;
+        // === Prefix Trigger Handling ===
+        // If resolved key is "Escape" or "C-x", set prefix and return
+        if (key_name == "Escape") {
+            prefix_ = Prefix::Meta;
+            message_line_ = "M-";
             return true;
         }
-
-        // Fallback to default bindings
-        
-        // Quit commands (Explicit Q binding if generic quit failed)
-        // Note: Escape is now handled by meta_pending logic. To quit via Escape, press ESC ESC.
-        // Or we can check if generic "Escape" key_name was produced (from double esc).
-        if (key_name == "Escape") {
-             core_->request_quit();
-             return true;
+        if (key_name == "C-x") {
+            prefix_ = Prefix::CtrlX;
+            message_line_ = "C-x-";
+            return true;
         }
 
-        // Ctrl+C to quit
-        if (event.is_character()) {
-            auto input = event.input();
-            if (!input.empty() && input[0] == 3) {  // Ctrl+C
-                core_->request_quit();
-                return true;
-            }
+        // === Execution ===
+
+        // 1. Try Lua key binding
+        if (lua_api_->execute_key_binding(key_name)) {
+            message_line_ = "Executed: " + key_name;
+            return true;
         }
 
-        // Arrow keys for cursor movement
-        if (event == Event::ArrowUp) {
-            core_->move_up();
+        // 2. C++ Fallback Bindings
+        
+        // Global
+        if (key_name == "C-q") {
+            core_->request_quit();
             return true;
         }
-        if (event == Event::ArrowDown) {
-            core_->move_down();
+        if (key_name == "C-s") {
+            core_->buffer().save();
+            message_line_ = "Saved";
             return true;
         }
-        if (event == Event::ArrowLeft) {
-            core_->move_left();
+        if (key_name == "M-x") {
+            mode_ = Mode::Command;
+            message_line_ = ":";
             return true;
         }
-        if (event == Event::ArrowRight) {
-            core_->move_right();
+        if (key_name == ":") { // Vim style
+            mode_ = Mode::Command;
+            message_line_ = ":";
             return true;
         }
 
-        // Home/End
-        if (event == Event::Home) {
-            core_->move_to_line_start();
+        // C-x Prefix Commands
+        if (key_name == "C-x o") {
+            core_->next_window();
             return true;
         }
-        if (event == Event::End) {
-            core_->move_to_line_end();
+        if (key_name == "C-x 2") {
+            core_->split_horizontally();
+            return true;
+        }
+        if (key_name == "C-x 3") {
+            core_->split_vertically();
+            return true;
+        }
+        if (key_name == "C-x 0") {
+            core_->close_active_window();
+            return true;
+        }
+        if (key_name == "C-x C-f") {
+            mode_ = Mode::FindFile;
+            command_buffer_.clear();
+            message_line_ = "Find file: ";
             return true;
         }
 
-        // Backspace
-        if (event == Event::Backspace) {
+        // Editing / Navigation
+        if (key_name == "ArrowUp") { core_->move_up(); return true; }
+        if (key_name == "ArrowDown") { core_->move_down(); return true; }
+        if (key_name == "ArrowLeft") { core_->move_left(); return true; }
+        if (key_name == "ArrowRight") { core_->move_right(); return true; }
+        if (key_name == "Home") { core_->move_to_line_start(); return true; }
+        if (key_name == "End") { core_->move_to_line_end(); return true; }
+        
+        if (key_name == "Backspace") {
             auto cursor = core_->cursor();
             core_->buffer().erase_char(cursor);
-            // Move cursor back if we deleted a character
             if (cursor.column > 0) {
                 core_->set_cursor({cursor.line, cursor.column - 1});
             } else if (cursor.line > 0) {
-                // Moved to end of previous line
                 size_t prev_line_len = core_->buffer().line(cursor.line - 1).size();
                 core_->set_cursor({cursor.line - 1, prev_line_len});
             }
             return true;
         }
-
-        // Delete key (delete character at cursor)
-        if (event == Event::Delete) {
+        if (key_name == "Delete") {
             auto cursor = core_->cursor();
-            // Delete the character after cursor by erasing at cursor+1
             if (cursor.column < core_->buffer().line(cursor.line).size()) {
                 core_->buffer().erase_char({cursor.line, cursor.column + 1});
             } else if (cursor.line < core_->buffer().line_count() - 1) {
-                // At end of line, join with next line
                 core_->buffer().erase_char({cursor.line + 1, 0});
             }
             return true;
         }
-
-        // Return/Enter - insert newline
-        if (event == Event::Return) {
+        if (key_name == "Return") {
             auto cursor = core_->cursor();
             core_->buffer().insert_newline(cursor);
-            // Move cursor to start of next line
             core_->set_cursor({cursor.line + 1, 0});
             return true;
         }
-
-        // Tab - insert tab character or spaces
-        if (event == Event::Tab) {
+        if (key_name == "Tab") {
             auto cursor = core_->cursor();
-            // Insert 4 spaces (or you could insert '\t')
             core_->buffer().insert(cursor, "    ");
             core_->set_cursor({cursor.line, cursor.column + 4});
             return true;
         }
 
-        // Regular printable characters
-        if (event.is_character()) {
-            std::string input = event.input();
-            if (!input.empty() && input[0] >= 32 && input[0] < 127) {
-                // Printable ASCII character
-                auto cursor = core_->cursor();
-                core_->buffer().insert_char(cursor, input[0]);
-                // Move cursor forward
-                core_->set_cursor({cursor.line, cursor.column + 1});
-                return true;
-            }
+        // Insert printable characters
+        if (key_name.size() == 1) {
+            auto cursor = core_->cursor();
+            core_->buffer().insert_char(cursor, key_name[0]);
+            core_->set_cursor({cursor.line, cursor.column + 1});
+            return true;
         }
 
         return false;
@@ -460,23 +456,43 @@ private:
     void execute_command(const std::string& cmd) {
         if (cmd.empty()) return; 
         
-        if (cmd == "q" || cmd == "quit") {
+        // Simple command parsing (first word is command, rest is args)
+        std::istringstream iss(cmd);
+        std::string command;
+        iss >> command;
+        
+        if (command == "q" || command == "quit") {
             core_->request_quit();
             return;
         }
         
-        if (cmd == "w" || cmd == "write") {
+        if (command == "w" || command == "write") {
             core_->buffer().save();
             message_line_ = "Saved";
             return;
         }
         
-        if (cmd == "wq") {
+        if (command == "wq") {
             core_->buffer().save();
             core_->request_quit();
             return;
         }
         
+        if (command == "e" || command == "edit") {
+            std::string path;
+            std::getline(iss >> std::ws, path); // Read rest of line
+            if (!path.empty()) {
+                if (core_->load_file(path)) {
+                    message_line_ = "Loaded: " + path;
+                } else {
+                    message_line_ = "Failed to load: " + path;
+                }
+            } else {
+                message_line_ = "Usage: :e <filename>";
+            }
+            return;
+        }
+        
         // Try executing as Lua
         if (lua_api_->execute(cmd)) {
              message_line_ = "Lua Executed";
@@ -537,10 +553,16 @@ private:
         auto status_elem = text(status) | inverted;
         if (!is_active) status_elem = status_elem | dim;
         
-        return vbox({
+        auto window_content = vbox({
             vbox(lines) | flex,
             status_elem
-        }) | border | size(WIDTH, EQUAL, width) | size(HEIGHT, EQUAL, height);
+        });
+
+        if (is_active) {
+            return window_content | border | color(Color::Cyan) | bold | size(WIDTH, EQUAL, width) | size(HEIGHT, EQUAL, height);
+        } else {
+            return window_content | border | size(WIDTH, EQUAL, width) | size(HEIGHT, EQUAL, height);
+        }
     }
 
     void update_layout_sizes(LayoutNode* node, int w, int h) {
@@ -619,6 +641,13 @@ private:
                  text(command_buffer_), 
                  text(" ") | inverted // Fake cursor
              });
+        } else if (mode_ == Mode::FindFile) {
+             debug_log << "  Mode: FindFile" << std::endl;
+             bottom_bar = hbox({
+                 text("Find file: ") | bold | color(Color::Cyan),
+                 text(command_buffer_), 
+                 text(" ") | inverted // Fake cursor
+             });
         } else {
             debug_log << "  Mode: Normal" << std::endl;
             if (!message_line_.empty()) {