1936 lines · 62.6 KB · Lua
RawDownload
-- Copyright (C) 2019 the mpv developers---- Permission to use, copy, modify, and/or distribute this software for any-- purpose with or without fee is hereby granted, provided that the above-- copyright notice and this permission notice appear in all copies.---- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. local utils = require 'mp.utils'local assdraw = require 'mp.assdraw' local function detect_platform()    local platform = mp.get_property_native('platform')    if platform == 'darwin' or platform == 'windows' then        return platform    elseif os.getenv('WAYLAND_DISPLAY') then        return 'wayland'    end    return 'x11'end local platform = detect_platform() -- Default optionslocal opts = {    font = "",    font_size = 24,    border_size = 1.5,    scale_with_window = "auto",    case_sensitive = platform ~= 'windows' and true or false,    history_dedup = true,    font_hw_ratio = 'auto',} -- Apply user-set optionsrequire 'mp.options'.read_options(opts) 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&}',    suggestion = '{\\1c&Hcc99cc&}',    selected_suggestion = '{\\1c&H2fbdfa&\\b1}',    default_item = '{\\1c&H2fbdfa&}',    disabled = '{\\1c&Hcccccc&}',} local terminal_styles = {    debug = '\027[90m',    v = '\027[32m',    warn = '\027[33m',    error = '\027[31m',    fatal = '\027[91m',    selected_suggestion = '\027[7m',    default_item = '\027[1m',    disabled = '\027[38;5;8m',} local repl_active = falselocal osd_msg_active = falselocal insert_mode = falselocal pending_update = falselocal line = ''local cursor = 1local default_prompt = '>'local prompt = default_promptlocal bottom_left_margin = 6local default_id = 'default'local id = default_idlocal histories = {[id] = {}}local history = histories[id]local history_pos = 1local searching_history = falselocal log_buffers = {[id] = {}}local key_bindings = {}local dont_bind_up_down = falselocal global_margins = { t = 0, b = 0 }local input_caller local suggestion_buffer = {}local selected_suggestion_indexlocal completion_poslocal completion_appendlocal path_separator = platform == 'windows' and '\\' or '/'local completion_old_linelocal completion_old_cursor local selectable_itemslocal matches = {}local selected_match = 1local first_match_to_print = 1local default_item local set_active  local function get_font()    if opts.font ~= '' then        return opts.font    end     if selectable_items and not searching_history then        return    end     -- Pick a better default font for Windows and macOS    if platform == 'windows' then        return 'Consolas'    end     if platform == 'darwin' then        return 'Menlo'    end     return 'monospace'end  -- Naive helper function to find the next UTF-8 character in 'str' after 'pos'-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.local function next_utf8(str, pos)    if pos > str:len() then return pos end    repeat        pos = pos + 1    until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf    return posend -- As above, but finds the previous UTF-8 character in 'str' before 'pos'local function prev_utf8(str, pos)    if pos <= 1 then return pos end    repeat        pos = pos - 1    until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf    return posend local function len_utf8(str)    local len = 0    local pos = 1    while pos <= str:len() do        pos = next_utf8(str, pos)        len = len + 1    end    return lenend  -- Functions to calculate the font width.local width_length_ratio = 0.5local osd_width, osd_height = 100, 100 ---Update osd resolution if validlocal function update_osd_resolution()    local dim = mp.get_property_native('osd-dimensions')    if not dim or dim.w == 0 or dim.h == 0 then        return    end    osd_width = dim.w    osd_height = dim.hend local text_osd = mp.create_osd_overlay('ass-events')text_osd.compute_bounds, text_osd.hidden = true, true local function measure_bounds(ass_text)    update_osd_resolution()    text_osd.res_x, text_osd.res_y = osd_width, osd_height    text_osd.data = ass_text    local res = text_osd:update()    return res.x0, res.y0, res.x1, res.y1end ---Measure text width and normalize to a font size of 1---text has to be ass safelocal function normalized_text_width(text, size, horizontal)    local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90    local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s'    size = size / 0.8    local width    -- Limit to 5 iterations    local repetitions_left = 5    for i = 1, repetitions_left do        size = size * 0.8        local ass = assdraw.ass_new()        ass.text = template:format(align, size, get_font(), rotation, text)        local _, _, x1, y1 = measure_bounds(ass.text)        -- Check if nothing got clipped        if x1 and x1 < osd_width and y1 < osd_height then            width = horizontal and x1 or y1            break        end        if i == repetitions_left then            width = 0        end    end    return width / size, horizontal and osd_width or osd_heightend local function fit_on_osd(text)    local estimated_width = #text * width_length_ratio    if osd_width >= osd_height then        -- Fill the osd as much as possible, bigger is more accurate.        return math.min(osd_width / estimated_width, osd_height), true    else        return math.min(osd_height / estimated_width, osd_width), false    endend local measured_font_hw_ratio = nillocal function get_font_hw_ratio()    local font_hw_ratio = tonumber(opts.font_hw_ratio)    if font_hw_ratio then        return font_hw_ratio    end    if not measured_font_hw_ratio then        local alphabet = 'abcdefghijklmnopqrstuvwxyz'        local text = alphabet:rep(3)        update_osd_resolution()        local size, horizontal = fit_on_osd(text)        local normalized_width = normalized_text_width(text, size * 0.9, horizontal)        measured_font_hw_ratio = #text / normalized_width * 0.95    end    return measured_font_hw_ratioend  -- Escape a string for verbatim display on the OSDlocal function ass_escape(str)    return mp.command_native({'escape-ass', str})end local function should_scale()    return opts.scale_with_window == "yes" or           (opts.scale_with_window == "auto" and mp.get_property_native("osd-scale-by-window"))end local function scale_factor()    local height = mp.get_property_native('osd-height')     if should_scale() and height > 0 then        return height / 720    end     return mp.get_property_native('display-hidpi-scale', 1)end local function get_scaled_osd_dimensions()    local dims = mp.get_property_native('osd-dimensions')    local scale = scale_factor()     return dims.w / scale, dims.h /scaleend local function calculate_max_log_lines()    if not mp.get_property_native('vo-configured')       or not mp.get_property_native('video-osd') 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((select(2, get_scaled_osd_dimensions())                       * (1 - global_margins.t - global_margins.b)                       - bottom_left_margin)                      / opts.font_size                      -- Subtract 1 for the input line and 0.5 for the empty                      -- line between the log and the input line.                      - 1.5)end -- Takes a list of strings, a max width in characters and-- optionally a max row count.-- The result contains at least one column.-- Rows are cut off from the top if rows_max is specified.-- returns a string containing the formatted table and the row countlocal function format_table(list, width_max, rows_max)    if #list == 0 then        return '', 0    end     local spaces_min = 2    local spaces_max = 8    local list_size = #list    local column_count = 1    local row_count = list_size    local column_widths    -- total width without spacing    local width_total = 0     local list_widths = {}    for i, item in ipairs(list) do        list_widths[i] = len_utf8(item)    end     -- use as many columns as possible    for columns = 2, list_size do        local rows_lower_bound = math.min(rows_max, math.ceil(list_size / columns))        local rows_upper_bound = math.min(rows_max, list_size,                                          math.ceil(list_size / (columns - 1) - 1))        for rows = rows_upper_bound, rows_lower_bound, -1 do            local cw = {}            width_total = 0             -- find out width of each column            for column = 1, columns do                local width = 0                for row = 1, rows do                    local i = row + (column - 1) * rows                    local item_width = list_widths[i]                    if not item_width then break end                    if width < item_width then                        width = item_width                    end                end                cw[column] = width                width_total = width_total + width                if width_total + (columns - 1) * spaces_min > width_max then                    break                end            end             if width_total + (columns - 1) * spaces_min <= width_max then                row_count = rows                column_count = columns                column_widths = cw            else                break            end        end        if width_total + (columns - 1) * spaces_min > width_max then            break        end    end     local spaces = math.floor((width_max - width_total) / (column_count - 1))    spaces = math.max(spaces_min, math.min(spaces_max, spaces))    local spacing = column_count > 1                    and ass_escape(string.format('%' .. spaces .. 's', ' '))                    or ''     local rows = {}    for row = 1, row_count do        local columns = {}        for column = 1, column_count do            local i = row + (column - 1) * row_count            if i > #list then break end            -- more then 99 leads to 'invalid format (width or precision too long)'            local format_string = column == column_count and '%s'                                  or '%-' .. math.min(column_widths[column], 99) .. 's'            columns[column] = ass_escape(string.format(format_string, list[i]))             if i == selected_suggestion_index then                columns[column] = styles.selected_suggestion .. columns[column]                                  .. '{\\b0}'.. styles.suggestion            end        end        -- first row is at the bottom        rows[row_count - row + 1] = table.concat(columns, spacing)    end    return table.concat(rows, ass_escape('\n')), row_countend local function fuzzy_find(needle, haystacks)    local result = require 'mp.fzy'.filter(needle, haystacks)    if line ~= '' then -- Prevent table.sort() from reordering the items.        table.sort(result, function (i, j)            return i[3] > j[3]        end)    end    for i, value in ipairs(result) do        result[i] = value[1]    end    return resultend local function populate_log_with_matches()    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()    local print_counter = false     if #matches > max_log_lines then        print_counter = true        max_log_lines = max_log_lines - 1    end     if selected_match < first_match_to_print then        first_match_to_print = selected_match    elseif selected_match > first_match_to_print + max_log_lines - 1 then        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 print_counter then        log[1] = {            text = '',            style = styles.disabled .. selected_match .. '/' .. #matches ..                    ' {\\fs' .. opts.font_size * 0.75 .. '}[' ..                    first_match_to_print .. '-' .. last_match_to_print .. ']',            terminal_style = terminal_styles.disabled .. selected_match .. '/' ..                             #matches .. ' [' .. first_match_to_print .. '-' ..                             last_match_to_print .. ']',        }    end     for i = first_match_to_print, last_match_to_print do        local style = ''        local terminal_style = ''         if i == selected_match then            style = styles.selected_suggestion            terminal_style = terminal_styles.selected_suggestion        end        if matches[i].index == default_item then            style = style .. styles.default_item            terminal_style = terminal_style .. terminal_styles.default_item        end         log[#log + 1] = {            text = matches[i].text,            style = style,            terminal_style = terminal_style,        }    endend local function print_to_terminal()    -- Clear the log after closing the console.    if not repl_active then        if osd_msg_active then            mp.osd_message('')        end        osd_msg_active = false        return    end     populate_log_with_matches()     local log = ''    local clip = selectable_items and mp.get_property('term-clip-cc') or ''    for _, log_line in ipairs(log_buffers[id]) do        log = log .. clip .. log_line.terminal_style .. log_line.text .. '\027[0m\n'    end     local suggestions = ''    for i, suggestion in ipairs(suggestion_buffer) do        if i == selected_suggestion_index then            suggestions = suggestions .. terminal_styles.selected_suggestion ..                          suggestion .. '\027[0m'        else            suggestions = suggestions .. suggestion        end        suggestions = suggestions .. (i < #suggestion_buffer and '\t' or '\n')    end     local before_cur = line:sub(1, cursor - 1)    local after_cur = line:sub(cursor)    -- Ensure there is a character with inverted colors to print.    if after_cur == '' then        after_cur = ' '    end     mp.osd_message(log .. suggestions .. prompt .. ' ' .. before_cur ..                  '\027[7m' .. after_cur:sub(1, 1) .. '\027[0m' ..                   after_cur:sub(2), 999)    osd_msg_active = trueend -- Render the REPL and console as an ASS OSDlocal function update()    pending_update = false     -- Unlike vo-configured, current-vo doesn't become falsy while switching VO,    -- which would print the log to the OSD.    if not mp.get_property('current-vo') or not mp.get_property_native('video-osd') then        print_to_terminal()

Lines 1–500 of 1936.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t