|
|
@@ -6,6 +6,7 @@
|
|
|
#include "lumacs/theme.hpp"
|
|
|
#include "lumacs/config.hpp"
|
|
|
#include "lumacs/keybinding.hpp"
|
|
|
+#include "lumacs/modeline.hpp"
|
|
|
#include <memory>
|
|
|
#include <functional>
|
|
|
#include <vector>
|
|
|
@@ -17,27 +18,36 @@ namespace lumacs {
|
|
|
class LuaApi; // Forward declaration
|
|
|
class CommandSystem; // Forward declaration
|
|
|
|
|
|
-/// Editor state change events
|
|
|
+/// @brief Editor state change events triggered by core actions.
|
|
|
+/// Used by the UI layer to react to model updates.
|
|
|
enum class EditorEvent {
|
|
|
- BufferModified,
|
|
|
- CursorMoved,
|
|
|
- ViewportChanged,
|
|
|
- WindowLayoutChanged, // New event
|
|
|
- WindowFocused, // New event: a different window gained focus
|
|
|
- Message, // New event
|
|
|
- CommandMode, // Trigger command mode (minibuffer)
|
|
|
- BufferSwitchMode, // Trigger buffer switch mode
|
|
|
- KillBufferMode, // Trigger kill buffer mode
|
|
|
- FindFileMode, // Trigger find file mode
|
|
|
- ThemeSelectionMode, // Trigger theme selection mode
|
|
|
- ISearchMode, // Trigger incremental search mode
|
|
|
- ISearchBackwardMode, // Trigger incremental search mode (backward)
|
|
|
- Quit
|
|
|
+ BufferModified, ///< The content of the active buffer has changed.
|
|
|
+ CursorMoved, ///< The cursor position has changed.
|
|
|
+ ViewportChanged, ///< The visible area (scroll/size) has changed.
|
|
|
+ WindowLayoutChanged, ///< The window tree structure (splits) has changed.
|
|
|
+ WindowFocused, ///< A different window has gained focus.
|
|
|
+ Message, ///< A transient message should be displayed (e.g., in minibuffer).
|
|
|
+
|
|
|
+ // Modal Interaction Events
|
|
|
+ CommandMode, ///< Enter command input mode (M-x).
|
|
|
+ BufferSwitchMode, ///< Enter buffer switching mode (C-x b).
|
|
|
+ KillBufferMode, ///< Enter kill buffer mode (C-x k).
|
|
|
+ FindFileMode, ///< Enter file finding mode (C-x C-f).
|
|
|
+ ThemeSelectionMode, ///< Enter theme selection mode.
|
|
|
+ ISearchMode, ///< Enter incremental search mode (C-s).
|
|
|
+ ISearchBackwardMode, ///< Enter backward incremental search mode (C-r).
|
|
|
+
|
|
|
+ Quit ///< The application should exit.
|
|
|
};
|
|
|
|
|
|
struct LayoutNode;
|
|
|
|
|
|
-/// Core editor logic, independent of UI framework
|
|
|
+/// @brief Core logic of the Lumacs editor, independent of the UI framework.
|
|
|
+///
|
|
|
+/// This class acts as the central controller/facade for the editor's logic.
|
|
|
+/// It manages buffers, windows, the kill ring, registers, macros, configuration,
|
|
|
+/// and subsystems like the command system and Lua API.
|
|
|
+/// It emits events to notify the UI (IEditorView) of state changes.
|
|
|
class EditorCore {
|
|
|
public:
|
|
|
EditorCore();
|
|
|
@@ -48,68 +58,63 @@ public:
|
|
|
EditorCore& operator=(const EditorCore&) = delete;
|
|
|
EditorCore(EditorCore&&) noexcept = default;
|
|
|
EditorCore& operator=(EditorCore&&) noexcept = default;
|
|
|
+
|
|
|
// === Message System ===
|
|
|
+
|
|
|
+ /// @brief Display a message to the user (usually in the minibuffer).
|
|
|
+ /// @param msg The message string to display.
|
|
|
void set_message(std::string msg) {
|
|
|
last_message_ = std::move(msg);
|
|
|
emit_event(EditorEvent::Message);
|
|
|
}
|
|
|
|
|
|
+ /// @brief Get the last set message.
|
|
|
const std::string& last_message() const { return last_message_; }
|
|
|
|
|
|
// === Actions ===
|
|
|
- void enter_command_mode() {
|
|
|
- emit_event(EditorEvent::CommandMode);
|
|
|
- }
|
|
|
-
|
|
|
- void enter_buffer_switch_mode() {
|
|
|
- emit_event(EditorEvent::BufferSwitchMode);
|
|
|
- }
|
|
|
-
|
|
|
- void enter_kill_buffer_mode() {
|
|
|
- emit_event(EditorEvent::KillBufferMode);
|
|
|
- }
|
|
|
-
|
|
|
- void enter_find_file_mode() {
|
|
|
- emit_event(EditorEvent::FindFileMode);
|
|
|
- }
|
|
|
+ // These methods trigger specific input modes in the UI.
|
|
|
|
|
|
- void enter_theme_selection_mode() {
|
|
|
- emit_event(EditorEvent::ThemeSelectionMode);
|
|
|
- }
|
|
|
-
|
|
|
- void enter_isearch_mode() {
|
|
|
- emit_event(EditorEvent::ISearchMode);
|
|
|
- }
|
|
|
-
|
|
|
- void enter_isearch_backward_mode() {
|
|
|
- emit_event(EditorEvent::ISearchBackwardMode);
|
|
|
- }
|
|
|
+ void enter_command_mode() { emit_event(EditorEvent::CommandMode); }
|
|
|
+ void enter_buffer_switch_mode() { emit_event(EditorEvent::BufferSwitchMode); }
|
|
|
+ void enter_kill_buffer_mode() { emit_event(EditorEvent::KillBufferMode); }
|
|
|
+ void enter_find_file_mode() { emit_event(EditorEvent::FindFileMode); }
|
|
|
+ void enter_theme_selection_mode() { emit_event(EditorEvent::ThemeSelectionMode); }
|
|
|
+ void enter_isearch_mode() { emit_event(EditorEvent::ISearchMode); }
|
|
|
+ void enter_isearch_backward_mode() { emit_event(EditorEvent::ISearchBackwardMode); }
|
|
|
|
|
|
// === Buffer Management ===
|
|
|
|
|
|
- /// Get the current buffer (of the active window)
|
|
|
+ /// @brief Get the active buffer (associated with the active window).
|
|
|
[[nodiscard]] const Buffer& buffer() const noexcept;
|
|
|
[[nodiscard]] Buffer& buffer() noexcept;
|
|
|
|
|
|
- /// Load a file into the current window
|
|
|
+ /// @brief Load a file into a buffer and display it in the active window.
|
|
|
+ /// @param path Filesystem path to load.
|
|
|
+ /// @return true if successful, false otherwise.
|
|
|
bool load_file(const std::filesystem::path& path);
|
|
|
|
|
|
- /// Create a new empty buffer in current window
|
|
|
+ /// @brief Create a new empty buffer (e.g., *scratch*) and display it.
|
|
|
+ /// @param name The name of the new buffer.
|
|
|
void new_buffer(std::string name = "*scratch*");
|
|
|
|
|
|
- /// Get list of all buffer names
|
|
|
+ /// @brief Get a list of names of all open buffers.
|
|
|
[[nodiscard]] std::vector<std::string> get_buffer_names() const;
|
|
|
|
|
|
- /// Get buffer by name (returns nullptr if not found)
|
|
|
+ /// @brief Find a buffer by its name.
|
|
|
+ /// @return Shared pointer to the buffer, or nullptr if not found.
|
|
|
[[nodiscard]] std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name);
|
|
|
|
|
|
- /// Switch active window to buffer by name
|
|
|
+ /// @brief Switch the active window to display the specified buffer.
|
|
|
+ /// @param name The name of the buffer to switch to.
|
|
|
+ /// @return true if successful.
|
|
|
bool switch_buffer_in_window(const std::string& name);
|
|
|
|
|
|
- /// Close buffer by name (returns false if buffer is displayed or doesn't exist)
|
|
|
+ /// @brief Close (kill) a buffer.
|
|
|
+ /// @param name The name of the buffer to close.
|
|
|
+ /// @return true if closed, false if it doesn't exist or is the last buffer.
|
|
|
bool close_buffer(const std::string& name);
|
|
|
|
|
|
- /// Buffer information for list display
|
|
|
+ /// @brief Structure containing summary information about a buffer.
|
|
|
struct BufferInfo {
|
|
|
std::string name;
|
|
|
size_t size;
|
|
|
@@ -118,33 +123,33 @@ public:
|
|
|
std::optional<std::filesystem::path> filepath;
|
|
|
};
|
|
|
|
|
|
- /// Get information about all buffers
|
|
|
+ /// @brief Get detailed information about all buffers.
|
|
|
[[nodiscard]] std::vector<BufferInfo> get_all_buffer_info() const;
|
|
|
|
|
|
// === Window Management ===
|
|
|
|
|
|
- /// Split the current window horizontally (active window becomes top, new one bottom)
|
|
|
+ /// @brief Split the active window horizontally (active becomes top).
|
|
|
void split_horizontally();
|
|
|
|
|
|
- /// Split the current window vertically (active window becomes left, new one right)
|
|
|
+ /// @brief Split the active window vertically (active becomes left).
|
|
|
void split_vertically();
|
|
|
|
|
|
- /// Close the current window
|
|
|
+ /// @brief Close the active window and remove it from the layout tree.
|
|
|
void close_active_window();
|
|
|
|
|
|
- /// Move focus to the next window
|
|
|
+ /// @brief Move focus to the next window in the tree traversal order.
|
|
|
void next_window();
|
|
|
|
|
|
- /// Move focus to the next window (only if called intentionally, not during rapid typing)
|
|
|
+ /// @brief Safe version of next_window (deprecated, alias for next_window).
|
|
|
void next_window_safe();
|
|
|
|
|
|
- /// Get the active window
|
|
|
+ /// @brief Get the currently focused window.
|
|
|
std::shared_ptr<Window> active_window() const { return active_window_; }
|
|
|
|
|
|
- /// Set the active window (if it exists in the window tree)
|
|
|
+ /// @brief Set the active window explicitly (must exist in the tree).
|
|
|
bool set_active_window(std::shared_ptr<Window> window);
|
|
|
|
|
|
- /// Get the root of the layout tree (for rendering)
|
|
|
+ /// @brief Get the root node of the window layout tree.
|
|
|
std::shared_ptr<LayoutNode> root_layout() const { return root_node_; }
|
|
|
|
|
|
// === Cursor Management (Proxies to active window) ===
|
|
|
@@ -179,16 +184,19 @@ public:
|
|
|
|
|
|
using EventCallback = std::function<void(EditorEvent)>;
|
|
|
|
|
|
+ /// @brief Register a callback to be notified of editor events.
|
|
|
void on_event(EventCallback callback) {
|
|
|
event_callbacks_.push_back(std::move(callback));
|
|
|
}
|
|
|
|
|
|
+ /// @brief Clear all registered event callbacks.
|
|
|
void clear_event_callbacks() {
|
|
|
event_callbacks_.clear();
|
|
|
}
|
|
|
|
|
|
// === Actions ===
|
|
|
|
|
|
+ /// @brief Request the application to quit.
|
|
|
void request_quit() {
|
|
|
emit_event(EditorEvent::Quit);
|
|
|
}
|
|
|
@@ -202,101 +210,104 @@ public:
|
|
|
|
|
|
// === Kill Ring ===
|
|
|
|
|
|
- /// Get the kill ring
|
|
|
+ /// @brief Access the global Kill Ring (clipboard history).
|
|
|
[[nodiscard]] KillRing& kill_ring() noexcept { return kill_ring_; }
|
|
|
[[nodiscard]] const KillRing& kill_ring() const noexcept { return kill_ring_; }
|
|
|
|
|
|
// === Registers ===
|
|
|
|
|
|
- /// Copy text to register
|
|
|
+ /// @brief Save text to a named register.
|
|
|
void copy_to_register(char register_name, const std::string& text);
|
|
|
|
|
|
- /// Insert text from register
|
|
|
+ /// @brief Insert text from a named register.
|
|
|
bool insert_register(char register_name);
|
|
|
|
|
|
- /// Save region to register (C-x r s)
|
|
|
+ /// @brief Copy the active region to a register (C-x r s).
|
|
|
void copy_region_to_register(char register_name);
|
|
|
|
|
|
- /// Insert register at cursor (C-x r i)
|
|
|
+ /// @brief Insert text from a register (C-x r i).
|
|
|
bool yank_from_register(char register_name);
|
|
|
|
|
|
// === Keyboard Macros ===
|
|
|
|
|
|
- /// Start recording a keyboard macro (F3)
|
|
|
+ /// @brief Start recording a keyboard macro (F3).
|
|
|
void start_kbd_macro();
|
|
|
|
|
|
- /// End macro recording or call last macro (F4)
|
|
|
+ /// @brief Stop recording or execute the last macro (F4).
|
|
|
void end_kbd_macro_or_call();
|
|
|
|
|
|
- /// Add a key sequence to the current macro being recorded
|
|
|
+ /// @brief Record a key sequence if macro recording is active.
|
|
|
void record_key_sequence(const std::string& key_sequence);
|
|
|
|
|
|
- /// Check if currently recording a macro
|
|
|
+ /// @brief Check if macro recording is currently active.
|
|
|
[[nodiscard]] bool is_recording_macro() const noexcept { return recording_macro_; }
|
|
|
|
|
|
// === Rectangles ===
|
|
|
|
|
|
- /// Kill rectangle (C-x r k) - Cut rectangular region
|
|
|
+ /// @brief Kill (cut) a rectangular region (C-x r k).
|
|
|
void kill_rectangle();
|
|
|
|
|
|
- /// Yank rectangle (C-x r y) - Paste last rectangle
|
|
|
+ /// @brief Yank (paste) the last killed rectangle (C-x r y).
|
|
|
void yank_rectangle();
|
|
|
|
|
|
- /// String rectangle (C-x r t) - Fill rectangle with string
|
|
|
+ /// @brief Replace a rectangular region with a string (C-x r t).
|
|
|
void string_rectangle(const std::string& text);
|
|
|
|
|
|
- /// Kill (cut) text from position to end of line
|
|
|
+ // === Standard Editing Commands ===
|
|
|
+
|
|
|
+ /// @brief Kill text from cursor to end of line (C-k).
|
|
|
void kill_line();
|
|
|
|
|
|
- /// Kill (cut) the active region
|
|
|
+ /// @brief Kill the text in the active region (C-w).
|
|
|
void kill_region();
|
|
|
|
|
|
- /// Copy the active region to kill ring (without deleting)
|
|
|
+ /// @brief Copy the active region to the kill ring (M-w).
|
|
|
void copy_region_as_kill();
|
|
|
|
|
|
- /// Yank (paste) from kill ring
|
|
|
+ /// @brief Yank (paste) from the kill ring (C-y).
|
|
|
void yank();
|
|
|
|
|
|
- /// Yank and pop to previous kill ring entry
|
|
|
+ /// @brief Replace the just-yanked text with the next item in kill ring (M-y).
|
|
|
void yank_pop();
|
|
|
|
|
|
- /// Kill word forward
|
|
|
+ /// @brief Kill word forward (M-d).
|
|
|
void kill_word();
|
|
|
|
|
|
- /// Kill word backward
|
|
|
+ /// @brief Kill word backward (M-Backspace).
|
|
|
void backward_kill_word();
|
|
|
|
|
|
// === Theme Management ===
|
|
|
|
|
|
- /// Get the theme manager
|
|
|
[[nodiscard]] ThemeManager& theme_manager() noexcept { return theme_manager_; }
|
|
|
[[nodiscard]] const ThemeManager& theme_manager() const noexcept { return theme_manager_; }
|
|
|
|
|
|
- /// Set active theme
|
|
|
+ /// @brief Set the active theme by name.
|
|
|
void set_theme(const std::string& name) { theme_manager_.set_active_theme(name); }
|
|
|
|
|
|
- /// Get active theme
|
|
|
+ /// @brief Get the currently active theme.
|
|
|
[[nodiscard]] std::shared_ptr<Theme> active_theme() const { return theme_manager_.active_theme(); }
|
|
|
|
|
|
// === Configuration ===
|
|
|
|
|
|
- /// Get the configuration manager
|
|
|
[[nodiscard]] Config& config() noexcept { return config_; }
|
|
|
[[nodiscard]] const Config& config() const noexcept { return config_; }
|
|
|
|
|
|
- // === Key Binding System ===
|
|
|
- [[nodiscard]] KeyBindingManager& keybinding_manager() noexcept { return keybinding_manager_; }
|
|
|
- [[nodiscard]] const KeyBindingManager& keybinding_manager() const noexcept { return keybinding_manager_; }
|
|
|
-
|
|
|
- // === Lua API ===
|
|
|
- [[nodiscard]] LuaApi* lua_api() const { return lua_api_.get(); }
|
|
|
-
|
|
|
- // === Command System ===
|
|
|
- [[nodiscard]] CommandSystem& command_system() noexcept { return *command_system_; }
|
|
|
- [[nodiscard]] const CommandSystem& command_system() const noexcept { return *command_system_; }
|
|
|
-
|
|
|
- private:
|
|
|
- // All open buffers
|
|
|
+ // === Key Binding System ===
|
|
|
+ [[nodiscard]] KeyBindingManager& keybinding_manager() noexcept { return keybinding_manager_; }
|
|
|
+ [[nodiscard]] const KeyBindingManager& keybinding_manager() const noexcept { return keybinding_manager_; }
|
|
|
+
|
|
|
+ // === Lua API ===
|
|
|
+ [[nodiscard]] LuaApi* lua_api() const { return lua_api_.get(); }
|
|
|
+
|
|
|
+ // === Command System ===
|
|
|
+ [[nodiscard]] CommandSystem& command_system() noexcept { return *command_system_; }
|
|
|
+ [[nodiscard]] const CommandSystem& command_system() const noexcept { return *command_system_; }
|
|
|
+
|
|
|
+ // === Modeline Manager ===
|
|
|
+ [[nodiscard]] ModelineManager& modeline_manager() noexcept { return modeline_manager_; }
|
|
|
+ [[nodiscard]] const ModelineManager& modeline_manager() const noexcept { return modeline_manager_; }
|
|
|
+
|
|
|
+private:
|
|
|
std::list<std::shared_ptr<Buffer>> buffers_;
|
|
|
|
|
|
// Word movement helpers
|
|
|
@@ -326,25 +337,19 @@ public:
|
|
|
std::vector<std::string> last_macro_;
|
|
|
bool recording_macro_ = false;
|
|
|
|
|
|
- // Rectangle storage (each string is one row of the rectangle)
|
|
|
+ // Rectangle storage
|
|
|
std::vector<std::string> rectangle_kill_ring_;
|
|
|
|
|
|
- // Theme manager
|
|
|
+ // Subsystems
|
|
|
ThemeManager theme_manager_;
|
|
|
-
|
|
|
- // Configuration
|
|
|
Config config_;
|
|
|
+ KeyBindingManager keybinding_manager_;
|
|
|
+ std::unique_ptr<LuaApi> lua_api_;
|
|
|
+ std::unique_ptr<CommandSystem> command_system_;
|
|
|
+ ModelineManager modeline_manager_;
|
|
|
|
|
|
- // Key binding system
|
|
|
- KeyBindingManager keybinding_manager_;
|
|
|
-
|
|
|
- // Lua API
|
|
|
- std::unique_ptr<LuaApi> lua_api_;
|
|
|
-
|
|
|
- // Command System
|
|
|
- std::unique_ptr<CommandSystem> command_system_;
|
|
|
-
|
|
|
- void emit_event(EditorEvent event);
|
|
|
+ void emit_event(EditorEvent event);
|
|
|
+
|
|
|
// Helper to find a node containing the active window
|
|
|
LayoutNode* find_parent_node(LayoutNode* root, std::shared_ptr<Window> target);
|
|
|
|
|
|
@@ -352,6 +357,7 @@ public:
|
|
|
void collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows);
|
|
|
};
|
|
|
|
|
|
+/// @brief Represents a node in the window layout tree.
|
|
|
struct LayoutNode {
|
|
|
enum class Type { Leaf, HorizontalSplit, VerticalSplit };
|
|
|
Type type;
|
|
|
@@ -362,7 +368,7 @@ struct LayoutNode {
|
|
|
// If Split
|
|
|
std::shared_ptr<LayoutNode> child1;
|
|
|
std::shared_ptr<LayoutNode> child2;
|
|
|
- float ratio = 0.5f; // For future resizing
|
|
|
+ float ratio = 0.5f; // Split ratio (0.0 to 1.0)
|
|
|
|
|
|
LayoutNode(std::shared_ptr<Window> w) : type(Type::Leaf), window(w) {}
|
|
|
LayoutNode(Type t, std::shared_ptr<LayoutNode> c1, std::shared_ptr<LayoutNode> c2)
|