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 From ca6a58063a1c164d72af820b40cf18a19ec204f3 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 27 Jun 2025 23:00:34 +0800 Subject: fix: prevent cursor rendering conflicts by simplifying cursor positioning logic --- src/highlighters.cc | 10 ---------- src/terminal_ui.cc | 46 ++++++++++++---------------------------------- src/terminal_ui.hh | 4 ---- 3 files changed, 12 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/highlighters.cc b/src/highlighters.cc index 6ae0a57b..25f93245 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -16,7 +16,6 @@ #include "regex.hh" #include "register_manager.hh" #include "string.hh" -#include "terminal_ui.hh" #include "utf8.hh" #include "utf8_iterator.hh" #include "window.hh" @@ -1291,20 +1290,11 @@ 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/terminal_ui.cc b/src/terminal_ui.cc index de332677..7d01117f 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -539,29 +539,10 @@ void TerminalUI::redraw(bool force) format_with(writer, "\033[{};{}H", (int)c.line + 1, (int)c.column + 1); }; - DisplayCoord target_pos; if (m_cursor.mode == CursorMode::Prompt) - target_pos = {m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column}; + set_cursor_pos({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column}); else - 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); - } + set_cursor_pos(m_cursor.coord + content_line_offset()); } void TerminalUI::set_cursor(CursorMode mode, DisplayCoord coord) @@ -1504,17 +1485,13 @@ 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 - ); - write(STDOUT_FILENO, cursor_cmd); // show or hide cursor based on mode - write(STDOUT_FILENO, + "\033[?25l" // hide cursor "\033=" // set application keypad mode, so the keypad keys send unique codes "\033[?2004h" // force enable bracketed-paste events ); @@ -1524,13 +1501,7 @@ void TerminalUI::restore_terminal() { write(STDOUT_FILENO, "\033>" - ); - - // 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[?25h" "\033[23t" "\033[4;0m" @@ -1604,7 +1575,14 @@ 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); + + bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); + if (new_cursor_native != m_terminal_cursor_native) + { + m_terminal_cursor_native = new_cursor_native; + // Emit cursor visibility command when the option changes + write(STDOUT_FILENO, m_terminal_cursor_native ? "\033[?25h" : "\033[?25l"); + } 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 6ad78bf9..5869515c 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -61,8 +61,6 @@ public: static void restore_terminal(); void suspend(); - - bool is_cursor_native() const { return m_terminal_cursor_native; } struct Rect { @@ -136,8 +134,6 @@ private: CursorMode mode; DisplayCoord coord; } m_cursor; - - struct Cursor m_prev_cursor; FDWatcher m_stdin_watcher; OnKeyCallback m_on_key; -- cgit v1.2.3 From 549a5d2c223d422390795741537b150b492a3935 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 27 Jun 2025 23:08:09 +0800 Subject: chore: remove terminal option --- src/main.cc | 1 - src/terminal_ui.cc | 11 +++-------- src/terminal_ui.hh | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/main.cc b/src/main.cc index 09101341..bf382dd2 100644 --- a/src/main.cc +++ b/src/main.cc @@ -604,7 +604,6 @@ 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 7d01117f..8b353b4e 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -1575,14 +1575,9 @@ 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); - - bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); - if (new_cursor_native != m_terminal_cursor_native) - { - m_terminal_cursor_native = new_cursor_native; - // Emit cursor visibility command when the option changes - write(STDOUT_FILENO, m_terminal_cursor_native ? "\033[?25h" : "\033[?25l"); - } + + // Emit cursor show sequence + write(STDOUT_FILENO, "\033[?25h"); 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 5869515c..02085532 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -167,7 +167,6 @@ private: Codepoint m_padding_char = '~'; bool m_padding_fill = false; - bool m_terminal_cursor_native = false; bool m_dirty = false; -- cgit v1.2.3 From 58414edb7b9b3d4cfde27a37ebec7f3906025675 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 27 Jun 2025 23:08:54 +0800 Subject: fix: revert new line changes --- src/terminal_ui.cc | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index 8b353b4e..e5eb6c26 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -538,7 +538,6 @@ void TerminalUI::redraw(bool force) auto set_cursor_pos = [&](DisplayCoord c) { format_with(writer, "\033[{};{}H", (int)c.line + 1, (int)c.column + 1); }; - if (m_cursor.mode == CursorMode::Prompt) set_cursor_pos({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column}); else -- cgit v1.2.3 From 883a6cb987a4fa3fd040bb865f24d23d1ff4622e Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sat, 28 Jun 2025 11:22:21 +0800 Subject: Revert "chore: remove terminal option" This reverts commit 549a5d2c223d422390795741537b150b492a3935. --- src/main.cc | 1 + src/terminal_ui.cc | 11 ++++++++--- src/terminal_ui.hh | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') 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 e5eb6c26..c5562a4c 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -1574,9 +1574,14 @@ 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); - - // Emit cursor show sequence - write(STDOUT_FILENO, "\033[?25h"); + + bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); + if (new_cursor_native != m_terminal_cursor_native) + { + m_terminal_cursor_native = new_cursor_native; + // Emit cursor visibility command when the option changes + write(STDOUT_FILENO, m_terminal_cursor_native ? "\033[?25h" : "\033[?25l"); + } 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..5869515c 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -167,6 +167,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 From de79206356f15f641c34dc53c99c03a7de21828d Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sat, 28 Jun 2025 12:26:15 +0800 Subject: fix: remove comment and document usage --- src/terminal_ui.cc | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index c5562a4c..39192013 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -1579,7 +1579,6 @@ void TerminalUI::set_ui_options(const Options& options) if (new_cursor_native != m_terminal_cursor_native) { m_terminal_cursor_native = new_cursor_native; - // Emit cursor visibility command when the option changes write(STDOUT_FILENO, m_terminal_cursor_native ? "\033[?25h" : "\033[?25l"); } -- cgit v1.2.3 From 0d20aea4284cbbe436fa7599c70a1c87c4601803 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sat, 28 Jun 2025 16:31:25 +0800 Subject: fix: simplify variable by removing terminal_ part --- src/terminal_ui.cc | 6 +++--- src/terminal_ui.hh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index 39192013..c55d0763 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -1576,10 +1576,10 @@ void TerminalUI::set_ui_options(const Options& options) m_padding_fill = find("terminal_padding_fill").map(to_bool).value_or(false); bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); - if (new_cursor_native != m_terminal_cursor_native) + if (new_cursor_native != m_cursor_native) { - m_terminal_cursor_native = new_cursor_native; - write(STDOUT_FILENO, m_terminal_cursor_native ? "\033[?25h" : "\033[?25l"); + m_cursor_native = new_cursor_native; + write(STDOUT_FILENO, m_cursor_native ? "\033[?25h" : "\033[?25l"); } 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 5869515c..064edafe 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -167,7 +167,7 @@ private: Codepoint m_padding_char = '~'; bool m_padding_fill = false; - bool m_terminal_cursor_native = false; + bool m_cursor_native = false; bool m_dirty = false; -- cgit v1.2.3