diff options
Diffstat (limited to 'player/lua')
-rw-r--r-- | player/lua/auto_profiles.lua | 8 | ||||
-rw-r--r-- | player/lua/console.lua | 596 | ||||
-rw-r--r-- | player/lua/defaults.lua | 13 | ||||
-rw-r--r-- | player/lua/fzy.lua | 297 | ||||
-rw-r--r-- | player/lua/input.lua | 38 | ||||
-rw-r--r-- | player/lua/meson.build | 2 | ||||
-rw-r--r-- | player/lua/options.lua | 22 | ||||
-rw-r--r-- | player/lua/osc.lua | 249 | ||||
-rw-r--r-- | player/lua/select.lua | 375 | ||||
-rw-r--r-- | player/lua/stats.lua | 118 | ||||
-rw-r--r-- | player/lua/ytdl_hook.lua | 16 |
11 files changed, 1382 insertions, 352 deletions
diff --git a/player/lua/auto_profiles.lua b/player/lua/auto_profiles.lua index a0f580298b..167724344c 100644 --- a/player/lua/auto_profiles.lua +++ b/player/lua/auto_profiles.lua @@ -1,6 +1,5 @@ -- Note: anything global is accessible by profile condition expressions. -local utils = require 'mp.utils' local msg = require 'mp.msg' local profiles = {} @@ -128,7 +127,7 @@ end local evil_magic = {} setmetatable(evil_magic, { - __index = function(table, key) + __index = function(_, key) -- interpret everything as property, unless it already exists as -- a non-nil global value local v = _G[key] @@ -141,7 +140,7 @@ setmetatable(evil_magic, { p = {} setmetatable(p, { - __index = function(table, key) + __index = function(_, key) return magic_get(key) end, }) @@ -149,6 +148,8 @@ setmetatable(p, { local function compile_cond(name, s) local code, chunkname = "return " .. s, "profile " .. name .. " condition" local chunk, err + -- luacheck: push + -- luacheck: ignore setfenv loadstring if setfenv then -- lua 5.1 chunk, err = loadstring(code, chunkname) if chunk then @@ -157,6 +158,7 @@ local function compile_cond(name, s) else -- lua 5.2 chunk, err = load(code, chunkname, "t", evil_magic) end + -- luacheck: pop if not chunk then msg.error("Profile '" .. name .. "' condition: " .. err) chunk = function() return false end diff --git a/player/lua/console.lua b/player/lua/console.lua index bbfaf478f7..f8d52704cb 100644 --- a/player/lua/console.lua +++ b/player/lua/console.lua @@ -75,6 +75,7 @@ local styles = { fatal = '{\\1c&H5791f9&\\b1}', suggestion = '{\\1c&Hcc99cc&}', selected_suggestion = '{\\1c&H2fbdfa&\\b1}', + disabled = '{\\1c&Hcccccc&}', } local terminal_styles = { @@ -84,6 +85,7 @@ local terminal_styles = { error = '\027[31m', fatal = '\027[1;31m', selected_suggestion = '\027[7m', + disabled = '\027[38;5;8m', } local repl_active = false @@ -105,13 +107,17 @@ local input_caller local suggestion_buffer = {} local selected_suggestion_index -local completion_start_position +local completion_pos local completion_append -local file_commands = {} local path_separator = platform == 'windows' and '\\' or '/' local completion_old_line local completion_old_cursor +local selectable_items +local matches = {} +local selected_match = 1 +local first_match_to_print = 1 + local update_timer = nil update_timer = mp.add_periodic_timer(0.05, function() if pending_update then @@ -161,19 +167,24 @@ do local function normalized_text_width(text, size, horizontal) local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90 local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s' - local x1, y1 = nil, nil size = size / 0.8 - -- prevent endless loop + local width + -- Limit to 5 iterations local repetitions_left = 5 - repeat + for i = 1, repetitions_left do size = size * 0.8 local ass = assdraw.ass_new() ass.text = template:format(align, size, opts.font, rotation, text) - _, _, x1, y1 = measure_bounds(ass.text) - repetitions_left = repetitions_left - 1 - -- make sure nothing got clipped - until (x1 and x1 < osd_width and y1 < osd_height) or repetitions_left == 0 - local width = (repetitions_left == 0 and not x1) and 0 or (horizontal and x1 or y1) + local _, _, x1, y1 = measure_bounds(ass.text) + -- Check if nothing got clipped + if x1 and x1 < osd_width and y1 < osd_height then + width = horizontal and x1 or y1 + break + end + if i == repetitions_left then + width = 0 + end + end return width / size, horizontal and osd_width or osd_height end @@ -232,6 +243,24 @@ function ass_escape(str) return mp.command_native({'escape-ass', str}) end +local function calculate_max_log_lines() + if not mp.get_property_native('vo-configured') then + -- Subtract 1 for the input line and for each line in the status line. + -- This does not detect wrapped lines. + return mp.get_property_native('term-size/h', 24) - 2 - + select(2, mp.get_property('term-status-msg'):gsub('\\n', '')) + end + + return math.floor(mp.get_property_native('osd-height') + / mp.get_property_native('display-hidpi-scale', 1) + / opts.scale + * (1 - global_margins.t - global_margins.b) + / opts.font_size + -- Subtract 1 for the input line and 1 for the newline + -- between the log and the input line. + - 2) +end + -- Takes a list of strings, a max width in characters and -- optionally a max row count. -- The result contains at least one column. @@ -323,6 +352,100 @@ function format_table(list, width_max, rows_max) return table.concat(rows, ass_escape('\n')), row_count end +local function fuzzy_find(needle, haystacks) + local result = require 'mp.fzy'.filter(needle, haystacks) + table.sort(result, function (i, j) + return i[3] > j[3] + end) + for i, value in ipairs(result) do + result[i] = value[1] + end + return result +end + +local function populate_log_with_matches(max_width) + if not selectable_items or selected_match == 0 then + return + end + + log_buffers[id] = {} + local log = log_buffers[id] + + local max_log_lines = calculate_max_log_lines() + + if selected_match < first_match_to_print then + first_match_to_print = selected_match + end + + if first_match_to_print > 1 then + -- Reserve the first line for "n hidden items". + max_log_lines = max_log_lines - 1 + end + + if selected_match > first_match_to_print + max_log_lines - 1 then + -- Reserve the first line for "n hidden items" if it wasn't already. + if first_match_to_print == 1 then + max_log_lines = max_log_lines - 1 + end + + first_match_to_print = selected_match - max_log_lines + 1 + end + + local last_match_to_print = math.min(first_match_to_print + max_log_lines - 1, + #matches) + + if last_match_to_print < #matches then + -- Reserve the last line for "n hidden items". + last_match_to_print = last_match_to_print - 1 + + -- After decrementing the last match to print, we need to check if the + -- selected match is beyond the last match to print again, and shift + -- both the first and last match to print when it is. + if selected_match > last_match_to_print then + if first_match_to_print == 1 then + -- Reserve the first line for "2 hidden items". + first_match_to_print = first_match_to_print + 1 + end + + first_match_to_print = first_match_to_print + 1 + last_match_to_print = last_match_to_print + 1 + end + end + + -- When there is only 1 hidden item, print it in the previously reserved + -- line instead of printing "1 hidden items". + if first_match_to_print == 2 then + first_match_to_print = 1 + end + if last_match_to_print == #matches - 1 then + last_match_to_print = #matches + end + + if first_match_to_print > 1 then + log[1] = { + text = '↑ (' .. (first_match_to_print - 1) .. ' hidden items)' .. '\n', + style = styles.disabled, + terminal_style = terminal_styles.disabled, + } + end + + for i = first_match_to_print, last_match_to_print do + log[#log + 1] = { + text = truncate_utf8(matches[i].text, max_width) .. '\n', + style = i == selected_match and styles.selected_suggestion or '', + terminal_style = i == selected_match and terminal_styles.selected_suggestion or '', + } + end + + if last_match_to_print < #matches then + log[#log + 1] = { + text = '↓ (' .. (#matches - last_match_to_print) .. ' hidden items)' .. '\n', + style = styles.disabled, + terminal_style = terminal_styles.disabled, + } + end +end + local function print_to_terminal() -- Clear the log after closing the console. if not repl_active then @@ -330,6 +453,8 @@ local function print_to_terminal() return end + populate_log_with_matches(mp.get_property_native('term-size/w', 80)) + local log = '' for _, log_line in ipairs(log_buffers[id]) do log = log .. log_line.terminal_style .. log_line.text .. '\027[0m' @@ -373,7 +498,7 @@ function update() dpi_scale = dpi_scale * opts.scale - local screenx, screeny, aspect = mp.get_osd_size() + local screenx, screeny = mp.get_osd_size() screenx = screenx / dpi_scale screeny = screeny / dpi_scale @@ -413,16 +538,15 @@ function update() -- Render log messages as ASS. -- This will render at most screeny / font_size - 1 messages. - -- lines above the prompt - -- subtract 1.5 to account for the input line - local screeny_factor = (1 - global_margins.t - global_margins.b) - local lines_max = math.ceil(screeny * screeny_factor / opts.font_size - 1.5) + local lines_max = calculate_max_log_lines() -- Estimate how many characters fit in one line local width_max = math.ceil(screenx / opts.font_size * get_font_hw_ratio()) local suggestions, rows = format_table(suggestion_buffer, width_max, lines_max) local suggestion_ass = style .. styles.suggestion .. suggestions + populate_log_with_matches(width_max) + local log_ass = '' local log_buffer = log_buffers[id] local log_messages = #log_buffer @@ -436,7 +560,7 @@ function update() ass:new_event() ass:an(1) - ass:pos(2, screeny - 2 - global_margins.b * screeny) + ass:pos(6, screeny - 6 - global_margins.b * screeny) ass:append(log_ass .. '\\N') if #suggestions > 0 then ass:append(suggestion_ass .. '\\N') @@ -449,7 +573,7 @@ function update() -- cursor appear in front of the text. ass:new_event() ass:an(1) - ass:pos(2, screeny - 2 - global_margins.b * screeny) + ass:pos(6, screeny - 6 - global_margins.b * screeny) ass:append(style .. '{\\alpha&HFF&}' .. ass_escape(prompt) .. ' ' .. before_cur) ass:append(cglyph) ass:append(style .. '{\\alpha&HFF&}' .. after_cur) @@ -481,10 +605,11 @@ function set_active(active) if input_caller then mp.commandv('script-message-to', input_caller, 'input-event', - 'closed', line, cursor) + 'closed', utils.format_json({line, cursor})) input_caller = nil line = '' cursor = 1 + selectable_items = nil end collectgarbage() end @@ -547,13 +672,35 @@ function len_utf8(str) return len end +function truncate_utf8(str, max_length) + local len = 0 + local pos = 1 + while pos <= #str do + pos = next_utf8(str, pos) + len = len + 1 + if len == max_length - 1 then + return str:sub(1, pos - 1) .. '⋯' + end + end + return str +end + local function handle_edit() + if selectable_items then + matches = {} + selected_match = 1 + + for i, match in ipairs(fuzzy_find(line, selectable_items)) do + matches[i] = { index = match, text = selectable_items[match] } + end + end + suggestion_buffer = {} update() if input_caller then mp.commandv('script-message-to', input_caller, 'input-event', 'edited', - line) + utils.format_json({line})) end end @@ -590,14 +737,14 @@ function handle_ins() end -- Move the cursor to the next character (Right) -function next_char(amount) +function next_char() cursor = next_utf8(line, cursor) suggestion_buffer = {} update() end -- Move the cursor to the previous character (Left) -function prev_char(amount) +function prev_char() cursor = prev_utf8(line, cursor) suggestion_buffer = {} update() @@ -690,9 +837,15 @@ function handle_enter() history_add(line) end - if input_caller then + 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 + set_active(false) + elseif input_caller then mp.commandv('script-message-to', input_caller, 'input-event', 'submit', - line) + utils.format_json({line})) else -- match "help [<text>]", return <text> or "", strip all whitespace local help = line:match('^%s*help%s+(.-)%s*$') or @@ -745,19 +898,63 @@ end -- Go to the specified relative position in the command history (Up, Down) function move_history(amount) + if selectable_items then + selected_match = selected_match + amount + if selected_match > #matches then + selected_match = 1 + elseif selected_match < 1 then + selected_match = #matches + end + update() + return + end + go_history(history_pos + amount) end -- Go to the first command in the command history (PgUp) function handle_pgup() + if selectable_items then + -- We don't know whether to count the "n hidden items" lines here; an + -- offset of 2 is better with 1 extra line because it scrolls from the + -- last to the first visible match, while with both extra lines that is + -- done with +3. When there are no "n hidden items" lines selected_match + -- becomes 1 with any offset >= 1. + selected_match = math.max(selected_match - calculate_max_log_lines() + 2, 1) + update() + return + end + go_history(1) end -- Stop browsing history and start editing a blank line (PgDown) function handle_pgdown() + if selectable_items then + selected_match = math.min(selected_match + calculate_max_log_lines() - 2, #matches) + update() + return + end + go_history(#history + 1) end +local function page_up_or_prev_char() + if selectable_items then + handle_pgup() + else + prev_char() + end +end + +local function page_down_or_next_char() + if selectable_items then + handle_pgdown() + else + next_char() + end +end + -- Move to the start of the current word, or if already at the start, the start -- of the previous word. (Ctrl+Left) function prev_word() @@ -786,13 +983,6 @@ local function command_list() return commands end -local function command_list_and_help() - local commands = command_list() - commands[#commands + 1] = 'help' - - return commands -end - local function property_list() local properties = mp.get_property_native('property-list') @@ -807,7 +997,7 @@ local function property_list() for _, sub_property in pairs({ 'name', 'type', 'set-from-commandline', 'set-locally', - 'default-value', 'min', 'max', 'choices', + 'expects-file', 'default-value', 'min', 'max', 'choices', }) do properties[#properties + 1] = 'option-info/' .. option .. '/' .. sub_property @@ -847,7 +1037,7 @@ local function list_option_list() return options end -local function list_option_verb_list(option) +local function list_option_action_list(option) local type = mp.get_property('option-info/' .. option .. '/type') if type == 'Key/value list' then @@ -857,33 +1047,33 @@ local function list_option_verb_list(option) if type == 'String list' or type == 'Object settings list' then return {'add', 'append', 'clr', 'pre', 'set', 'remove', 'toggle'} end - - return {} end -local function choice_list(option) - local info = mp.get_property_native('option-info/' .. option, {}) +local function list_option_value_list(option) + local values = mp.get_property_native(option) - if info.type == 'Flag' then - return { 'no', 'yes' } + if type(values) ~= 'table' then + return end - return info.choices or {} -end + if type(values[1]) ~= 'table' then + return values + end -local function find_commands_with_file_argument() - if #file_commands > 0 then - return file_commands + 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(mp.get_property_native('command-list')) do - if command.args[1] and - (command.args[1].name == 'filename' or command.args[1].name == 'url') then - file_commands[#file_commands + 1] = command.name + if command.name == candidate_command then + return command.args[1] and + (command.args[1].name == 'filename' or command.args[1].name == 'url') end end - - return file_commands end local function file_list(directory) @@ -900,61 +1090,39 @@ local function file_list(directory) return files end --- List of tab-completions: --- pattern: A Lua pattern used in string:match. It should return the start --- position of the word to be completed in the first capture (using --- the empty parenthesis notation "()"). In patterns with 2 --- captures, the first determines the completions, and the second is --- the start of the word to be completed. --- list: A function that returns a list of candidate completion values. --- append: An extra string to be appended to the end of a successful --- completion. It is only appended if 'list' contains exactly one --- match. -function build_completers() - local completers = { - { pattern = '^%s*()[%w_-]*$', list = command_list_and_help, append = ' ' }, - { pattern = '^%s*help%s+()[%w_-]*$', list = command_list }, - { pattern = '^%s*set%s+"?([%w_-]+)"?%s+()%S*$', list = choice_list }, - { pattern = '^%s*set%s+"?([%w_-]+)"?%s+"()%S*$', list = choice_list, append = '"' }, - { pattern = '^%s*cycle[-_]values%s+"?([%w_-]+)"?.-%s+()%S*$', list = choice_list, append = " " }, - { pattern = '^%s*cycle[-_]values%s+"?([%w_-]+)"?.-%s+"()%S*$', list = choice_list, append = '" ' }, - { pattern = '^%s*apply[-_]profile%s+"()%S*$', list = profile_list, append = '"' }, - { pattern = '^%s*apply[-_]profile%s+()%S*$', list = profile_list }, - { pattern = '^%s*change[-_]list%s+()[%w_-]*$', list = list_option_list, append = ' ' }, - { pattern = '^%s*change[-_]list%s+()"[%w_-]*$', list = list_option_list, append = '" ' }, - { pattern = '^%s*change[-_]list%s+"?([%w_-]+)"?%s+()%a*$', list = list_option_verb_list, append = ' ' }, - { pattern = '^%s*change[-_]list%s+"?([%w_-]+)"?%s+"()%a*$', list = list_option_verb_list, append = '" ' }, - { pattern = '^%s*([av]f)%s+()%a*$', list = list_option_verb_list, append = ' ' }, - { pattern = '^%s*([av]f)%s+"()%a*$', list = list_option_verb_list, append = '" ' }, - { pattern = '${[=>]?()[%w_/-]*$', list = property_list, append = '}' }, - } +local function handle_file_completion(before_cur, path_pos) + local directory, last_component_pos = + before_cur:sub(path_pos):match('(.-)()[^' .. path_separator ..']*$') + completion_pos = path_pos + last_component_pos - 1 - for _, command in pairs({'set', 'add', 'cycle', 'cycle[-_]values', 'multiply'}) do - completers[#completers + 1] = { - pattern = '^%s*' .. command .. '%s+()[%w_/-]*$', - list = property_list, - append = ' ', - } - completers[#completers + 1] = { - pattern = '^%s*' .. command .. '%s+"()[%w_/-]*$', - list = property_list, - append = '" ', - } + if directory:find('^~' .. path_separator) then + local home = mp.command_native({'expand-path', '~/'}) + before_cur = before_cur:sub(1, completion_pos - #directory - 1) .. + home .. + before_cur:sub(completion_pos - #directory + 1) + directory = home .. directory:sub(2) + completion_pos = completion_pos + #home - 1 end + -- Don't use completion_append for file completion to not add quotes after + -- directories whose entries you may want to complete afterwards. + completion_append = '' - for _, command in pairs(find_commands_with_file_argument()) do - completers[#completers + 1] = { - pattern = '^%s*' .. command:gsub('-', '[-_]') .. - '%s+["\']?(.-)()[^' .. path_separator ..']*$', - list = file_list, - -- Unfortunately appending " here would append it everytime a - -- directory is fully completed, even if you intend to browse it - -- afterwards. - } + return file_list(directory), before_cur +end + +local function handle_choice_completion(option, before_cur, path_pos) + 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, path_pos) end - return completers + return info.choices, before_cur end function common_prefix_length(s1, s2) @@ -1057,7 +1225,7 @@ local function cycle_through_suggestions(backwards) selected_suggestion_index = #suggestion_buffer end - local before_cur = line:sub(1, completion_start_position - 1) .. + local before_cur = line:sub(1, completion_pos - 1) .. suggestion_buffer[selected_suggestion_index] .. completion_append line = before_cur .. strip_common_characters(line:sub(cursor), completion_append) cursor = before_cur:len() + 1 @@ -1075,72 +1243,180 @@ function complete(backwards) completion_old_line = line completion_old_cursor = cursor mp.commandv('script-message-to', input_caller, 'input-event', - 'complete', line:sub(1, cursor - 1)) + 'complete', utils.format_json({line:sub(1, cursor - 1)})) return end local before_cur = line:sub(1, cursor - 1) local after_cur = line:sub(cursor) + 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 - -- Try the first completer that works - for _, completer in ipairs(build_completers()) do - -- Completer patterns should return the start of the word to be - -- completed as the first capture. - local s2 - completion_start_position, s2 = before_cur:match(completer.pattern) - if not completion_start_position then - -- Multiple input commands can be separated by semicolons, so all - -- completions that are anchored at the start of the string with - -- '^' can start from a semicolon as well. Replace ^ with ; and try - -- to match again. - completion_start_position, s2 = - before_cur:match(completer.pattern:gsub('^^', ';')) + 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 - if completion_start_position then - local hint - if s2 then - hint = completion_start_position - completion_start_position = s2 - end + end - -- Expand ~ in file completion. - if completer.list == file_list and hint:find('^~' .. path_separator) then - local home = mp.command_native({'expand-path', '~/'}) - before_cur = before_cur:sub(1, completion_start_position - #hint - 1) .. - home .. - before_cur:sub(completion_start_position - #hint + 1) - hint = home .. hint:sub(2) - completion_start_position = completion_start_position + #home - 1 - 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, ['async'] = true, ['sync'] = true + } - -- If the completer's pattern found a word, check the completer's - -- list for possible completions - local part = before_cur:sub(completion_start_position) - local completions, prefix = complete_match(part, completer.list(hint)) - if #completions > 0 then - -- If there was only one full match from the list, add - -- completer.append to the final string. This is normally a - -- space or a quotation mark followed by a space. - completion_append = completer.append or '' - if #completions == 1 then - prefix = prefix .. completion_append - after_cur = strip_common_characters(after_cur, completion_append) - else - table.sort(completions) - suggestion_buffer = completions - selected_suggestion_index = 0 - end + while tokens[first_useful_token_index] and + command_prefixes[tokens[first_useful_token_index].text] do + first_useful_token_index = first_useful_token_index + 1 + end + + -- Add an empty token if the cursor is after whitespace to simplify + -- comparisons. + if before_cur == '' or before_cur:find('%s$') then + tokens[#tokens + 1] = { text = "", pos = cursor } + end - -- Insert the completion and update - before_cur = before_cur:sub(1, completion_start_position - 1) .. - prefix - cursor = before_cur:len() + 1 - line = before_cur .. after_cur - update() - return + local add_actions = { + ['add'] = true, ['append'] = true, ['pre'] = true, ['set'] = true + } + + local first_useful_token = tokens[first_useful_token_index] + + completion_pos = before_cur:match('${[=>]?()[%w_/-]*$') + if completion_pos then + completions = property_list() + completion_append = '} ' + elseif #tokens == first_useful_token_index then + completions = command_list() + completions[#completions + 1] = 'help' + completion_pos = first_useful_token.pos + completion_append = completion_append .. ' ' + 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() + completion_pos = tokens[first_useful_token_index + 1].pos + completion_append = completion_append .. ' ' + elseif first_useful_token.text == 'help' then + completions = command_list() + completion_pos = tokens[first_useful_token_index + 1].pos + elseif first_useful_token.text == 'apply-profile' then + completions = profile_list() + completion_pos = tokens[first_useful_token_index + 1].pos + elseif first_useful_token.text == 'change-list' then + completions = list_option_list() + completion_pos = tokens[first_useful_token_index + 1].pos + completion_append = completion_append .. ' ' + elseif first_useful_token.text == 'vf' or + first_useful_token.text == 'af' then + completions = list_option_action_list(first_useful_token.text) + completion_pos = tokens[first_useful_token_index + 1].pos + completion_append = completion_append .. ' ' + elseif has_file_argument(first_useful_token.text) then + completions, before_cur = + handle_file_completion(before_cur, tokens[first_useful_token_index + 1].pos) + end + elseif first_useful_token.text == 'cycle-values' then + completion_pos = tokens[#tokens].pos + completion_append = completion_append .. ' ' + completions, before_cur = + handle_choice_completion(tokens[first_useful_token_index + 1].text, + before_cur, tokens[#tokens].pos) + elseif #tokens == first_useful_token_index + 2 then + if first_useful_token.text == 'set' then + completion_pos = tokens[#tokens].pos + completions, before_cur = + handle_choice_completion(tokens[first_useful_token_index + 1].text, + before_cur, + tokens[first_useful_token_index + 2].pos) + elseif first_useful_token.text == 'change-list' then + completions = list_option_action_list(tokens[first_useful_token_index + 1].text) + completion_pos = tokens[first_useful_token_index + 2].pos + completion_append = completion_append .. ' ' + elseif first_useful_token.text == 'vf' or + first_useful_token.text == 'af' then + if add_actions[tokens[first_useful_token_index + 1].text] then + completion_pos = tokens[#tokens].pos + completions, before_cur = + handle_choice_completion(first_useful_token.text, + before_cur, tokens[#tokens].pos) + elseif tokens[first_useful_token_index + 1].text == 'remove' then + completions = list_option_value_list(first_useful_token.text) + completion_pos = tokens[#tokens].pos end 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 + completion_pos = tokens[#tokens].pos + completions, before_cur = + handle_choice_completion(tokens[first_useful_token_index + 1].text, + before_cur, tokens[#tokens].pos) + elseif tokens[first_useful_token_index + 2].text == 'remove' then + completion_pos = tokens[#tokens].pos + completions = list_option_value_list(tokens[first_useful_token_index + 1].text) + end + elseif first_useful_token.text == 'dump-cache' then + completions, before_cur = + handle_file_completion(before_cur, + tokens[first_useful_token_index + 3].pos) + end end + + if completions == nil then + return + end + + local prefix + completions, prefix = + complete_match(before_cur:sub(completion_pos), completions) + + if #completions == 1 then + prefix = prefix .. completion_append + after_cur = strip_common_characters(after_cur, completion_append) + else + table.sort(completions) + suggestion_buffer = completions + selected_suggestion_index = 0 + end + + before_cur = before_cur:sub(1, completion_pos - 1) .. prefix + cursor = before_cur:len() + 1 + line = before_cur .. after_cur + update() end -- Move the cursor to the beginning of the line (HOME) @@ -1285,9 +1561,9 @@ function get_bindings() { 'shift+ins', function() paste(false) end }, { 'mbtn_mid', function() paste(false) end }, { 'left', function() prev_char() end }, - { 'ctrl+b', function() prev_char() end }, + { 'ctrl+b', function() page_up_or_prev_char() end }, { 'right', function() next_char() end }, - { 'ctrl+f', function() next_char() end }, + { 'ctrl+f', function() page_down_or_next_char() end}, { 'up', function() move_history(-1) end }, { 'ctrl+p', function() move_history(-1) end }, { 'wheel_up', function() move_history(-1) end }, @@ -1385,7 +1661,7 @@ mp.register_script_message('get-input', function (script_name, args) args = utils.parse_json(args) prompt = args.prompt or default_prompt line = args.default_text or '' - cursor = tonumber(args.cursor_position) or line:len() + 1 + cursor = args.cursor_position or line:len() + 1 id = args.id or script_name .. prompt if histories[id] == nil then histories[id] = {} @@ -1394,6 +1670,16 @@ mp.register_script_message('get-input', function (script_name, args) history = histories[id] history_pos = #history + 1 + selectable_items = args.items + if selectable_items then + matches = {} + selected_match = args.default_item or 1< |