summaryrefslogtreecommitdiffstats
path: root/player/lua
diff options
context:
space:
mode:
Diffstat (limited to 'player/lua')
-rw-r--r--player/lua/console.lua838
-rw-r--r--player/lua/defaults.lua4
-rw-r--r--player/lua/meson.build2
-rw-r--r--player/lua/osc.lua1209
-rw-r--r--player/lua/select.lua380
-rw-r--r--player/lua/stats.lua208
-rw-r--r--player/lua/ytdl_hook.lua22
7 files changed, 1656 insertions, 1007 deletions
diff --git a/player/lua/console.lua b/player/lua/console.lua
index 26c2e27988..686d04ff3b 100644
--- a/player/lua/console.lua
+++ b/player/lua/console.lua
@@ -36,7 +36,7 @@ local opts = {
font_hw_ratio = 'auto',
}
-function detect_platform()
+local function detect_platform()
local platform = mp.get_property_native('platform')
if platform == 'darwin' or platform == 'windows' then
return platform
@@ -118,128 +118,133 @@ local matches = {}
local selected_match = 1
local first_match_to_print = 1
-local update_timer = nil
-update_timer = mp.add_periodic_timer(0.05, function()
- if pending_update then
- update()
- else
- update_timer:kill()
- end
-end)
-update_timer:kill()
-
-mp.observe_property("user-data/osc/margins", "native", function(_, val)
- if val then
- global_margins = val
- else
- global_margins = { t = 0, b = 0 }
- end
- update()
-end)
+local set_active
-do
- local width_length_ratio = 0.5
- local osd_width, osd_height = 100, 100
- ---Update osd resolution if valid
- local function update_osd_resolution()
- local dim = mp.get_property_native('osd-dimensions')
- if not dim or dim.w == 0 or dim.h == 0 then
- return
- end
- osd_width = dim.w
- osd_height = dim.h
- end
+-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
+-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
+local function next_utf8(str, pos)
+ if pos > str:len() then return pos end
+ repeat
+ pos = pos + 1
+ until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
- local text_osd = mp.create_osd_overlay('ass-events')
- text_osd.compute_bounds, text_osd.hidden = true, true
+-- As above, but finds the previous UTF-8 character in 'str' before 'pos'
+local function prev_utf8(str, pos)
+ if pos <= 1 then return pos end
+ repeat
+ pos = pos - 1
+ until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
- local function measure_bounds(ass_text)
- update_osd_resolution()
- text_osd.res_x, text_osd.res_y = osd_width, osd_height
- text_osd.data = ass_text
- local res = text_osd:update()
- return res.x0, res.y0, res.x1, res.y1
- end
-
- ---Measure text width and normalize to a font size of 1
- ---text has to be ass safe
- local function normalized_text_width(text, size, horizontal)
- local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90
- local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s'
- size = size / 0.8
- local width
- -- Limit to 5 iterations
- local repetitions_left = 5
- for i = 1, repetitions_left do
- size = size * 0.8
- local ass = assdraw.ass_new()
- ass.text = template:format(align, size, opts.font, rotation, text)
- local _, _, x1, y1 = measure_bounds(ass.text)
- -- Check if nothing got clipped
- if x1 and x1 < osd_width and y1 < osd_height then
- width = horizontal and x1 or y1
- break
- end
- if i == repetitions_left then
- width = 0
- end
- end
- return width / size, horizontal and osd_width or osd_height
+local function len_utf8(str)
+ local len = 0
+ local pos = 1
+ while pos <= str:len() do
+ pos = next_utf8(str, pos)
+ len = len + 1
end
+ return len
+end
- local function fit_on_osd(text)
- local estimated_width = #text * width_length_ratio
- if osd_width >= osd_height then
- -- Fill the osd as much as possible, bigger is more accurate.
- return math.min(osd_width / estimated_width, osd_height), true
- else
- return math.min(osd_height / estimated_width, osd_width), false
+local function truncate_utf8(str, max_length)
+ local len = 0
+ local pos = 1
+ while pos <= #str do
+ pos = next_utf8(str, pos)
+ len = len + 1
+ if len == max_length - 1 then
+ return str:sub(1, pos - 1) .. '⋯'
end
end
+ return str
+end
+
- local measured_font_hw_ratio = nil
- function get_font_hw_ratio()
- local font_hw_ratio = tonumber(opts.font_hw_ratio)
- if font_hw_ratio then
- return font_hw_ratio
+-- Functions to calculate the font width.
+local width_length_ratio = 0.5
+local osd_width, osd_height = 100, 100
+
+---Update osd resolution if valid
+local function update_osd_resolution()
+ local dim = mp.get_property_native('osd-dimensions')
+ if not dim or dim.w == 0 or dim.h == 0 then
+ return
+ end
+ osd_width = dim.w
+ osd_height = dim.h
+end
+
+local text_osd = mp.create_osd_overlay('ass-events')
+text_osd.compute_bounds, text_osd.hidden = true, true
+
+local function measure_bounds(ass_text)
+ update_osd_resolution()
+ text_osd.res_x, text_osd.res_y = osd_width, osd_height
+ text_osd.data = ass_text
+ local res = text_osd:update()
+ return res.x0, res.y0, res.x1, res.y1
+end
+
+---Measure text width and normalize to a font size of 1
+---text has to be ass safe
+local function normalized_text_width(text, size, horizontal)
+ local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90
+ local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s'
+ size = size / 0.8
+ local width
+ -- Limit to 5 iterations
+ local repetitions_left = 5
+ for i = 1, repetitions_left do
+ size = size * 0.8
+ local ass = assdraw.ass_new()
+ ass.text = template:format(align, size, opts.font, rotation, text)
+ local _, _, x1, y1 = measure_bounds(ass.text)
+ -- Check if nothing got clipped
+ if x1 and x1 < osd_width and y1 < osd_height then
+ width = horizontal and x1 or y1
+ break
end
- if not measured_font_hw_ratio then
- local alphabet = 'abcdefghijklmnopqrstuvwxyz'
- local text = alphabet:rep(3)
- update_osd_resolution()
- local size, horizontal = fit_on_osd(text)
- local normalized_width = normalized_text_width(text, size * 0.9, horizontal)
- measured_font_hw_ratio = #text / normalized_width * 0.95
+ if i == repetitions_left then
+ width = 0
end
- return measured_font_hw_ratio
end
+ return width / size, horizontal and osd_width or osd_height
end
--- Add a line to the log buffer (which is limited to 100 lines)
-function log_add(text, style, terminal_style)
- local log_buffer = log_buffers[id]
- log_buffer[#log_buffer + 1] = {
- text = text,
- style = style or '',
- terminal_style = terminal_style or '',
- }
- if #log_buffer > 100 then
- table.remove(log_buffer, 1)
+local function fit_on_osd(text)
+ local estimated_width = #text * width_length_ratio
+ if osd_width >= osd_height then
+ -- Fill the osd as much as possible, bigger is more accurate.
+ return math.min(osd_width / estimated_width, osd_height), true
+ else
+ return math.min(osd_height / estimated_width, osd_width), false
end
+end
- if repl_active then
- if not update_timer:is_enabled() then
- update()
- update_timer:resume()
- else
- pending_update = true
- end
+local measured_font_hw_ratio = nil
+local function get_font_hw_ratio()
+ local font_hw_ratio = tonumber(opts.font_hw_ratio)
+ if font_hw_ratio then
+ return font_hw_ratio
+ end
+ if not measured_font_hw_ratio then
+ local alphabet = 'abcdefghijklmnopqrstuvwxyz'
+ local text = alphabet:rep(3)
+ update_osd_resolution()
+ local size, horizontal = fit_on_osd(text)
+ local normalized_width = normalized_text_width(text, size * 0.9, horizontal)
+ measured_font_hw_ratio = #text / normalized_width * 0.95
end
+ return measured_font_hw_ratio
end
+
-- Escape a string for verbatim display on the OSD
-function ass_escape(str)
+local function ass_escape(str)
return mp.command_native({'escape-ass', str})
end
@@ -251,13 +256,14 @@ local function calculate_max_log_lines()
select(2, mp.get_property('term-status-msg'):gsub('\\n', ''))
end
- -- Subtract 1.5 to account for the input line.
return math.floor(mp.get_property_native('osd-height')
/ mp.get_property_native('display-hidpi-scale', 1)
/ opts.scale
* (1 - global_margins.t - global_margins.b)
/ opts.font_size
- - 1.5)
+ -- Subtract 1 for the input line and 1 for the newline
+ -- between the log and the input line.
+ - 2)
end
-- Takes a list of strings, a max width in characters and
@@ -265,7 +271,7 @@ end
-- The result contains at least one column.
-- Rows are cut off from the top if rows_max is specified.
-- returns a string containing the formatted table and the row count
-function format_table(list, width_max, rows_max)
+local function format_table(list, width_max, rows_max)
if #list == 0 then
return '', 0
end
@@ -287,9 +293,10 @@ function format_table(list, width_max, rows_max)
-- use as many columns as possible
for columns = 2, list_size do
local rows_lower_bound = math.min(rows_max, math.ceil(list_size / columns))
- local rows_upper_bound = math.min(rows_max, list_size, math.ceil(list_size / (columns - 1) - 1))
+ local rows_upper_bound = math.min(rows_max, list_size,
+ math.ceil(list_size / (columns - 1) - 1))
for rows = rows_upper_bound, rows_lower_bound, -1 do
- cw = {}
+ local cw = {}
width_total = 0
-- find out width of each column
@@ -370,15 +377,56 @@ local function populate_log_with_matches(max_width)
log_buffers[id] = {}
local log = log_buffers[id]
- -- Subtract 2 for the "(n hidden items)" lines.
- local max_log_lines = calculate_max_log_lines() - 2
+ local max_log_lines = calculate_max_log_lines()
if selected_match < first_match_to_print then
first_match_to_print = selected_match
- elseif selected_match > first_match_to_print + max_log_lines - 1 then
+ end
+
+ if first_match_to_print > 1 then
+ -- Reserve the first line for "n hidden items".
+ max_log_lines = max_log_lines - 1
+ end
+
+ if selected_match > first_match_to_print + max_log_lines - 1 then
+ -- Reserve the first line for "n hidden items" if it wasn't already.
+ if first_match_to_print == 1 then
+ max_log_lines = max_log_lines - 1
+ end
+
first_match_to_print = selected_match - max_log_lines + 1
end
+ local last_match_to_print = math.min(first_match_to_print + max_log_lines - 1,
+ #matches)
+
+ if last_match_to_print < #matches then
+ -- Reserve the last line for "n hidden items".
+ last_match_to_print = last_match_to_print - 1
+
+ -- After decrementing the last match to print, we need to check if the
+ -- selected match is beyond the last match to print again, and shift
+ -- both the first and last match to print when it is.
+ if selected_match > last_match_to_print then
+ if first_match_to_print == 1 then
+ -- Reserve the first line for "2 hidden items".
+ first_match_to_print = first_match_to_print + 1
+ end
+
+ first_match_to_print = first_match_to_print + 1
+ last_match_to_print = last_match_to_print + 1
+ end
+ end
+
+ -- When there is only 1 hidden item, print it in the previously reserved
+ -- line instead of printing "1 hidden items".
+ if first_match_to_print == 2 then
+ first_match_to_print = 1
+ end
+ if last_match_to_print == #matches - 1 then
+ last_match_to_print = #matches
+ end
+
if first_match_to_print > 1 then
log[1] = {
text = '↑ (' .. (first_match_to_print - 1) .. ' hidden items)' .. '\n',
@@ -387,9 +435,6 @@ local function populate_log_with_matches(max_width)
}
end
- local last_match_to_print = math.min(first_match_to_print + max_log_lines - 1,
- #matches)
-
for i = first_match_to_print, last_match_to_print do
log[#log + 1] = {
text = truncate_utf8(matches[i].text, max_width) .. '\n',
@@ -445,7 +490,7 @@ local function print_to_terminal()
end
-- Render the REPL and console as an ASS OSD
-function update()
+local function update()
pending_update = false
-- Unlike vo-configured, current-vo doesn't become falsy while switching VO,
@@ -542,108 +587,51 @@ function update()
mp.set_osd_ass(screenx, screeny, ass.text)
end
--- Set the REPL visibility ("enable", Esc)
-function set_active(active)
- if active == repl_active then return end
- if active then
- repl_active = true
- insert_mode = false
- mp.enable_key_bindings('console-input', 'allow-hide-cursor+allow-vo-dragging')
- define_key_bindings()
-
- if not input_caller then
- prompt = default_prompt
- id = default_id
- history = histories[id]
- history_pos = #history + 1
- mp.enable_messages('terminal-default')
- end
+local update_timer = nil
+update_timer = mp.add_periodic_timer(0.05, function()
+ if pending_update then
+ update()
else
- repl_active = false
- suggestion_buffer = {}
- undefine_key_bindings()
- mp.enable_messages('silent:terminal-default')
-
- if input_caller then
- mp.commandv('script-message-to', input_caller, 'input-event',
- 'closed', utils.format_json({line, cursor}))
- input_caller = nil
- line = ''
- cursor = 1
- selectable_items = nil
- end
- collectgarbage()
+ update_timer:kill()
end
- update()
-end
-
--- Show the repl if hidden and replace its contents with 'text'
--- (script-message-to repl type)
-function show_and_type(text, cursor_pos)
- text = text or ''
- cursor_pos = tonumber(cursor_pos)
+end)
+update_timer:kill()
- -- Save the line currently being edited, just in case
- if line ~= text and line ~= '' and history[#history] ~= line then
- history_add(line)
+-- Add a line to the log buffer (which is limited to 100 lines)
+local function log_add(text, style, terminal_style)
+ local log_buffer = log_buffers[id]
+ log_buffer[#log_buffer + 1] = {
+ text = text,
+ style = style or '',
+ terminal_style = terminal_style or '',
+ }
+ if #log_buffer > 100 then
+ table.remove(log_buffer, 1)
end
- line = text
- if cursor_pos ~= nil and cursor_pos >= 1
- and cursor_pos <= line:len() + 1 then
- cursor = math.floor(cursor_pos)
- else
- cursor = line:len() + 1
- end
- history_pos = #history + 1
- insert_mode = false
if repl_active then
- update()
- else
- set_active(true)
- end
-end
-
--- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
--- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
-function next_utf8(str, pos)
- if pos > str:len() then return pos end
- repeat
- pos = pos + 1
- until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
- return pos
-end
-
--- As above, but finds the previous UTF-8 character in 'str' before 'pos'
-function prev_utf8(str, pos)
- if pos <= 1 then return pos end
- repeat
- pos = pos - 1
- until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
- return pos
-end
-
-function len_utf8(str)
- local len = 0
- local pos = 1
- while pos <= str:len() do
- pos = next_utf8(str, pos)
- len = len + 1
+ if not update_timer:is_enabled() then
+ update()
+ update_timer:resume()
+ else
+ pending_update = true
+ end
end
- return len
end
-function truncate_utf8(str, max_length)
- local len = 0
- local pos = 1
- while pos <= #str do
- pos = next_utf8(str, pos)
- len = len + 1
- if len == max_length - 1 then
- return str:sub(1, pos - 1) .. '⋯'
+-- Add a line to the history and deduplicate
+local function history_add(text)
+ if opts.history_dedup then
+ -- More recent entries are more likely to be repeated
+ for i = #history, 1, -1 do
+ if history[i] == text then
+ table.remove(history, i)
+ break
+ end
end
end
- return str
+
+ history[#history + 1] = text
end
local function handle_edit()
@@ -666,7 +654,7 @@ local function handle_edit()
end
-- Insert a character at the current cursor position (any_unicode)
-function handle_char_input(c)
+local function handle_char_input(c)
if insert_mode then
line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
else
@@ -677,7 +665,7 @@ function handle_char_input(c)
end
-- Remove the character behind the cursor (Backspace)
-function handle_backspace()
+local function handle_backspace()
if cursor <= 1 then return end
local prev = prev_utf8(line, cursor)
line = line:sub(1, prev - 1) .. line:sub(cursor)
@@ -686,33 +674,33 @@ function handle_backspace()
end
-- Remove the character in front of the cursor (Del)
-function handle_del()
+local function handle_del()
if cursor > line:len() then return end
line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
handle_edit()
end
-- Toggle insert mode (Ins)
-function handle_ins()
+local function handle_ins()
insert_mode = not insert_mode
end
-- Move the cursor to the next character (Right)
-function next_char()
+local function next_char()
cursor = next_utf8(line, cursor)
suggestion_buffer = {}
update()
end
-- Move the cursor to the previous character (Left)
-function prev_char()
+local function prev_char()
cursor = prev_utf8(line, cursor)
suggestion_buffer = {}
update()
end
-- Clear the current line (Ctrl+C)
-function clear()
+local function clear()
line = ''
cursor = 1
insert_mode = false
@@ -722,7 +710,7 @@ end
-- Close the REPL if the current line is empty, otherwise delete the next
-- character (Ctrl+D)
-function maybe_exit()
+local function maybe_exit()
if line == '' then
set_active(false)
else
@@ -730,7 +718,7 @@ function maybe_exit()
end
end
-function help_command(param)
+local function help_command(param)
local cmdlist = mp.get_property_native('command-list')
table.sort(cmdlist, function(c1, c2)
return c1.name < c2.name
@@ -774,23 +762,8 @@ function help_command(param)
log_add(output)
end
--- Add a line to the history and deduplicate
-function history_add(text)
- if opts.history_dedup then
- -- More recent entries are more likely to be repeated
- for i = #history, 1, -1 do
- if history[i] == text then
- table.remove(history, i)
- break
- end
- end
- end
-
- history[#history + 1] = text
-end
-
-- Run the current command and clear the line (Enter)
-function handle_enter()
+local function handle_enter()
if line == '' and input_caller == nil then
return
end
@@ -822,7 +795,7 @@ function handle_enter()
end
-- Go to the specified position in the command history
-function go_history(new_pos)
+local function go_history(new_pos)
local old_pos = history_pos
history_pos = new_pos
@@ -858,7 +831,7 @@ function go_history(new_pos)
end
-- Go to the specified relative position in the command history (Up, Down)
-function move_history(amount)
+local function move_history(amount)
if selectable_items then
selected_match = selected_match + amount
if selected_match > #matches then
@@ -874,9 +847,14 @@ function move_history(amount)
end
-- Go to the first command in the command history (PgUp)
-function handle_pgup()
+local function handle_pgup()
if selectable_items then
- selected_match = math.max(selected_match - calculate_max_log_lines() + 1, 1)
+ -- We don't know whether to count the "n hidden items" lines here; an
+ -- offset of 2 is better with 1 extra line because it scrolls from the
+ -- last to the first visible match, while with both extra lines that is
+ -- done with +3. When there are no "n hidden items" lines selected_match
+ -- becomes 1 with any offset >= 1.
+ selected_match = math.max(selected_match - calculate_max_log_lines() + 2, 1)
update()
return
end
@@ -885,9 +863,9 @@ function handle_pgup()
end
-- Stop browsing history and start editing a blank line (PgDown)
-function handle_pgdown()
+local function handle_pgdown()
if selectable_items then
- selected_match = math.min(selected_match + calculate_max_log_lines() - 1, #matches)
+ selected_match = math.min(selected_match + calculate_max_log_lines() - 2, #matches)
update()
return
end
@@ -913,7 +891,7 @@ end
-- Move to the start of the current word, or if already at the start, the start
-- of the previous word. (Ctrl+Left)
-function prev_word()
+local function prev_word()
-- This is basically the same as next_word() but backwards, so reverse the
-- string in order to do a "backwards" find. This wouldn't be as annoying
-- to do if Lua didn't insist on 1-based indexing.
@@ -924,12 +902,142 @@ end
-- Move to the end of the current word, or if already at the end, the end of
-- the next word. (Ctrl+Right)
-function next_word()
+local function next_word()
cursor = select(2, line:find('%s*[^%s]*', cursor)) + 1
suggestion_buffer = {}
update()
end
+-- Move the cursor to the beginning of the line (HOME)
+local function go_home()
+ cursor = 1
+ suggestion_buffer = {}
+ update()
+end
+
+-- Move the cursor to the end of the line (END)
+local function go_end()
+ cursor = line:len() + 1
+ suggestion_buffer = {}
+ update()
+end
+
+-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
+local function del_word()
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ before_cur = before_cur:gsub('[^%s]+%s*$', '', 1)
+ line = before_cur .. after_cur
+ cursor = before_cur:len() + 1
+ handle_edit()
+end
+
+-- Delete from the cursor to the end of the word (Ctrl+Del)
+local function del_next_word()
+ if cursor > line:len() then return end
+
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
+ line = before_cur .. after_cur
+ handle_edit()
+end
+
+-- Delete from the cursor to the end of the line (Ctrl+K)
+local function del_to_eol()
+ line = line:sub(1, cursor - 1)
+ handle_edit()
+end
+
+-- Delete from the cursor back to the start of the line (Ctrl+U)
+local function del_to_start()
+ line = line:sub(cursor)
+ cursor = 1
+ handle_edit()
+end
+
+-- Empty the log buffer of all messages (Ctrl+L)
+local function clear_log_buffer()
+ log_buffers[id] = {}
+ update()
+end
+
+-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
+local function get_clipboard(clip)
+ if platform == 'x11' then
+ local res = utils.subprocess({
+ args = { 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == 'wayland' then
+ local res = utils.subprocess({
+ args = { 'wl-paste', clip and '-n' or '-np' },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == 'windows' then
+ local res = utils.subprocess({
+ args = { 'powershell', '-NoProfile', '-Command', [[& {
+ Trap {
+ Write-Error -ErrorRecord $_
+ Exit 1
+ }
+
+ $clip = ""
+ if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
+ $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
+ } else {
+ Add-Type -AssemblyName PresentationCore
+ $clip = [Windows.Clipboard]::GetText()
+ }
+
+ $clip = $clip -Replace "`r",""
+ $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
+ [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
+ }]] },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ elseif platform == 'darwin' then
+ local res = utils.subprocess({
+ args = { 'pbpaste' },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
+ end
+ return ''
+end
+
+-- Paste text from the window-system's clipboard. 'clip' determines whether the
+-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
+local function paste(clip)
+ local text = get_clipboard(clip)
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+ line = before_cur .. text .. after_cur
+ cursor = cursor + text:len()
+ handle_edit()
+end
+
+local function text_input(info)
+ if info.key_text and (info.event == "press" or info.event == "down"
+ or info.event == "repeat")
+ then
+ handle_char_input(info.key_text)
+ end
+end
+
local function command_list()
local commands = {}
for i, command in ipairs(mp.get_property_native('command-list')) do
@@ -1006,17 +1114,21 @@ local function list_option_action_list(option)
end
local function list_option_value_list(option)
- if option ~= 'af' and option ~= 'vf' then
- return mp.get_property_native(option)
+ local values = mp.get_property_native(option)
+
+ if type(values) ~= 'table' then
+ return
end
- local filters = {}
+ if type(values[1]) ~= 'table' then
+ return values
+ end
- for i, filter in ipairs(mp.get_property_native(option)) do
- filters[i] = filter.label and '@' .. filter.label or filter.name
+ for i, value in ipairs(values) do
+ values[i] = value.label and '@' .. value.label or value.name
end
- return filters
+ return values
end
local function has_file_argument(candidate_command)
@@ -1077,7 +1189,7 @@ local function handle_choice_completion(option, before_cur, path_pos)
return info.choices, before_cur
end
-function common_prefix_length(s1, s2)
+local function common_prefix_length(s1, s2)
local common_count = 0
for i = 1, #s1 do
if s1:byte(i) ~= s2:byte(i) then
@@ -1088,7 +1200,7 @@ function common_prefix_length(s1, s2)
return common_count
end
-function max_overlap_length(s1, s2)
+local function max_overlap_length(s1, s2)
for s1_offset = 0, #s1 - 1 do
local match = true
for i = 1, #s1 - s1_offset do
@@ -1136,7 +1248,7 @@ local function complete_match(part, list)
local prefix = find_common_prefix(completions)
if opts.case_sensitive then
- return completions, prefix
+ return completions, prefix or part
end
completions = {}
@@ -1185,7 +1297,7 @@ local function cycle_through_suggestions(backwards)
end
-- Complete the option or property at the cursor (TAB)
-function complete(backwards)
+local function complete(backwards)
if #suggestion_buffer > 0 then
cycle_through_suggestions(backwards)
return
@@ -1243,8 +1355,8 @@ function complete(backwards)
local command_prefixes = {
['osd-auto'] = true, ['no-osd'] = true, ['osd-bar'] = true,
['osd-msg'] = true, ['osd-msg-bar'] = true, ['raw'] = true,
- ['expand-properties'] = true, ['repeatable'] = true, ['async'] = true,
- ['sync'] = true
+ ['expand-properties'] = true, ['repeatable'] = true,
+ ['nonrepeatable'] = true, ['async'] = true, ['sync'] = true
}
while tokens[first_useful_token_index] and
@@ -1254,7 +1366,7 @@ function complete(backwards)
-- Add an empty token if the cursor is after whitespace to simplify
-- comparisons.
- if before_cur == '' or before_cur:find('%s$') then
+ if before_cur == '' or before_cur:find('[%s;]$') then
tokens[#tokens + 1] = { text = "", pos = cursor }
end
@@ -1371,131 +1483,9 @@ function complete(backwards)
update()
end
--- Move the cursor to the beginning of the line (HOME)
-function go_home()
- cursor = 1
- suggestion_buffer = {}
- update()
-end
-
--- Move the cursor to the end of the line (END)
-function go_end()
- cursor = line:len() + 1
- suggestion_buffer = {}
- update()
-end
-
--- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
-function del_word()
- local before_cur = line:sub(1, cursor - 1)
- local after_cur = line:sub(cursor)
-
- before_cur = before_cur:gsub('[^%s]+%s*$', '', 1)
- line = before_cur .. after_cur
- cursor = before_cur:len() + 1
- handle_edit()
-end
-
--- Delete from the cursor to the end of the word (Ctrl+Del)
-function del_next_word()
- if cursor > line:len() then return end
-
- local before_cur = line:sub(1, cursor - 1)
- local after_cur = line:sub(cursor)
-
- after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
- line = before_cur .. after_cur
- handle_edit()
-end
-
--- Delete from the cursor to the end of the line (Ctrl+K)
-function del_to_eol()
- line = line:sub(1, cursor - 1)
- handle_edit()
-end
-
--- Delete from the cursor back to the start of the line (Ctrl+U)
-function del_to_start()
- line = line:sub(cursor)
- cursor = 1
- handle_edit()
-end
-
--- Empty the log buffer of all messages (Ctrl+L)
-function clear_log_buffer()
- log_buffers[id] = {}
- update()
-end
-
--- Returns a string of UTF-8 text from the clipboard (or the primary selection)
-function get_clipboard(clip)
- if platform == 'x11' then
- local res = utils.subprocess({
- args = { 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' },
- playback_only = false,
- })
- if not res.error then
- return res.stdout
- end
- elseif platform == 'wayland' then
- local res = utils.subprocess({
- args = { 'wl-paste', clip and '-n' or '-np' },
- playback_only = false,
- })
- if not res.error then
- return res.stdout
- end
- elseif platform == 'windows' then
- local res = utils.subprocess({
- args = { 'powershell', '-NoProfile', '-Command', [[& {
- Trap {
- Write-Error -ErrorRecord $_
- Exit 1
- }
-
- $clip = ""
- if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
- $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
- } else {
- Add-Type -AssemblyName PresentationCore
- $clip = [Windows.Clipboard]::GetText()
- }
-
- $clip = $clip -Replace "`r",""
- $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
- [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
- }]] },
- playback_only = false,
- })
- if not res.error then
- return res.stdout
- end
- elseif platform == 'darwin' then
- local res = utils.subprocess({
- args = { 'pbpaste' },
- playback_only = false,
- })
- if not res.error then
- return res.stdout
- end
- end
- return ''
-end
-
--- Paste text from the window-system's clipboard. 'clip' determines whether the
--- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
-function paste(clip)
- local text = get_clipboard(clip)
- local before_cur = line:sub(1, cursor - 1)
- local after_cur = line:sub(cursor)
- line = before_cur .. text .. after_cur
- cursor = cursor + text:len()
- handle_edit()
-end
-
-- List of input bindings. This is a weird mashup between common GUI text-input
-- bindings and readline bindings.
-function get_bindings()
+local function get_bindings()
local bindings = {
{ 'esc', function() set_active(false) end },
{ 'ctrl+[', function() set_active(false) end },
@@ -1559,15 +1549,7 @@ function get_bindings()
return bindings
end<