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();
+ }
}