Skip to content

Commit

Permalink
stats.lua: truncate long lines for the terminal
Browse files Browse the repository at this point in the history
The terminal is assumed to be 80x24 in size, the new options
`term_width_limit` and `term_height_limit` can be used to overwrite
that.

Lines longer then the terminal width cause problems with scrolling
pages and need to be shortened.

The algorithm used for shortening can deal with tabs and escape
sequences, has rudimentary support for UTF-8 and runs in O(n).

avih helped in the creation of the term_ellipsis() function and split()
is also from him.
  • Loading branch information
christoph-heinrich authored and kasper93 committed Mar 21, 2024
1 parent 344ac55 commit 777f69b
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 30 deletions.
12 changes: 12 additions & 0 deletions DOCS/man/stats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ Configurable Options
respective duration. This can result in overlapping text when multiple
scripts decide to print text at the same time.

``term_width_limit``
Default: -1

Sets the terminal width.
A value of 0 means the width is infinite, -1 means it's automatic.

``term_height_limit``
Default: -1

Sets the terminal height.
A value of 0 means the height is infinite, -1 means it's automatic.

``plot_perfdata``
Default: yes

Expand Down
131 changes: 101 additions & 30 deletions player/lua/stats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ local o = {
persistent_overlay = false, -- whether the stats can be overwritten by other output
print_perfdata_passes = false, -- when true, print the full information about all passes
filter_params_max_length = 100, -- a filter list longer than this many characters will be shown one filter per line instead
<<<<<<< HEAD
show_frame_info = false, -- whether to show the current frame info
=======
term_width_limit = -1, -- overwrites the terminal width
term_height_limit = -1, -- overwrites the terminal height
>>>>>>> 89493c38fb (stats.lua: truncate long lines for the terminal)
debug = false,

-- Graph options and style
Expand Down Expand Up @@ -83,6 +88,15 @@ local o = {
}
options.read_options(o)

o.term_width_limit = tonumber(o.term_width_limit) or -1
o.term_height_limit = tonumber(o.term_height_limit) or -1
if o.term_width_limit < 0 then
o.term_width_limit = nil
end
if o.term_height_limit < 0 then
o.term_height_limit = nil
end

local format = string.format
local max = math.max
local min = math.min
Expand Down Expand Up @@ -366,7 +380,7 @@ end

local function ellipsis(s, maxlen)
if not maxlen or s:len() <= maxlen then return s end
return s:sub(1, maxlen - 3) .. "..."
return s:sub(1, max(0, maxlen - 3)) .. "..."
end

-- command prefix tokens to strip - includes generic property commands
Expand Down Expand Up @@ -994,24 +1008,89 @@ local function eval_ass_formatting()
end
end

-- assumptions:
-- s is composed of SGR escape sequences and/or printable UTF8 sequences
-- printable codepoints occupy one terminal cell (we don't have wcwidth)
-- tabstops are 8, 16, 24..., and the output starts at 0 or a tabstop
-- note: if maxwidth <= 2 and s doesn't fit: the result is "..." (more than 2)
function term_ellipsis(s, maxwidth)
local TAB, ESC, SGR_END = 9, 27, ("m"):byte()
local width, ellipsis = 0, "..."
local fit_len, in_sgr

for i = 1, #s do
local x = s:byte(i)

if in_sgr then
in_sgr = x ~= SGR_END
elseif x == ESC then
in_sgr = true
ellipsis = "\27[0m..." -- ensure SGR reset
elseif x < 128 or x >= 192 then -- non UTF8-continuation
-- tab adds till the next stop, else add 1
width = width + (x == TAB and 8 - width % 8 or 1)

if fit_len == nil and width > maxwidth - 3 then
fit_len = i - 1 -- adding "..." still fits maxwidth
end
if width > maxwidth then
return s:sub(1, fit_len) .. ellipsis
end
end
end

return s
end

local function term_ellipsis_array(arr, from, to, max_width)
for i = from, to do
arr[i] = term_ellipsis(arr[i], max_width)
end
return arr
end

-- split str into a table
-- example: local t = split(s, "\n")
-- plain: whether pat is a plain string (default false - pat is a pattern)
local function split(str, pat, plain)
local init = 1
local r, i, find, sub = {}, 1, string.find, string.sub
repeat
local f0, f1 = find(str, pat, init, plain)
r[i], i = sub(str, init, f0 and f0 - 1), i+1
init = f0 and f1 + 1
until f0 == nil
return r
end

-- Composes the output with header and scrollable content
-- Returns string of the finished page and the actually chosen offset
--
-- header : table of the header where each entry is one line
-- content : table of the content where each entry is one line
-- offset : the desired scroll offset of the content from the first line at the top
local function compose_page(header, content, offset)
-- up to 22 lines for the terminal - so that mpv can also print
-- the status line without scrolling, and up to 40 lines for libass
-- because it can put a big performance toll on libass to process
-- many lines which end up outside (below) the screen.
local max_content_lines = (o.use_ass and 40 or 22) - #header
-- in the terminal the scrolling should stop once the last line is visible
local max_offset = o.use_ass and #content or #content - max_content_lines + 1
local from = max(1, min((offset or 1), max_offset))
local to = min(#content, from + max_content_lines - 1)
return table.concat(header) .. table.concat(content, "", from, to), from
-- header : table of the header where each entry is one line
-- content : table of the content where each entry is one line
-- apply_scroll: scroll the content
local function finalize_page(header, content, apply_scroll)
local term_width = o.term_width_limit or 80
local term_height = o.term_height_limit or 24
local from, to = 1, #content
if apply_scroll and term_height > 0 then
-- Up to 40 lines for libass because it can put a big performance toll on
-- libass to process many lines which end up outside (below) the screen.
-- In the terminal reduce height by 2 for the status line (can be more then one line)
local max_content_lines = (o.use_ass and 40 or term_height - 2) - #header
-- in the terminal the scrolling should stop once the last line is visible
local max_offset = o.use_ass and #content or #content - max_content_lines + 1
from = max(1, min((pages[curr_page].offset or 1), max_offset))
to = min(#content, from + max_content_lines - 1)
pages[curr_page].offset = from
end
local output = table.concat(header) .. table.concat(content, "", from, to)
if not o.use_ass and term_width > 0 and curr_page ~= o.key_page_4 then
local t = split(output, "\n", true)
-- limit width for the terminal
output = table.concat(term_ellipsis_array(t, 1, #t, term_width), "\n")
end
return output, from
end

-- Returns an ASS string with "normal" stats
Expand All @@ -1023,7 +1102,7 @@ local function default_stats()
add_video_out(stats)
add_video(stats)
add_audio(stats)
return table.concat(stats)
return finalize_page({}, stats, false)
end

-- Returns an ASS string with extended VO stats
Expand All @@ -1033,11 +1112,7 @@ local function vo_stats()
add_header(header)
append_perfdata(header, content, true, true)
header = {table.concat(header)}

local page = pages[o.key_page_2]
local res = nil
res, page.offset = compose_page(header, content, page.offset)
return res
return finalize_page(header, content, false)
end

local kbinfo_lines = nil
Expand All @@ -1052,12 +1127,10 @@ local function keybinding_info(after_scroll)
header = {table.concat(header)}

if not kbinfo_lines or not after_scroll then
kbinfo_lines = get_kbinfo_lines()
kbinfo_lines = get_kbinfo_lines(o.term_width_limit)
end

local res = nil
res, page.offset = compose_page(header, kbinfo_lines, page.offset)
return res
return finalize_page(header, kbinfo_lines, true)
end

local function perf_stats()
Expand All @@ -1068,9 +1141,7 @@ local function perf_stats()
append(header, "", {prefix=page.desc .. ":", nl="", indent=""})
append_general_perfdata(content)
header = {table.concat(header)}
local res = nil
res, page.offset = compose_page(header, content, page.offset)
return res
return finalize_page(header, content, true)
end

local function opt_time(t)
Expand All @@ -1091,7 +1162,7 @@ local function cache_stats()
local info = mp.get_property_native("demuxer-cache-state")
if info == nil then
append(stats, "Unavailable.", {})
return table.concat(stats)
return finalize_page({}, stats, false)
end

local a = info["reader-pts"]
Expand Down Expand Up @@ -1169,7 +1240,7 @@ local function cache_stats()
{prefix = format("Range %s:", n)})
end

return table.concat(stats)
return finalize_page({}, stats, false)
end

-- Record 1 sample of cache statistics.
Expand Down

0 comments on commit 777f69b

Please sign in to comment.