Просмотр исходного кода

feat: Implement abstract Face system for styling (replaces direct color mapping)

Bernardo Magri 1 месяц назад
Родитель
Сommit
e80b8093d9
7 измененных файлов с 313 добавлено и 151 удалено
  1. 24 6
      include/lumacs/buffer.hpp
  2. 25 0
      include/lumacs/color.hpp
  3. 61 0
      include/lumacs/face.hpp
  4. 46 37
      include/lumacs/theme.hpp
  5. 2 3
      src/lua_api.cpp
  6. 34 75
      src/main_ncurses.cpp
  7. 121 30
      src/theme.cpp

+ 24 - 6
include/lumacs/buffer.hpp

@@ -6,6 +6,7 @@
 #include <optional>
 #include <filesystem>
 #include <memory>
+#include "lumacs/theme.hpp" // For ThemeElement mapping if needed
 
 namespace lumacs {
 
@@ -59,10 +60,9 @@ struct TextAttribute {
         Bold = 1,
         Italic = 2,
         Underline = 4,
-        // Can combine with bitwise OR
     };
 
-    // Common semantic colors for syntax highlighting
+    // Common semantic colors for syntax highlighting (Legacy, used for mapping)
     enum class ColorType {
         Default,
         Keyword,      // if, for, while, etc.
@@ -77,11 +77,29 @@ struct TextAttribute {
         Error,        // Error highlighting
     };
 
-    ColorType color = ColorType::Default;
-    int style_flags = 0; // Combination of Style flags
+    std::string face_name = "default";
 
     TextAttribute() = default;
-    TextAttribute(ColorType c, int s = 0) : color(c), style_flags(s) {}
+    TextAttribute(std::string face) : face_name(std::move(face)) {}
+    
+    // Legacy constructor
+    TextAttribute(ColorType c, int /*s*/ = 0) {
+        // Map legacy ColorType to face name
+        switch (c) {
+            case ColorType::Keyword: face_name = "font-lock-keyword-face"; break;
+            case ColorType::String: face_name = "font-lock-string-face"; break;
+            case ColorType::Comment: face_name = "font-lock-comment-face"; break;
+            case ColorType::Function: face_name = "font-lock-function-name-face"; break;
+            case ColorType::Type: face_name = "font-lock-type-face"; break;
+            case ColorType::Number: face_name = "font-lock-constant-face"; break; // Map number to constant
+            case ColorType::Operator: face_name = "default"; break; // No specific face yet
+            case ColorType::Variable: face_name = "font-lock-variable-name-face"; break;
+            case ColorType::Constant: face_name = "font-lock-constant-face"; break;
+            case ColorType::Error: face_name = "error"; break;
+            default: face_name = "default"; break;
+        }
+        // Note: style flags (s) are ignored here as faces define weight/slant
+    }
 };
 
 /// A range with associated styling
@@ -299,4 +317,4 @@ private:
     void emit_event(BufferEvent event, size_t line = 0);
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 25 - 0
include/lumacs/color.hpp

@@ -0,0 +1,25 @@
+#pragma once
+
+namespace lumacs {
+
+/// RGB Color representation
+struct Color {
+    int r, g, b;
+    Color(int r = 0, int g = 0, int b = 0) : r(r), g(g), b(b) {}
+
+    /// Convert to ncurses color index (will be assigned dynamically)
+    int to_ncurses_color() const;
+
+    /// Comparison operator for use in maps
+    bool operator<(const Color& other) const {
+        if (r != other.r) return r < other.r;
+        if (g != other.g) return g < other.g;
+        return b < other.b;
+    }
+    
+    bool operator==(const Color& other) const {
+        return r == other.r && g == other.g && b == other.b;
+    }
+};
+
+} // namespace lumacs

+ 61 - 0
include/lumacs/face.hpp

@@ -0,0 +1,61 @@
+#pragma once
+
+#include <string>
+#include <optional>
+#include <map>
+#include <memory>
+#include "lumacs/color.hpp"
+
+namespace lumacs {
+
+// Font weight
+enum class FontWeight {
+    Normal,
+    Bold,
+    Light,
+};
+
+// Font slant
+enum class FontSlant {
+    Normal,
+    Italic,
+    Oblique
+};
+
+struct FaceAttributes {
+    std::optional<std::string> family;
+    std::optional<int> height; // For GUI: 1/10 pt or px. For TUI: ignored.
+    std::optional<FontWeight> weight;
+    std::optional<FontSlant> slant;
+    std::optional<Color> foreground;
+    std::optional<Color> background;
+    std::optional<bool> underline;
+    std::optional<bool> 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;
+    }
+};
+
+class Face {
+public:
+    Face(std::string name) : name_(std::move(name)) {}
+    
+    const std::string& name() const { return name_; }
+    FaceAttributes& attributes() { return attributes_; }
+    const FaceAttributes& attributes() const { return attributes_; }
+
+private:
+    std::string name_;
+    FaceAttributes attributes_;
+};
+
+} // namespace lumacs

+ 46 - 37
include/lumacs/theme.hpp

@@ -4,22 +4,13 @@
 #include <map>
 #include <memory>
 #include <vector>
+#include <optional>
+#include "lumacs/color.hpp"
+#include "lumacs/face.hpp"
 
 namespace lumacs {
 
-/// RGB Color representation
-struct Color {
-    int r, g, b;
-    Color(int r = 0, int g = 0, int b = 0) : r(r), g(g), b(b) {}
-    
-    /// Convert to ncurses color index (will be assigned dynamically)
-    int to_ncurses_color() const;
-    
-    /// Comparison operator for use in maps
-    bool operator<(const Color& other) const;
-};
-
-/// Theme element types
+/// Theme element types (Legacy / Semantic Names)
 enum class ThemeElement {
     // Text elements
     Normal,
@@ -31,7 +22,7 @@ enum class ThemeElement {
     Number,
     Constant,
     Error,
-    
+
     // UI elements
     StatusLine,
     StatusLineInactive,
@@ -41,42 +32,60 @@ enum class ThemeElement {
     Selection,
     SearchMatch,
     SearchFail,
-    
+
     // Window elements
     WindowBorder,
     WindowBorderActive,
-    
+
     // Special
     Background
 };
 
-/// A theme defines colors and attributes for various UI elements
+/// A theme defines colors and attributes for various UI elements and named faces
 class Theme {
 public:
     Theme(const std::string& name) : name_(name) {}
-    
-    /// Set color for a theme element
+
+    /// Set color for a theme element (Legacy)
     void set_color(ThemeElement element, const Color& fg, const Color& bg = Color(-1, -1, -1));
+
+    /// Set full face attributes
+    void set_face(const std::string& name, const FaceAttributes& attrs);
     
-    /// Get foreground color for an element
+    /// Get face attributes
+    std::optional<FaceAttributes> get_face(const std::string& name) const;
+
+    /// Get foreground color for an element (Legacy)
     Color get_fg_color(ThemeElement element) const;
-    
-    /// Get background color for an element
+
+    /// Get background color for an element (Legacy)
     Color get_bg_color(ThemeElement element) const;
-    
-    /// Get ncurses color pair for an element
+
+    /// Get ncurses color pair for an element (Legacy - resolves via faces now)
     int get_color_pair(ThemeElement element) const;
     
+    /// Get ncurses color pair for a face
+    int get_face_color_pair(const std::string& name) const;
+    
+    /// Get ncurses attributes (color pair + flags) for a face
+    int get_face_attributes_ncurses(const std::string& name) const;
+
     /// Theme name
     const std::string& name() const { return name_; }
-    
+
     /// Initialize all color pairs for ncurses
     void initialize_ncurses_colors();
-    
+
+    /// Helper to convert ThemeElement to face name
+    static std::string element_to_face_name(ThemeElement element);
+
 private:
     std::string name_;
-    std::map<ThemeElement, std::pair<Color, Color>> colors_; // fg, bg
-    mutable std::map<ThemeElement, int> color_pairs_;
+    // std::map<ThemeElement, std::pair<Color, Color>> colors_; // Legacy storage - removed
+    
+    std::map<std::string, FaceAttributes> faces_;
+    
+    mutable std::map<std::string, int> face_pairs_; // Cache for ncurses pairs
     mutable int next_pair_id_ = 1;
 };
 
@@ -85,31 +94,31 @@ class ThemeManager {
 public:
     /// Register a new theme
     void register_theme(std::shared_ptr<Theme> theme);
-    
+
     /// Set the active theme
     void set_active_theme(const std::string& name);
-    
+
     /// Get the active theme
     std::shared_ptr<Theme> active_theme() const { return active_theme_; }
-    
+
     /// Get available theme names
     std::vector<std::string> theme_names() const;
-    
+
     /// Create default themes
     void create_default_themes();
-    
+
     /// Create Everforest dark theme
     std::shared_ptr<Theme> create_everforest_theme();
-    
+
     /// Create default light theme
     std::shared_ptr<Theme> create_default_theme();
-    
+
     /// Create Dracula theme
     std::shared_ptr<Theme> create_dracula_theme();
-    
+
 private:
     std::map<std::string, std::shared_ptr<Theme>> themes_;
     std::shared_ptr<Theme> active_theme_;
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 2 - 3
src/lua_api.cpp

@@ -158,9 +158,8 @@ void LuaApi::register_types() {
 
     // TextAttribute type
     lua_.new_usertype<TextAttribute>("TextAttribute",
-        sol::constructors<TextAttribute(), TextAttribute(TextAttribute::ColorType, int)>(),
-        "color", &TextAttribute::color,
-        "style_flags", &TextAttribute::style_flags
+        sol::constructors<TextAttribute(), TextAttribute(std::string), TextAttribute(TextAttribute::ColorType, int)>(),
+        "face_name", &TextAttribute::face_name
     );
 
     // TextAttribute::ColorType enum

+ 34 - 75
src/main_ncurses.cpp

@@ -849,21 +849,10 @@ private:
         }
     }
     
-    int color_for_attribute(TextAttribute::ColorType color) {
+    int get_attributes_for_face(const std::string& face_name) {
         auto theme = core_->active_theme();
         if (!theme) return 0;
-        
-        switch (color) {
-            case TextAttribute::ColorType::Keyword: return theme->get_color_pair(ThemeElement::Keyword);
-            case TextAttribute::ColorType::String: return theme->get_color_pair(ThemeElement::String);
-            case TextAttribute::ColorType::Comment: return theme->get_color_pair(ThemeElement::Comment);
-            case TextAttribute::ColorType::Function: return theme->get_color_pair(ThemeElement::Function);
-            case TextAttribute::ColorType::Type: return theme->get_color_pair(ThemeElement::Type);
-            case TextAttribute::ColorType::Number: return theme->get_color_pair(ThemeElement::Number);
-            case TextAttribute::ColorType::Constant: return theme->get_color_pair(ThemeElement::Constant);
-            case TextAttribute::ColorType::Error: return theme->get_color_pair(ThemeElement::Error);
-            default: return theme->get_color_pair(ThemeElement::Normal);
-        }
+        return theme->get_face_attributes_ncurses(face_name);
     }
     
     void render() {
@@ -917,19 +906,15 @@ private:
 
             // Draw separator if enabled
             if (separator_width > 0) {
-                auto theme = core_->active_theme();
-                if (theme) {
-                    attron(theme->get_color_pair(ThemeElement::WindowBorder));
-                }
+                int attrs = get_attributes_for_face("window-divider");
+                attron(attrs);
                 
                 int sep_x = x + left_width;
                 for (int i = 0; i < height; ++i) {
                     mvaddch(y + i, sep_x, ACS_VLINE);
                 }
                 
-                if (theme) {
-                    attroff(theme->get_color_pair(ThemeElement::WindowBorder));
-                }
+                attroff(attrs);
             }
 
             render_layout_node(node->child2, x + left_width + separator_width, y, right_width, height);
@@ -1011,10 +996,10 @@ private:
                             std::string styled_text = line_text.substr(pos, len);
 
                             // Apply color
-                            int color_pair = color_for_attribute(styled.attr.color);
-                            attron(color_pair);
+                            int attrs = get_attributes_for_face(styled.attr.face_name);
+                            attron(attrs);
                             mvprintw(y + screen_y, screen_x, "%s", styled_text.c_str());
-                            attroff(color_pair);
+                            attroff(attrs);
 
                             screen_x += styled_text.length();
                             pos = end;
@@ -1043,18 +1028,12 @@ private:
                     int match_x = x + line_number_width + match_start;
                     // Simple clipping check
                     if (match_x < x + width) {
-                        auto theme = core_->active_theme();
-                        if (theme) {
-                            attron(theme->get_color_pair(isearch_failed_ ? ThemeElement::SearchFail : ThemeElement::SearchMatch));
-                        } else {
-                            attron(A_REVERSE);
-                        }
+                        int attrs = get_attributes_for_face(isearch_failed_ ? "isearch-fail" : "isearch");
+                        if (attrs == 0) attrs = A_REVERSE; // Fallback
+                        
+                        attron(attrs);
                         mvprintw(y + screen_y, match_x, "%s", matched_text.c_str());
-                        if (theme) {
-                            attroff(theme->get_color_pair(isearch_failed_ ? ThemeElement::SearchFail : ThemeElement::SearchMatch));
-                        } else {
-                            attroff(A_REVERSE);
-                        }
+                        attroff(attrs);
                     }
                 }
             }
@@ -1067,18 +1046,13 @@ private:
                     if (cursor.column < line_text.size()) {
                         cursor_char = line_text[cursor.column];
                     }
-                    auto theme = core_->active_theme();
-                    if (theme) {
-                        attron(theme->get_color_pair(ThemeElement::Cursor));
-                    } else {
-                        attron(A_REVERSE);
-                    }
+                    
+                    int attrs = get_attributes_for_face("cursor");
+                    if (attrs == 0) attrs = A_REVERSE;
+                    
+                    attron(attrs);
                     mvaddch(y + screen_y, cursor_screen_x, cursor_char);
-                    if (theme) {
-                        attroff(theme->get_color_pair(ThemeElement::Cursor));
-                    } else {
-                        attroff(A_REVERSE);
-                    }
+                    attroff(attrs);
                 }
             }
         }
@@ -1099,15 +1073,13 @@ private:
     void render_window_modeline(std::shared_ptr<Window> window, int x, int y, int width, bool is_active) {
         const auto& buffer = window->buffer();
         const auto cursor = window->cursor();
-        auto theme = core_->active_theme();
         
         // Choose modeline colors
-        ThemeElement modeline_element = is_active ? ThemeElement::StatusLine : ThemeElement::StatusLineInactive;
-        if (theme) {
-            attron(theme->get_color_pair(modeline_element));
-        } else {
-            attron(is_active ? A_REVERSE : A_DIM);
-        }
+        std::string face_name = is_active ? "mode-line" : "mode-line-inactive";
+        int attrs = get_attributes_for_face(face_name);
+        if (attrs == 0) attrs = is_active ? A_REVERSE : A_DIM;
+        
+        attron(attrs);
         
         // Clear the modeline
         move(y, x);
@@ -1148,11 +1120,7 @@ private:
         }
         
         // Turn off modeline attributes
-        if (theme) {
-            attroff(theme->get_color_pair(modeline_element));
-        } else {
-            attroff(is_active ? A_REVERSE : A_DIM);
-        }
+        attroff(attrs);
     }
     
     void render_status_line() {
@@ -1160,12 +1128,10 @@ private:
         const auto& buffer = core_->buffer();
 
         int status_y = height_ - 2;
-        auto theme = core_->active_theme();
-        if (theme) {
-            attron(theme->get_color_pair(ThemeElement::StatusLine));
-        } else {
-            attron(A_REVERSE);
-        }
+        int attrs = get_attributes_for_face("mode-line");
+        if (attrs == 0) attrs = A_REVERSE;
+
+        attron(attrs);
         move(status_y, 0);
         clrtoeol();
 
@@ -1181,19 +1147,14 @@ private:
         else if (mode_ == Mode::ISearch) status += " [I-SEARCH]";
 
         mvprintw(status_y, 0, "%s", status.c_str());
-        if (theme) {
-            attroff(theme->get_color_pair(ThemeElement::StatusLine));
-        } else {
-            attroff(A_REVERSE);
-        }
+        attroff(attrs);
     }
     
     void render_message_line() {
         int msg_y = height_ - 1;
-        auto theme = core_->active_theme();
-        if (theme) {
-            attron(theme->get_color_pair(ThemeElement::MessageLine));
-        }
+        int attrs = get_attributes_for_face("minibuffer-prompt");
+        
+        attron(attrs);
         move(msg_y, 0);
         clrtoeol();
 
@@ -1222,9 +1183,7 @@ private:
             mvprintw(msg_y, 0, "%s", message_line_.c_str());
         }
         
-        if (theme) {
-            attroff(theme->get_color_pair(ThemeElement::MessageLine));
-        }
+        attroff(attrs);
     }
 };
 

+ 121 - 30
src/theme.cpp

@@ -1,6 +1,7 @@
 #include "lumacs/theme.hpp"
 #include <ncurses.h>
 #include <algorithm>
+#include <climits>
 
 namespace lumacs {
 
@@ -33,7 +34,6 @@ int Color::to_ncurses_color() const {
     }
     
     // Fallback to closest basic color
-    // This is a simple approximation - could be improved
     int min_dist = INT_MAX;
     int closest = COLOR_WHITE;
     
@@ -63,67 +63,159 @@ int Color::to_ncurses_color() const {
     return closest;
 }
 
-bool Color::operator<(const Color& other) const {
-    if (r != other.r) return r < other.r;
-    if (g != other.g) return g < other.g;
-    return b < other.b;
+std::string Theme::element_to_face_name(ThemeElement element) {
+    switch (element) {
+        case ThemeElement::Normal: return "default";
+        case ThemeElement::Keyword: return "font-lock-keyword-face";
+        case ThemeElement::String: return "font-lock-string-face";
+        case ThemeElement::Comment: return "font-lock-comment-face";
+        case ThemeElement::Function: return "font-lock-function-name-face";
+        case ThemeElement::Type: return "font-lock-type-face";
+        case ThemeElement::Number: return "font-lock-constant-face"; // Mapping number to constant for now
+        case ThemeElement::Constant: return "font-lock-constant-face";
+        case ThemeElement::Error: return "error";
+        case ThemeElement::StatusLine: return "mode-line";
+        case ThemeElement::StatusLineInactive: return "mode-line-inactive";
+        case ThemeElement::MessageLine: return "minibuffer-prompt";
+        case ThemeElement::LineNumber: return "line-number";
+        case ThemeElement::Cursor: return "cursor";
+        case ThemeElement::Selection: return "region";
+        case ThemeElement::SearchMatch: return "isearch";
+        case ThemeElement::SearchFail: return "isearch-fail";
+        case ThemeElement::WindowBorder: return "window-divider";
+        case ThemeElement::WindowBorderActive: return "window-divider-active";
+        case ThemeElement::Background: return "default"; // Background usually part of default
+        default: return "default";
+    }
+}
+
+void Theme::set_face(const std::string& name, const FaceAttributes& attrs) {
+    faces_[name] = attrs;
+    // Invalidate cache
+    face_pairs_.erase(name);
+}
+
+std::optional<FaceAttributes> Theme::get_face(const std::string& name) const {
+    auto it = faces_.find(name);
+    if (it != faces_.end()) {
+        return it->second;
+    }
+    return std::nullopt;
 }
 
 void Theme::set_color(ThemeElement element, const Color& fg, const Color& bg) {
-    colors_[element] = {fg, bg};
-    // Invalidate cached color pair
-    color_pairs_.erase(element);
+    std::string face_name = element_to_face_name(element);
+    FaceAttributes attrs;
+    attrs.foreground = fg;
+    attrs.background = bg;
+    
+    // Merge with existing if possible, or set new
+    auto existing = get_face(face_name);
+    if (existing) {
+        FaceAttributes merged = *existing;
+        merged.foreground = fg;
+        merged.background = bg;
+        set_face(face_name, merged);
+    } else {
+        set_face(face_name, attrs);
+    }
 }
 
 Color Theme::get_fg_color(ThemeElement element) const {
-    auto it = colors_.find(element);
-    if (it != colors_.end()) {
-        return it->second.first;
-    }
-    // Default foreground
+    auto face = get_face(element_to_face_name(element));
+    if (face && face->foreground) return *face->foreground;
     return Color(255, 255, 255);
 }
 
 Color Theme::get_bg_color(ThemeElement element) const {
-    auto it = colors_.find(element);
-    if (it != colors_.end()) {
-        return it->second.second;
-    }
-    // Default background
+    auto face = get_face(element_to_face_name(element));
+    if (face && face->background) return *face->background;
     return Color(-1, -1, -1);
 }
 
 int Theme::get_color_pair(ThemeElement element) const {
-    auto it = color_pairs_.find(element);
-    if (it != color_pairs_.end()) {
+    return get_face_color_pair(element_to_face_name(element));
+}
+
+int Theme::get_face_color_pair(const std::string& name) const {
+    // Check cache
+    auto it = face_pairs_.find(name);
+    if (it != face_pairs_.end()) {
         return COLOR_PAIR(it->second);
     }
     
+    // Get attributes
+    auto face = get_face(name);
+    
+    // If face not found, try default
+    if (!face && name != "default") {
+        face = get_face("default");
+    }
+    
+    if (!face) {
+        return 0; // No attributes found
+    }
+    
     // Create new color pair
     int pair_id = next_pair_id_++;
     if (pair_id >= COLOR_PAIRS) {
-        return 0; // Fallback to default
+        return 0; // Fallback to default if out of pairs
     }
     
-    Color fg = get_fg_color(element);
-    Color bg = get_bg_color(element);
+    // Resolve colors (fallback to default if missing)
+    Color fg = face->foreground.value_or(Color(255, 255, 255));
+    Color bg = face->background.value_or(Color(-1, -1, -1));
+    
+    // Special case: if this is a face that should inherit from default, we might need logic here.
+    // For now, just use what we have.
     
     int fg_color = fg.to_ncurses_color();
     int bg_color = bg.to_ncurses_color();
     
     init_pair(pair_id, fg_color, bg_color);
-    color_pairs_[element] = pair_id;
+    face_pairs_[name] = pair_id;
     
     return COLOR_PAIR(pair_id);
 }
 
+int Theme::get_face_attributes_ncurses(const std::string& name) const {
+    int pair = get_face_color_pair(name);
+    int attrs = pair;
+    
+    auto face = get_face(name);
+    if (!face && name != "default") {
+        face = get_face("default");
+    }
+    
+    if (face) {
+        if (face->weight == FontWeight::Bold) attrs |= A_BOLD;
+        // A_ITALIC might not be defined on all ncurses versions, use ifdef or ignore for now
+        #ifdef A_ITALIC
+        if (face->slant == FontSlant::Italic) attrs |= A_ITALIC;
+        #endif
+        if (face->underline.value_or(false)) attrs |= A_UNDERLINE;
+        if (face->inverse.value_or(false)) attrs |= A_REVERSE;
+    }
+    
+    return attrs;
+}
+
 void Theme::initialize_ncurses_colors() {
-    // Pre-initialize all color pairs for this theme
-    for (auto& [element, colors] : colors_) {
-        get_color_pair(element);
+    // Initialize known standard faces
+    std::vector<std::string> standard_faces = {
+        "default", "font-lock-keyword-face", "font-lock-string-face", "font-lock-comment-face",
+        "font-lock-function-name-face", "font-lock-type-face", "font-lock-constant-face",
+        "error", "mode-line", "mode-line-inactive", "minibuffer-prompt", "line-number",
+        "cursor", "region", "isearch", "isearch-fail", "window-divider"
+    };
+    
+    for (const auto& name : standard_faces) {
+        get_face_color_pair(name);
     }
 }
 
+// ... (ThemeManager implementation remains mostly same, but ensures we set faces correctly)
+
 void ThemeManager::register_theme(std::shared_ptr<Theme> theme) {
     themes_[theme->name()] = theme;
     if (!active_theme_) {
@@ -159,7 +251,6 @@ std::shared_ptr<Theme> ThemeManager::create_everforest_theme() {
     // Everforest color palette
     Color bg0(45, 49, 48);          // #2d3139
     Color bg1(52, 56, 56);          // #343839
-    Color bg2(67, 72, 71);          // #434847
     Color fg(211, 198, 170);        // #d3c6aa
     
     Color red(230, 126, 128);       // #e67e80
@@ -171,7 +262,7 @@ std::shared_ptr<Theme> ThemeManager::create_everforest_theme() {
     Color purple(208, 135, 162);    // #d699b5
     Color grey(146, 131, 116);      // #928374
     
-    // Text elements
+    // Text elements via set_color (which now sets faces)
     theme->set_color(ThemeElement::Normal, fg, bg0);
     theme->set_color(ThemeElement::Keyword, red, bg0);
     theme->set_color(ThemeElement::String, green, bg0);
@@ -274,4 +365,4 @@ std::shared_ptr<Theme> ThemeManager::create_dracula_theme() {
     return theme;
 }
 
-} // namespace lumacs
+} // namespace lumacs