Преглед изворни кода

Start Phase 6: GTK4 Frontend and Single Binary Architecture

Bernardo Magri пре 1 месец
родитељ
комит
5114a45098

+ 0 - 1
.gitignore

@@ -18,7 +18,6 @@ Makefile
 *.dylib
 *.dll
 *.exe
-lumacs
 
 # IDE
 .vscode/

+ 19 - 23
CMakeLists.txt

@@ -27,16 +27,11 @@ include(FetchContent)
 # ncurses for TUI (better control key support)
 find_package(Curses REQUIRED)
 
-# Optional: Keep FTXUI for comparison (commented out for now)
-# FetchContent_Declare(
-#     ftxui
-#     GIT_REPOSITORY https://github.com/ArthurSonzogni/FTXUI.git
-#     GIT_TAG v5.0.0
-# )
-# set(FTXUI_BUILD_DOCS OFF CACHE BOOL "" FORCE)
-# set(FTXUI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
-# set(FTXUI_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
-# FetchContent_MakeAvailable(ftxui)
+# GTK4 / gtkmm for GUI
+find_package(PkgConfig QUIET)
+if(PKG_CONFIG_FOUND)
+    pkg_check_modules(GTKMM gtkmm-4.0)
+endif()
 
 # Lua and sol2
 find_package(Lua 5.4 REQUIRED)
@@ -72,9 +67,11 @@ target_link_libraries(lumacs_core PUBLIC
     ${LUA_LIBRARIES}
 )
 
-# TUI executable (ncurses-based) 
+# Main Executable (Single binary)
 add_executable(lumacs
-    src/main_ncurses.cpp
+    src/main.cpp
+    src/tui_editor.cpp
+    src/gtk_editor.cpp
 )
 
 target_link_libraries(lumacs PRIVATE
@@ -86,17 +83,16 @@ target_include_directories(lumacs PRIVATE
     ${CURSES_INCLUDE_DIR}
 )
 
-# Keep old FTXUI version for comparison (temporarily disabled)
-# add_executable(lumacs_ftxui
-#     src/main.cpp
-# )
-# target_link_libraries(lumacs_ftxui PRIVATE
-#     lumacs_core
-#     ftxui::screen
-#     ftxui::dom
-#     ftxui::component
-# )
+# Configure GTK if available
+if(GTKMM_FOUND)
+    message(STATUS "GTKMM 4.0 found - Building with GUI support")
+    target_compile_definitions(lumacs PRIVATE LUMACS_WITH_GTK)
+    target_include_directories(lumacs PRIVATE ${GTKMM_INCLUDE_DIRS})
+    target_link_libraries(lumacs PRIVATE ${GTKMM_LIBRARIES})
+else()
+    message(WARNING "GTKMM 4.0 not found - Building TUI only")
+endif()
 
 # Enable testing
 enable_testing()
-add_subdirectory(tests EXCLUDE_FROM_ALL)
+add_subdirectory(tests EXCLUDE_FROM_ALL)

+ 36 - 137
CONTINUATION_PROMPT.md

@@ -8,125 +8,34 @@ I'm working on **Lumacs**, an Emacs-inspired text editor written in C++20 with n
 
 ## Current Status
 
-**Phase 4: Polish & Advanced Features - COMPLETE ✅**
-**Phase 5: Minibuffer & Face System - IN PROGRESS**
-- Emacs compatibility: **~97/100** (Minibuffer & Face System foundations laid)
+**Phase 5: Face System Complete - READY FOR GUI**
+- Emacs compatibility: **~98/100**
 - Build: Successful, binary at `./build/lumacs`
-- Registers, Minibuffer History, Keyboard Macros, Rectangles implemented.
-- Minibuffer command input with completion implemented.
-- Initial abstract Face system for styling implemented.
-
-### What's Been Implemented
-
-#### 1. Phase 5 Features (NEW!)
-
-*   **Minibuffer Command System**:
-    *   Implemented `M-x` (or `:`) for minibuffer command input.
-    *   Created a Lua-based command registry (`define_command`, `execute_extended_command`, `get_command_names`).
-    *   Populated with core commands (`save-buffer`, `find-file`, `next-line`, etc.).
-    *   Added tab completion for commands in the minibuffer.
-    *   Exposed `FindFileMode` to Lua API.
-    *   Removed hardcoded `:` key binding for command mode.
-
-*   **Window Separators**:
-    *   Removed dashed horizontal window borders.
-    *   Added vertical `|` separator for vertical splits.
-
-*   **Line Number Display**:
-    *   Removed `|` separator after line numbers.
-
-*   **Face System (Initial Implementation)**:
-    *   Introduced abstract `Face` and `FaceAttributes` for styling (font, color, weight, slant, etc.).
-    *   Refactored `Theme` to manage faces instead of direct colors.
-    *   Updated `TextAttribute` in `Buffer` to store `face_name`.
-    *   Migrated `NcursesEditor` rendering to use the new face system.
-
-#### 2. Phase 4 Features
-- **C-x r s/i** - Registers: Save/restore text from named registers
-- **M-p/M-n** - Minibuffer History: Navigate command/search history  
-- **F3/F4** - Keyboard Macros: Record and replay key sequences
-- **C-x r k/y/t** - Rectangles: Column-based cut/paste/fill operations
-
-#### 3. Phase 3 Features
-- **C-s/C-r** - Incremental Search with highlighting
-- **M-d/M-DEL** - Kill word / Backward kill word
-- **M-u/M-l/M-c** - Word case conversion
-- **C-x C-u/C-l** - Region case conversion
-- **M-;** - Comment/Uncomment (DWIM)
-
-#### 4. Buffer Management (Phase 2)
-- **C-x b** - Switch buffer
-- **C-x k** - Kill buffer
-- **C-x C-b** - List buffers
-
-#### 5. Core (Phase 1)
-- Kill Ring, Mark/Region
-- Undo/Redo
-- Lua configuration
-
-## Project Architecture
-
-### C++ Core
-- **EditorCore:** Registers, macros, rectangles, complete feature set.
-- **Buffer:** Advanced text manipulation and region support. Now uses abstract face names for styling.
-- **NcursesEditor:** Full minibuffer with history, macro recording. Now renders using the abstract face system.
-- **Theme/Face System:** Dedicated classes (`Color`, `FaceAttributes`, `Face`) for modular and extensible styling.
-
-### Lua Configuration
-- **init.lua:** Complete keybinding suite for all Phase 4 features. Now also defines editor commands.
-
-## What Has Been Accomplished
-
-### Phase 4: Polish & Advanced Features - COMPLETE ✅
-**Goal: Power user features**
-**Target:** 95% Emacs compatibility - ACHIEVED!
-
-#### Implemented Features:
-
-1. **Registers:** ✅ COMPLETE
-   - **C-x r s <char>** (copy-to-register) - Save region to register
-   - **C-x r i <char>** (insert-register) - Paste from register
-   - Full register system with a-z, A-Z, 0-9 support.
-
-2. **Rectangles:** ✅ COMPLETE
-   - **C-x r k** (kill-rectangle) - Cut rectangular regions
-   - **C-x r y** (yank-rectangle) - Paste rectangular regions
-   - **C-x r t** (string-rectangle) - Fill rectangle with text
-   - Complete column-based operations with proper alignment.
-
-3. **Keyboard Macros:** ✅ COMPLETE
-   - **F3** (start-kbd-macro) - Begin macro recording
-   - **F4** (end-kbd-macro / call-last-kbd-macro) - End recording or playback
-   - Full macro recording and playback system.
-
-4. **Minibuffer History:** ✅ COMPLETE
-   - **M-p / M-n** in minibuffer to cycle previous commands/search queries
-   - Separate history for each mode with 100-item limit.
-
-## Next Steps (Phase 5 & Roadmap)
-
-### Phase 5: Face System Completion (Immediate Priority)
-*   **Lua API for Faces**: Expose `define_face` to Lua for user-defined faces.
-*   **Face Inheritance**: Implement face inheritance (e.g., `error` face inherits from `default`).
-*   **Semantic Face Definitions**: Define more semantic faces (e.g., `font-lock-variable-name-face`, `font-lock-builtin-face`, `region`, etc.) in themes.
-*   **Buffer Styling via Faces**: Ensure all buffer styling is done using face names consistently (e.g. major modes).
-
-### Future Roadmap / TODOs
-*   **Performance**:
-    *   Optimize redisplay algorithm (only redraw changed lines).
-    *   Implement gap buffer or piece table for better large file handling.
-*   **UI/UX**:
-    *   **GUI Frontend**: Implement a GTK or SDL frontend using the abstract Face system.
-    *   **Mouse Support**: Add mouse click positioning and scrolling.
-    *   **Menu Bar**: Add a terminal-compatible menu bar (like `emacs -nw`).
-*   **Features**:
-    *   **Auto-Complete Popup**: Implement a popup menu for auto-completion (not just minibuffer).
-    *   **Project Management**: `projectile`-like features (find file in project).
-    *   **LSP Support**: Integrate a Language Server Protocol client for intelligent code intelligence.
-    *   **Tree-sitter**: Replace regex-based highlighting with Tree-sitter for robust parsing.
-*   **Documentation**:
-    *   Add Lua API auto-generated docs.
-    *   Write specific tutorials for writing Major Modes.
+- **Face System**: Fully implemented with Inheritance and Lua API (`define_face`).
+- **Architecture**: UI decoupled via `IEditorView`.
+
+### Recent Accomplishments
+
+#### 1. Face System Complete ✅
+*   **Lua API**: Exposed `define_face` and `FaceAttributes` (with inheritance) to Lua.
+*   **Inheritance**: Implemented recursive face attribute resolution in `Theme`.
+*   **Semantic Mapping**: `TextAttribute` legacy enums map to semantic faces (e.g., `ColorType::Keyword` -> `font-lock-keyword-face`).
+
+#### 2. GUI Readiness
+*   `IEditorView` interface established.
+*   `TuiEditor` refactored to implement `IEditorView`.
+
+## Next Steps (Phase 6: GUI Frontend)
+
+### Phase 6: GTK4 Frontend (New Priority)
+*   **Build System**: Add GTK4 dependencies to `CMakeLists.txt`.
+*   **Entry Point**: Create `src/main_gtk.cpp`.
+*   **Implementation**: Create `GtkEditor` class implementing `IEditorView`.
+*   **Rendering**: Implement text rendering using Pango/Cairo and the Face system.
+
+### Future Roadmap
+*   **Performance**: Redisplay optimization.
+*   **LSP**: Language Server Protocol integration.
 
 ## Testing Instructions
 
@@ -135,26 +44,16 @@ I'm working on **Lumacs**, an Emacs-inspired text editor written in C++20 with n
 cmake --build build
 ```
 
-### Test Phase 5 Features:
-- **Minibuffer**: Run `./build/lumacs`, press `M-x` (Alt-x), type `list-` then TAB for completion, then Enter. Try `find-file` and load a file.
-- **Window Separator**: Press `M-x split-window-right`.
-- **Face System**: Observe consistent styling as before; changes are architectural.
-
-### Implementation Details
-
-1. "How are registers implemented?" → Check `src/editor_core.cpp` register functions
-2. "Where is minibuffer history?" → `src/main_ncurses.cpp` history management  
-3. "How do macros work?" → `src/editor_core.cpp` macro recording system
-4. "Where are rectangles?" → `src/editor_core.cpp` rectangle operations
-5. "Where is the new Face system?" -> `include/lumacs/face.hpp`, `include/lumacs/theme.hpp`, `src/theme.cpp`, `include/lumacs/buffer.hpp`, `src/main_ncurses.cpp`.
-
-## Phase 4 Success Criteria - ALL ACHIEVED ✅
+### Test Face API:
+```bash
+# Verify Face API and Inheritance
+./build/lumacs scripts/test_face_api.lua
+```
 
-✅ Save/Restore text from registers (C-x r s/i)
-✅ Record and replay macros (F3/F4) 
-✅ Minibuffer has history (M-p/M-n)
-✅ Rectangle operations work (C-x r k/y/t)
+### Verification:
+*   **Lua API**: `define_face` works as expected.
+*   **Faces**: UI elements are styled correctly using the Face system.
 
 ---
 
-**Phase 4 Complete! Lumacs now provides 95% Emacs compatibility with all major power-user features implemented.**
+**Phase 5 Complete! Face System & Architecture Ready for GTK4.**

+ 2 - 1
README.md

@@ -80,13 +80,14 @@ lumacs/
 
 ## Development
 
-### Current Status: **Phase 4 Complete - 95% Emacs Compatibility Achieved**
+### Current Status: **Phase 5 In Progress - Face System & Minibuffer**
 
 The project uses modern C++ practices:
 - RAII for resource management  
 - Smart pointers for ownership
 - `std::optional` for fallible operations
 - Modern C++20 features
+- Decoupled UI architecture (`IEditorView`)
 
 ### Testing
 ```bash

+ 5 - 16
documentation/GUI_ROADMAP.md

@@ -14,23 +14,12 @@ This document outlines the phased approach to integrate a graphical frontend int
 
 ## Phase 1: Abstract UI Interface Extraction
 
+*   **Status**: Complete
 *   **Goal**: Define a clean separation between `EditorCore` and any UI frontend.
-*   **Tasks**:
-    1.  **Define `IEditorView` Interface**: Create an abstract base class (e.g., `IEditorView` or `EditorFrontend`) in `include/lumacs/ui_interface.hpp` that `EditorCore` will interact with. This interface will declare methods for:
-        *   Receiving buffer content updates.
-        *   Receiving cursor position changes.
-        *   Receiving messages.
-        *   Requesting input (e.g., for minibuffer).
-        *   Triggering UI mode changes (Command, FindFile, etc.).
-        *   Rendering updates.
-    2.  **Refactor `NcursesEditor`**:
-        *   Rename `NcursesEditor` to `TuiEditor`.
-        *   Make `TuiEditor` implement `IEditorView`.
-        *   Modify `EditorCore` to hold a pointer/reference to `IEditorView` instead of tightly coupled `NcursesEditor` logic.
-        *   Update `main.cpp` (or `main_ncurses.cpp`) to instantiate `TuiEditor` and pass it to `EditorCore`.
-    3.  **Refine `EditorEvent` Usage**: Ensure `EditorCore` communicates solely via `IEditorView` or its existing event system (`EditorEvent`).
-*   **Output**: A decoupled `EditorCore` that can interact with any `IEditorView` implementation.
-*   **Dependencies**: Existing C++ codebase.
+*   **Achievements**:
+    *   `IEditorView` interface defined in `include/lumacs/ui_interface.hpp`.
+    *   `NcursesEditor` refactored to `TuiEditor` implementing `IEditorView`.
+    *   `EditorCore` decoupled from specific UI implementation.
 
 ## Phase 2: GTK4 Environment Setup & Basic Window
 

+ 24 - 47
documentation/STATUS.md

@@ -1,55 +1,32 @@
-# Lumacs Status - Phase 3 Complete! ✅
+# Lumacs Status
 
-## What Was Done Today
+## Current Status
+**Phase 6: GTK4 Frontend - READY TO START**
 
-### Phase 3: Enhanced Editing - FULLY IMPLEMENTED
+*   **Core Editor**: Stable.
+*   **Face System**: Complete (C++ & Lua API).
+*   **UI Architecture**: Decoupled (`IEditorView`).
 
-**Emacs Compatibility: 70% → 85%** 🎉
+## Completed Phases
 
-#### 1. Word Operations ✅
-- **M-d** (kill-word)
-- **M-Backspace** (backward-kill-word)
-- Fixed `M-f` to stop at end of word (Emacs style)
+*   **Phase 5: Face System** ✅
+    *   Full abstract styling system.
+    *   Lua API `define_face` with inheritance.
+    *   Recursive attribute resolution.
+*   **Phase 4: Polish & Advanced Features** ✅
+    *   Registers, Rectangles, Macros, Minibuffer History.
+*   **Phase 3: Enhanced Editing** ✅
+*   **Phase 2: Buffer Management** ✅
+*   **Phase 1: Core Emacs Feel** ✅
 
-#### 2. Case Conversion ✅
-- **M-u, M-l, M-c** (word case)
-- **C-x C-u, C-x C-l** (region case)
+## Immediate Goals (Phase 6)
 
-#### 3. Commenting ✅
-- **M-;** (comment-dwim) - Smart comment toggle
+1.  **GTK4 Integration**:
+    *   Update CMake for GTK4/gtkmm.
+    *   Implement `GtkEditor` class.
+    *   Render text with Pango.
 
-#### 4. Incremental Search (ISearch) ✅
-- **C-s** (forward), **C-r** (backward)
-- Real-time highlighting
-- Navigation between matches
+## Roadmap Overview
 
-### Phase 2: Buffer Management - COMPLETE
-- Buffer switching (C-x b), List (C-x C-b), Kill (C-x k)
-
-### Phase 1: Core Emacs Feel - COMPLETE
-- Kill Ring, Mark/Region, Navigation
-
-## Test It Now
-
-```bash
-./build/lumacs
-# Try C-s to search, M-d to kill words, M-u to uppercase
-```
-
-See `PHASE3_TEST.md` for detailed test steps.
-
-## Build Status
-
-✅ Build successful
-✅ Tests passed
-✅ No known regressions
-
-## Next: Phase 4
-
-Polish & Advanced Features:
-- Registers (C-x r s)
-- Rectangles (C-x r k)
-- Keyboard Macros (F3/F4)
-- Minibuffer History
-
-Target: 95% Emacs compatibility
+*   **Phase 6**: GTK4 Frontend (Current)
+*   **Phase 7**: Performance & Optimization

+ 0 - 0
documentation/PHASE1_COMPLETE.md → documentation/history/PHASE1_COMPLETE.md


+ 97 - 0
documentation/history/PHASE2_COMPLETE.md

@@ -0,0 +1,97 @@
+# Phase 2: Buffer Management - COMPLETE! ✅
+
+## Overview
+
+Phase 2 implementation is complete! Lumacs now supports full buffer management, allowing you to work with multiple files effectively. The editor has jumped from **~55% to ~70% Emacs compatibility**.
+
+## What Was Implemented
+
+### 1. Buffer Switching (C-x b) ⭐⭐⭐
+
+**Efficiently switch between open buffers.**
+
+- **Command:** `C-x b` (switch-to-buffer)
+- **Features:**
+  - Minibuffer prompt
+  - **Tab Completion:** Cycle through available buffer names
+  - Auto-activates major mode for the switched buffer
+  - Displays progress (e.g., `[1/3]`) when cycling
+
+### 2. Buffer List (C-x C-b) ⭐⭐⭐
+
+**View and manage all open buffers.**
+
+- **Command:** `C-x C-b` (list-buffers)
+- **Features:**
+  - Opens a read-only `*Buffer List*` buffer
+  - Shows: Modification status (`*`), Buffer Name, Size (lines), and File Path
+  - Formatted in columns for readability
+  - Refreshes content on every invocation
+
+### 3. Safe Buffer Killing (C-x k) ⭐⭐⭐
+
+**Close buffers safely.**
+
+- **Command:** `C-x k` (kill-buffer)
+- **Features:**
+  - Minibuffer prompt with tab completion
+  - **Safety Check:** Warns if the buffer is modified (`Buffer modified! Kill anyway? (y/n)`)
+  - Prevents closing the last buffer (keeps at least one open)
+  - Automatically switches windows displaying the killed buffer to another buffer
+
+## Architecture Changes
+
+### C++ Core (`src/`, `include/lumacs/`)
+- **Buffer Class:** Added `clear()` method to efficiently empty a buffer (exposed to Lua).
+- **EditorCore:** 
+  - Added `get_buffer_names()`, `get_buffer_by_name()`, `switch_buffer_in_window()`, `close_buffer()`, `get_all_buffer_info()`.
+  - Implemented logic to handle buffer closing safety.
+- **NcursesEditor (`src/main_ncurses.cpp`):**
+  - Added input modes: `BufferSwitch`, `KillBuffer`, `ConfirmKill`.
+  - Implemented generic tab completion logic for minibuffer.
+  - Added visual feedback for modes in status line.
+
+### Lua Configuration (`init.lua`)
+- Bound `C-x b`, `C-x k`, `C-x C-b` to new functionality.
+- Implemented `C-x C-b` formatting logic in Lua using `get_all_buffer_info` and `Buffer:clear()`.
+
+## Testing
+
+### Manual Verification:
+1. **Switch Buffer:** Open multiple files (`./build/lumacs file1 file2`). Use `C-x b` to switch. Press Tab to cycle.
+2. **List Buffers:** Press `C-x C-b`. Check the `*Buffer List*` content. Modify a buffer and check for `*` marker.
+3. **Kill Buffer:**
+   - Kill an unmodified buffer (`C-x k` -> Name -> Enter). It should close immediately.
+   - Modify a buffer. Try to kill it. Check for warning prompt `(y/n)`.
+   - Press `n`: Cancelled.
+   - Press `y`: Buffer closed.
+
+### Unit Tests:
+- Added `Buffer_Clear` test to `tests/test_buffer.cpp` to verify `Buffer::clear()` functionality.
+
+## Keybinding Summary
+
+| Key | Command | Description |
+|-----|---------|-------------|
+| `C-x b` | switch-to-buffer | Switch to another buffer (Tab completion) |
+| `C-x C-b` | list-buffers | List all open buffers |
+| `C-x k` | kill-buffer | Close current or specified buffer |
+
+## Emacs Compatibility Progress
+
+**Before Phase 2:** ~55/100
+**After Phase 2:** ~70/100 ✅
+
+We now have:
+✅ Working Minibuffer with completion
+✅ Multiple buffer management
+✅ Safety mechanisms (modified check)
+✅ Buffer list visualization
+
+## What's Next: Phase 3
+
+Phase 3 focuses on **Enhanced Editing**:
+1. **Word operations** (M-d, M-DEL, M-t)
+2. **Case conversion** (M-u, M-l, M-c)
+3. **Comment/Uncomment** (M-;)
+4. **Incremental Search** (C-s, C-r)

+ 58 - 0
documentation/history/PHASE3_COMPLETE.md

@@ -0,0 +1,58 @@
+# Phase 3: Enhanced Editing - COMPLETE! ✅
+
+## Overview
+
+Phase 3 implementation is complete! Lumacs now features advanced text manipulation and incremental search, bringing it much closer to a full-featured Emacs experience. Emacs compatibility is now estimated at **85%**.
+
+## What Was Implemented
+
+### 1. Word Operations ⭐⭐⭐
+- **M-d** (kill-word): Kills forward to the end of the next word.
+- **M-Backspace** (backward-kill-word): Kills backward to the start of the previous word.
+- **Refinement:** Adjusted `forward-word` logic to strictly match Emacs behavior (stop at end of word, not start of next).
+
+### 2. Case Conversion ⭐⭐⭐
+- **M-u** (upcase-word): Converts following word to UPPERCASE.
+- **M-l** (downcase-word): Converts following word to lowercase.
+- **M-c** (capitalize-word): Capitalizes the following word (Title case).
+- **C-x C-u** (upcase-region): Uppercases selected region.
+- **C-x C-l** (downcase-region): Lowercases selected region.
+
+### 3. Commenting ⭐⭐
+- **M-;** (comment-dwim): Smart toggle for comments.
+  - If region active: Comments/Uncomments all lines in region.
+  - If no region: Toggles comment on current line.
+  - Respects major mode syntax (defaults to `--` for now, extensible).
+
+### 4. Incremental Search (ISearch) ⭐⭐⭐⭐
+**Major new feature!**
+- **C-s** (isearch-forward): Starts forward incremental search.
+- **C-r** (isearch-backward): Starts backward incremental search.
+- **Real-time Highlighting:** Matches are highlighted in green as you type.
+- **Navigation:** Press `C-s`/`C-r` to jump to next/prev occurrences.
+- **Exit:** `RET` to keep cursor at match, `C-g` (or ESC) to cancel and return.
+
+## Architecture Changes
+
+### C++ Core
+- **EditorCore:** Added `kill_word`, `backward_kill_word`, `enter_isearch_mode`.
+- **Buffer:** Added `find_backward` for reverse search support.
+- **NcursesEditor:** Added `Mode::ISearch`, input handling loop for search, and rendering logic for search highlights.
+
+### Lua Configuration
+- implemented case conversion and commenting logic directly in Lua to demonstrate API flexibility.
+- Bound all new keys in `init.lua`.
+
+## Testing
+- Unit tests added for `kill_word` operations.
+- Manual test guide created: `PHASE3_TEST.md`.
+
+## Emacs Compatibility Progress
+**Before Phase 3:** ~70/100
+**After Phase 3:** ~85/100 ✅
+
+## Next: Phase 4 (Polish)
+- Registers
+- Rectangles
+- Macros
+- Better Minibuffer history

+ 91 - 0
documentation/history/PHASE3_TEST.md

@@ -0,0 +1,91 @@
+# Phase 3 Testing Guide: Enhanced Editing
+
+This document outlines how to test the new Phase 3 features.
+
+## Prerequisites
+Build the project:
+```bash
+cd /Users/user/Projects/lumacs
+cmake --build build
+```
+
+Start editor with a test file:
+```bash
+./build/lumacs test_highlight.lua
+```
+
+## 1. Word Operations
+
+### Kill Word (M-d)
+1. Place cursor at the beginning of a word (e.g., "local").
+2. Press `M-d` (Meta+d or ESC then d).
+3. **Expected:** The word "local" is deleted.
+4. Move cursor elsewhere and press `C-y`.
+5. **Expected:** "local" is pasted (yanked).
+
+### Backward Kill Word (M-Backspace)
+1. Place cursor at the end of a word.
+2. Press `M-Backspace` (Meta+Backspace or ESC then Backspace).
+3. **Expected:** The word before cursor is deleted.
+4. Press `C-y`.
+5. **Expected:** Deleted word is pasted.
+
+## 2. Case Conversion
+
+### Word Case (M-u, M-l, M-c)
+1. Type "hello world".
+2. Cursor at start of "hello".
+3. Press `M-u` (upcase-word).
+   - **Expected:** "HELLO world", cursor after "HELLO".
+4. Press `M-l` (downcase-word).
+   - **Expected:** "hello world", cursor after "world" (since it moved forward).
+5. Move back to start. Press `M-c` (capitalize-word).
+   - **Expected:** "Hello world".
+
+### Region Case (C-x C-u, C-x C-l)
+1. Select "Hello World" (using `C-@` and movement).
+2. Press `C-x C-u`.
+   - **Expected:** "HELLO WORLD".
+3. Select again. Press `C-x C-l`.
+   - **Expected:** "hello world".
+
+## 3. Commenting (M-;)
+
+1. Open a lua file (e.g., `test_highlight.lua`).
+2. Place cursor on a line of code.
+3. Press `M-;`.
+   - **Expected:** Line is commented (`-- code`).
+4. Press `M-;` again.
+   - **Expected:** Line is uncommented.
+5. Select multiple lines.
+6. Press `M-;`.
+   - **Expected:** All lines commented.
+7. Press `M-;` again.
+   - **Expected:** All lines uncommented.
+
+## 4. Incremental Search (C-s, C-r)
+
+### Forward Search (C-s)
+1. Press `C-s`. Status line should show `[I-SEARCH]`.
+2. Type "fun".
+   - **Expected:** Cursor moves to first "fun", match highlighted in green.
+   - Minibuffer shows "I-search: fun".
+3. Press `C-s` again.
+   - **Expected:** Moves to next occurrence.
+4. Press `Backspace`.
+   - **Expected:** Goes back to "fu" search result.
+5. Press `RET`.
+   - **Expected:** Search ends, cursor stays at match.
+
+### Backward Search (C-r)
+1. Go to end of buffer (`M->`).
+2. Press `C-r`. Status shows `[I-SEARCH]`.
+3. Type "local".
+   - **Expected:** Finds last "local" in file.
+4. Press `C-r` again.
+   - **Expected:** Finds previous occurrence.
+5. Press `C-g` (or ESC).
+   - **Expected:** Cancel search, cursor returns to start position (end of buffer).
+
+## Debugging
+Check `lumacs_debug.log` for details.

+ 1 - 1
include/lumacs/editor_core.hpp

@@ -39,7 +39,7 @@ struct LayoutNode;
 class EditorCore {
 public:
     EditorCore();
-    ~EditorCore() = default;
+    ~EditorCore();
 
     // Disable copy, allow move
     EditorCore(const EditorCore&) = delete;

+ 16 - 15
include/lumacs/face.hpp

@@ -33,22 +33,23 @@ struct FaceAttributes {
     
     std::optional<Color> foreground;
     std::optional<Color> background;
-    std::optional<bool> underline;
-    std::optional<bool> inverse;
+        std::optional<bool> underline;
+        std::optional<bool> inverse;
+        std::optional<std::string> inherit; // Name of face to inherit from
     
-    // Merging logic: apply other on top of this
-    void merge(const FaceAttributes& other) {
-        if (other.family) family = other.family;
-        if (other.height) height = other.height;
-        if (other.weight) weight = other.weight;
-        if (other.slant) slant = other.slant;
-        if (other.foreground) foreground = other.foreground;
-        if (other.background) background = other.background;
-        if (other.underline) underline = other.underline;
-        if (other.inverse) inverse = other.inverse;
-    }
-};
-
+        // Merging logic: apply other on top of this
+        void merge(const FaceAttributes& other) {
+            if (other.family) family = other.family;
+            if (other.height) height = other.height;
+            if (other.weight) weight = other.weight;
+            if (other.slant) slant = other.slant;
+            if (other.foreground) foreground = other.foreground;
+            if (other.background) background = other.background;
+            if (other.underline) underline = other.underline;
+            if (other.inverse) inverse = other.inverse;
+            if (other.inherit) inherit = other.inherit;
+        }
+    };
 class Face {
 public:
     Face(std::string name) : name_(std::move(name)) {}

+ 11 - 0
include/lumacs/gtk_editor.hpp

@@ -0,0 +1,11 @@
+#pragma once
+
+#include "lumacs/ui_interface.hpp"
+#include <memory>
+
+namespace lumacs {
+
+// Factory function to create a GTK editor instance
+std::unique_ptr<IEditorView> create_gtk_editor();
+
+} // namespace lumacs

+ 3 - 0
include/lumacs/theme.hpp

@@ -87,6 +87,9 @@ private:
     
     mutable std::map<std::string, int> face_pairs_; // Cache for ncurses pairs
     mutable int next_pair_id_ = 1;
+
+    // Helper for inheritance resolution
+    std::optional<FaceAttributes> get_face_recursive(const std::string& name, int depth) const;
 };
 
 /// Theme manager - handles multiple themes and switching between them

+ 11 - 0
include/lumacs/tui_editor.hpp

@@ -0,0 +1,11 @@
+#pragma once
+
+#include "lumacs/ui_interface.hpp"
+#include <memory>
+
+namespace lumacs {
+
+// Factory function to create a TUI editor instance
+std::unique_ptr<IEditorView> create_tui_editor();
+
+} // namespace lumacs

+ 2 - 0
src/editor_core.cpp

@@ -43,6 +43,8 @@ EditorCore::EditorCore() :
     lua_api_->load_init_file();
 }
 
+EditorCore::~EditorCore() = default;
+
 // === Buffer Management ===
 
 const Buffer& EditorCore::buffer() const noexcept {

+ 131 - 0
src/gtk_editor.cpp

@@ -0,0 +1,131 @@
+#include "lumacs/gtk_editor.hpp"
+#include "lumacs/editor_core.hpp"
+#include "lumacs/lua_api.hpp"
+#include <iostream>
+
+// Check if GTK is enabled in build
+#ifdef LUMACS_WITH_GTK
+#include <gtkmm.h>
+#include <pangomm.h>
+
+namespace lumacs {
+
+class GtkEditor : public IEditorView {
+public:
+    GtkEditor() : core_(nullptr) {}
+    ~GtkEditor() override = default;
+
+    void init() override {
+        // Initialize GTK application
+        app_ = Gtk::Application::create("org.lumacs.editor");
+    }
+
+    void run() override {
+        // Create main window
+        auto window = std::make_shared<Gtk::Window>();
+        window->set_title("Lumacs - GTK4");
+        window->set_default_size(1024, 768);
+        
+        // Create drawing area for text rendering
+        drawing_area_ = Gtk::make_managed<Gtk::DrawingArea>();
+        drawing_area_->set_draw_func(sigc::mem_fun(*this, &GtkEditor::on_draw));
+        
+        // Add to window
+        window->set_child(*drawing_area_);
+
+        // Input handling
+        auto controller = Gtk::EventControllerKey::create();
+        controller->signal_key_pressed().connect(sigc::mem_fun(*this, &GtkEditor::on_key_pressed), false);
+        window->add_controller(controller);
+
+        // Store reference to keep window alive
+        window_ = window;
+
+        // Connect tick callback for animations/cursor blinking (optional, mostly for redraw requests)
+        // For now, we rely on event-based redraws
+
+        // Run the application
+        app_->run(*window);
+    }
+
+    void handle_editor_event(EditorEvent event) override {
+        // Request redraw on most events
+        if (drawing_area_) {
+            drawing_area_->queue_draw();
+        }
+        
+        if (event == EditorEvent::Quit) {
+            if (window_) window_->close();
+        }
+    }
+
+    void set_core(EditorCore* core) override {
+        core_ = core;
+    }
+
+private:
+    EditorCore* core_;
+    Glib::RefPtr<Gtk::Application> app_;
+    std::shared_ptr<Gtk::Window> window_;
+    Gtk::DrawingArea* drawing_area_ = nullptr;
+
+    // Rendering
+    void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
+        // Fill background
+        auto theme = core_->active_theme();
+        Color bg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
+        
+        cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
+        cr->paint();
+
+        // Create Pango layout
+        auto layout = Pango::Layout::create(drawing_area_->get_pango_context());
+        
+        // Font configuration
+        Pango::FontDescription font_desc("Monospace 12");
+        layout->set_font_description(font_desc);
+
+        // Render visible lines
+        // TODO: Calculate visible range based on scroll and font height
+        // For now, just render top lines
+        
+        const auto& buffer = core_->buffer();
+        double y = 0;
+        int line_height = 20; // Estimate, should get from metrics
+        
+        for (size_t i = 0; i < buffer.line_count() && y < height; ++i) {
+            layout->set_text(buffer.line(i));
+            
+            // TODO: Apply Pango Attributes based on buffer styles (Faces)
+            
+            cr->move_to(0, y);
+            layout->show_in_cairo_context(cr);
+            y += line_height;
+        }
+    }
+
+    // Input
+    bool on_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state) {
+        // TODO: Map GTK key events to Lumacs key names
+        // std::string key_name = map_key(keyval, state);
+        // core_->lua_api()->process_key(key_name);
+        return true;
+    }
+};
+
+std::unique_ptr<IEditorView> create_gtk_editor() {
+    return std::make_unique<GtkEditor>();
+}
+
+} // namespace lumacs
+
+#else // LUMACS_WITH_GTK not defined
+
+namespace lumacs {
+std::unique_ptr<IEditorView> create_gtk_editor() {
+    std::cerr << "Error: Lumacs was built without GTK support." << std::endl;
+    return nullptr;
+}
+} // namespace lumacs
+
+#endif

+ 43 - 0
src/lua_api.cpp

@@ -142,6 +142,9 @@ void LuaApi::setup_api() {
     lumacs_table["Style"] = lua_["Style"];
     lumacs_table["BufferEvent"] = lua_["BufferEvent"];
     lumacs_table["ThemeElement"] = lua_["ThemeElement"];
+    lumacs_table["FontWeight"] = lua_["FontWeight"];
+    lumacs_table["FontSlant"] = lua_["FontSlant"];
+    lumacs_table["FaceAttributes"] = lua_["FaceAttributes"];
 
     lua_["lumacs"] = lumacs_table;
 }
@@ -161,6 +164,38 @@ void LuaApi::register_types() {
         "end", &Range::end
     );
 
+    // Font weight
+    lua_.new_enum<FontWeight>("FontWeight",
+        {
+            {"Normal", FontWeight::Normal},
+            {"Bold", FontWeight::Bold},
+            {"Light", FontWeight::Light}
+        }
+    );
+
+    // Font slant
+    lua_.new_enum<FontSlant>("FontSlant",
+        {
+            {"Normal", FontSlant::Normal},
+            {"Italic", FontSlant::Italic},
+            {"Oblique", FontSlant::Oblique}
+        }
+    );
+
+    // FaceAttributes type
+    lua_.new_usertype<FaceAttributes>("FaceAttributes",
+        sol::constructors<FaceAttributes()>(),
+        "family", &FaceAttributes::family,
+        "height", &FaceAttributes::height,
+        "weight", &FaceAttributes::weight,
+        "slant", &FaceAttributes::slant,
+        "foreground", &FaceAttributes::foreground,
+        "background", &FaceAttributes::background,
+        "underline", &FaceAttributes::underline,
+        "inverse", &FaceAttributes::inverse,
+        "inherit", &FaceAttributes::inherit
+    );
+
     // TextAttribute type
     lua_.new_usertype<TextAttribute>("TextAttribute",
         sol::constructors<TextAttribute(), TextAttribute(std::string), TextAttribute(TextAttribute::ColorType, int)>(),
@@ -316,6 +351,7 @@ void LuaApi::register_types() {
             Color bg_color = bg.value_or(Color(-1, -1, -1));
             theme.set_color(element, fg, bg_color);
         },
+        "set_face", &Theme::set_face,
         "get_fg_color", &Theme::get_fg_color,
         "get_bg_color", &Theme::get_bg_color,
         "name", &Theme::name
@@ -445,6 +481,13 @@ void LuaApi::register_functions() {
     lua_["message"] = [this](std::string msg) {
         core_->set_message(std::move(msg));
     };
+
+    // Define face global function (wrapper around active_theme()->set_face)
+    lua_["define_face"] = [this](std::string name, const FaceAttributes& attrs) {
+        if (auto theme = core_->theme_manager().active_theme()) {
+            theme->set_face(name, attrs);
+        }
+    };
 }
 
 } // namespace lumacs

+ 59 - 772
src/main.cpp

@@ -1,801 +1,88 @@
 #include "lumacs/editor_core.hpp"
-#include "lumacs/lua_api.hpp"
-#include <ftxui/component/component.hpp>
-#include <ftxui/component/screen_interactive.hpp>
-#include <ftxui/dom/elements.hpp>
+#include "lumacs/tui_editor.hpp"
+#include "lumacs/gtk_editor.hpp"
 #include <iostream>
-#include <fstream>
-#include <memory>
-#include <chrono>
-#include <termios.h>
-#include <unistd.h>
+#include <string>
+#include <vector>
+#include <filesystem>
 
-// Global debug log
-std::ofstream debug_log("lumacs_debug.log");
-
-using namespace ftxui;
 using namespace lumacs;
 
-/// Convert TextAttribute::ColorType to FTXUI Color
-Color color_type_to_ftxui(TextAttribute::ColorType color) {
-    switch (color) {
-        case TextAttribute::ColorType::Keyword:
-            return Color::Blue;
-        case TextAttribute::ColorType::String:
-            return Color::Green;
-        case TextAttribute::ColorType::Comment:
-            return Color::GrayDark;
-        case TextAttribute::ColorType::Function:
-            return Color::Cyan;
-        case TextAttribute::ColorType::Type:
-            return Color::Yellow;
-        case TextAttribute::ColorType::Number:
-            return Color::Magenta;
-        case TextAttribute::ColorType::Operator:
-            return Color::White;
-        case TextAttribute::ColorType::Variable:
-            return Color::White;
-        case TextAttribute::ColorType::Constant:
-            return Color::Magenta;
-        case TextAttribute::ColorType::Error:
-            return Color::Red;
-        case TextAttribute::ColorType::Default:
-        default:
-            return Color::Default;
-    }
-}
-
-/// Apply style flags to an FTXUI Element
-Element apply_style_flags(Element elem, int style_flags) {
-    if (style_flags & static_cast<int>(TextAttribute::Style::Bold)) {
-        elem = elem | bold;
-    }
-    if (style_flags & static_cast<int>(TextAttribute::Style::Italic)) {
-        // FTXUI doesn't have italic, use dim as approximation
-        elem = elem | dim;
-    }
-    if (style_flags & static_cast<int>(TextAttribute::Style::Underline)) {
-        elem = elem | underlined;
-    }
-    return elem;
+void print_help() {
+    std::cout << "Usage: lumacs [options] [file]\n"
+              << "Options:\n"
+              << "  -nw, --no-window   Run in terminal mode (TUI)\n"
+              << "  --help             Show this help message\n";
 }
 
-/// FTXUI-based TUI frontend for Lumacs
-class TuiEditor {
-public:
-    TuiEditor() {
-        // Initialize Core first
-        core_ = std::make_unique<EditorCore>();
-        // Then LuaApi, which depends on Core
-        lua_api_ = std::make_unique<LuaApi>(*core_);
-
-        // Listen to editor events
-        core_->on_event([this](EditorEvent event) {
-            handle_editor_event(event);
-        });
-
-        // Load init.lua configuration
-        lua_api_->load_init_file();
-    }
-    
-    ~TuiEditor() {
-        // Explicitly destroy Core (and its Buffers/Callbacks) BEFORE LuaApi
-        // This prevents callbacks from trying to access a closed Lua state
-        core_.reset();
-        lua_api_.reset();
-    }
-
-    void load_file(const std::filesystem::path& path) {
-        if (!core_->load_file(path)) {
-            std::cerr << "Failed to load file: " << path << std::endl;
-        }
-    }
-
-    Component create_ui(ScreenInteractive* screen) {
-        screen_ = screen;
-        auto renderer = Renderer([this] { return render(); });
-
-        // Add keyboard event handling
-        auto component = CatchEvent(renderer, [this](Event event) {
-            return handle_input(event);
-        });
-
-        return component;
-    }
-
-private:
-    enum class Mode {
-        Normal,
-        Command,   // Minibuffer entry (:)
-        FindFile   // Find file prompt (C-x C-f)
-    };
-
-    enum class Prefix {
-        None,
-        Meta,  // Alt/Meta pressed (ESC+key sequence)
-        CtrlX  // C-x pressed
-    };
-
-    struct PendingPrefix {
-        Prefix type = Prefix::None;
-        std::chrono::steady_clock::time_point timestamp;
-        static constexpr auto TIMEOUT = std::chrono::milliseconds(500);
-    };
-
-    std::unique_ptr<EditorCore> core_;
-    std::unique_ptr<LuaApi> lua_api_;
-    ScreenInteractive* screen_ = nullptr;
-    bool should_quit_ = false;
-    std::string message_line_;  // For displaying messages in the UI
-    
-    // Command/Input state
-    Mode mode_ = Mode::Normal;
-    std::string command_buffer_;
-    PendingPrefix pending_prefix_;
-    
-    bool is_prefix_expired() const {
-        if (pending_prefix_.type == Prefix::None) return false;
-        auto now = std::chrono::steady_clock::now();
-        return (now - pending_prefix_.timestamp) > PendingPrefix::TIMEOUT;
-    }
-
-    void handle_editor_event(EditorEvent event) {
-        if (event == EditorEvent::Quit) {
-            should_quit_ = true;
-            if (screen_) {
-                screen_->Exit();
-            }
-        } else if (event == EditorEvent::Message) {
-            message_line_ = core_->last_message();
-        } 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.
-        }
-    }
-    
-    /// Render a line with syntax highlighting
-    Element render_styled_line(lumacs::Window& window, const std::string& line_text, size_t line_num,
-                                 size_t cursor_col, bool has_cursor) {
-        const auto& styles = window.buffer().get_line_styles(line_num);
-
-        // If no styles, render as plain text
-        if (styles.empty()) {
-            if (has_cursor) {
-                return render_line_with_cursor(line_text, cursor_col);
-            }
-            return text(line_text);
-        }
-
-        // Build segments with styles
-        Elements segments;
-        size_t pos = 0;
-
-        for (const auto& styled : styles) {
-            size_t start = styled.range.start.column;
-            size_t end = styled.range.end.column;
-
-            // Add unstyled text before this styled range
-            if (pos < start) {
-                std::string unstyled = line_text.substr(pos, start - pos);
-                if (has_cursor && cursor_col >= pos && cursor_col < start) {
-                    // Cursor is in unstyled part
-                    segments.push_back(render_line_with_cursor(unstyled, cursor_col - pos));
-                } else {
-                    segments.push_back(text(unstyled));
-                }
-                pos = start;
-            }
-
-            // Add styled text
-            if (pos < end && pos < line_text.size()) {
-                size_t segment_end = std::min(end, line_text.size());
-                std::string styled_text = line_text.substr(pos, segment_end - pos);
-
-                Element elem;
-                if (has_cursor && cursor_col >= pos && cursor_col < segment_end) {
-                    // Cursor is in styled part
-                    elem = render_line_with_cursor(styled_text, cursor_col - pos);
-                } else {
-                    elem = text(styled_text);
-                }
-
-                // Apply color and style
-                auto color = color_type_to_ftxui(styled.attr.color);
-                elem = elem | ftxui::color(color);
-                elem = apply_style_flags(elem, styled.attr.style_flags);
-
-                segments.push_back(elem);
-                pos = segment_end;
-            }
-        }
-
-        // Add remaining unstyled text
-        if (pos < line_text.size()) {
-            std::string remaining = line_text.substr(pos);
-            if (has_cursor && cursor_col >= pos) {
-                segments.push_back(render_line_with_cursor(remaining, cursor_col - pos));
-            } else {
-                segments.push_back(text(remaining));
+int main(int argc, char* argv[]) {
+    bool force_tui = false;
+    std::string filename;
+
+    // Parse arguments
+    for (int i = 1; i < argc; ++i) {
+        std::string arg = argv[i];
+        if (arg == "-nw" || arg == "--no-window") {
+            force_tui = true;
+        } else if (arg == "--help") {
+            print_help();
+            return 0;
+        } else {
+            // Assume it's a filename
+            if (filename.empty()) {
+                filename = arg;
             }
-        } else if (has_cursor && cursor_col >= line_text.size()) {
-            // Cursor at end of line
-            segments.push_back(text(" ") | inverted);
         }
-
-        return hbox(segments);
     }
 
-    /// Render a line segment with cursor highlighting
-    Element render_line_with_cursor(const std::string& text_str, size_t cursor_pos) {
-        if (cursor_pos >= text_str.size()) {
-            return hbox({text(text_str), text(" ") | inverted});
-        }
-
-        std::string before = text_str.substr(0, cursor_pos);
-        std::string at_cursor = text_str.substr(cursor_pos, 1);
-        std::string after = text_str.substr(cursor_pos + 1);
-
-        return hbox({
-            text(before),
-            text(at_cursor) | inverted,
-            text(after)
-        });
-    }
-
-    bool handle_input(Event event) {
-        // Debug input in hex with more details
-        std::string input_debug = event.input();
-        debug_log << "=== INPUT DEBUG ===" << std::endl;
-        debug_log << "Event type: ";
-        if (event == Event::Escape) debug_log << "Escape";
-        else if (event == Event::Return) debug_log << "Return";
-        else if (event == Event::Tab) debug_log << "Tab";
-        else if (event == Event::Backspace) debug_log << "Backspace";
-        else if (event == Event::Delete) debug_log << "Delete";
-        else if (event == Event::ArrowUp) debug_log << "ArrowUp";
-        else if (event == Event::ArrowDown) debug_log << "ArrowDown";
-        else if (event == Event::ArrowLeft) debug_log << "ArrowLeft";
-        else if (event == Event::ArrowRight) debug_log << "ArrowRight";
-        else debug_log << "Other";
-        debug_log << std::endl;
-        debug_log << "is_character: " << event.is_character() << std::endl;
-        debug_log << "input_size: " << input_debug.size() << std::endl;
-        debug_log << "bytes: [";
-        for(unsigned char c : input_debug) debug_log << std::hex << (int)c << " ";
-        debug_log << std::dec << "]" << std::endl;
-        debug_log << "chars: \"" << input_debug << "\"" << std::endl;
-        debug_log << "===================" << std::endl;
-
-        // Handle Minibuffer Inputs (Command / FindFile)
-        if (mode_ == Mode::Command || mode_ == Mode::FindFile) {
-            if (event == Event::Escape) {
-                mode_ = Mode::Normal;
-                command_buffer_.clear();
-                message_line_ = "Cancelled";
-                return true;
-            }
-            if (event == Event::Return) {
-                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;
-            }
-            if (event == Event::Backspace) {
-                if (!command_buffer_.empty()) {
-                    command_buffer_.pop_back();
-                } else {
-                    mode_ = Mode::Normal;
-                }
-                return true;
-            }
-            if (event.is_character()) {
-                command_buffer_ += event.input();
-                return true;
-            }
-            return true; 
-        }
-
-        // Check for expired prefix first
-        if (is_prefix_expired()) {
-            pending_prefix_.type = Prefix::None;
-        }
-
-        // === Key Resolution ===
-        std::string key_name;
-        std::string input_str = event.input();
-        bool is_meta_sequence = false;
-
-        // 1. Detect Meta sequences (Alt/ESC + key)
-        if (input_str.size() >= 2 && input_str[0] == 27) {
-            // Debug the meta sequence we got
-            debug_log << "Meta sequence detected: size=" << input_str.size() << " bytes=";
-            for (size_t i = 0; i < input_str.size(); ++i) {
-                debug_log << std::hex << (int)(unsigned char)input_str[i] << " ";
-            }
-            debug_log << std::dec << std::endl;
-            
-            // ESC followed by another key - this is a Meta sequence
-            if (input_str.size() == 3 && input_str[1] == '[') {
-                // Meta+Arrow keys: ESC[A, ESC[B, ESC[C, ESC[D  
-                switch (input_str[2]) {
-                    case 'A': key_name = "M-ArrowUp"; break;
-                    case 'B': key_name = "M-ArrowDown"; break;
-                    case 'C': key_name = "M-ArrowRight"; break;
-                    case 'D': key_name = "M-ArrowLeft"; break;
-                    default: 
-                        debug_log << "Unknown ESC[ sequence: " << input_str[2] << std::endl;
-                        break;
-                }
-                is_meta_sequence = true;
-            } else if (input_str.size() >= 6 && input_str.substr(1, 5) == "[1;3") {
-                // Alternative Meta+Arrow format: ESC[1;3A, ESC[1;3B, ESC[1;3C, ESC[1;3D
-                if (input_str.size() == 6) {
-                    switch (input_str[5]) {
-                        case 'A': key_name = "M-ArrowUp"; break;
-                        case 'B': key_name = "M-ArrowDown"; break;
-                        case 'C': key_name = "M-ArrowRight"; break;
-                        case 'D': key_name = "M-ArrowLeft"; break;
-                        default: 
-                            debug_log << "Unknown ESC[1;3 sequence: " << input_str[5] << std::endl;
-                            break;
-                    }
-                    is_meta_sequence = true;
-                }
-            } else if (input_str[1] != '[') {
-                // Regular Meta+char (not arrow keys)
-                key_name = "M-" + std::string(1, input_str[1]);
-                is_meta_sequence = true;
-            }
-            // If it's ESC[ but not a recognized pattern, let it fall through to normal arrow key handling
-        }
-
-        // 2. Basic Key Mapping (if not Meta sequence)  
-        if (!is_meta_sequence) {
-            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)
-                if (!input_str.empty()) {
-                    int code = static_cast<int>(static_cast<unsigned char>(input_str[0]));
-                    debug_log << "Character code analysis: " << code << std::endl;
-                    if (code > 0 && code < 27 && code != 9 && code != 10 && code != 13) {
-                        char letter = 'a' + (code - 1);
-                        key_name = "C-" + std::string(1, letter);
-                        debug_log << "Detected control key: " << key_name << std::endl;
-                    } else if (code >= 32 || code < 0) { // Printable (including extended ASCII/UTF-8 starts)
-                        key_name = input_str; 
-                        debug_log << "Detected printable char: " << key_name << std::endl;
-                    } else {
-                        debug_log << "Unhandled character code: " << code << std::endl;
-                    }
-                }
-            }
-        }
-
-        // 3. Apply Pending Prefixes
-        if (pending_prefix_.type == Prefix::Meta && !is_meta_sequence) {
-            pending_prefix_.type = Prefix::None;
-            if (key_name.size() == 1) { // Single char
-                key_name = "M-" + key_name;
-            } else if (key_name == "ArrowUp") key_name = "M-ArrowUp";
-            else if (key_name == "ArrowDown") key_name = "M-ArrowDown";
-            else if (key_name == "ArrowLeft") key_name = "M-ArrowLeft";
-            else if (key_name == "ArrowRight") key_name = "M-ArrowRight";
-            // For other special keys, just prefix with M-
-            else if (!key_name.empty()) key_name = "M-" + key_name;
-        } 
-        else if (pending_prefix_.type == Prefix::CtrlX) {
-            pending_prefix_.type = Prefix::None;
-            key_name = "C-x " + key_name;
-        }
+    try {
+        // Initialize Core
+        EditorCore core;
 
-        // Debug: show what key was resolved
-        if (!key_name.empty()) {
-            debug_log << "Resolved Key: " << key_name << std::endl;
-            debug_log << "Pending prefix: " << static_cast<int>(pending_prefix_.type) << std::endl;
-        } else {
-            debug_log << "No key resolved for input: size=" << input_str.size() << std::endl;
-            return false;
-        }
-
-        // === Prefix Trigger Handling ===
-        // Handle C-x prefix (but not Escape - let it be a normal key)
-        if (key_name == "C-x") {
-            pending_prefix_.type = Prefix::CtrlX;
-            pending_prefix_.timestamp = std::chrono::steady_clock::now();
-            message_line_ = "C-x-";
-            debug_log << "Set C-x prefix" << std::endl;
-            return true;
-        }
-        
-        // Handle single Escape as Meta prefix only if no other Meta sequence detected
-        if (key_name == "Escape" && !is_meta_sequence && pending_prefix_.type == Prefix::None) {
-            pending_prefix_.type = Prefix::Meta;
-            pending_prefix_.timestamp = std::chrono::steady_clock::now();
-            message_line_ = "M-";
-            debug_log << "Set Meta prefix" << std::endl;
-            return true;
-        }
-
-        // === Execution ===
+        // Select Frontend
+        std::unique_ptr<IEditorView> view;
 
-        // 1. Try Lua key binding
-        debug_log << "Trying Lua binding for: " << key_name << std::endl;
-        if (lua_api_->execute_key_binding(key_name)) {
-            debug_log << "Lua binding executed successfully" << std::endl;
-            return true;
+#ifdef LUMACS_WITH_GTK
+        if (!force_tui) {
+            view = create_gtk_editor();
         }
-        debug_log << "No Lua binding found, trying C++ fallbacks" << std::endl;
+#endif
 
-        // 2. C++ Fallback Bindings
-        
-        // Global
-        if (key_name == "C-q") {
-            core_->request_quit();
-            return true;
-        }
-        if (key_name == "C-s") {
-            core_->buffer().save();
-            message_line_ = "Saved";
-            return true;
-        }
-        if (key_name == "M-x") {
-            mode_ = Mode::Command;
-            message_line_ = ":";
-            return true;
-        }
-        if (key_name == ":") { // Vim style
-            mode_ = Mode::Command;
-            message_line_ = ":";
-            return true;
-        }
-
-        // C-x Prefix Commands (commented out - using Lua bindings instead)
-        // if (key_name == "C-x o") {
-        //     core_->next_window();
-        //     return true;
-        // }
-        // 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;
-        }
-
-        // 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);
-            if (cursor.column > 0) {
-                core_->set_cursor({cursor.line, cursor.column - 1});
-            } else if (cursor.line > 0) {
-                size_t prev_line_len = core_->buffer().line(cursor.line - 1).size();
-                core_->set_cursor({cursor.line - 1, prev_line_len});
-            }
-            return true;
-        }
-        if (key_name == "Delete") {
-            auto cursor = core_->cursor();
-            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) {
-                core_->buffer().erase_char({cursor.line + 1, 0});
+        // Fallback to TUI if GTK not available or TUI forced
+        if (!view) {
+            if (!force_tui && !view) {
+                // If we tried to load GTK and failed (e.g. create_gtk_editor returned null), warn user?
+                // For now, create_gtk_editor prints to stderr if built without GTK.
             }
-            return true;
-        }
-        if (key_name == "Return") {
-            auto cursor = core_->cursor();
-            core_->buffer().insert_newline(cursor);
-            core_->set_cursor({cursor.line + 1, 0});
-            return true;
-        }
-        if (key_name == "Tab") {
-            auto cursor = core_->cursor();
-            core_->buffer().insert(cursor, "    ");
-            core_->set_cursor({cursor.line, cursor.column + 4});
-            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;
+            view = create_tui_editor();
         }
 
-        return false;
-    }
-    
-    void execute_command(const std::string& cmd) {
-        if (cmd.empty()) return; 
-        
-        // 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 (command == "w" || command == "write") {
-            core_->buffer().save();
-            message_line_ = "Saved";
-            return;
-        }
-        
-        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";
+        if (!view) {
+            std::cerr << "Failed to create editor interface." << std::endl;
+            return 1;
         }
-        else {
-             message_line_ = "Lua Error (check stderr)";
-        }
-    }
 
-    // Helper to render a single window
-    Element render_window(lumacs::Window& window, int width, int height) {
-        debug_log << "render_window: " << width << "x" << height << std::endl;
-        
-        const auto& buffer = window.buffer();
-        const auto& cursor = window.cursor();
-        bool is_active = (core_->active_window().get() == &window);
-        
-        // Calculate available height for text lines
-        // Total height - 2 (border) - 1 (status)
-        int lines_height = std::max(0, height - 3);
-        
-        // Get visible range for this window
-        auto [start_line, end_line] = window.visible_line_range();
-        const auto& buffer_lines = buffer.lines();
-        
-        Elements lines;
-        
-        // Render visible lines
-        for (size_t i = start_line; i < end_line; ++i) {
-            const auto& line_text = buffer_lines[i];
-
-            // Build line
-            Elements line_elements;
-            
-            // Line number
-            line_elements.push_back(text(std::to_string(i + 1) + " │ "));
-
-            // Styled line
-            bool has_cursor = (is_active && i == cursor.line && mode_ == Mode::Normal);
-            line_elements.push_back(render_styled_line(window, line_text, i, cursor.column, has_cursor));
-
-            lines.push_back(hbox(line_elements));
-        }
-        
-        // Fill remaining space
-        while (lines.size() < static_cast<size_t>(lines_height)) {
-            lines.push_back(text("~"));
-        }
-        
-        // Status line
-        std::string status = buffer.name() +
-                           (buffer.is_modified() ? " [+]" : "") +
-                           " | " + std::to_string(cursor.line + 1) +
-                           ":" + std::to_string(cursor.column + 1);
-        
-        if (is_active && mode_ == Mode::Command) status += " [CMD]";
-        
-        auto status_elem = text(status) | inverted;
-        if (!is_active) status_elem = status_elem | dim;
-        
-        auto window_content = vbox({
-            vbox(lines) | flex,
-            status_elem
+        // Link Core and View
+        view->set_core(&core);
+        core.on_event([&view](EditorEvent event) {
+            view->handle_editor_event(event);
         });
 
-        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) {
-        debug_log << "update_layout_sizes: node=" << (int)node->type << " w=" << w << " h=" << h << std::endl;
-        
-        if (node->type == LayoutNode::Type::Leaf) {
-            // Account for border (2 lines) and status (1 line)
-            // render_window produces an element of height: viewport.height + 1 (status) + 2 (border)
-            // We want this to be <= h.
-            // viewport.height + 3 <= h  =>  viewport.height <= h - 3
-            int vp_h = std::max(1, h - 3);
-            int vp_w = std::max(1, w - 2);
-            debug_log << "  Leaf: setting viewport to " << vp_w << "x" << vp_h << std::endl;
-            node->window->set_viewport_size(vp_w, vp_h);
-        } else if (node->type == LayoutNode::Type::HorizontalSplit) {
-            // Account for separator (1 line)
-            int available_h = std::max(0, h - 1);
-            int h1 = available_h / 2;
-            int h2 = available_h - h1;
-            debug_log << "  HSplit: h1=" << h1 << " h2=" << h2 << std::endl;
-            update_layout_sizes(node->child1.get(), w, h1);
-            update_layout_sizes(node->child2.get(), w, h2);
-        } else { // Vertical
-            // Account for separator (1 column)
-            int available_w = std::max(0, w - 1);
-            int w1 = available_w / 2;
-            int w2 = available_w - w1;
-            debug_log << "  VSplit: w1=" << w1 << " w2=" << w2 << std::endl;
-            update_layout_sizes(node->child1.get(), w1, h);
-            update_layout_sizes(node->child2.get(), w2, h);
-        }
-    }
+        // Initialize View
+        view->init();
 
-    Element render_node(LayoutNode* node) {
-        if (node->type == LayoutNode::Type::Leaf) {
-            // dimensions are updated in update_layout_sizes
-            const auto& vp = node->window->viewport();
-            // Reconstruct approximated outer size for render_window helper
-            // content height = vp.height. outer = height + 3
-            return render_window(*node->window, vp.width + 2, vp.height + 3);
-        } else if (node->type == LayoutNode::Type::HorizontalSplit) {
-            return vbox({
-                render_node(node->child1.get()) | flex,
-                separator(),
-                render_node(node->child2.get()) | flex
-            });
-        } else { // Vertical
-            return hbox({
-                render_node(node->child1.get()) | flex,
-                separator(),
-                render_node(node->child2.get()) | flex
-            });
+        // Load initial file if provided
+        if (!filename.empty()) {
+            core.load_file(filename);
         }
-    }
 
-    Element render() {
-        // Dimensions
-        auto term_size = Terminal::Size();
-        debug_log << "Render Frame. Term Size: " << term_size.dimx << "x" << term_size.dimy << std::endl;
+        // Run main loop
+        view->run();
 
-        int height = std::max(1, term_size.dimy - 1); // -1 for minibuffer/message line
-        
-        // Calculate layout sizes
-        update_layout_sizes(core_->root_layout().get(), term_size.dimx, height);
-        
-        // Render Tree
-        Element editor_area = render_node(core_->root_layout().get());
-        
-        // Minibuffer / Message Line
-        Element bottom_bar;
-        
-        if (mode_ == Mode::Command) {
-             debug_log << "  Mode: Command" << std::endl;
-             bottom_bar = hbox({
-                 text(":") | bold | color(Color::Yellow),
-                 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()) {
-                bottom_bar = text(message_line_) | dim;
-            } else {
-                bottom_bar = text(""); 
-            }
-        }
-        
-        // Force bottom bar to take exactly 1 line
-        bottom_bar = bottom_bar | size(HEIGHT, EQUAL, 1);
-        
-        return vbox({
-            editor_area | flex,
-            bottom_bar
-        });
+    } catch (const std::exception& e) {
+        std::cerr << "Fatal Error: " << e.what() << std::endl;
+        return 1;
     }
 
-};
-
-// Function to configure terminal for raw input
-void configure_terminal_for_control_chars() {
-    struct termios tty;
-    if (tcgetattr(STDIN_FILENO, &tty) == 0) {
-        // Disable flow control (Ctrl+S, Ctrl+Q)
-        tty.c_iflag &= ~(IXON | IXOFF | IXANY);
-        
-        // Apply settings
-        tcsetattr(STDIN_FILENO, TCSANOW, &tty);
-        
-        debug_log << "Terminal configured for control char capture" << std::endl;
-    } else {
-        debug_log << "Failed to configure terminal" << std::endl;
-    }
-}
-
-int main(int argc, char* argv[]) {
-    // Configure terminal to allow more control characters
-    configure_terminal_for_control_chars();
-    
-    auto screen = ScreenInteractive::Fullscreen();
-    TuiEditor editor;
-
-    // Load file if provided
-    if (argc > 1) {
-        editor.load_file(argv[1]);
-    }
-
-    auto ui = editor.create_ui(&screen);
-
-    std::cout << "Lumacs - A modern text editor with Lua scripting" << std::endl;
-    std::cout << "Navigation: Arrow keys | Editing: Type, Backspace, Enter, Tab" << std::endl;
-    std::cout << "Commands: Ctrl+S=save, Ctrl+L=highlight, Esc/Q=quit" << std::endl;
-    std::cout << std::endl;
-
-    screen.Loop(ui);
     return 0;
-}
+}

+ 27 - 3
src/theme.cpp

@@ -96,11 +96,35 @@ void Theme::set_face(const std::string& name, const FaceAttributes& attrs) {
 }
 
 std::optional<FaceAttributes> Theme::get_face(const std::string& name) const {
+    return get_face_recursive(name, 0);
+}
+
+std::optional<FaceAttributes> Theme::get_face_recursive(const std::string& name, int depth) const {
+    // Prevent infinite recursion
+    if (depth > 10) {
+        return std::nullopt;
+    }
+
     auto it = faces_.find(name);
-    if (it != faces_.end()) {
-        return it->second;
+    if (it == faces_.end()) {
+        return std::nullopt;
     }
-    return std::nullopt;
+
+    FaceAttributes current = it->second;
+
+    // Handle inheritance
+    if (current.inherit) {
+        auto parent = get_face_recursive(*current.inherit, depth + 1);
+        if (parent) {
+            // Start with parent attributes
+            FaceAttributes final_attrs = *parent;
+            // Merge current (child) attributes on top, overriding parent
+            final_attrs.merge(current);
+            return final_attrs;
+        }
+    }
+
+    return current;
 }
 
 void Theme::set_color(ThemeElement element, const Color& fg, const Color& bg) {

+ 10 - 31
src/main_ncurses.cpp → src/tui_editor.cpp

@@ -1,6 +1,6 @@
+#include "lumacs/tui_editor.hpp"
 #include "lumacs/editor_core.hpp"
 #include "lumacs/lua_api.hpp"
-#include "lumacs/ui_interface.hpp" // New include
 #include <ncurses.h>
 #include <iostream>
 #include <fstream>
@@ -11,12 +11,13 @@
 #include <algorithm>
 
 // Global debug log
+extern std::ofstream debug_log;
 std::ofstream debug_log("lumacs_debug.log");
 
 using namespace lumacs;
 
 /// ncurses-based TUI frontend for Lumacs
-class TuiEditor : public IEditorView { // Renamed and inherited
+class TuiEditor : public IEditorView {
 public:
     TuiEditor() : core_(nullptr) {}
     
@@ -536,14 +537,14 @@ bool TuiEditor::handle_input(int ch) {
             } else if (mode_ == Mode::FindFile) {
                 if (core_->load_file(command_buffer_)) {
                     message_line_ = "Loaded: " + command_buffer_;
-                    core_->lua_api()->execute("auto_activate_major_mode()");
+                    core_->lua_api()->execute("auto_activate_major_mode()") ;
                 } else {
                     message_line_ = "Failed to load: " + command_buffer_;
                 }
             } else if (mode_ == Mode::BufferSwitch) {
                 if (core_->switch_buffer_in_window(command_buffer_)) {
                     message_line_ = "Switched to: " + command_buffer_;
-                    core_->lua_api()->execute("auto_activate_major_mode()");
+                    core_->lua_api()->execute("auto_activate_major_mode()") ;
                 } else {
                     message_line_ = "Buffer not found: " + command_buffer_;
                 }
@@ -810,7 +811,7 @@ void TuiEditor::execute_command(const std::string& cmd) {
                     if (!path.empty()) {
                         if (core_->load_file(path)) {
                             message_line_ = "Loaded: " + path;
-                            core_->lua_api()->execute("auto_activate_major_mode()");
+                            core_->lua_api()->execute("auto_activate_major_mode()") ;
                         } else {                message_line_ = "Failed to load: " + path;
             }
         } else {
@@ -1174,30 +1175,8 @@ void TuiEditor::render_message_line() {
     attroff(attrs);
 }
 
-int main(int argc, char* argv[]) {
-    try {
-        EditorCore core; // Create core first
-        TuiEditor editor; // Create TUI editor
-
-        editor.set_core(&core); // Set core for the view
-        core.on_event([&editor](EditorEvent event) { // Hook up event handler
-            editor.handle_editor_event(event);
-        });
-
-        editor.init(); // Initialize ncurses after core is set
-
-        // Load file if provided
-        if (argc > 1) {
-            core.load_file(argv[1]); // Core loads file now
-        }
-        
-        editor.run();
-        
-        std::cout << "Goodbye!" << std::endl;
-    } catch (const std::exception& e) {
-        std::cerr << "Error: " << e.what() << std::endl;
-        return 1;
-    }
-    
-    return 0;
+namespace lumacs {
+std::unique_ptr<IEditorView> create_tui_editor() {
+    return std::make_unique<TuiEditor>();
+}
 }