commands.lua: split this script out of console.lua

It was suggested by CogentRedTester in
https://github.com/mpv-player/mpv/pull/10282#issuecomment-1858727729 and
https://github.com/mpv-player/mpv/pull/10282#issuecomment-1858809580 and
by avih that making running commands an mp.input client is a better
architecture.

A practical advantage is that completions are calculated in a different
thread, which prevents hanging when completing slow/network filesystems.

script-binding console/enable becomes script-binding commands/open,
though the console one is kept as an alias. I took the opportunity to
rename this because open makes more sense for a graphical modal, and it
is the word used in mp.input and user-data.

script-message-to console type becomes script-message-to commands type,
though the console one is kept as an alias. It is also changed to
automatically close on submit without having to append '; keypress ESC'
as you don't need to keep the console open after running prefilled
commands.

Also convert to double quotes like other scripts and rename some
inconsistent functions.
This commit is contained in:
Guido Cella
2025-02-25 23:05:58 +01:00
committed by Dudemanguy
parent ce6a52d40a
commit 0b3cc3a167
15 changed files with 679 additions and 639 deletions

View File

@@ -0,0 +1,4 @@
commands.lua is split out of console.lua. commands.lua runs and completes commands and adds mpv's log entries to the console's log, while console.lua handles the UI for other scripts
add `--load-commands` option
`script-binding console/enable` becomes `script-binding commands/open`, though the console one is kept as an alias
`script-message-to console type` becomes `script-message-to commands type`, though the console one is kept as an alias. This also now automatically closes the console after running the command.

52
DOCS/man/commands.rst Normal file
View File

@@ -0,0 +1,52 @@
COMMANDS
========
This script allows running and completing input commands in the console
interactively, and also adds mpv's log to the console's log.
Keybindings
-----------
\`
Open the console to enter commands.
Commands
--------
``script-binding commands/open``
Open the console to enter commands.
``script-message-to commands type <text> [<cursor_pos>]``
Show the console and pre-fill it with the provided text, optionally
specifying the initial cursor position as a positive integer starting from
1. The console is automatically closed after running the command.
.. admonition:: Examples for input.conf
``% script-message-to commands type "seek absolute-percent" 6``
Enter a percent position to seek to.
``Ctrl+o script-message-to console type "loadfile ''" 11``
Enter a file or URL to play, with autocompletion of paths in the
filesystem.
Configuration
-------------
This script can be customized through a config file ``script-opts/commands.conf``
placed in mpv's user directory and through the ``--script-opts`` command-line
option. The configuration syntax is described in `mp.options functions`_.
Configurable Options
~~~~~~~~~~~~~~~~~~~~
``persist_history``
Default: no
Whether to save the command history to a file and load it.
``history_path``
Default: ``~~state/command_history.txt``
The file path for ``persist_history`` (see `PATHS`_).

View File

@@ -2,16 +2,15 @@ CONSOLE
======= =======
This script provides the ability to process the user's textual input to other This script provides the ability to process the user's textual input to other
scripts through the ``mp.input`` API. It also has a builtin mode of operation to scripts through the ``mp.input`` API. It can be displayed on both the video
complete and run mpv input commands and print mpv's log. It can be displayed on window and the terminal. It can be disabled entirely using the
both the video window and the terminal. It can be disabled entirely using the
``--load-console=no`` option. ``--load-console=no`` option.
Keybindings Console can either process free-form text or select from a predefined list of
----------- items.
\` Free-form text mode keybindings
Show the console. -------------------------------
ESC and Ctrl+[ ESC and Ctrl+[
Hide the console. Hide the console.
@@ -80,7 +79,7 @@ PGDN
Stop navigating the command history. Stop navigating the command history.
Ctrl+r Ctrl+r
Search the command history. Search the command history. See `SELECT`_ for the key bindings in this mode.
INSERT INSERT
Toggle insert mode. Toggle insert mode.
@@ -109,23 +108,6 @@ WHEEL_UP
WHEEL_DOWN WHEEL_DOWN
Move forward in the command history. Move forward in the command history.
Commands
--------
``script-message-to console type <text> [<cursor_pos>]``
Show the console and pre-fill it with the provided text, optionally
specifying the initial cursor position as a positive integer starting from
1.
.. admonition:: Examples for input.conf
``% script-message-to console type "seek absolute-percent; keypress ESC" 6``
Enter a percent position to seek to and close the console.
``Ctrl+o script-message-to console type "loadfile ''; keypress ESC" 11``
Enter a file or URL to play, with autocompletion of paths in the
filesystem.
Known issues Known issues
------------ ------------
@@ -139,9 +121,6 @@ This script can be customized through a config file ``script-opts/console.conf``
placed in mpv's user directory and through the ``--script-opts`` command-line placed in mpv's user directory and through the ``--script-opts`` command-line
option. The configuration syntax is described in `mp.options functions`_. option. The configuration syntax is described in `mp.options functions`_.
Key bindings can be changed in a standard way, see for example stats.lua
documentation.
Configurable Options Configurable Options
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
@@ -232,16 +211,6 @@ Configurable Options
Remove duplicate entries in history as to only keep the latest one. Remove duplicate entries in history as to only keep the latest one.
``persist_history``
Default: no
Whether to save the command history to a file and load it.
``history_path``
Default: ``~~state/command_history.txt``
The file path for ``persist_history`` (see `PATHS`_).
``font_hw_ratio`` ``font_hw_ratio``
Default: auto Default: auto

View File

@@ -984,8 +984,8 @@ REPL.
that are used when the console is displayed in the terminal. that are used when the console is displayed in the terminal.
``input.log_error(message)`` ``input.log_error(message)``
Helper to add a line to the log buffer with the same color as the one the Helper to add a line to the log buffer with the same color as the one used
console uses for errors. Useful when the user submits invalid input. for commands that error. Useful when the user submits invalid input.
``input.set_log(log)`` ``input.set_log(log)``
Replace the entire log buffer. Replace the entire log buffer.

View File

@@ -1544,6 +1544,8 @@ works like in older mpv releases:
.. include:: console.rst .. include:: console.rst
.. include:: commands.rst
.. include:: select.rst .. include:: select.rst
.. include:: positioning.rst .. include:: positioning.rst

View File

@@ -1054,9 +1054,11 @@ Program Behavior
the overlay permanent). the overlay permanent).
``--load-console=<yes|no>`` ``--load-console=<yes|no>``
Enable the built-in script that shows a console on a key binding and lets Enable the built-in script to handle textual input (default: yes).
you enter commands (default: yes). The ````` key is used to show the
console by default, and ``ESC`` to hide it again. ``--load-commands=<yes|no>``
Enable the built-in script to enter commands in the console (default: yes).
The ````` key is used to activate this by default.
``--load-auto-profiles=<yes|no|auto>`` ``--load-auto-profiles=<yes|no|auto>``
Enable the builtin script that does auto profiles (default: auto). See Enable the builtin script that does auto profiles (default: auto). See

View File

@@ -104,7 +104,7 @@
#i script-binding stats/display-stats # display information and statistics #i script-binding stats/display-stats # display information and statistics
#I script-binding stats/display-stats-toggle # toggle displaying information and statistics #I script-binding stats/display-stats-toggle # toggle displaying information and statistics
#? script-binding stats/display-page-4-toggle # toggle displaying key bindings #? script-binding stats/display-page-4-toggle # toggle displaying key bindings
#` script-binding console/enable # open the console #` script-binding commands/open # open the console
#z add sub-delay -0.1 # shift subtitles 100 ms earlier #z add sub-delay -0.1 # shift subtitles 100 ms earlier
#Z add sub-delay +0.1 # delay subtitles by 100 ms #Z add sub-delay +0.1 # delay subtitles by 100 ms
#x add sub-delay +0.1 # delay subtitles by 100 ms #x add sub-delay +0.1 # delay subtitles by 100 ms

View File

@@ -555,6 +555,7 @@ static const m_option_t mp_opts[] = {
.flags = UPDATE_BUILTIN_SCRIPTS}, .flags = UPDATE_BUILTIN_SCRIPTS},
{"load-select", OPT_BOOL(lua_load_select), .flags = UPDATE_BUILTIN_SCRIPTS}, {"load-select", OPT_BOOL(lua_load_select), .flags = UPDATE_BUILTIN_SCRIPTS},
{"load-positioning", OPT_BOOL(lua_load_positioning), .flags = UPDATE_BUILTIN_SCRIPTS}, {"load-positioning", OPT_BOOL(lua_load_positioning), .flags = UPDATE_BUILTIN_SCRIPTS},
{"load-commands", OPT_BOOL(lua_load_commands), .flags = UPDATE_BUILTIN_SCRIPTS},
#endif #endif
// ------------------------- stream options -------------------- // ------------------------- stream options --------------------
@@ -988,6 +989,7 @@ static const struct MPOpts mp_default_opts = {
.lua_load_auto_profiles = -1, .lua_load_auto_profiles = -1,
.lua_load_select = true, .lua_load_select = true,
.lua_load_positioning = true, .lua_load_positioning = true,
.lua_load_commands = true,
#endif #endif
.auto_load_scripts = true, .auto_load_scripts = true,
.loop_times = 1, .loop_times = 1,

View File

@@ -175,6 +175,7 @@ typedef struct MPOpts {
int lua_load_auto_profiles; int lua_load_auto_profiles;
bool lua_load_select; bool lua_load_select;
bool lua_load_positioning; bool lua_load_positioning;
bool lua_load_commands;
bool auto_load_scripts; bool auto_load_scripts;

View File

@@ -444,7 +444,7 @@ typedef struct MPContext {
struct mp_ipc_ctx *ipc_ctx; struct mp_ipc_ctx *ipc_ctx;
int64_t builtin_script_ids[7]; int64_t builtin_script_ids[8];
mp_mutex abort_lock; mp_mutex abort_lock;

View File

@@ -87,6 +87,9 @@ static const char * const builtin_lua_scripts[][2] = {
}, },
{"@positioning.lua", {"@positioning.lua",
# include "player/lua/positioning.lua.inc" # include "player/lua/positioning.lua.inc"
},
{"@commands.lua",
# include "player/lua/commands.lua.inc"
}, },
{0} {0}
}; };

566
player/lua/commands.lua Normal file
View File

@@ -0,0 +1,566 @@
--[[
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 options = {
persist_history = false,
history_path = "~~state/command_history.txt",
}
local input = require "mp.input"
local utils = require "mp.utils"
local styles = {
-- Colors are stolen from base16 Eighties by Chris Kempson
-- and converted to BGR as is required by ASS.
-- 2d2d2d 393939 515151 697374
-- 939fa0 c8d0d3 dfe6e8 ecf0f2
-- 7a77f2 5791f9 66ccff 99cc99
-- cccc66 cc9966 cc99cc 537bd2
debug = "{\\1c&Ha09f93&}",
v = "{\\1c&H99cc99&}",
warn = "{\\1c&H66ccff&}",
error = "{\\1c&H7a77f2&}",
fatal = "{\\1c&H5791f9&}",
completion = "{\\1c&Hcc99cc&}",
}
for key, style in pairs(styles) do
styles[key] = style .. "{\\3c&H111111&}"
end
local terminal_styles = {
debug = "\027[90m",
v = "\027[32m",
warn = "\027[33m",
error = "\027[31m",
fatal = "\027[91m",
}
local platform = mp.get_property("platform")
local path_separator = platform == "windows" and "\\" or "/"
local completion_pos
local completion_append
local last_text
local last_cursor_position
local commands
local function get_commands()
if not commands then
commands = mp.get_property_native("command-list")
end
return commands
end
local function help_command(param)
table.sort(get_commands(), function(c1, c2)
return c1.name < c2.name
end)
local output = ""
if param == "" then
output = "Available commands:\n"
for _, cmd in ipairs(get_commands()) do
output = output .. " " .. cmd.name
end
output = output .. "\n"
output = output .. 'Use "help command" to show information about a command.\n'
output = output .. "ESC or Ctrl+d exits the console.\n"
else
local cmd = nil
for _, curcmd in ipairs(get_commands()) do
if curcmd.name:find(param, 1, true) then
cmd = curcmd
if curcmd.name == param then
break -- exact match
end
end
end
if not cmd then
input.log('No command matches "' .. param .. '"!', styles.error,
terminal_styles.error)
return
end
output = output .. 'Command "' .. cmd.name .. '"\n'
for _, arg in ipairs(cmd.args) do
output = output .. " " .. arg.name .. " (" .. arg.type .. ")"
if arg.optional then
output = output .. " (optional)"
end
output = output .. "\n"
end
if cmd.vararg then
output = output .. "This command supports variable arguments.\n"
end
end
input.log(output:sub(1, -2))
end
local function submit(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)
elseif line ~= "" then
mp.command(line)
end
end
local function opened()
mp.enable_messages("terminal-default")
end
local function closed(text, cursor_position)
mp.enable_messages("silent:terminal-default")
last_text = text
last_cursor_position = cursor_position
end
local function command_list()
local cmds = {}
for i, command in ipairs(get_commands()) do
cmds[i] = command.name
end
return cmds
end
local function property_list()
local properties = mp.get_property_native("property-list")
for _, sub_property in pairs({"video", "audio", "sub", "sub2"}) do
properties[#properties + 1] = "current-tracks/" .. sub_property
end
for _, sub_property in pairs({"text", "text-primary"}) do
properties[#properties + 1] = "clipboard/" .. sub_property
end
return properties
end
local function profile_list()
local profiles = {}
for i, profile in ipairs(mp.get_property_native("profile-list")) do
profiles[i] = profile.name
end
return profiles
end
local function list_option_list()
local opts = {}
-- Don"t log errors for renamed and removed properties.
-- (Just mp.enable_messages("fatal") still logs them to the terminal.)
local msg_level_backup = mp.get_property("msg-level")
mp.set_property("msg-level", msg_level_backup == "" and "cplayer=no"
or msg_level_backup .. ",cplayer=no")
for _, option in pairs(mp.get_property_native("options")) do
if mp.get_property("option-info/" .. option .. "/type", ""):find(" list$") then
opts[#opts + 1] = option
end
end
mp.set_property("msg-level", msg_level_backup)
return opts
end
local function list_option_action_list(option)
local type = mp.get_property("option-info/" .. option .. "/type")
if type == "Key/value list" then
return {"add", "append", "set", "remove"}
end
if type == "String list" or type == "Object settings list" then
return {"add", "append", "clr", "pre", "set", "remove", "toggle"}
end
end
local function list_option_value_list(option)
local values = mp.get_property_native(option)
if type(values) ~= "table" then
return
end
if type(values[1]) ~= "table" then
return values
end
for i, value in ipairs(values) do
values[i] = value.label and "@" .. value.label or value.name
end
return values
end
local function has_file_argument(candidate_command)
for _, command in pairs(get_commands()) do
if command.name == candidate_command then
return command.args[1] and
(command.args[1].name == "filename" or command.args[1].name == "url")
end
end
end
local function file_list(directory)
if directory == "" then
directory = "."
else
directory = mp.command_native({"expand-path", directory})
end
local files = utils.readdir(directory, "files") or {}
for _, dir in pairs(utils.readdir(directory, "dirs") or {}) do
files[#files + 1] = dir .. path_separator
end
return files
end
local function handle_file_completion(before_cur)
local directory, last_component_pos =
before_cur:sub(completion_pos):match("(.-)()[^" .. path_separator .."]*$")
completion_pos = completion_pos + last_component_pos - 1
-- Don"t use completion_append for file completion to not add quotes after
-- directories whose entries you may want to complete afterwards.
completion_append = ""
return file_list(directory)
end
local function handle_choice_completion(option, before_cur)
local info = mp.get_property_native("option-info/" .. option, {})
if info.type == "Flag" then
return { "no", "yes" }, before_cur
end
if info["expects-file"] then
return handle_file_completion(before_cur)
end
-- Fix completing the empty value for --dscale and --cscale.
if info.choices and info.choices[1] == "" and completion_append == "" then
info.choices[1] = '""'
end
return info.choices
end
local function command_flags_at_1st_argument_list(command)
local flags = {
["playlist-next"] = {"weak", "force"},
["playlist-play-index"] = {"current", "none"},
["playlist-remove"] = {"current"},
["rescan-external-files"] = {"reselect", "keep-selection"},
["revert-seek"] = {"mark", "mark-permanent"},
["screenshot"] = {"subtitles", "video", "window", "each-frame"},
["stop"] = {"keep-playlist"},
}
flags["playlist-prev"] = flags["playlist-next"]
flags["screenshot-raw"] = flags.screenshot
return flags[command]
end
local function command_flags_at_2nd_argument_list(command)
local flags = {
["apply-profile"] = {"default", "restore"},
["frame-step"] = {"play", "seek", "mute"},
["loadfile"] = {"replace", "append", "append-play", "insert-next",
"insert-next-play", "insert-at", "insert-at-play"},
["screenshot-to-file"] = {"subtitles", "video", "window", "each-frame"},
["screenshot-raw"] = {"bgr0", "bgra", "rgba", "rgba64"},
["seek"] = {"relative", "absolute", "absolute-percent",
"relative-percent", "keyframes", "exact"},
["sub-add"] = {"select", "auto", "cached"},
["sub-seek"] = {"primary", "secondary"},
}
flags.loadlist = flags.loadfile
flags["audio-add"] = flags["sub-add"]
flags["video-add"] = flags["sub-add"]
flags["sub-step"] = flags["sub-seek"]
return flags[command]
end
local function handle_flags(command, arg_index, flags)
for _, cmd in pairs(get_commands()) do
if cmd.name == command then
if cmd.args[arg_index] and cmd.args[arg_index].type == "Flags" then
break
else
return
end
end
end
local plus_pos = flags:find("%+[^%+]*$")
if plus_pos then
completion_pos = completion_pos + plus_pos
end
end
local function executable_list()
local executable_map = {}
local path = os.getenv("PATH") or ""
local separator = platform == "windows" and ";" or ":"
local exts = {}
for ext in (os.getenv("PATHEXT") or ""):gmatch("[^;]+") do
exts[ext:lower()] = true
end
for directory in path:gmatch("[^" .. separator .. "]+") do
for _, executable in pairs(utils.readdir(directory, "files") or {}) do
if not next(exts) or exts[(executable:match("%.%w+$") or ""):lower()] then
executable_map[executable] = true
end
end
end
local executables = {}
for executable, _ in pairs(executable_map) do
executables[#executables + 1] = executable
end
return executables
end
local function filter_label_list(type)
local values = {"all"}
for _, value in pairs(mp.get_property_native(type)) do
if value.label then
values[#values + 1] = value.label
end
end
return values
end
local function complete(before_cur)
local tokens = {}
local first_useful_token_index = 1
local completions
local begin_new_token = true
local last_quote
for pos, char in before_cur:gmatch("()(.)") do
if char:find("[%s;]") and not last_quote then
begin_new_token = true
if char == ";" then
first_useful_token_index = #tokens + 1
end
elseif begin_new_token then
tokens[#tokens + 1] = { text = char, pos = pos }
last_quote = char:match("['\"]")
begin_new_token = false
else
tokens[#tokens].text = tokens[#tokens].text .. char
if char == last_quote then
last_quote = nil
end
end
end
completion_append = last_quote or ""
-- Strip quotes from tokens.
for _, token in pairs(tokens) do
if token.text:find('^"') then
token.text = token.text:sub(2):gsub('"$', "")
token.pos = token.pos + 1
elseif token.text:find("^'") then
token.text = token.text:sub(2):gsub('"$', "")
token.pos = token.pos + 1
end
end
-- Skip command prefixes because it is not worth lumping them together with
-- command completions when they are useless for interactive usage.
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,
["nonrepeatable"] = true, ["nonscalable"] = true,
["async"] = true, ["sync"] = true
}
-- Add an empty token if the cursor is after whitespace or ; to simplify
-- comparisons.
if before_cur == "" or before_cur:find("[%s;]$") then
tokens[#tokens + 1] = { text = "", pos = #before_cur + 1 }
end
while tokens[first_useful_token_index] and
command_prefixes[tokens[first_useful_token_index].text] do
if first_useful_token_index == #tokens then
return
end
first_useful_token_index = first_useful_token_index + 1
end
completion_pos = tokens[#tokens].pos
local add_actions = {
["add"] = true, ["append"] = true, ["pre"] = true, ["set"] = true
}
local first_useful_token = tokens[first_useful_token_index]
local property_pos = before_cur:match("${[=>]?()[%w_/-]*$")
if property_pos then
completion_pos = property_pos
completions = property_list()
completion_append = "}"
elseif #tokens == first_useful_token_index then
completions = command_list()
completions[#completions + 1] = "help"
elseif #tokens == first_useful_token_index + 1 then
if first_useful_token.text == "set" or
first_useful_token.text == "add" or
first_useful_token.text == "cycle" or
first_useful_token.text == "cycle-values" or
first_useful_token.text == "multiply" then
completions = property_list()
elseif first_useful_token.text == "help" then
completions = command_list()
elseif first_useful_token.text == "apply-profile" then
completions = profile_list()
elseif first_useful_token.text == "change-list" then
completions = list_option_list()
elseif first_useful_token.text == "run" then
completions = executable_list()
elseif first_useful_token.text == "vf" or
first_useful_token.text == "af" then
completions = list_option_action_list(first_useful_token.text)
elseif first_useful_token.text == "vf-command" or
first_useful_token.text == "af-command" then
completions = filter_label_list(first_useful_token.text:sub(1,2))
elseif has_file_argument(first_useful_token.text) then
completions = handle_file_completion(before_cur)
else
completions = command_flags_at_1st_argument_list(first_useful_token.text)
handle_flags(first_useful_token.text, 1, tokens[#tokens].text)
end
elseif first_useful_token.text == "cycle-values" then
completions = handle_choice_completion(tokens[first_useful_token_index + 1].text,
before_cur)
elseif #tokens == first_useful_token_index + 2 then
if first_useful_token.text == "set" then
completions = handle_choice_completion(tokens[first_useful_token_index + 1].text,
before_cur)
elseif first_useful_token.text == "change-list" then
completions = list_option_action_list(tokens[first_useful_token_index + 1].text)
elseif first_useful_token.text == "vf" or
first_useful_token.text == "af" then
if add_actions[tokens[first_useful_token_index + 1].text] then
completions = handle_choice_completion(first_useful_token.text, before_cur)
elseif tokens[first_useful_token_index + 1].text == "remove" then
completions = list_option_value_list(first_useful_token.text)
end
else
completions = command_flags_at_2nd_argument_list(first_useful_token.text)
handle_flags(first_useful_token.text, 2, tokens[#tokens].text)
end
elseif #tokens == first_useful_token_index + 3 then
if first_useful_token.text == "change-list" then
if add_actions[tokens[first_useful_token_index + 2].text] then
completions = handle_choice_completion(tokens[first_useful_token_index + 1].text,
before_cur)
elseif tokens[first_useful_token_index + 2].text == "remove" then
completions = list_option_value_list(tokens[first_useful_token_index + 1].text)
end
elseif first_useful_token.text == "dump-cache" then
completions = handle_file_completion(before_cur)
end
end
return completions or {}, completion_pos, completion_append
end
local function open(text, cursor_position, keep_open)
input.get({
prompt = ">",
submit = submit,
keep_open = keep_open,
opened = opened,
closed = closed,
complete = complete,
autoselect_completion = true,
default_text = text,
cursor_position = cursor_position,
history_path = options.persist_history
and mp.command_native({"expand-path", options.history_path}) or nil,
})
end
mp.add_key_binding(nil, "open", function ()
open(last_text, last_cursor_position, true)
end)
-- Open the console with passed text and cursor_position arguments
mp.register_script_message("type", open)
mp.register_event("log-message", function(e)
-- Ignore log messages from the OSD because of paranoia, since writing them
-- to the OSD could generate more messages in an infinite loop.
if e.prefix:sub(1, 3) == "osd" then return end
-- Ignore messages output by this script.
if e.prefix == mp.get_script_name() then return end
-- Ignore buffer overflow warning messages. Overflowed log messages would
-- have been offscreen anyway.
if e.prefix == "overflow" then return end
-- Filter out trace-level log messages, even if the terminal-default log
-- level includes them. These aren"t too useful for an on-screen display
-- without scrollback and they include messages that are generated from the
-- OSD display itself.
if e.level == "trace" then return end
-- Use color for debug/v/warn/error/fatal messages.
input.log("[" .. e.prefix .. "] " .. e.text:sub(1, -2), styles[e.level],
terminal_styles[e.level])
end)
-- Enable log messages. In silent mode, mpv will queue log messages in a buffer
-- until enable_messages is called again without the silent: prefix.
mp.enable_messages("silent:terminal-default")
require "mp.options".read_options(options)

View File

@@ -45,24 +45,11 @@ local opts = {
match_color = '#0088FF', match_color = '#0088FF',
case_sensitive = platform ~= 'windows' and true or false, case_sensitive = platform ~= 'windows' and true or false,
history_dedup = true, history_dedup = true,
persist_history = false,
history_path = '~~state/command_history.txt',
font_hw_ratio = 'auto', font_hw_ratio = 'auto',
} }
local styles = { local styles = {
-- Colors are stolen from base16 Eighties by Chris Kempson
-- and converted to BGR as is required by ASS.
-- 2d2d2d 393939 515151 697374
-- 939fa0 c8d0d3 dfe6e8 ecf0f2
-- 7a77f2 5791f9 66ccff 99cc99
-- cccc66 cc9966 cc99cc 537bd2
debug = '{\\1c&Ha09f93&}',
v = '{\\1c&H99cc99&}',
warn = '{\\1c&H66ccff&}',
error = '{\\1c&H7a77f2&}', error = '{\\1c&H7a77f2&}',
fatal = '{\\1c&H5791f9&}',
completion = '{\\1c&Hcc99cc&}', completion = '{\\1c&Hcc99cc&}',
} }
for key, style in pairs(styles) do for key, style in pairs(styles) do
@@ -70,11 +57,7 @@ for key, style in pairs(styles) do
end end
local terminal_styles = { local terminal_styles = {
debug = '\027[90m',
v = '\027[32m',
warn = '\027[33m',
error = '\027[31m', error = '\027[31m',
fatal = '\027[91m',
selected_completion = '\027[7m', selected_completion = '\027[7m',
default_item = '\027[1m', default_item = '\027[1m',
disabled = '\027[38;5;8m', disabled = '\027[38;5;8m',
@@ -89,19 +72,17 @@ local pending_update = false
local ime_active = mp.get_property_bool('input-ime') local ime_active = mp.get_property_bool('input-ime')
local line = '' local line = ''
local cursor = 1 local cursor = 1
local default_prompt = '>' local prompt
local prompt = default_prompt local id
local default_id = 'default'
local id = default_id
local histories = {[id] = {}} local histories = {}
local history = histories[id] local history
local history_pos = 1 local history_pos = 1
local searching_history = false local searching_history = false
local history_paths = {} local history_paths = {}
local histories_to_save = {} local histories_to_save = {}
local log_buffers = {[id] = {}} local log_buffers = {}
local key_bindings = {} local key_bindings = {}
local dont_bind_up_down = false local dont_bind_up_down = false
local overlay = mp.create_osd_overlay('ass-events') local overlay = mp.create_osd_overlay('ass-events')
@@ -114,11 +95,9 @@ local completion_buffer = {}
local selected_completion_index local selected_completion_index
local completion_pos local completion_pos
local completion_append local completion_append
local path_separator = platform == 'windows' and '\\' or '/'
local completion_old_line local completion_old_line
local completion_old_cursor local completion_old_cursor
local autoselect_completion local autoselect_completion
local commands
local selectable_items local selectable_items
local matches = {} local matches = {}
@@ -162,14 +141,6 @@ local function get_margin_y()
return opts.margin_y > -1 and opts.margin_y or mp.get_property_native('osd-margin-y') return opts.margin_y > -1 and opts.margin_y or mp.get_property_native('osd-margin-y')
end end
local function get_commands()
if not commands then
commands = mp.get_property_native('command-list')
end
return commands
end
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos' -- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. -- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
@@ -960,10 +931,8 @@ local function handle_edit()
handle_cursor_move() handle_cursor_move()
if input_caller then mp.commandv('script-message-to', input_caller, 'input-event', 'edited',
mp.commandv('script-message-to', input_caller, 'input-event', 'edited', utils.format_json({line}))
utils.format_json({line}))
end
end end
-- Insert a character at the current cursor position (any_unicode) -- Insert a character at the current cursor position (any_unicode)
@@ -1029,49 +998,6 @@ local function maybe_exit()
end end
end end
local function help_command(param)
table.sort(get_commands(), function(c1, c2)
return c1.name < c2.name
end)
local output = ''
if param == '' then
output = 'Available commands:\n'
for _, cmd in ipairs(get_commands()) do
output = output .. ' ' .. cmd.name
end
output = output .. '\n'
output = output .. 'Use "help command" to show information about a command.\n'
output = output .. "ESC or Ctrl+d exits the console.\n"
else
local cmd = nil
for _, curcmd in ipairs(get_commands()) do
if curcmd.name:find(param, 1, true) then
cmd = curcmd
if curcmd.name == param then
break -- exact match
end
end
end
if not cmd then
log_add('No command matches "' .. param .. '"!', styles.error,
terminal_styles.error)
return
end
output = output .. 'Command "' .. cmd.name .. '"\n'
for _, arg in ipairs(cmd.args) do
output = output .. ' ' .. arg.name .. ' (' .. arg.type .. ')'
if arg.optional then
output = output .. ' (optional)'
end
output = output .. '\n'
end
if cmd.vararg then
output = output .. 'This command supports variable arguments.\n'
end
end
log_add(output:sub(1, -2))
end
local function unbind_mouse() local function unbind_mouse()
mp.remove_key_binding('_console_mouse_move') mp.remove_key_binding('_console_mouse_move')
mp.remove_key_binding('_console_mbtn_left') mp.remove_key_binding('_console_mbtn_left')
@@ -1089,42 +1015,25 @@ local function submit()
return return
end end
if line == '' and input_caller == nil then
return
end
if selectable_items then if selectable_items then
if #matches > 0 then if #matches > 0 then
mp.commandv('script-message-to', input_caller, 'input-event', 'submit', mp.commandv('script-message-to', input_caller, 'input-event', 'submit',
utils.format_json({matches[selected_match].index})) utils.format_json({matches[selected_match].index}))
end end
elseif input_caller then else
if selected_completion_index == 0 and autoselect_completion then if selected_completion_index == 0 and autoselect_completion then
cycle_through_completions() cycle_through_completions()
end end
mp.commandv('script-message-to', input_caller, 'input-event', 'submit', mp.commandv('script-message-to', input_caller, 'input-event', 'submit',
utils.format_json({line})) utils.format_json({line}))
else
if selected_completion_index == 0 then
cycle_through_completions()
end
-- match "help [<text>]", return <text> or "", strip all whitespace if history[#history] ~= line and line ~= '' then
local help = line:match('^%s*help%s+(.-)%s*$') or history_add(line)
(line:match('^%s*help$') and '')
if help then
help_command(help)
else
mp.command(line)
end end
end end
if history[#history] ~= line and line ~= '' then if not keep_open then
history_add(line)
end
if input_caller and not keep_open then
set_active(false) set_active(false)
elseif not selectable_items then elseif not selectable_items then
clear() clear()
@@ -1428,238 +1337,6 @@ local function text_input(info)
end end
end end
local function command_list()
local cmds = {}
for i, command in ipairs(get_commands()) do
cmds[i] = command.name
end
return cmds
end
local function property_list()
local properties = mp.get_property_native('property-list')
for _, sub_property in pairs({'video', 'audio', 'sub', 'sub2'}) do
properties[#properties + 1] = 'current-tracks/' .. sub_property
end
for _, sub_property in pairs({'text', 'text-primary'}) do
properties[#properties + 1] = 'clipboard/' .. sub_property
end
return properties
end
local function profile_list()
local profiles = {}
for i, profile in ipairs(mp.get_property_native('profile-list')) do
profiles[i] = profile.name
end
return profiles
end
local function list_option_list()
local options = {}
-- Don't log errors for renamed and removed properties.
-- (Just mp.enable_messages('fatal') still logs them to the terminal.)
local msg_level_backup = mp.get_property('msg-level')
mp.set_property('msg-level', msg_level_backup == '' and 'cplayer=no'
or msg_level_backup .. ',cplayer=no')
for _, option in pairs(mp.get_property_native('options')) do
if mp.get_property('option-info/' .. option .. '/type', ''):find(' list$') then
options[#options + 1] = option
end
end
mp.set_property('msg-level', msg_level_backup)
return options
end
local function list_option_action_list(option)
local type = mp.get_property('option-info/' .. option .. '/type')
if type == 'Key/value list' then
return {'add', 'append', 'set', 'remove'}
end
if type == 'String list' or type == 'Object settings list' then
return {'add', 'append', 'clr', 'pre', 'set', 'remove', 'toggle'}
end
end
local function list_option_value_list(option)
local values = mp.get_property_native(option)
if type(values) ~= 'table' then
return
end
if type(values[1]) ~= 'table' then
return values
end
for i, value in ipairs(values) do
values[i] = value.label and '@' .. value.label or value.name
end
return values
end
local function has_file_argument(candidate_command)
for _, command in pairs(get_commands()) do
if command.name == candidate_command then
return command.args[1] and
(command.args[1].name == 'filename' or command.args[1].name == 'url')
end
end
end
local function file_list(directory)
if directory == '' then
directory = '.'
else
directory = mp.command_native({'expand-path', directory})
end
local files = utils.readdir(directory, 'files') or {}
for _, dir in pairs(utils.readdir(directory, 'dirs') or {}) do
files[#files + 1] = dir .. path_separator
end
return files
end
local function handle_file_completion(before_cur)
local directory, last_component_pos =
before_cur:sub(completion_pos):match('(.-)()[^' .. path_separator ..']*$')
completion_pos = completion_pos + last_component_pos - 1
-- Don't use completion_append for file completion to not add quotes after
-- directories whose entries you may want to complete afterwards.
completion_append = ''
return file_list(directory)
end
local function handle_choice_completion(option, before_cur)
local info = mp.get_property_native('option-info/' .. option, {})
if info.type == 'Flag' then
return { 'no', 'yes' }, before_cur
end
if info['expects-file'] then
return handle_file_completion(before_cur)
end
-- Fix completing the empty value for --dscale and --cscale.
if info.choices and info.choices[1] == '' and completion_append == '' then
info.choices[1] = '""'
end
return info.choices
end
local function command_flags_at_1st_argument_list(command)
local flags = {
['playlist-next'] = {'weak', 'force'},
['playlist-play-index'] = {'current', 'none'},
['playlist-remove'] = {'current'},
['rescan-external-files'] = {'reselect', 'keep-selection'},
['revert-seek'] = {'mark', 'mark-permanent'},
['screenshot'] = {'subtitles', 'video', 'window', 'each-frame'},
['stop'] = {'keep-playlist'},
}
flags['playlist-prev'] = flags['playlist-next']
flags['screenshot-raw'] = flags.screenshot
return flags[command]
end
local function command_flags_at_2nd_argument_list(command)
local flags = {
['apply-profile'] = {'default', 'restore'},
['frame-step'] = {'play', 'seek', 'mute'},
['loadfile'] = {'replace', 'append', 'append-play', 'insert-next',
'insert-next-play', 'insert-at', 'insert-at-play'},
['screenshot-to-file'] = {'subtitles', 'video', 'window', 'each-frame'},
['screenshot-raw'] = {'bgr0', 'bgra', 'rgba', 'rgba64'},
['seek'] = {'relative', 'absolute', 'absolute-percent',
'relative-percent', 'keyframes', 'exact'},
['sub-add'] = {'select', 'auto', 'cached'},
['sub-seek'] = {'primary', 'secondary'},
}
flags.loadlist = flags.loadfile
flags['audio-add'] = flags['sub-add']
flags['video-add'] = flags['sub-add']
flags['sub-step'] = flags['sub-seek']
return flags[command]
end
local function handle_flags(command, arg_index, flags)
for _, cmd in pairs(get_commands()) do
if cmd.name == command then
if cmd.args[arg_index] and cmd.args[arg_index].type == 'Flags' then
break
else
return
end
end
end
local plus_pos = flags:find('%+[^%+]*$')
if plus_pos then
completion_pos = completion_pos + plus_pos
end
end
local function list_executables()
local executable_map = {}
local path = os.getenv('PATH') or ''
local separator = platform == 'windows' and ';' or ':'
local exts = {}
for ext in (os.getenv('PATHEXT') or ''):gmatch('[^;]+') do
exts[ext:lower()] = true
end
for directory in path:gmatch('[^' .. separator .. ']+') do
for _, executable in pairs(utils.readdir(directory, 'files') or {}) do
if not next(exts) or exts[(executable:match('%.%w+$') or ''):lower()] then
executable_map[executable] = true
end
end
end
local executables = {}
for executable, _ in pairs(executable_map) do
executables[#executables + 1] = executable
end
return executables
end
local function list_filter_labels(type)
local values = {'all'}
for _, value in pairs(mp.get_property_native(type)) do
if value.label then
values[#values + 1] = value.label
end
end
return values
end
local function common_prefix_length(s1, s2) local function common_prefix_length(s1, s2)
local common_count = 0 local common_count = 0
@@ -1723,166 +1400,10 @@ end
-- Show autocompletions. -- Show autocompletions.
complete = function () complete = function ()
if input_caller then completion_old_line = line
completion_old_line = line completion_old_cursor = cursor
completion_old_cursor = cursor mp.commandv('script-message-to', input_caller, 'input-event',
mp.commandv('script-message-to', input_caller, 'input-event', 'complete', utils.format_json({line:sub(1, cursor - 1)}))
'complete', utils.format_json({line:sub(1, cursor - 1)}))
render()
return
end
local before_cur = line:sub(1, cursor - 1)
local tokens = {}
local first_useful_token_index = 1
local completions
local begin_new_token = true
local last_quote
for pos, char in before_cur:gmatch('()(.)') do
if char:find('[%s;]') and not last_quote then
begin_new_token = true
if char == ';' then
first_useful_token_index = #tokens + 1
end
elseif begin_new_token then
tokens[#tokens + 1] = { text = char, pos = pos }
last_quote = char:match('["\']')
begin_new_token = false
else
tokens[#tokens].text = tokens[#tokens].text .. char
if char == last_quote then
last_quote = nil
end
end
end
completion_append = last_quote or ''
-- Strip quotes from tokens.
for _, token in pairs(tokens) do
if token.text:find('^"') then
token.text = token.text:sub(2):gsub('"$', '')
token.pos = token.pos + 1
elseif token.text:find("^'") then
token.text = token.text:sub(2):gsub("'$", '')
token.pos = token.pos + 1
end
end
-- Skip command prefixes because it is not worth lumping them together with
-- command completions when they are useless for interactive usage.
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,
['nonrepeatable'] = true, ['nonscalable'] = true,
['async'] = true, ['sync'] = true
}
-- Add an empty token if the cursor is after whitespace or ; to simplify
-- comparisons.
if before_cur == '' or before_cur:find('[%s;]$') then
tokens[#tokens + 1] = { text = "", pos = cursor }
end
while tokens[first_useful_token_index] and
command_prefixes[tokens[first_useful_token_index].text] do
if first_useful_token_index == #tokens then
completion_buffer = {}
render()
return
end
first_useful_token_index = first_useful_token_index + 1
end
completion_pos = tokens[#tokens].pos
local add_actions = {
['add'] = true, ['append'] = true, ['pre'] = true, ['set'] = true
}
local first_useful_token = tokens[first_useful_token_index]
local property_pos = before_cur:match('${[=>]?()[%w_/-]*$')
if property_pos then
completion_pos = property_pos
completions = property_list()
completion_append = '}'
elseif #tokens == first_useful_token_index then
completions = command_list()
completions[#completions + 1] = 'help'
elseif #tokens == first_useful_token_index + 1 then
if first_useful_token.text == 'set' or
first_useful_token.text == 'add' or
first_useful_token.text == 'cycle' or
first_useful_token.text == 'cycle-values' or
first_useful_token.text == 'multiply' then
completions = property_list()
elseif first_useful_token.text == 'help' then
completions = command_list()
elseif first_useful_token.text == 'apply-profile' then
completions = profile_list()
elseif first_useful_token.text == 'change-list' then
completions = list_option_list()
elseif first_useful_token.text == 'run' then
completions = list_executables()
elseif first_useful_token.text == 'vf' or
first_useful_token.text == 'af' then
completions = list_option_action_list(first_useful_token.text)
elseif first_useful_token.text == 'vf-command' or
first_useful_token.text == 'af-command' then
completions = list_filter_labels(first_useful_token.text:sub(1,2))
elseif has_file_argument(first_useful_token.text) then
completions = handle_file_completion(before_cur)
else
completions = command_flags_at_1st_argument_list(first_useful_token.text)
handle_flags(first_useful_token.text, 1, tokens[#tokens].text)
end
elseif first_useful_token.text == 'cycle-values' then
completions = handle_choice_completion(tokens[first_useful_token_index + 1].text,
before_cur)
elseif #tokens == first_useful_token_index + 2 then
if first_useful_token.text == 'set' then
completions = handle_choice_completion(tokens[first_useful_token_index + 1].text,
before_cur)
elseif first_useful_token.text == 'change-list' then
completions = list_option_action_list(tokens[first_useful_token_index + 1].text)
elseif first_useful_token.text == 'vf' or
first_useful_token.text == 'af' then
if add_actions[tokens[first_useful_token_index + 1].text] then
completions = handle_choice_completion(first_useful_token.text, before_cur)
elseif tokens[first_useful_token_index + 1].text == 'remove' then
completions = list_option_value_list(first_useful_token.text)
end
else
completions = command_flags_at_2nd_argument_list(first_useful_token.text)
handle_flags(first_useful_token.text, 2, tokens[#tokens].text)
end
elseif #tokens == first_useful_token_index + 3 then
if first_useful_token.text == 'change-list' then
if add_actions[tokens[first_useful_token_index + 2].text] then
completions = handle_choice_completion(tokens[first_useful_token_index + 1].text,
before_cur)
elseif tokens[first_useful_token_index + 2].text == 'remove' then
completions = list_option_value_list(tokens[first_useful_token_index + 1].text)
end
elseif first_useful_token.text == 'dump-cache' then
completions = handle_file_completion(before_cur)
end
end
completion_buffer = {}
selected_completion_index = 0
completions = completions or {}
table.sort(completions)
completion_pos = completion_pos or 1
for i, match in ipairs(fuzzy_find(before_cur:sub(completion_pos),
completions, opts.case_sensitive)) do
completion_buffer[i] = completions[match[1]]
end
render() render()
end end
@@ -2032,99 +1553,43 @@ set_active = function (active)
mp.set_property_bool('user-data/mpv/console/open', true) mp.set_property_bool('user-data/mpv/console/open', true)
ime_active = mp.get_property_bool('input-ime') ime_active = mp.get_property_bool('input-ime')
mp.set_property_bool('input-ime', true) mp.set_property_bool('input-ime', true)
if not input_caller then
prompt = default_prompt
id = default_id
history = histories[id]
history_paths[id] = opts.persist_history and opts.history_path or nil
histories_to_save[id] = histories_to_save[id] or ''
read_history()
history_pos = #history + 1
mp.enable_messages('terminal-default')
end
elseif searching_history then elseif searching_history then
searching_history = false searching_history = false
selectable_items = nil selectable_items = nil
unbind_mouse() unbind_mouse()
else else
open = false open = false
completion_buffer = {}
undefine_key_bindings() undefine_key_bindings()
mp.enable_messages('silent:terminal-default') unbind_mouse()
mp.set_property_bool('user-data/mpv/console/open', false) mp.set_property_bool('user-data/mpv/console/open', false)
mp.set_property_bool('input-ime', ime_active) mp.set_property_bool('input-ime', ime_active)
mp.commandv('script-message-to', input_caller, 'input-event',
if input_caller then 'closed', utils.format_json({line, cursor}))
mp.commandv('script-message-to', input_caller, 'input-event',
'closed', utils.format_json({line, cursor}))
input_caller = nil
line = ''
cursor = 1
selectable_items = nil
default_item = nil
dont_bind_up_down = false
unbind_mouse()
end
collectgarbage() collectgarbage()
end end
render() render()
end end
-- Show the console if hidden and replace its contents with 'text'
local function show_and_type(text, cursor_pos)
text = text or ''
cursor_pos = tonumber(cursor_pos)
-- Save the line currently being edited, just in case
if line ~= text and line ~= '' and history[#history] ~= line then
history_add(line)
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 open then
render()
else
set_active(true)
end
end
mp.add_key_binding(nil, 'enable', function()
autoselect_completion = true
set_active(true)
end)
mp.register_script_message('disable', function() mp.register_script_message('disable', function()
set_active(false) set_active(false)
end) end)
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) mp.register_script_message('get-input', function (script_name, args)
if open and input_caller and script_name ~= input_caller then if open and script_name ~= input_caller then
mp.commandv('script-message-to', input_caller, 'input-event', mp.commandv('script-message-to', input_caller, 'input-event',
'closed', utils.format_json({line, cursor})) 'closed', utils.format_json({line, cursor}))
end end
input_caller = script_name input_caller = script_name
args = utils.parse_json(args) args = utils.parse_json(args)
prompt = args.prompt or default_prompt prompt = args.prompt
line = args.default_text or '' line = args.default_text or ''
cursor = args.cursor_position or line:len() + 1 cursor = args.cursor_position or line:len() + 1
id = args.id or script_name .. prompt id = args.id or script_name .. prompt
keep_open = args.keep_open keep_open = args.keep_open
autoselect_completion = args.autoselect_completion autoselect_completion = args.autoselect_completion
dont_bind_up_down = args.dont_bind_up_down dont_bind_up_down = args.dont_bind_up_down
completion_buffer = {}
if histories[id] == nil then if histories[id] == nil then
histories[id] = {} histories[id] = {}
@@ -2163,12 +1628,6 @@ mp.register_script_message('get-input', function (script_name, args)
end) end)
mp.register_script_message('log', function (message) mp.register_script_message('log', function (message)
-- input.get edited handler is invoked after submit, so avoid modifying
-- the default log.
if input_caller == nil then
return
end
message = utils.parse_json(message) message = utils.parse_json(message)
log_add(message.text, log_add(message.text,
@@ -2177,10 +1636,6 @@ mp.register_script_message('log', function (message)
end) end)
mp.register_script_message('set-log', function (log) mp.register_script_message('set-log', function (log)
if input_caller == nil then
return
end
log = utils.parse_json(log) log = utils.parse_json(log)
log_buffers[id] = {} log_buffers[id] = {}
@@ -2238,33 +1693,6 @@ mp.observe_property("user-data/osc/margins", "native", function(_, val)
render() render()
end) end)
-- Enable log messages. In silent mode, mpv will queue log messages in a buffer
-- until enable_messages is called again without the silent: prefix.
mp.enable_messages('silent:terminal-default')
mp.register_event('log-message', function(e)
-- Ignore log messages from the OSD because of paranoia, since writing them
-- to the OSD could generate more messages in an infinite loop.
if e.prefix:sub(1, 3) == 'osd' then return end
-- Ignore messages output by this script.
if e.prefix == mp.get_script_name() then return end
-- Ignore buffer overflow warning messages. Overflowed log messages would
-- have been offscreen anyway.
if e.prefix == 'overflow' then return end
-- Filter out trace-level log messages, even if the terminal-default log
-- level includes them. These aren't too useful for an on-screen display
-- without scrollback and they include messages that are generated from the
-- OSD display itself.
if e.level == 'trace' then return end
-- Use color for debug/v/warn/error/fatal messages.
log_add('[' .. e.prefix .. '] ' .. e.text:sub(1, -2), styles[e.level],
terminal_styles[e.level])
end)
mp.register_event('shutdown', function () mp.register_event('shutdown', function ()
mp.del_property('user-data/mpv/console') mp.del_property('user-data/mpv/console')
@@ -2281,6 +1709,15 @@ mp.register_event('shutdown', function ()
end end
end) end)
-- These are for backwards compatibility only.
mp.add_key_binding(nil, 'enable', function ()
mp.command('script-message-to commands open')
end)
mp.register_script_message('type', function (text, cursor_pos)
mp.commandv('script-message-to', 'commands', 'type', unpack({text, cursor_pos}))
end)
require 'mp.options'.read_options(opts, nil, render) require 'mp.options'.read_options(opts, nil, render)
collectgarbage() collectgarbage()

View File

@@ -1,6 +1,7 @@
lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua', 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', 'fzy.lua', 'select.lua', 'positioning.lua'] 'input.lua', 'fzy.lua', 'select.lua', 'positioning.lua',
'commands.lua']
foreach file: lua_files foreach file: lua_files
lua_file = custom_target(file, lua_file = custom_target(file,
input: file, input: file,

View File

@@ -268,6 +268,7 @@ void mp_load_builtin_scripts(struct MPContext *mpctx)
"@auto_profiles.lua"); "@auto_profiles.lua");
load_builtin_script(mpctx, 5, mpctx->opts->lua_load_select, "@select.lua"); load_builtin_script(mpctx, 5, mpctx->opts->lua_load_select, "@select.lua");
load_builtin_script(mpctx, 6, mpctx->opts->lua_load_positioning, "@positioning.lua"); load_builtin_script(mpctx, 6, mpctx->opts->lua_load_positioning, "@positioning.lua");
load_builtin_script(mpctx, 7, mpctx->opts->lua_load_commands, "@commands.lua");
} }
bool mp_load_scripts(struct MPContext *mpctx) bool mp_load_scripts(struct MPContext *mpctx)