mirror of
https://github.com/mpv-player/mpv.git
synced 2025-12-23 19:30:20 +00:00
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:
4
DOCS/interface-changes/commands.txt
Normal file
4
DOCS/interface-changes/commands.txt
Normal 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
52
DOCS/man/commands.rst
Normal 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`_).
|
||||
@@ -2,16 +2,15 @@ CONSOLE
|
||||
=======
|
||||
|
||||
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
|
||||
complete and run mpv input commands and print mpv's log. It can be displayed on
|
||||
both the video window and the terminal. It can be disabled entirely using the
|
||||
scripts through the ``mp.input`` API. It can be displayed on both the video
|
||||
window and the terminal. It can be disabled entirely using the
|
||||
``--load-console=no`` option.
|
||||
|
||||
Keybindings
|
||||
-----------
|
||||
Console can either process free-form text or select from a predefined list of
|
||||
items.
|
||||
|
||||
\`
|
||||
Show the console.
|
||||
Free-form text mode keybindings
|
||||
-------------------------------
|
||||
|
||||
ESC and Ctrl+[
|
||||
Hide the console.
|
||||
@@ -80,7 +79,7 @@ PGDN
|
||||
Stop navigating the command history.
|
||||
|
||||
Ctrl+r
|
||||
Search the command history.
|
||||
Search the command history. See `SELECT`_ for the key bindings in this mode.
|
||||
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
@@ -109,23 +108,6 @@ WHEEL_UP
|
||||
WHEEL_DOWN
|
||||
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
|
||||
------------
|
||||
|
||||
@@ -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
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -232,16 +211,6 @@ Configurable Options
|
||||
|
||||
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``
|
||||
Default: auto
|
||||
|
||||
|
||||
@@ -984,8 +984,8 @@ REPL.
|
||||
that are used when the console is displayed in the terminal.
|
||||
|
||||
``input.log_error(message)``
|
||||
Helper to add a line to the log buffer with the same color as the one the
|
||||
console uses for errors. Useful when the user submits invalid input.
|
||||
Helper to add a line to the log buffer with the same color as the one used
|
||||
for commands that error. Useful when the user submits invalid input.
|
||||
|
||||
``input.set_log(log)``
|
||||
Replace the entire log buffer.
|
||||
|
||||
@@ -1544,6 +1544,8 @@ works like in older mpv releases:
|
||||
|
||||
.. include:: console.rst
|
||||
|
||||
.. include:: commands.rst
|
||||
|
||||
.. include:: select.rst
|
||||
|
||||
.. include:: positioning.rst
|
||||
|
||||
@@ -1054,9 +1054,11 @@ Program Behavior
|
||||
the overlay permanent).
|
||||
|
||||
``--load-console=<yes|no>``
|
||||
Enable the built-in script that shows a console on a key binding and lets
|
||||
you enter commands (default: yes). The ````` key is used to show the
|
||||
console by default, and ``ESC`` to hide it again.
|
||||
Enable the built-in script to handle textual input (default: yes).
|
||||
|
||||
``--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>``
|
||||
Enable the builtin script that does auto profiles (default: auto). See
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
#i script-binding stats/display-stats # display 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 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 # delay subtitles by 100 ms
|
||||
#x add sub-delay +0.1 # delay subtitles by 100 ms
|
||||
|
||||
@@ -555,6 +555,7 @@ static const m_option_t mp_opts[] = {
|
||||
.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-commands", OPT_BOOL(lua_load_commands), .flags = UPDATE_BUILTIN_SCRIPTS},
|
||||
#endif
|
||||
|
||||
// ------------------------- stream options --------------------
|
||||
@@ -988,6 +989,7 @@ static const struct MPOpts mp_default_opts = {
|
||||
.lua_load_auto_profiles = -1,
|
||||
.lua_load_select = true,
|
||||
.lua_load_positioning = true,
|
||||
.lua_load_commands = true,
|
||||
#endif
|
||||
.auto_load_scripts = true,
|
||||
.loop_times = 1,
|
||||
|
||||
@@ -175,6 +175,7 @@ typedef struct MPOpts {
|
||||
int lua_load_auto_profiles;
|
||||
bool lua_load_select;
|
||||
bool lua_load_positioning;
|
||||
bool lua_load_commands;
|
||||
|
||||
bool auto_load_scripts;
|
||||
|
||||
|
||||
@@ -444,7 +444,7 @@ typedef struct MPContext {
|
||||
|
||||
struct mp_ipc_ctx *ipc_ctx;
|
||||
|
||||
int64_t builtin_script_ids[7];
|
||||
int64_t builtin_script_ids[8];
|
||||
|
||||
mp_mutex abort_lock;
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@ static const char * const builtin_lua_scripts[][2] = {
|
||||
},
|
||||
{"@positioning.lua",
|
||||
# include "player/lua/positioning.lua.inc"
|
||||
},
|
||||
{"@commands.lua",
|
||||
# include "player/lua/commands.lua.inc"
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
566
player/lua/commands.lua
Normal file
566
player/lua/commands.lua
Normal 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)
|
||||
@@ -45,24 +45,11 @@ local opts = {
|
||||
match_color = '#0088FF',
|
||||
case_sensitive = platform ~= 'windows' and true or false,
|
||||
history_dedup = true,
|
||||
persist_history = false,
|
||||
history_path = '~~state/command_history.txt',
|
||||
font_hw_ratio = 'auto',
|
||||
}
|
||||
|
||||
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
|
||||
@@ -70,11 +57,7 @@ for key, style in pairs(styles) do
|
||||
end
|
||||
|
||||
local terminal_styles = {
|
||||
debug = '\027[90m',
|
||||
v = '\027[32m',
|
||||
warn = '\027[33m',
|
||||
error = '\027[31m',
|
||||
fatal = '\027[91m',
|
||||
selected_completion = '\027[7m',
|
||||
default_item = '\027[1m',
|
||||
disabled = '\027[38;5;8m',
|
||||
@@ -89,19 +72,17 @@ local pending_update = false
|
||||
local ime_active = mp.get_property_bool('input-ime')
|
||||
local line = ''
|
||||
local cursor = 1
|
||||
local default_prompt = '>'
|
||||
local prompt = default_prompt
|
||||
local default_id = 'default'
|
||||
local id = default_id
|
||||
local prompt
|
||||
local id
|
||||
|
||||
local histories = {[id] = {}}
|
||||
local history = histories[id]
|
||||
local histories = {}
|
||||
local history
|
||||
local history_pos = 1
|
||||
local searching_history = false
|
||||
local history_paths = {}
|
||||
local histories_to_save = {}
|
||||
|
||||
local log_buffers = {[id] = {}}
|
||||
local log_buffers = {}
|
||||
local key_bindings = {}
|
||||
local dont_bind_up_down = false
|
||||
local overlay = mp.create_osd_overlay('ass-events')
|
||||
@@ -114,11 +95,9 @@ local completion_buffer = {}
|
||||
local selected_completion_index
|
||||
local completion_pos
|
||||
local completion_append
|
||||
local path_separator = platform == 'windows' and '\\' or '/'
|
||||
local completion_old_line
|
||||
local completion_old_cursor
|
||||
local autoselect_completion
|
||||
local commands
|
||||
|
||||
local selectable_items
|
||||
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')
|
||||
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'
|
||||
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
|
||||
@@ -960,10 +931,8 @@ local function handle_edit()
|
||||
|
||||
handle_cursor_move()
|
||||
|
||||
if input_caller then
|
||||
mp.commandv('script-message-to', input_caller, 'input-event', 'edited',
|
||||
utils.format_json({line}))
|
||||
end
|
||||
mp.commandv('script-message-to', input_caller, 'input-event', 'edited',
|
||||
utils.format_json({line}))
|
||||
end
|
||||
|
||||
-- Insert a character at the current cursor position (any_unicode)
|
||||
@@ -1029,49 +998,6 @@ local function maybe_exit()
|
||||
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()
|
||||
mp.remove_key_binding('_console_mouse_move')
|
||||
mp.remove_key_binding('_console_mbtn_left')
|
||||
@@ -1089,42 +1015,25 @@ local function submit()
|
||||
return
|
||||
end
|
||||
|
||||
if line == '' and input_caller == nil then
|
||||
return
|
||||
end
|
||||
|
||||
if selectable_items then
|
||||
if #matches > 0 then
|
||||
mp.commandv('script-message-to', input_caller, 'input-event', 'submit',
|
||||
utils.format_json({matches[selected_match].index}))
|
||||
end
|
||||
elseif input_caller then
|
||||
else
|
||||
if selected_completion_index == 0 and autoselect_completion then
|
||||
cycle_through_completions()
|
||||
end
|
||||
|
||||
mp.commandv('script-message-to', input_caller, 'input-event', 'submit',
|
||||
utils.format_json({line}))
|
||||
else
|
||||
if selected_completion_index == 0 then
|
||||
cycle_through_completions()
|
||||
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)
|
||||
else
|
||||
mp.command(line)
|
||||
if history[#history] ~= line and line ~= '' then
|
||||
history_add(line)
|
||||
end
|
||||
end
|
||||
|
||||
if history[#history] ~= line and line ~= '' then
|
||||
history_add(line)
|
||||
end
|
||||
|
||||
if input_caller and not keep_open then
|
||||
if not keep_open then
|
||||
set_active(false)
|
||||
elseif not selectable_items then
|
||||
clear()
|
||||
@@ -1428,238 +1337,6 @@ local function text_input(info)
|
||||
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 common_count = 0
|
||||
@@ -1723,166 +1400,10 @@ end
|
||||
|
||||
-- Show autocompletions.
|
||||
complete = function ()
|
||||
if input_caller then
|
||||
completion_old_line = line
|
||||
completion_old_cursor = cursor
|
||||
mp.commandv('script-message-to', input_caller, 'input-event',
|
||||
'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
|
||||
|
||||
completion_old_line = line
|
||||
completion_old_cursor = cursor
|
||||
mp.commandv('script-message-to', input_caller, 'input-event',
|
||||
'complete', utils.format_json({line:sub(1, cursor - 1)}))
|
||||
render()
|
||||
end
|
||||
|
||||
@@ -2032,99 +1553,43 @@ set_active = function (active)
|
||||
mp.set_property_bool('user-data/mpv/console/open', true)
|
||||
ime_active = mp.get_property_bool('input-ime')
|
||||
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
|
||||
searching_history = false
|
||||
selectable_items = nil
|
||||
unbind_mouse()
|
||||
else
|
||||
open = false
|
||||
completion_buffer = {}
|
||||
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('input-ime', ime_active)
|
||||
|
||||
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
|
||||
default_item = nil
|
||||
dont_bind_up_down = false
|
||||
unbind_mouse()
|
||||
end
|
||||
mp.commandv('script-message-to', input_caller, 'input-event',
|
||||
'closed', utils.format_json({line, cursor}))
|
||||
collectgarbage()
|
||||
end
|
||||
render()
|
||||
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()
|
||||
set_active(false)
|
||||
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)
|
||||
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',
|
||||
'closed', utils.format_json({line, cursor}))
|
||||
end
|
||||
|
||||
input_caller = script_name
|
||||
args = utils.parse_json(args)
|
||||
prompt = args.prompt or default_prompt
|
||||
prompt = args.prompt
|
||||
line = args.default_text or ''
|
||||
cursor = args.cursor_position or line:len() + 1
|
||||
id = args.id or script_name .. prompt
|
||||
keep_open = args.keep_open
|
||||
autoselect_completion = args.autoselect_completion
|
||||
dont_bind_up_down = args.dont_bind_up_down
|
||||
completion_buffer = {}
|
||||
|
||||
if histories[id] == nil then
|
||||
histories[id] = {}
|
||||
@@ -2163,12 +1628,6 @@ mp.register_script_message('get-input', function (script_name, args)
|
||||
end)
|
||||
|
||||
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)
|
||||
|
||||
log_add(message.text,
|
||||
@@ -2177,10 +1636,6 @@ mp.register_script_message('log', function (message)
|
||||
end)
|
||||
|
||||
mp.register_script_message('set-log', function (log)
|
||||
if input_caller == nil then
|
||||
return
|
||||
end
|
||||
|
||||
log = utils.parse_json(log)
|
||||
log_buffers[id] = {}
|
||||
|
||||
@@ -2238,33 +1693,6 @@ mp.observe_property("user-data/osc/margins", "native", function(_, val)
|
||||
render()
|
||||
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.del_property('user-data/mpv/console')
|
||||
|
||||
@@ -2281,6 +1709,15 @@ mp.register_event('shutdown', function ()
|
||||
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)
|
||||
|
||||
collectgarbage()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.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
|
||||
lua_file = custom_target(file,
|
||||
input: file,
|
||||
|
||||
@@ -268,6 +268,7 @@ void mp_load_builtin_scripts(struct MPContext *mpctx)
|
||||
"@auto_profiles.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, 7, mpctx->opts->lua_load_commands, "@commands.lua");
|
||||
}
|
||||
|
||||
bool mp_load_scripts(struct MPContext *mpctx)
|
||||
|
||||
Reference in New Issue
Block a user