From bf025cd289ab6e5f094204d8aee070811fd99bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Mon, 23 Sep 2024 02:13:17 +0200 Subject: [PATCH] msg: allow to truncate the message to terminal width --- DOCS/interface-changes/term-clip-cc.txt | 1 + DOCS/man/input.rst | 5 ++++ common/msg.c | 37 ++++++++++++++++++++++--- common/msg.h | 2 ++ misc/codepoint_width.c | 36 ++++++++++++++++++------ misc/codepoint_width.h | 5 +++- player/command.c | 8 ++++++ sub/osd_libass.c | 4 +++ test/codepoint_width.c | 23 ++++++++++++++- 9 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 DOCS/interface-changes/term-clip-cc.txt diff --git a/DOCS/interface-changes/term-clip-cc.txt b/DOCS/interface-changes/term-clip-cc.txt new file mode 100644 index 0000000000..79c12b0310 --- /dev/null +++ b/DOCS/interface-changes/term-clip-cc.txt @@ -0,0 +1 @@ +add `term-clip-cc` diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 2be4d3d30f..08a4930065 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -3377,6 +3377,11 @@ Property list See ``--cursor-autohide``. Setting this to a new value will always update the cursor, and reset the internal timer. +``term-clip-cc`` + Inserts the symbol to force line truncation to the current terminal width. + This can be used for ``show-text`` and other OSD messages. It must be the + first character in the line. It takes effect until the end of the line. + ``osd-sym-cc`` Inserts the current OSD symbol as opaque OSD control code (cc). This makes sense only with the ``show-text`` command or options which set OSD messages. diff --git a/common/msg.c b/common/msg.c index b231986235..ca925ff72a 100644 --- a/common/msg.c +++ b/common/msg.c @@ -374,7 +374,7 @@ static bool test_terminal_level(struct mp_log *log, int lev) } static void append_terminal_line(struct mp_log *log, int lev, - bstr text, bstr *term_msg, int *line_w) + bstr text, bstr *term_msg, int *line_w, int term_w) { struct mp_log_root *root = log->root; @@ -394,8 +394,35 @@ static void append_terminal_line(struct mp_log *log, int lev, } bstr_xappend(root, term_msg, text); + + const unsigned char *cut_pos = NULL; + int width = term_disp_width(bstr_splice(*term_msg, start, term_msg->len), + term_w - 3, &cut_pos); + if (cut_pos) { + int new_len = cut_pos - term_msg->start; + bstr rem = {(unsigned char *)cut_pos, term_msg->len - new_len}; + term_msg->len = new_len; + + bstr_xappend(root, term_msg, bstr0("...")); + + while (rem.len) { + if (bstr_eatstart0(&rem, "\033[")) { + bstr_xappend(root, term_msg, bstr0("\033[")); + + while (rem.len && !((*rem.start >= '@' && *rem.start <= '~') || *rem.start == 'm')) { + bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); + rem = bstr_cut(rem, 1); + } + bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); + } + rem = bstr_cut(rem, 1); + } + + bstr_xappend(root, term_msg, bstr0("\n")); + width += 3; + } *line_w = root->isatty[term_msg_fileno(root, lev)] - ? term_disp_width(bstr_splice(*term_msg, start, term_msg->len)) : 0; + ? width : 0; } static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer) @@ -496,7 +523,8 @@ static void write_term_msg(struct mp_log *log, int lev, bstr text, bstr *out) if (print_term) { int line_w; - append_terminal_line(log, lev, line, &root->term_msg_tmp, &line_w); + append_terminal_line(log, lev, line, &root->term_msg_tmp, &line_w, + bstr_eatstart0(&line, TERM_MSG_0) ? term_w : INT_MAX); term_msg_lines += (!line_w || !term_w) ? 1 : (line_w + term_w - 1) / term_w; } @@ -506,7 +534,8 @@ static void write_term_msg(struct mp_log *log, int lev, bstr text, bstr *out) if (lev == MSGL_STATUS) { int line_w = 0; if (str.len && print_term) - append_terminal_line(log, lev, str, &root->term_msg_tmp, &line_w); + append_terminal_line(log, lev, str, &root->term_msg_tmp, &line_w, + bstr_eatstart0(&str, TERM_MSG_0) ? term_w : INT_MAX); term_msg_lines += !term_w ? (str.len ? 1 : 0) : (line_w + term_w - 1) / term_w; } else if (str.len) { diff --git a/common/msg.h b/common/msg.h index a32dffaa33..715cca99f8 100644 --- a/common/msg.h +++ b/common/msg.h @@ -25,6 +25,8 @@ #include "osdep/compiler.h" +#define TERM_MSG_0 "\xFC" + struct mp_log; // A mp_log instance that never outputs anything. diff --git a/misc/codepoint_width.c b/misc/codepoint_width.c index 2d894028e0..39808631f1 100644 --- a/misc/codepoint_width.c +++ b/misc/codepoint_width.c @@ -662,20 +662,26 @@ static int ucdToCharacterWidth(const int val) * License along with mpv. If not, see . */ -int term_disp_width(bstr str) +#include "common/common.h" + +int term_disp_width(bstr str, int max_width, const unsigned char **cut_pos) { static const int ambiguous_width = 1; int width = 0; + const unsigned char *prev_pos = str.start; while (str.len) { + int current_width = 0; + if (bstr_eatstart0(&str, "\033[")) { while (str.len && !((*str.start >= '@' && *str.start <= '~') || *str.start == 'm')) str = bstr_cut(str, 1); str = bstr_cut(str, 1); - continue; + goto next; } + prev_pos = str.start; int cp = bstr_decode_utf8(str, &str); // Stop processing on any invalid input @@ -684,18 +690,17 @@ int term_disp_width(bstr str) if (cp == '\r') { width = 0; - continue; + goto next; } if (cp < 0x20) - continue; + goto next; if (cp <= 0x7E) { - width++; - continue; + current_width = 1; + goto next; } - int grapheme_width = 0; int state = 0; while (true) { @@ -711,7 +716,7 @@ int term_disp_width(bstr str) if (cp == 0xFE0F) w = 2; - grapheme_width += w; + current_width += w; if (!str.len) break; @@ -730,7 +735,20 @@ int term_disp_width(bstr str) str = cluster_end; } - width += grapheme_width > 2 ? 2 : grapheme_width; + +next: + current_width = MPMIN(current_width, 2); + if (width + current_width > max_width) { + assert(prev_pos < str.start + str.len); + *cut_pos = prev_pos; + break; + } + width += current_width; + if (width == max_width) { + if (str.len) + *cut_pos = str.start; + break; + } } return width; diff --git a/misc/codepoint_width.h b/misc/codepoint_width.h index 6bb068fd7d..e9c8db5d74 100644 --- a/misc/codepoint_width.h +++ b/misc/codepoint_width.h @@ -6,6 +6,9 @@ * @brief Determines the number of columns required to display a given string. * * @param str Sequence of UTF-8 chars + * @param max_width Maximum allowed width of string + * @param cut_pos If max_width is exceeded, this will be initialized to last + * full printable character before width limit. * @return int width of the string */ -int term_disp_width(bstr str); +int term_disp_width(bstr str, int max_width, const unsigned char **cut_pos); diff --git a/player/command.c b/player/command.c index 7392bcf5f0..5f2b1c0f0d 100644 --- a/player/command.c +++ b/player/command.c @@ -2926,6 +2926,12 @@ static int mp_property_osd_ass(void *ctx, struct m_property *prop, return m_property_read_sub(props, action, arg); } +static int mp_property_term_clip(void *ctx, struct m_property *prop, + int action, void *arg) +{ + return m_property_strdup_ro(action, arg, TERM_MSG_0); +} + static int mp_property_term_size(void *ctx, struct m_property *prop, int action, void *arg) { @@ -4159,6 +4165,8 @@ static const struct m_property mp_properties_base[] = { {"osd-sym-cc", mp_property_osd_sym}, {"osd-ass-cc", mp_property_osd_ass}, + {"term-clip-cc", mp_property_term_clip}, + {"mouse-pos", mp_property_mouse_pos}, {"touch-pos", mp_property_touch_pos}, diff --git a/sub/osd_libass.c b/sub/osd_libass.c index 8ce203e66e..3a462e2717 100644 --- a/sub/osd_libass.c +++ b/sub/osd_libass.c @@ -207,6 +207,10 @@ void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines) in += 1; continue; } + if (*in == TERM_MSG_0[0]) { + in += 1; + continue; + } if (escape_ass && *in == '{') bstr_xappend(NULL, dst, bstr0("\\")); // Replace newlines with \N for escape-ass. This is necessary to apply diff --git a/test/codepoint_width.c b/test/codepoint_width.c index 86ae6dfe3f..8f9fcd45ab 100644 --- a/test/codepoint_width.c +++ b/test/codepoint_width.c @@ -17,9 +17,11 @@ #include "test_utils.h" +#include + #include "misc/codepoint_width.h" -#define W(s) term_disp_width((bstr)bstr0_lit(s)) +#define W(s) term_disp_width((bstr)bstr0_lit(s), INT_MAX, &(const unsigned char *){NULL}) int main(void) { assert_int_equal(W("A"), 1); // Single ASCII character @@ -64,4 +66,23 @@ int main(void) { // ASCII characters with carriage return assert_int_equal(W("ABC\rDEF"), 3); + + bstr str = bstr0("ABCDEF"); + const unsigned char *cut_pos; + + cut_pos = NULL; + assert_int_equal(term_disp_width(str, 3, &cut_pos), 3); + assert_int_equal(cut_pos - str.start, 3); + + cut_pos = NULL; + assert_int_equal(term_disp_width(str, -2, &cut_pos), 0); + assert_int_equal(cut_pos - str.start, 0); + + cut_pos = NULL; + assert_int_equal(term_disp_width(str, str.len, &cut_pos), 6); + if (cut_pos) { + printf("%s:%d: cut_pos != NULL\n", __FILE__, __LINE__); + fflush(stdout); + abort(); + } }