From 6a19bb0493f17c10329eefd411f4c58ae9381a90 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Tue, 24 Jun 2025 20:36:50 +0800 Subject: feat(terminal): add option for native terminal cursor rendering --- src/highlighters.cc | 10 ++++++++++ src/main.cc | 1 + src/terminal_ui.cc | 39 +++++++++++++++++++++++++++++++++++---- src/terminal_ui.hh | 5 +++++ 4 files changed, 51 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/highlighters.cc b/src/highlighters.cc index 25f93245..6ae0a57b 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -16,6 +16,7 @@ #include "regex.hh" #include "register_manager.hh" #include "string.hh" +#include "terminal_ui.hh" #include "utf8.hh" #include "utf8_iterator.hh" #include "window.hh" @@ -1290,11 +1291,20 @@ void highlight_selections(HighlightContext context, DisplayBuffer& display_buffe highlight_range(display_buffer, begin, end, false, apply_face(sel_faces[primary ? 0 : 1])); } + // Check if we should skip drawing the primary cursor (for terminal cursor native mode) + const bool skip_primary_cursor = TerminalUI::has_instance() && + TerminalUI::instance().is_cursor_native(); + for (size_t i = 0; i < selections.size(); ++i) { auto& sel = selections[i]; const BufferCoord coord = sel.cursor(); const bool primary = (i == selections.main_index()); + + // Skip drawing primary cursor if terminal_cursor_native is enabled + if (primary && skip_primary_cursor) + continue; + const bool eol = buffer[coord.line].length() - 1 == coord.column; highlight_range(display_buffer, coord, buffer.char_next(coord), false, apply_face(sel_faces[2 + (eol ? 2 : 0) + (primary ? 0 : 1)])); diff --git a/src/main.cc b/src/main.cc index bf382dd2..09101341 100644 --- a/src/main.cc +++ b/src/main.cc @@ -604,6 +604,7 @@ void register_options() " terminal_shift_function_key int\n" " terminal_padding_char codepoint\n" " terminal_padding_fill bool\n" + " terminal_cursor_native bool\n" " terminal_info_max_width int\n", UserInterface::Options{}); reg.declare_option("modelinefmt", "format string used to generate the modeline", diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index 672626e4..de332677 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -538,10 +538,30 @@ void TerminalUI::redraw(bool force) auto set_cursor_pos = [&](DisplayCoord c) { format_with(writer, "\033[{};{}H", (int)c.line + 1, (int)c.column + 1); }; + + DisplayCoord target_pos; if (m_cursor.mode == CursorMode::Prompt) - set_cursor_pos({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column}); + target_pos = {m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column}; else - set_cursor_pos(m_cursor.coord + content_line_offset()); + target_pos = m_cursor.coord + content_line_offset(); + + if (m_terminal_cursor_native) + { + // Always position cursor in native mode to ensure it's correct after screen updates + set_cursor_pos(target_pos); + + // Only send show cursor command when cursor changes or on force refresh + if (m_cursor.mode != m_prev_cursor.mode || m_cursor.coord != m_prev_cursor.coord || force) + { + format_with(writer, "\033[?25h"); // ensure cursor is visible + m_prev_cursor = m_cursor; + } + } + else + { + // Always position cursor (original behavior) + set_cursor_pos(target_pos); + } } void TerminalUI::set_cursor(CursorMode mode, DisplayCoord coord) @@ -1484,13 +1504,17 @@ void TerminalUI::set_resize_pending() void TerminalUI::setup_terminal() { + const char* cursor_cmd = instance().m_terminal_cursor_native ? "\033[?25h" : "\033[?25l"; + write(STDOUT_FILENO, "\033[?1049h" // enable alternative screen buffer "\033[?1004h" // enable focus notify "\033[>4;1m" // request CSI u style key reporting "\033[>5u" // kitty progressive enhancement - report shifted key codes "\033[22t" // save the current window title - "\033[?25l" // hide cursor + ); + write(STDOUT_FILENO, cursor_cmd); // show or hide cursor based on mode + write(STDOUT_FILENO, "\033=" // set application keypad mode, so the keypad keys send unique codes "\033[?2004h" // force enable bracketed-paste events ); @@ -1500,7 +1524,13 @@ void TerminalUI::restore_terminal() { write(STDOUT_FILENO, "\033>" - "\033[?25h" + ); + + // Only restore cursor visibility if it was hidden (non-native mode) + if (not instance().m_terminal_cursor_native) + write(STDOUT_FILENO, "\033[?25h"); + + write(STDOUT_FILENO, "\033[23t" "\033[4;0m" @@ -1574,6 +1604,7 @@ void TerminalUI::set_ui_options(const Options& options) m_padding_char = find("terminal_padding_char").map([](StringView s) { return s.column_length() < 1 ? ' ' : s[0_char]; }).value_or(Codepoint{'~'}); m_padding_fill = find("terminal_padding_fill").map(to_bool).value_or(false); + m_terminal_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); m_info_max_width = find("terminal_info_max_width").map(str_to_int_ifp).value_or(0); } diff --git a/src/terminal_ui.hh b/src/terminal_ui.hh index 02085532..6ad78bf9 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -61,6 +61,8 @@ public: static void restore_terminal(); void suspend(); + + bool is_cursor_native() const { return m_terminal_cursor_native; } struct Rect { @@ -134,6 +136,8 @@ private: CursorMode mode; DisplayCoord coord; } m_cursor; + + struct Cursor m_prev_cursor; FDWatcher m_stdin_watcher; OnKeyCallback m_on_key; @@ -167,6 +171,7 @@ private: Codepoint m_padding_char = '~'; bool m_padding_fill = false; + bool m_terminal_cursor_native = false; bool m_dirty = false; -- cgit v1.2.3