summaryrefslogtreecommitdiffstats
path: root/player/lua
diff options
context:
space:
mode:
authorGuido Cella <guido@guidocella.xyz>2024-01-02 18:58:32 +0100
committerDudemanguy <random342@airmail.cc>2024-01-13 02:53:08 +0000
commit871f7a152a3f0b3561a627d21f7417d10ac9c25c (patch)
treec793a9c700b2e52b2a896234b679af5ec9c3f006 /player/lua
parent2dd3951a9c05e7a4fc0a614f13de1bfe0e7f132f (diff)
downloadmpv-871f7a152a3f0b3561a627d21f7417d10ac9c25c.tar.bz2
mpv-871f7a152a3f0b3561a627d21f7417d10ac9c25c.tar.xz
scripting: add mp.input
This lets scripts get textual input from the user using console.lua.
Diffstat (limited to 'player/lua')
-rw-r--r--player/lua/console.lua195
-rw-r--r--player/lua/input.lua66
-rw-r--r--player/lua/meson.build3
3 files changed, 228 insertions, 36 deletions
diff --git a/player/lua/console.lua b/player/lua/console.lua
index 1ddfca151a..92c2195623 100644
--- a/player/lua/console.lua
+++ b/player/lua/console.lua
@@ -82,11 +82,17 @@ local insert_mode = false
local pending_update = false
local line = ''
local cursor = 1
-local history = {}
+local default_prompt = '>'
+local prompt = default_prompt
+local default_id = 'default'
+local id = default_id
+local histories = {[id] = {}}
+local history = histories[id]
local history_pos = 1
-local log_buffer = {}
+local log_buffers = {[id] = {}}
local key_bindings = {}
local global_margins = { t = 0, b = 0 }
+local input_caller
local suggestion_buffer = {}
local selected_suggestion_index
@@ -94,6 +100,8 @@ local completion_start_position
local completion_append
local file_commands = {}
local path_separator = platform == 'windows' and '\\' or '/'
+local completion_old_line
+local completion_old_cursor
local update_timer = nil
update_timer = mp.add_periodic_timer(0.05, function()
@@ -190,6 +198,7 @@ end
-- Add a line to the log buffer (which is limited to 100 lines)
function log_add(style, text)
+ local log_buffer = log_buffers[id]
log_buffer[#log_buffer + 1] = { style = style, text = text }
if #log_buffer > 100 then
table.remove(log_buffer, 1)
@@ -321,7 +330,7 @@ local function print_to_terminal()
end
local log = ''
- for _, log_line in ipairs(log_buffer) do
+ for _, log_line in ipairs(log_buffers[id]) do
log = log .. log_line.text
end
@@ -337,8 +346,9 @@ local function print_to_terminal()
after_cur = ' '
end
- mp.osd_message(log .. suggestions .. '> ' .. before_cur .. '\027[7m' ..
- after_cur:sub(1, 1) .. '\027[0m' .. after_cur:sub(2), 999)
+ mp.osd_message(log .. suggestions .. prompt .. ' ' .. before_cur ..
+ '\027[7m' .. after_cur:sub(1, 1) .. '\027[0m' ..
+ after_cur:sub(2), 999)
end
-- Render the REPL and console as an ASS OSD
@@ -407,6 +417,7 @@ function update()
local suggestion_ass = style .. styles.suggestion .. suggestions
local log_ass = ''
+ local log_buffer = log_buffers[id]
local log_messages = #log_buffer
local log_max_lines = math.max(0, lines_max - rows)
if log_max_lines < log_messages then
@@ -423,7 +434,7 @@ function update()
if #suggestions > 0 then
ass:append(suggestion_ass .. '\\N')
end
- ass:append(style .. '> ' .. before_cur)
+ ass:append(style .. ass_escape(prompt) .. ' ' .. before_cur)
ass:append(cglyph)
ass:append(style .. after_cur)
@@ -432,7 +443,7 @@ function update()
ass:new_event()
ass:an(1)
ass:pos(2, screeny - 2 - global_margins.b * screeny)
- ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur)
+ ass:append(style .. '{\\alpha&HFF&}' .. ass_escape(prompt) .. ' ' .. before_cur)
ass:append(cglyph)
ass:append(style .. '{\\alpha&HFF&}' .. after_cur)
@@ -446,12 +457,28 @@ function set_active(active)
repl_active = true
insert_mode = false
mp.enable_key_bindings('console-input', 'allow-hide-cursor+allow-vo-dragging')
- mp.enable_messages('terminal-default')
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
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', line, cursor)
+ input_caller = nil
+ line = ''
+ cursor = 1
+ end
collectgarbage()
end
update()
@@ -513,6 +540,16 @@ function len_utf8(str)
return len
end
+local function handle_edit()
+ suggestion_buffer = {}
+ update()
+
+ if input_caller then
+ mp.commandv('script-message-to', input_caller, 'input-event', 'edited',
+ line)
+ end
+end
+
-- Insert a character at the current cursor position (any_unicode)
function handle_char_input(c)
if insert_mode then
@@ -521,8 +558,7 @@ function handle_char_input(c)
line = line:sub(1, cursor - 1) .. c .. line:sub(cursor)
end
cursor = cursor + #c
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- Remove the character behind the cursor (Backspace)
@@ -531,16 +567,14 @@ function handle_backspace()
local prev = prev_utf8(line, cursor)
line = line:sub(1, prev - 1) .. line:sub(cursor)
cursor = prev
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- Remove the character in front of the cursor (Del)
function handle_del()
if cursor > line:len() then return end
line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor))
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- Toggle insert mode (Ins)
@@ -568,8 +602,7 @@ function clear()
cursor = 1
insert_mode = false
history_pos = #history + 1
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- Close the REPL if the current line is empty, otherwise delete the next
@@ -642,20 +675,25 @@ end
-- Run the current command and clear the line (Enter)
function handle_enter()
- if line == '' then
+ if line == '' and input_caller == nil then
return
end
- if history[#history] ~= line then
+ if history[#history] ~= line and line ~= '' then
history_add(line)
end
- -- match "help [<text>]", return <text> or "", strip all whitespace
- local help = line:match('^%s*help%s+(.-)%s*$') or
- (line:match('^%s*help$') and '')
- if help then
- help_command(help)
+ if input_caller then
+ mp.commandv('script-message-to', input_caller, 'input-event', 'submit',
+ line)
else
- mp.command(line)
+ -- match "help [<text>]", return <text> or "", strip all whitespace
+ local help = line:match('^%s*help%s+(.-)%s*$') or
+ (line:match('^%s*help$') and '')
+ if help then
+ help_command(help)
+ else
+ mp.command(line)
+ end
end
clear()
@@ -1025,6 +1063,14 @@ function complete(backwards)
return
end
+ if input_caller then
+ completion_old_line = line
+ completion_old_cursor = cursor
+ mp.commandv('script-message-to', input_caller, 'input-event',
+ 'complete', line:sub(1, cursor - 1))
+ return
+ end
+
local before_cur = line:sub(1, cursor - 1)
local after_cur = line:sub(cursor)
@@ -1111,8 +1157,7 @@ function del_word()
before_cur = before_cur:gsub('[^%s]+%s*$', '', 1)
line = before_cur .. after_cur
cursor = before_cur:len() + 1
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- Delete from the cursor to the end of the word (Ctrl+Del)
@@ -1124,28 +1169,25 @@ function del_next_word()
after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
line = before_cur .. after_cur
- suggestion_buffer = {}
- update()
+ 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)
- suggestion_buffer = {}
- update()
+ 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
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- Empty the log buffer of all messages (Ctrl+L)
function clear_log_buffer()
- log_buffer = {}
+ log_buffers[id] = {}
update()
end
@@ -1212,8 +1254,7 @@ function paste(clip)
local after_cur = line:sub(cursor)
line = before_cur .. text .. after_cur
cursor = cursor + text:len()
- suggestion_buffer = {}
- update()
+ handle_edit()
end
-- List of input bindings. This is a weird mashup between common GUI text-input
@@ -1318,11 +1359,95 @@ mp.add_key_binding(nil, 'enable', function()
set_active(true)
end)
+mp.register_script_message('disable', function()
+ set_active(false)
+end)
+
-- Add a script-message to show the REPL and fill it with the provided text
mp.register_script_message('type', function(text, cursor_pos)
show_and_type(text, cursor_pos)
end)
+mp.register_script_message('get-input', function (script_name, args)
+ if repl_active then
+ return
+ end
+
+ input_caller = script_name
+ args = utils.parse_json(args)
+ prompt = args.prompt or default_prompt
+ line = args.default_text or ''
+ cursor = tonumber(args.cursor_position) or line:len() + 1
+ id = args.id or script_name .. prompt
+ if histories[id] == nil then
+ histories[id] = {}
+ log_buffers[id] = {}
+ end
+ history = histories[id]
+ history_pos = #history + 1
+
+ set_active(true)
+ mp.commandv('script-message-to', input_caller, 'input-event', 'opened')
+end)
+
+mp.register_script_message('log', function (message)
+ -- input.get's edited handler is invoked after submit, so avoid modifying
+ -- the default log.
+ if input_caller == nil then
+ return
+ end
+
+ message = utils.parse_json(message)
+
+ log_add(message.error and styles.error or message.style or '',
+ message.text .. '\n')
+end)
+
+mp.register_script_message('set-log', function (log)
+ if input_caller == nil then
+ return
+ end
+
+ log = utils.parse_json(log)
+ log_buffers[id] = {}
+
+ for i = 1, #log do
+ if type(log[i]) == 'table' then
+ log[i].text = log[i].text .. '\n'
+ log_buffers[id][i] = log[i]
+ else
+ log_buffers[id][i] = {
+ style = '',
+ text = log[i] .. '\n',
+ }
+ end
+ end
+
+ update()
+end)
+
+mp.register_script_message('complete', function(list, start_pos)
+ if line ~= completion_old_line or cursor ~= completion_old_cursor then
+ return
+ end
+
+ local completions, prefix = complete_match(line:sub(start_pos, cursor),
+ utils.parse_json(list))
+ local before_cur = line:sub(1, start_pos - 1) .. prefix
+ local after_cur = line:sub(cursor)
+ cursor = before_cur:len() + 1
+ line = before_cur .. after_cur
+
+ if #completions > 1 then
+ suggestion_buffer = completions
+ selected_suggestion_index = 0
+ completion_start_position = start_pos
+ completion_append = ''
+ end
+
+ update()
+end)
+
-- Redraw the REPL when the OSD size changes. This is needed because the
-- PlayRes of the OSD will need to be adjusted.
mp.observe_property('osd-width', 'native', update)
diff --git a/player/lua/input.lua b/player/lua/input.lua
new file mode 100644
index 0000000000..a73128ec7d
--- /dev/null
+++ b/player/lua/input.lua
@@ -0,0 +1,66 @@
+--[[
+This file is part of mpv.
+
+mpv is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+mpv is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local utils = require "mp.utils"
+local input = {}
+
+function input.get(t)
+ mp.commandv("script-message-to", "console", "get-input",
+ mp.get_script_name(), utils.format_json({
+ prompt = t.prompt,
+ default_text = t.default_text,
+ cursor_position = t.cursor_position,
+ id = t.id,
+ }))
+
+ mp.register_script_message("input-event", function (type, text, cursor_position)
+ if t[type] then
+ local suggestions, completion_start_position = t[type](text, cursor_position)
+
+ if type == "complete" and suggestions then
+ mp.commandv("script-message-to", "console", "complete",
+ utils.format_json(suggestions), completion_start_position)
+ end
+ end
+
+ if type == "closed" then
+ mp.unregister_script_message("input-event")
+ end
+ end)
+
+ return true
+end
+
+function input.terminate()
+ mp.commandv("script-message-to", "console", "disable")
+end
+
+function input.log(message, style)
+ mp.commandv("script-message-to", "console", "log",
+ utils.format_json({ text = message, style = style }))
+end
+
+function input.log_error(message)
+ mp.commandv("script-message-to", "console", "log",
+ utils.format_json({ text = message, error = true }))
+end
+
+function input.set_log(log)
+ mp.commandv("script-message-to", "console", "set-log", utils.format_json(log))
+end
+
+return input
diff --git a/player/lua/meson.build b/player/lua/meson.build
index 362c87cbb7..1d87938f1a 100644
--- a/player/lua/meson.build
+++ b/player/lua/meson.build
@@ -1,5 +1,6 @@
lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua',
- 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua']
+ 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua',
+ 'input.lua']
foreach file: lua_files
lua_file = custom_target(file,
input: join_paths(source_root, 'player', 'lua', file),