mirror of https://github.com/mpv-player/mpv
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.
This commit is contained in:
parent
59c10274b4
commit
9fb200b641
|
@ -24,6 +24,7 @@ stats:
|
|||
1 Show usual stats
|
||||
2 Show frame timings (scroll)
|
||||
3 Input cache stats
|
||||
4 Active key bindings (scroll)
|
||||
0 Internal stuff (scroll)
|
||||
==== ==================
|
||||
|
||||
|
@ -64,6 +65,8 @@ Configurable Options
|
|||
Default: 2
|
||||
``key_page_3``
|
||||
Default: 3
|
||||
``key_page_4``
|
||||
Default: 4
|
||||
``key_page_0``
|
||||
Default: 0
|
||||
|
||||
|
@ -183,6 +186,20 @@ Using ``input.conf``, it is also possible to directly display a certain page::
|
|||
i script-binding stats/display-page-1
|
||||
e script-binding stats/display-page-2
|
||||
|
||||
Active key bindings page
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lists the active key bindings and the commands they're bound to, excluding the
|
||||
interactive keys of the stats script itself. See also `--input-test`_ for more
|
||||
detailed view of each binding.
|
||||
|
||||
The keys are grouped automatically using a simple analysis of the command
|
||||
string, and one should not expect documentation-level grouping accuracy,
|
||||
however, it should still be reasonably useful.
|
||||
|
||||
Like with ``--input-test``, the list includes bindings from ``input.conf`` and
|
||||
from user scripts. Use `--no-config`` to list only built-in bindings.
|
||||
|
||||
Internal stuff page
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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 },
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue