|
@@ -3,6 +3,7 @@
|
|
|
#include "lumacs/lua_api.hpp"
|
|
#include "lumacs/lua_api.hpp"
|
|
|
#include "lumacs/mode_activator.hpp"
|
|
#include "lumacs/mode_activator.hpp"
|
|
|
#include "lumacs/keybinding.hpp"
|
|
#include "lumacs/keybinding.hpp"
|
|
|
|
|
+#include "lumacs/cursor_blink.hpp"
|
|
|
#include <ncurses.h>
|
|
#include <ncurses.h>
|
|
|
#include <memory>
|
|
#include <memory>
|
|
|
#include <chrono>
|
|
#include <chrono>
|
|
@@ -17,7 +18,7 @@ using namespace lumacs;
|
|
|
class TuiEditor : public IEditorView {
|
|
class TuiEditor : public IEditorView {
|
|
|
public:
|
|
public:
|
|
|
TuiEditor() : core_(nullptr) {}
|
|
TuiEditor() : core_(nullptr) {}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
~TuiEditor() override {
|
|
~TuiEditor() override {
|
|
|
endwin(); // Cleanup ncurses
|
|
endwin(); // Cleanup ncurses
|
|
|
}
|
|
}
|
|
@@ -34,12 +35,9 @@ private:
|
|
|
bool should_quit_ = false;
|
|
bool should_quit_ = false;
|
|
|
std::string message_line_;
|
|
std::string message_line_;
|
|
|
int height_ = 0, width_ = 0;
|
|
int height_ = 0, width_ = 0;
|
|
|
-
|
|
|
|
|
- // For cursor blinking logic
|
|
|
|
|
- bool cursor_visible_ = true;
|
|
|
|
|
- std::chrono::steady_clock::time_point last_cursor_move_time_ = std::chrono::steady_clock::now();
|
|
|
|
|
- static constexpr std::chrono::milliseconds BLINK_INTERVAL = std::chrono::milliseconds(500);
|
|
|
|
|
- static constexpr std::chrono::milliseconds BLINK_STATIONARY_THRESHOLD = std::chrono::milliseconds(1000); // 1 second
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Cursor blinking using shared controller
|
|
|
|
|
+ CursorBlinkController cursor_blink_;
|
|
|
|
|
|
|
|
// Viewport bounds calculation (avoids code duplication)
|
|
// Viewport bounds calculation (avoids code duplication)
|
|
|
struct ViewportBounds {
|
|
struct ViewportBounds {
|
|
@@ -112,8 +110,6 @@ void TuiEditor::run() {
|
|
|
|
|
|
|
|
// Initial render
|
|
// Initial render
|
|
|
render();
|
|
render();
|
|
|
-
|
|
|
|
|
- std::chrono::steady_clock::time_point last_blink_toggle_time = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
|
|
while (!should_quit_) {
|
|
while (!should_quit_) {
|
|
|
// Handle screen resize
|
|
// Handle screen resize
|
|
@@ -126,39 +122,23 @@ void TuiEditor::run() {
|
|
|
core_->set_viewport_size(bounds.content_width, bounds.content_height);
|
|
core_->set_viewport_size(bounds.content_width, bounds.content_height);
|
|
|
spdlog::debug("Screen resized to: {}x{}", width_, height_);
|
|
spdlog::debug("Screen resized to: {}x{}", width_, height_);
|
|
|
spdlog::debug("Content area: {}x{}", bounds.content_width, bounds.content_height);
|
|
spdlog::debug("Content area: {}x{}", bounds.content_width, bounds.content_height);
|
|
|
- // Force cursor to be visible after resize, as it implies movement.
|
|
|
|
|
- last_cursor_move_time_ = std::chrono::steady_clock::now();
|
|
|
|
|
- cursor_visible_ = true;
|
|
|
|
|
- render(); // Re-render after resize
|
|
|
|
|
|
|
+ cursor_blink_.notify_cursor_moved(); // Resize implies movement
|
|
|
|
|
+ render();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Get input (with timeout)
|
|
// Get input (with timeout)
|
|
|
int ch = getch();
|
|
int ch = getch();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Only process input and render if we got actual input (not timeout)
|
|
// Only process input and render if we got actual input (not timeout)
|
|
|
if (ch != ERR) {
|
|
if (ch != ERR) {
|
|
|
- last_cursor_move_time_ = std::chrono::steady_clock::now();
|
|
|
|
|
- cursor_visible_ = true; // Ensure cursor is visible on input
|
|
|
|
|
|
|
+ cursor_blink_.notify_cursor_moved();
|
|
|
handle_input(ch);
|
|
handle_input(ch);
|
|
|
render();
|
|
render();
|
|
|
- last_blink_toggle_time = std::chrono::steady_clock::now(); // Reset blink timer after input
|
|
|
|
|
- } else { // No input (timeout occurred)
|
|
|
|
|
- auto now = std::chrono::steady_clock::now();
|
|
|
|
|
- if (now - last_cursor_move_time_ > BLINK_STATIONARY_THRESHOLD) {
|
|
|
|
|
- // If stationary for long enough, start/continue blinking
|
|
|
|
|
- if (now - last_blink_toggle_time > BLINK_INTERVAL) {
|
|
|
|
|
- cursor_visible_ = !cursor_visible_;
|
|
|
|
|
- core_->check_and_clear_message(); // Check and clear messages (like for GtkEditor's blink)
|
|
|
|
|
- render(); // Re-render to show/hide cursor
|
|
|
|
|
- last_blink_toggle_time = now;
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- // Still "moving" recently, keep cursor visible.
|
|
|
|
|
- // If it was just hidden by a blink, make it visible again.
|
|
|
|
|
- if (!cursor_visible_) {
|
|
|
|
|
- cursor_visible_ = true;
|
|
|
|
|
- render(); // Make sure it's visible
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // No input (timeout) - update blink state
|
|
|
|
|
+ if (cursor_blink_.update()) {
|
|
|
|
|
+ core_->check_and_clear_message();
|
|
|
|
|
+ render();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -173,8 +153,7 @@ void TuiEditor::handle_editor_event(EditorEvent event) {
|
|
|
message_line_ = core_->last_message();
|
|
message_line_ = core_->last_message();
|
|
|
return;
|
|
return;
|
|
|
} else if (event == EditorEvent::CursorMoved) {
|
|
} else if (event == EditorEvent::CursorMoved) {
|
|
|
- last_cursor_move_time_ = std::chrono::steady_clock::now();
|
|
|
|
|
- cursor_visible_ = true;
|
|
|
|
|
|
|
+ cursor_blink_.notify_cursor_moved();
|
|
|
render();
|
|
render();
|
|
|
return;
|
|
return;
|
|
|
} else if (event == EditorEvent::TransientMessageCleared) {
|
|
} else if (event == EditorEvent::TransientMessageCleared) {
|
|
@@ -355,7 +334,7 @@ void TuiEditor::render() {
|
|
|
curs_set(2); // Ensure visible (high visibility)
|
|
curs_set(2); // Ensure visible (high visibility)
|
|
|
} else {
|
|
} else {
|
|
|
// Minibuffer inactive: place at buffer cursor
|
|
// Minibuffer inactive: place at buffer cursor
|
|
|
- if (hw_cursor_x_ != -1 && hw_cursor_y_ != -1 && cursor_visible_) {
|
|
|
|
|
|
|
+ if (hw_cursor_x_ != -1 && hw_cursor_y_ != -1 && cursor_blink_.is_visible()) {
|
|
|
move(hw_cursor_y_, hw_cursor_x_);
|
|
move(hw_cursor_y_, hw_cursor_x_);
|
|
|
curs_set(2); // High visibility
|
|
curs_set(2); // High visibility
|
|
|
} else {
|
|
} else {
|
|
@@ -543,7 +522,7 @@ void TuiEditor::render_window(std::shared_ptr<Window> window, int x, int y, int
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Show cursor if this is the cursor line and this is the active window
|
|
// Show cursor if this is the cursor line and this is the active window
|
|
|
- if (buffer_line_idx == cursor.line && is_active && cursor_visible_) {
|
|
|
|
|
|
|
+ if (buffer_line_idx == cursor.line && is_active && cursor_blink_.is_visible()) {
|
|
|
int cursor_screen_x = x + line_number_width + cursor.column;
|
|
int cursor_screen_x = x + line_number_width + cursor.column;
|
|
|
if (cursor_screen_x < x + width) {
|
|
if (cursor_screen_x < x + width) {
|
|
|
char cursor_char = ' ';
|
|
char cursor_char = ' ';
|