|
|
@@ -171,10 +171,7 @@ private:
|
|
|
size_t history_index_ = 0;
|
|
|
|
|
|
// Prefix handling
|
|
|
- bool waiting_for_prefix_ = false;
|
|
|
- std::string prefix_key_;
|
|
|
- std::chrono::steady_clock::time_point prefix_time_;
|
|
|
- static constexpr auto PREFIX_TIMEOUT = std::chrono::milliseconds(1000);
|
|
|
+ // Old prefix system removed - now handled by KeyBindingManager
|
|
|
|
|
|
// Meta key handling
|
|
|
bool waiting_for_meta_ = false;
|
|
|
@@ -651,110 +648,84 @@ private:
|
|
|
}
|
|
|
|
|
|
bool process_key(const std::string& key_name) {
|
|
|
- // Check for expired prefix
|
|
|
- if (waiting_for_prefix_) {
|
|
|
- auto now = std::chrono::steady_clock::now();
|
|
|
- if (now - prefix_time_ > PREFIX_TIMEOUT) {
|
|
|
- debug_log << "Prefix timeout, clearing" << std::endl;
|
|
|
- waiting_for_prefix_ = false;
|
|
|
- prefix_key_.clear();
|
|
|
- message_line_ = "Prefix timeout";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Handle prefix sequences
|
|
|
- std::string final_key_name = key_name;
|
|
|
- if (waiting_for_prefix_) {
|
|
|
- final_key_name = prefix_key_ + " " + key_name;
|
|
|
- waiting_for_prefix_ = false;
|
|
|
- prefix_key_.clear();
|
|
|
- debug_log << "Composite key: " << final_key_name << std::endl;
|
|
|
- }
|
|
|
-
|
|
|
- // Check if this key should start a prefix
|
|
|
- if (key_name == "C-x" && !waiting_for_prefix_) {
|
|
|
- waiting_for_prefix_ = true;
|
|
|
- prefix_key_ = "C-x";
|
|
|
- prefix_time_ = std::chrono::steady_clock::now();
|
|
|
- message_line_ = "C-x-";
|
|
|
- debug_log << "Starting C-x prefix" << std::endl;
|
|
|
- return true;
|
|
|
- }
|
|
|
+ debug_log << "Processing key: " << key_name << std::endl;
|
|
|
|
|
|
- // Show what we're trying to bind
|
|
|
- message_line_ = "Key: " + final_key_name;
|
|
|
-
|
|
|
- // Try Lua key binding first
|
|
|
- debug_log << "Trying Lua binding for: " << final_key_name << std::endl;
|
|
|
- bool has_lua_binding = lua_api_->has_key_binding(final_key_name);
|
|
|
- debug_log << "Has Lua binding: " << (has_lua_binding ? "yes" : "no") << std::endl;
|
|
|
-
|
|
|
- if (lua_api_->execute_key_binding(final_key_name)) {
|
|
|
- debug_log << "Lua binding executed successfully" << std::endl;
|
|
|
- // Record the key if we're recording a macro (but not if it's F3/F4 themselves)
|
|
|
- if (final_key_name != "F3" && final_key_name != "F4") {
|
|
|
- core_->record_key_sequence(final_key_name);
|
|
|
- }
|
|
|
- return true;
|
|
|
+ // Use the new keybinding system
|
|
|
+ KeyResult result = lua_api_->process_key(key_name);
|
|
|
+
|
|
|
+ switch (result) {
|
|
|
+ case KeyResult::Executed:
|
|
|
+ debug_log << "Key binding executed successfully" << std::endl;
|
|
|
+ message_line_.clear(); // Clear any partial sequence display
|
|
|
+ // Record the key if we're recording a macro (but not if it's F3/F4 themselves)
|
|
|
+ if (key_name != "F3" && key_name != "F4") {
|
|
|
+ core_->record_key_sequence(key_name);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+
|
|
|
+ case KeyResult::Failed:
|
|
|
+ debug_log << "Key binding execution failed" << std::endl;
|
|
|
+ message_line_ = "Command failed";
|
|
|
+ return true; // Key was handled, even though it failed
|
|
|
+
|
|
|
+ case KeyResult::Partial:
|
|
|
+ // Building a multi-key sequence
|
|
|
+ message_line_ = core_->keybinding_manager().current_sequence_display();
|
|
|
+ debug_log << "Partial sequence: " << message_line_ << std::endl;
|
|
|
+ return true;
|
|
|
+
|
|
|
+ case KeyResult::Timeout:
|
|
|
+ debug_log << "Key sequence timed out" << std::endl;
|
|
|
+ message_line_ = "Sequence timeout";
|
|
|
+ // Fall through to try fallback bindings
|
|
|
+ break;
|
|
|
+
|
|
|
+ case KeyResult::Unbound:
|
|
|
+ debug_log << "No key binding found, trying C++ fallbacks" << std::endl;
|
|
|
+ // Fall through to C++ fallback bindings
|
|
|
+ break;
|
|
|
}
|
|
|
- debug_log << "No Lua binding executed, trying C++ fallbacks" << std::endl;
|
|
|
|
|
|
- // C++ fallback bindings (using final_key_name for composite keys)
|
|
|
+ // Clear any sequence display since we're not in a partial state
|
|
|
+ message_line_.clear();
|
|
|
+
|
|
|
+ // C++ fallback bindings - these should eventually be moved to Lua
|
|
|
|
|
|
// Quit
|
|
|
- if (final_key_name == "C-q") {
|
|
|
+ if (key_name == "C-q") {
|
|
|
core_->request_quit();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// Command mode
|
|
|
- if (final_key_name == "M-x" || final_key_name == ":") {
|
|
|
+ if (key_name == "M-x" || key_name == ":") {
|
|
|
mode_ = Mode::Command;
|
|
|
command_buffer_.clear();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- // File operations
|
|
|
- if (final_key_name == "C-x C-f") {
|
|
|
- mode_ = Mode::FindFile;
|
|
|
- command_buffer_.clear();
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // Navigation
|
|
|
- if (final_key_name == "ArrowUp") {
|
|
|
- auto before = core_->cursor();
|
|
|
- core_->move_up();
|
|
|
- auto after = core_->cursor();
|
|
|
- debug_log << "ArrowUp: cursor moved from (" << before.line << "," << before.column << ") to (" << after.line << "," << after.column << ")" << std::endl;
|
|
|
+ // Navigation fallbacks (these should be in Lua)
|
|
|
+ if (key_name == "ArrowUp") {
|
|
|
+ core_->move_up();
|
|
|
return true;
|
|
|
}
|
|
|
- if (final_key_name == "ArrowDown") {
|
|
|
- auto before = core_->cursor();
|
|
|
- core_->move_down();
|
|
|
- auto after = core_->cursor();
|
|
|
- debug_log << "ArrowDown: cursor moved from (" << before.line << "," << before.column << ") to (" << after.line << "," << after.column << ")" << std::endl;
|
|
|
+ if (key_name == "ArrowDown") {
|
|
|
+ core_->move_down();
|
|
|
return true;
|
|
|
}
|
|
|
- if (final_key_name == "ArrowLeft") {
|
|
|
- auto before = core_->cursor();
|
|
|
- core_->move_left();
|
|
|
- auto after = core_->cursor();
|
|
|
- debug_log << "ArrowLeft: cursor moved from (" << before.line << "," << before.column << ") to (" << after.line << "," << after.column << ")" << std::endl;
|
|
|
+ if (key_name == "ArrowLeft") {
|
|
|
+ core_->move_left();
|
|
|
return true;
|
|
|
}
|
|
|
- if (final_key_name == "ArrowRight") {
|
|
|
- auto before = core_->cursor();
|
|
|
- core_->move_right();
|
|
|
- auto after = core_->cursor();
|
|
|
- debug_log << "ArrowRight: cursor moved from (" << before.line << "," << before.column << ") to (" << after.line << "," << after.column << ")" << std::endl;
|
|
|
+ if (key_name == "ArrowRight") {
|
|
|
+ core_->move_right();
|
|
|
return true;
|
|
|
}
|
|
|
- if (final_key_name == "Home") { core_->move_to_line_start(); return true; }
|
|
|
- if (final_key_name == "End") { core_->move_to_line_end(); return true; }
|
|
|
+ if (key_name == "Home") { core_->move_to_line_start(); return true; }
|
|
|
+ if (key_name == "End") { core_->move_to_line_end(); return true; }
|
|
|
|
|
|
- // Editing
|
|
|
- if (final_key_name == "Backspace") {
|
|
|
+ // Editing fallbacks (these should also be in Lua)
|
|
|
+ if (key_name == "Backspace") {
|
|
|
auto cursor = core_->cursor();
|
|
|
core_->buffer().erase_char(cursor);
|
|
|
if (cursor.column > 0) {
|
|
|
@@ -766,7 +737,7 @@ private:
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- if (final_key_name == "Delete") {
|
|
|
+ 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});
|
|
|
@@ -776,14 +747,14 @@ private:
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- if (final_key_name == "Return") {
|
|
|
+ if (key_name == "Return") {
|
|
|
auto cursor = core_->cursor();
|
|
|
core_->buffer().insert_newline(cursor);
|
|
|
core_->set_cursor({cursor.line + 1, 0});
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- if (final_key_name == "Tab") {
|
|
|
+ if (key_name == "Tab") {
|
|
|
auto cursor = core_->cursor();
|
|
|
core_->buffer().insert(cursor, " ");
|
|
|
core_->set_cursor({cursor.line, cursor.column + 4});
|
|
|
@@ -869,20 +840,24 @@ private:
|
|
|
|
|
|
void render() {
|
|
|
// Clear and update screen info
|
|
|
- clear();
|
|
|
getmaxyx(stdscr, height_, width_);
|
|
|
|
|
|
- // Calculate content area (leave room for status and message lines)
|
|
|
- int content_height = height_ - 2;
|
|
|
+ // Set background color from theme
|
|
|
+ auto theme = core_->active_theme();
|
|
|
+ if (theme) {
|
|
|
+ int bg_color_pair = theme->get_color_pair(ThemeElement::Background);
|
|
|
+ bkgd(bg_color_pair);
|
|
|
+ }
|
|
|
+ clear();
|
|
|
+
|
|
|
+ // Calculate content area (leave room for message line only)
|
|
|
+ int content_height = height_ - 1;
|
|
|
int content_width = width_;
|
|
|
|
|
|
- // Render the layout tree recursively
|
|
|
+ // Render the layout tree recursively (now includes per-window modelines)
|
|
|
render_layout_node(core_->root_layout(), 0, 0, content_width, content_height);
|
|
|
|
|
|
- // Status line (second to last line)
|
|
|
- render_status_line();
|
|
|
-
|
|
|
- // Message/command line (last line)
|
|
|
+ // Global message/command line (last line)
|
|
|
render_message_line();
|
|
|
|
|
|
// Refresh screen
|
|
|
@@ -915,13 +890,16 @@ private:
|
|
|
void render_window(std::shared_ptr<Window> window, int x, int y, int width, int height) {
|
|
|
if (!window) return;
|
|
|
|
|
|
- // Check configuration for line numbers
|
|
|
+ // Check configuration for line numbers and modeline
|
|
|
bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
|
|
|
+ bool show_modeline = core_->config().get<bool>("show_modeline", true);
|
|
|
int line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
|
|
|
+ int modeline_height = show_modeline ? 1 : 0;
|
|
|
|
|
|
- // Update window viewport size
|
|
|
+ // Update window viewport size (reserve space for modeline)
|
|
|
int content_width = width - line_number_width;
|
|
|
- window->set_viewport_size(content_width, height);
|
|
|
+ int content_height = height - modeline_height;
|
|
|
+ window->set_viewport_size(content_width, content_height);
|
|
|
|
|
|
// Get window data
|
|
|
const auto& buffer = window->buffer();
|
|
|
@@ -935,7 +913,7 @@ private:
|
|
|
<< " active=" << is_active << std::endl;
|
|
|
|
|
|
// Render buffer lines
|
|
|
- for (int screen_y = 0; screen_y < height && start_line + screen_y < end_line; ++screen_y) {
|
|
|
+ for (int screen_y = 0; screen_y < content_height && start_line + screen_y < end_line; ++screen_y) {
|
|
|
size_t buffer_line_idx = start_line + screen_y;
|
|
|
const auto& line_text = buffer.line(buffer_line_idx);
|
|
|
|
|
|
@@ -1057,12 +1035,17 @@ private:
|
|
|
}
|
|
|
|
|
|
// Fill remaining lines (for empty lines below buffer) - no tildes
|
|
|
- size_t displayed_lines = std::min((size_t)height, end_line - start_line);
|
|
|
- for (int screen_y = displayed_lines; screen_y < height; ++screen_y) {
|
|
|
+ size_t displayed_lines = std::min((size_t)content_height, end_line - start_line);
|
|
|
+ for (int screen_y = displayed_lines; screen_y < content_height; ++screen_y) {
|
|
|
move(y + screen_y, x);
|
|
|
for (int i = 0; i < width; ++i) addch(' ');
|
|
|
}
|
|
|
|
|
|
+ // Render modeline for this window
|
|
|
+ if (show_modeline) {
|
|
|
+ render_window_modeline(window, x, y + content_height, width, is_active);
|
|
|
+ }
|
|
|
+
|
|
|
// Draw window border if there are multiple windows
|
|
|
if (is_active && core_->root_layout()->type != LayoutNode::Type::Leaf) {
|
|
|
// Draw a simple border around the active window
|
|
|
@@ -1079,6 +1062,65 @@ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clear the modeline
|
|
|
+ move(y, x);
|
|
|
+ for (int i = 0; i < width; ++i) addch(' ');
|
|
|
+
|
|
|
+ // Create modeline content
|
|
|
+ std::string modeline;
|
|
|
+
|
|
|
+ // Buffer name and modification status
|
|
|
+ modeline += buffer.name();
|
|
|
+ if (buffer.is_modified()) modeline += " [+]";
|
|
|
+
|
|
|
+ // Cursor position
|
|
|
+ modeline += " | " + std::to_string(cursor.line + 1) + ":" + std::to_string(cursor.column + 1);
|
|
|
+
|
|
|
+ // Major mode (if available)
|
|
|
+ // TODO: Add major mode support when available
|
|
|
+
|
|
|
+ // Right-aligned content (percentage through file)
|
|
|
+ std::string right_side;
|
|
|
+ if (buffer.line_count() > 0) {
|
|
|
+ int percentage = (cursor.line * 100) / (buffer.line_count() - 1);
|
|
|
+ right_side = " " + std::to_string(percentage) + "%";
|
|
|
+ }
|
|
|
+
|
|
|
+ // Truncate modeline if too long
|
|
|
+ int available_width = width - right_side.length();
|
|
|
+ if ((int)modeline.length() > available_width) {
|
|
|
+ modeline = modeline.substr(0, available_width - 3) + "...";
|
|
|
+ }
|
|
|
+
|
|
|
+ // Render left side
|
|
|
+ mvprintw(y, x, "%s", modeline.c_str());
|
|
|
+
|
|
|
+ // Render right side
|
|
|
+ if (!right_side.empty()) {
|
|
|
+ mvprintw(y, x + width - right_side.length(), "%s", right_side.c_str());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Turn off modeline attributes
|
|
|
+ if (theme) {
|
|
|
+ attroff(theme->get_color_pair(modeline_element));
|
|
|
+ } else {
|
|
|
+ attroff(is_active ? A_REVERSE : A_DIM);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
void render_status_line() {
|
|
|
const auto cursor = core_->cursor();
|
|
|
const auto& buffer = core_->buffer();
|