summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
authorAvi Halachmi (:avih) <avihpit@yahoo.com>2021-07-16 11:17:50 +0300
committeravih <avih@users.noreply.github.com>2021-07-19 22:06:50 +0300
commit9fb200b6419c2707a5ec8d0786ae24ed02194297 (patch)
tree60cd9c02887e619eed055fcd79f6426d35edc88e /player
parent59c10274b402c7fcca5d2765cb38e535e17907c2 (diff)
downloadmpv-9fb200b6419c2707a5ec8d0786ae24ed02194297.tar.bz2
mpv-9fb200b6419c2707a5ec8d0786ae24ed02194297.tar.xz
stats.lua: add page 4: active key-bindings list
This is a scrollable page which also works nicely with the terminal OSD. Typically there are more than 100 bound keys. It groups the binding using fuzzy property/command/script name after simple analysis of the command string, and then further sorts the binding in each group according to the "complexity" of the key itself (plain keys -> keys with modifiers, alphabetically, etc). The alignment/grouping style is heavily inspired by @medhefgo's #8924 but otherwise it's an independent implementation.
Diffstat (limited to 'player')
-rw-r--r--player/lua/stats.lua151
1 files changed, 151 insertions, 0 deletions
diff --git a/player/lua/stats.lua b/player/lua/stats.lua
index 93dc453da4..367c019cd4 100644
--- a/player/lua/stats.lua
+++ b/player/lua/stats.lua
@@ -18,6 +18,7 @@ local o = {
key_page_1 = "1",
key_page_2 = "2",
key_page_3 = "3",
+ key_page_4 = "4",
key_page_0 = "0",
-- For pages which support scrolling
key_scroll_up = "UP",
@@ -351,6 +352,132 @@ local function append_perfdata(s, dedicated_page)
end
end
+local function ellipsis(s, maxlen)
+ if not maxlen or s:len() <= maxlen then return s end
+ return s:sub(1, maxlen - 3) .. "..."
+end
+
+-- command prefix tokens to strip - includes generic property commands
+local cmd_prefixes = {
+ osd_auto=1, no_osd=1, osd_bar=1, osd_msg=1, osd_msg_bar=1, raw=1, sync=1,
+ async=1, expand_properties=1, repeatable=1, set=1, add=1, multiply=1,
+ toggle=1, cycle=1, cycle_values=1, ["!reverse"]=1, change_list=1,
+}
+-- commands/writable-properties prefix sub-words (followed by -) to strip
+local name_prefixes = {
+ define=1, delete=1, enable=1, disable=1, dump=1, write=1, drop=1, revert=1,
+ ab=1, hr=1, secondary=1,
+}
+-- extract a command "subject" from a command string, by removing all
+-- generic prefix tokens and then returning the first interesting sub-word
+-- of the next token. For target-script name we also check another token.
+-- The tokenizer works fine for things we care about - valid mpv commands,
+-- properties and script names, possibly quoted, white-space[s]-separated.
+-- It's decent in practice, and worst case is "incorrect" subject.
+local function cmd_subject(cmd)
+ cmd = cmd:gsub(";.*", ""):gsub("%-", "_") -- only first cmd, s/-/_/
+ local TOKEN = '^%s*"?([%w_!]*)' -- captures+ends before a (maybe) final "
+ local tok, sname, subw
+
+ repeat tok, cmd = cmd:match(TOKEN .. '"?(.*)')
+ until not cmd_prefixes[tok]
+ -- tok is the 1st non-generic command/property name token, cmd is the rest
+
+ sname = tok == "script_message_to" and cmd:match(TOKEN)
+ or tok == "script_binding" and cmd:match(TOKEN .. "/")
+ if sname and sname ~= "" then
+ return "script: " .. sname
+ end
+
+ -- return the first sub-word of tok which is not a useless prefix
+ repeat subw, tok = tok:match("([^_]*)_?(.*)")
+ until tok == "" or not name_prefixes[subw]
+ return subw:len() > 1 and subw or "[unknown]"
+end
+
+local function get_kbinfo_lines()
+ -- active keys: only highest priotity of each key, and not our (stats) keys
+ local bindings = mp.get_property_native("input-bindings", {})
+ local active = {} -- map: key-name -> bind-info
+ for _, bind in pairs(bindings) do
+ if bind.priority >= 0 and (
+ not active[bind.key] or
+ (active[bind.key].is_weak and not bind.is_weak) or
+ (bind.is_weak == active[bind.key].is_weak and
+ bind.priority > active[bind.key].priority)
+ ) and not bind.cmd:find("script-binding stats/__key", 1, true)
+ then
+ active[bind.key] = bind
+ end
+ end
+
+ -- make an array, find max key len, add sort keys (.subject/.mods[_count])
+ local ordered = {}
+ local kspaces = "" -- as many spaces as the longest key name
+ for _, bind in pairs(active) do
+ bind.subject = cmd_subject(bind.cmd)
+ if bind.subject ~= "ignore" then
+ ordered[#ordered+1] = bind
+ _,_, bind.mods = bind.key:find("(.*)%+.")
+ _, bind.mods_count = bind.key:gsub("%+.", "")
+ if bind.key:len() > kspaces:len() then
+ kspaces = string.rep(" ", bind.key:len())
+ end
+ end
+ end
+
+ local function align_right(key)
+ return kspaces:sub(key:len()) .. key
+ end
+
+ -- sort by: subject, mod(ifier)s count, mods, key-len, lowercase-key, key
+ table.sort(ordered, function(a, b)
+ if a.subject ~= b.subject then
+ return a.subject < b.subject
+ elseif a.mods_count ~= b.mods_count then
+ return a.mods_count < b.mods_count
+ elseif a.mods ~= b.mods then
+ return a.mods < b.mods
+ elseif a.key:len() ~= b.key:len() then
+ return a.key:len() < b.key:len()
+ elseif a.key:lower() ~= b.key:lower() then
+ return a.key:lower() < b.key:lower()
+ else
+ return a.key > b.key -- only case differs, lowercase first
+ end
+ end)
+
+ -- key/subject pre/post formatting for terminal/ass.
+ -- key/subject alignment uses spaces (with mono font if ass)
+ -- word-wrapping is disabled for ass, or cut at 79 for the terminal
+ local term = not o.use_ass
+ local kpre = term and "" or format("{\\q2\\fn%s}", o.font_mono)
+ local kpost = term and " " or format(" {\\fn%s}", o.font)
+ local spre = term and kspaces .. " "
+ or format("{\\q2\\fn%s}%s {\\fn%s}{\\fs%d\\u1}",
+ o.font_mono, kspaces, o.font, 1.3*o.font_size)
+ local spost = term and "" or format("{\\u0\\fs%d}", o.font_size)
+ local _, itabs = o.indent:gsub("\t", "")
+ local cutoff = term and 79 - o.indent:len() - itabs * 7 - spre:len()
+
+ -- create the display lines
+ local info_lines = {}
+ local subject = nil
+ for _, bind in ipairs(ordered) do
+ if bind.subject ~= subject then -- new subject (title)
+ subject = bind.subject
+ append(info_lines, "", {})
+ append(info_lines, "", { prefix = spre .. subject .. spost })
+ end
+ if bind.comment then
+ bind.cmd = bind.cmd .. " # " .. bind.comment
+ end
+ append(info_lines, ellipsis(bind.cmd, cutoff),
+ { prefix = kpre .. no_ASS(align_right(bind.key)) .. kpost })
+ end
+ return info_lines
+end
+
local function append_general_perfdata(s, offset)
local perf_info = mp.get_property_native("perf-info") or {}
local count = 0
@@ -665,6 +792,29 @@ local function vo_stats()
return table.concat(stats)
end
+local kbinfo_lines = nil
+local function keybinding_info(after_scroll)
+ local header = {}
+ local page = pages[o.key_page_4]
+ eval_ass_formatting()
+ add_header(header)
+ append(header, "", {prefix=o.nl .. page.desc .. ":", nl="", indent=""})
+
+ if not kbinfo_lines or not after_scroll then
+ kbinfo_lines = get_kbinfo_lines()
+ end
+ -- up to 20 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 term = not o.use_ass
+ local nlines = #kbinfo_lines
+ page.offset = max(1, min((page.offset or 1), term and nlines - 20 or nlines))
+ local maxline = min(nlines, page.offset + (term and 20 or 40))
+ return table.concat(header) ..
+ table.concat(kbinfo_lines, "", page.offset, maxline)
+end
+
local function perf_stats()
local stats = {}
eval_ass_formatting()
@@ -800,6 +950,7 @@ pages = {
[o.key_page_1] = { f = default_stats, desc = "Default" },
[o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings", scroll = true },
[o.key_page_3] = { f = cache_stats, desc = "Cache Statistics" },
+ [o.key_page_4] = { f = keybinding_info, desc = "Active key bindings", scroll = true },
[o.key_page_0] = { f = perf_stats, desc = "Internal performance info", scroll = true },
}