| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831 |
- ---@param data MenuData
- ---@param opts? {submenu?: string; mouse_nav?: boolean; on_close?: string | string[]}
- function open_command_menu(data, opts)
- local function run_command(command)
- if type(command) == 'string' then
- mp.command(command)
- else
- ---@diagnostic disable-next-line: deprecated
- mp.commandv(unpack(command))
- end
- end
- ---@type MenuOptions
- local menu_opts = {}
- if opts then
- menu_opts.mouse_nav = opts.mouse_nav
- if opts.on_close then menu_opts.on_close = function() run_command(opts.on_close) end end
- end
- local menu = Menu:open(data, run_command, menu_opts)
- if opts and opts.submenu then menu:activate_submenu(opts.submenu) end
- return menu
- end
- ---@param opts? {submenu?: string; mouse_nav?: boolean; on_close?: string | string[]}
- function toggle_menu_with_items(opts)
- if Menu:is_open('menu') then
- Menu:close()
- else
- open_command_menu({type = 'menu', items = get_menu_items(), search_submenus = true}, opts)
- end
- end
- ---@param opts {type: string; title: string; list_prop: string; active_prop?: string; serializer: fun(list: any, active: any): MenuDataItem[]; on_select: fun(value: any); on_paste: fun(payload: string); on_move_item?: fun(from_index: integer, to_index: integer, submenu_path: integer[]); on_delete_item?: fun(index: integer, submenu_path: integer[])}
- function create_self_updating_menu_opener(opts)
- return function()
- if Menu:is_open(opts.type) then
- Menu:close()
- return
- end
- local list = mp.get_property_native(opts.list_prop)
- local active = opts.active_prop and mp.get_property_native(opts.active_prop) or nil
- local menu
- local function update() menu:update_items(opts.serializer(list, active)) end
- local ignore_initial_list = true
- local function handle_list_prop_change(name, value)
- if ignore_initial_list then
- ignore_initial_list = false
- else
- list = value
- update()
- end
- end
- local ignore_initial_active = true
- local function handle_active_prop_change(name, value)
- if ignore_initial_active then
- ignore_initial_active = false
- else
- active = value
- update()
- end
- end
- local initial_items, selected_index = opts.serializer(list, active)
- -- Items and active_index are set in the handle_prop_change callback, since adding
- -- a property observer triggers its handler immediately, we just let that initialize the items.
- menu = Menu:open(
- {
- type = opts.type,
- title = opts.title,
- items = initial_items,
- selected_index = selected_index,
- on_paste = opts.on_paste,
- },
- opts.on_select, {
- on_open = function()
- mp.observe_property(opts.list_prop, 'native', handle_list_prop_change)
- if opts.active_prop then
- mp.observe_property(opts.active_prop, 'native', handle_active_prop_change)
- end
- end,
- on_close = function()
- mp.unobserve_property(handle_list_prop_change)
- mp.unobserve_property(handle_active_prop_change)
- end,
- on_move_item = opts.on_move_item,
- on_delete_item = opts.on_delete_item,
- })
- end
- end
- function create_select_tracklist_type_menu_opener(menu_title, track_type, track_prop, load_command, download_command)
- local function serialize_tracklist(tracklist)
- local items = {}
- if download_command then
- items[#items + 1] = {
- title = t('Download'), bold = true, italic = true, hint = t('search online'), value = '{download}',
- }
- end
- if load_command then
- items[#items + 1] = {
- title = t('Load'), bold = true, italic = true, hint = t('open file'), value = '{load}',
- }
- end
- if #items > 0 then
- items[#items].separator = true
- end
- local first_item_index = #items + 1
- local active_index = nil
- local disabled_item = nil
- -- Add option to disable a subtitle track. This works for all tracks,
- -- but why would anyone want to disable audio or video? Better to not
- -- let people mistakenly select what is unwanted 99.999% of the time.
- -- If I'm mistaken and there is an active need for this, feel free to
- -- open an issue.
- if track_type == 'sub' then
- disabled_item = {title = t('Disabled'), italic = true, muted = true, hint = '—', value = nil, active = true}
- items[#items + 1] = disabled_item
- end
- for _, track in ipairs(tracklist) do
- if track.type == track_type then
- local hint_values = {}
- local function h(value) hint_values[#hint_values + 1] = value end
- if track.lang then h(track.lang:upper()) end
- if track['demux-h'] then
- h(track['demux-w'] and (track['demux-w'] .. 'x' .. track['demux-h']) or (track['demux-h'] .. 'p'))
- end
- if track['demux-fps'] then h(string.format('%.5gfps', track['demux-fps'])) end
- h(track.codec)
- if track['audio-channels'] then
- h(track['audio-channels'] == 1
- and t('%s channel', track['audio-channels'])
- or t('%s channels', track['audio-channels']))
- end
- if track['demux-samplerate'] then h(string.format('%.3gkHz', track['demux-samplerate'] / 1000)) end
- if track.forced then h(t('forced')) end
- if track.default then h(t('default')) end
- if track.external then h(t('external')) end
- items[#items + 1] = {
- title = (track.title and track.title or t('Track %s', track.id)),
- hint = table.concat(hint_values, ', '),
- value = track.id,
- active = track.selected,
- }
- if track.selected then
- if disabled_item then disabled_item.active = false end
- active_index = #items
- end
- end
- end
- return items, active_index or first_item_index
- end
- local function handle_select(value)
- if value == '{download}' then
- mp.command(download_command)
- elseif value == '{load}' then
- mp.command(load_command)
- else
- mp.commandv('set', track_prop, value and value or 'no')
- -- If subtitle track was selected, assume the user also wants to see it
- if value and track_type == 'sub' then
- mp.commandv('set', 'sub-visibility', 'yes')
- end
- end
- end
- return create_self_updating_menu_opener({
- title = menu_title,
- type = track_type,
- list_prop = 'track-list',
- serializer = serialize_tracklist,
- on_select = handle_select,
- on_paste = function(path) load_track(track_type, path) end,
- })
- end
- ---@alias NavigationMenuOptions {type: string, title?: string, allowed_types?: string[], keep_open?: boolean, active_path?: string, selected_path?: string; on_open?: fun(); on_close?: fun()}
- -- Opens a file navigation menu with items inside `directory_path`.
- ---@param directory_path string
- ---@param handle_select fun(path: string, mods: Modifiers): nil
- ---@param opts NavigationMenuOptions
- function open_file_navigation_menu(directory_path, handle_select, opts)
- directory = serialize_path(normalize_path(directory_path))
- opts = opts or {}
- if not directory then
- msg.error('Couldn\'t serialize path "' .. directory_path .. '.')
- return
- end
- local files, directories = read_directory(directory.path, {
- types = opts.allowed_types,
- hidden = options.show_hidden_files,
- })
- local is_root = not directory.dirname
- local path_separator = path_separator(directory.path)
- if not files or not directories then return end
- sort_strings(directories)
- sort_strings(files)
- -- Pre-populate items with parent directory selector if not at root
- -- Each item value is a serialized path table it points to.
- local items = {}
- if is_root then
- if state.platform == 'windows' then
- items[#items + 1] = {title = '..', hint = t('Drives'), value = '{drives}', separator = true}
- end
- else
- items[#items + 1] = {title = '..', hint = t('parent dir'), value = directory.dirname, separator = true}
- end
- local back_path = items[#items] and items[#items].value
- local selected_index = #items + 1
- for _, dir in ipairs(directories) do
- items[#items + 1] = {title = dir, value = join_path(directory.path, dir), hint = path_separator}
- end
- for _, file in ipairs(files) do
- items[#items + 1] = {title = file, value = join_path(directory.path, file)}
- end
- for index, item in ipairs(items) do
- if not item.value.is_to_parent and opts.active_path == item.value then
- item.active = true
- if not opts.selected_path then selected_index = index end
- end
- if opts.selected_path == item.value then selected_index = index end
- end
- ---@type MenuCallback
- local function open_path(path, meta)
- local is_drives = path == '{drives}'
- local is_to_parent = is_drives or #path < #directory_path
- local inheritable_options = {
- type = opts.type, title = opts.title, allowed_types = opts.allowed_types, active_path = opts.active_path,
- keep_open = opts.keep_open,
- }
- if is_drives then
- open_drives_menu(function(drive_path)
- open_file_navigation_menu(drive_path, handle_select, inheritable_options)
- end, {
- type = inheritable_options.type,
- title = inheritable_options.title,
- selected_path = directory.path,
- on_open = opts.on_open,
- on_close = opts.on_close,
- })
- return
- end
- local info, error = utils.file_info(path)
- if not info then
- msg.error('Can\'t retrieve path info for "' .. path .. '". Error: ' .. (error or ''))
- return
- end
- if info.is_dir and not meta.modifiers.alt and not meta.modifiers.ctrl then
- -- Preselect directory we are coming from
- if is_to_parent then
- inheritable_options.selected_path = directory.path
- end
- open_file_navigation_menu(path, handle_select, inheritable_options)
- else
- handle_select(path, meta.modifiers)
- end
- end
- local function handle_back()
- if back_path then open_path(back_path, {modifiers = {}}) end
- end
- local menu_data = {
- type = opts.type,
- title = opts.title or directory.basename .. path_separator,
- items = items,
- keep_open = opts.keep_open,
- selected_index = selected_index,
- }
- local menu_options = {on_open = opts.on_open, on_close = opts.on_close, on_back = handle_back}
- return Menu:open(menu_data, open_path, menu_options)
- end
- -- Opens a file navigation menu with Windows drives as items.
- ---@param handle_select fun(path: string): nil
- ---@param opts? NavigationMenuOptions
- function open_drives_menu(handle_select, opts)
- opts = opts or {}
- local process = mp.command_native({
- name = 'subprocess',
- capture_stdout = true,
- playback_only = false,
- args = {'wmic', 'logicaldisk', 'get', 'name', '/value'},
- })
- local items, selected_index = {}, 1
- if process.status == 0 then
- for _, value in ipairs(split(process.stdout, '\n')) do
- local drive = string.match(value, 'Name=([A-Z]:)')
- if drive then
- local drive_path = normalize_path(drive)
- items[#items + 1] = {
- title = drive, hint = t('drive'), value = drive_path, active = opts.active_path == drive_path,
- }
- if opts.selected_path == drive_path then selected_index = #items end
- end
- end
- else
- msg.error(process.stderr)
- end
- return Menu:open(
- {type = opts.type, title = opts.title or t('Drives'), items = items, selected_index = selected_index},
- handle_select
- )
- end
- -- On demand menu items loading
- do
- local items = nil
- function get_menu_items()
- if items then return items end
- local input_conf_property = mp.get_property_native('input-conf')
- local input_conf_iterator
- if input_conf_property:sub(1, 9) == 'memory://' then
- -- mpv.net v7
- local input_conf_lines = split(input_conf_property:sub(10), '\n')
- local i = 0
- input_conf_iterator = function()
- i = i + 1
- return input_conf_lines[i]
- end
- else
- local input_conf = input_conf_property == '' and '~~/input.conf' or input_conf_property
- local input_conf_path = mp.command_native({'expand-path', input_conf})
- local input_conf_meta, meta_error = utils.file_info(input_conf_path)
- -- File doesn't exist
- if not input_conf_meta or not input_conf_meta.is_file then
- items = create_default_menu_items()
- return items
- end
- input_conf_iterator = io.lines(input_conf_path)
- end
- local main_menu = {items = {}, items_by_command = {}}
- local by_id = {}
- for line in input_conf_iterator do
- local key, command, comment = string.match(line, '%s*([%S]+)%s+(.-)%s+#%s*(.-)%s*$')
- local title = ''
- if comment then
- local comments = split(comment, '#')
- local titles = itable_filter(comments, function(v, i) return v:match('^!') or v:match('^menu:') end)
- if titles and #titles > 0 then
- title = titles[1]:match('^!%s*(.*)%s*') or titles[1]:match('^menu:%s*(.*)%s*')
- end
- end
- if title ~= '' then
- local is_dummy = key:sub(1, 1) == '#'
- local submenu_id = ''
- local target_menu = main_menu
- local title_parts = split(title or '', ' *> *')
- for index, title_part in ipairs(#title_parts > 0 and title_parts or {''}) do
- if index < #title_parts then
- submenu_id = submenu_id .. title_part
- if not by_id[submenu_id] then
- local items = {}
- by_id[submenu_id] = {items = items, items_by_command = {}}
- target_menu.items[#target_menu.items + 1] = {title = title_part, items = items}
- end
- target_menu = by_id[submenu_id]
- else
- if command == 'ignore' then break end
- -- If command is already in menu, just append the key to it
- if key ~= '#' and command ~= '' and target_menu.items_by_command[command] then
- local hint = target_menu.items_by_command[command].hint
- target_menu.items_by_command[command].hint = hint and hint .. ', ' .. key or key
- else
- -- Separator
- if title_part:sub(1, 3) == '---' then
- local last_item = target_menu.items[#target_menu.items]
- if last_item then last_item.separator = true end
- else
- local item = {
- title = title_part,
- hint = not is_dummy and key or nil,
- value = command,
- }
- if command == '' then
- item.selectable = false
- item.muted = true
- item.italic = true
- else
- target_menu.items_by_command[command] = item
- end
- target_menu.items[#target_menu.items + 1] = item
- end
- end
- end
- end
- end
- end
- items = #main_menu.items > 0 and main_menu.items or create_default_menu_items()
- return items
- end
- end
- -- Adapted from `stats.lua`
- function get_keybinds_items()
- local items = {}
- local active = find_active_keybindings()
- -- Convert to menu items
- for _, bind in pairs(active) do
- items[#items + 1] = {title = bind.cmd, hint = bind.key, value = bind.cmd}
- end
- -- Sort
- table.sort(items, function(a, b) return a.title < b.title end)
- return #items > 0 and items or {
- {
- title = t('%s are empty', '`input-bindings`'),
- selectable = false,
- align = 'center',
- italic = true,
- muted = true,
- },
- }
- end
- function open_stream_quality_menu()
- if Menu:is_open('stream-quality') then
- Menu:close()
- return
- end
- local ytdl_format = mp.get_property_native('ytdl-format')
- local items = {}
- for _, height in ipairs(config.stream_quality_options) do
- local format = 'bestvideo[height<=?' .. height .. ']+bestaudio/best[height<=?' .. height .. ']'
- items[#items + 1] = {title = height .. 'p', value = format, active = format == ytdl_format}
- end
- Menu:open({type = 'stream-quality', title = t('Stream quality'), items = items}, function(format)
- mp.set_property('ytdl-format', format)
- -- Reload the video to apply new format
- -- This is taken from https://github.com/jgreco/mpv-youtube-quality
- -- which is in turn taken from https://github.com/4e6/mpv-reload/
- local duration = mp.get_property_native('duration')
- local time_pos = mp.get_property('time-pos')
- mp.command('playlist-play-index current')
- -- Tries to determine live stream vs. pre-recorded VOD. VOD has non-zero
- -- duration property. When reloading VOD, to keep the current time position
- -- we should provide offset from the start. Stream doesn't have fixed start.
- -- Decent choice would be to reload stream from it's current 'live' position.
- -- That's the reason we don't pass the offset when reloading streams.
- if duration and duration > 0 then
- local function seeker()
- mp.commandv('seek', time_pos, 'absolute')
- mp.unregister_event(seeker)
- end
- mp.register_event('file-loaded', seeker)
- end
- end)
- end
- function open_open_file_menu()
- if Menu:is_open('open-file') then
- Menu:close()
- return
- end
- local directory
- local active_file
- if state.path == nil or is_protocol(state.path) then
- local serialized = serialize_path(get_default_directory())
- if serialized then
- directory = serialized.path
- active_file = nil
- end
- else
- local serialized = serialize_path(state.path)
- if serialized then
- directory = serialized.dirname
- active_file = serialized.path
- end
- end
- if not directory then
- msg.error('Couldn\'t serialize path "' .. state.path .. '".')
- return
- end
- -- Update active file in directory navigation menu
- local menu = nil
- local function handle_file_loaded()
- if menu and menu:is_alive() then
- menu:activate_one_value(normalize_path(mp.get_property_native('path')))
- end
- end
- menu = open_file_navigation_menu(
- directory,
- function(path, mods)
- if mods.ctrl then
- mp.commandv('loadfile', path, 'append')
- else
- mp.commandv('loadfile', path)
- Menu:close()
- end
- end,
- {
- type = 'open-file',
- allowed_types = config.types.media,
- active_path = active_file,
- keep_open = true,
- on_open = function() mp.register_event('file-loaded', handle_file_loaded) end,
- on_close = function() mp.unregister_event(handle_file_loaded) end,
- }
- )
- end
- ---@param opts {name: 'subtitles'|'audio'|'video'; prop: 'sub'|'audio'|'video'; allowed_types: string[]}
- function create_track_loader_menu_opener(opts)
- local menu_type = 'load-' .. opts.name
- local title = ({
- subtitles = t('Load subtitles'),
- audio = t('Load audio'),
- video = t('Load video'),
- })[opts.name]
- return function()
- if Menu:is_open(menu_type) then
- Menu:close()
- return
- end
- local path = state.path
- if path then
- if is_protocol(path) then
- path = false
- else
- local serialized_path = serialize_path(path)
- path = serialized_path ~= nil and serialized_path.dirname or false
- end
- end
- if not path then
- path = get_default_directory()
- end
- local function handle_select(path) load_track(opts.prop, path) end
- open_file_navigation_menu(path, handle_select, {
- type = menu_type, title = title, allowed_types = opts.allowed_types,
- })
- end
- end
- function open_subtitle_downloader()
- local menu_type = 'download-subtitles'
- ---@type Menu
- local menu
- if Menu:is_open(menu_type) then
- Menu:close()
- return
- end
- local search_suggestion, file_path = '', nil
- local destination_directory = mp.command_native({'expand-path', '~~/subtitles'})
- local credentials = {'--api-key', config.open_subtitles_api_key, '--agent', config.open_subtitles_agent}
- if state.path then
- if is_protocol(state.path) then
- if not is_protocol(state.title) then search_suggestion = state.title end
- else
- local serialized_path = serialize_path(state.path)
- if serialized_path then
- search_suggestion = serialized_path.filename
- file_path = state.path
- destination_directory = serialized_path.dirname
- end
- end
- end
- local handle_select, handle_search
- -- Ensures response is valid, and returns its payload, or handles error reporting,
- -- and returns `nil`, indicating the consumer should abort response handling.
- local function ensure_response_data(success, result, error, check)
- local data
- if success and result and result.status == 0 then
- data = utils.parse_json(result.stdout)
- if not data or not check(data) then
- data = (data and data.error == true) and data or {
- error = true,
- message = t('invalid response json (see console for details)'),
- message_verbose = 'invalid response json: ' .. utils.to_string(result.stdout),
- }
- end
- else
- data = {
- error = true,
- message = error or t('process exited with code %s (see console for details)', result.status),
- message_verbose = result.stdout .. result.stderr,
- }
- end
- if data.error then
- local message, message_verbose = data.message or t('unknown error'), data.message_verbose or data.message
- if message_verbose then msg.error(message_verbose) end
- menu:update_items({
- {
- title = message,
- hint = t('error'),
- muted = true,
- italic = true,
- selectable = false,
- },
- })
- return
- end
- return data
- end
- ---@param data {kind: 'file', id: number}|{kind: 'page', query: string, page: number}
- handle_select = function(data)
- if data.kind == 'page' then
- handle_search(data.query, data.page)
- return
- end
- menu = Menu:open({
- type = menu_type .. '-result',
- search_style = 'disabled',
- items = {{icon = 'spinner', align = 'center', selectable = false, muted = true}},
- }, function() end)
- local args = itable_join({config.ziggy_path, 'download-subtitles'}, credentials, {
- '--file-id', tostring(data.id),
- '--destination', destination_directory,
- })
- mp.command_native_async({
- name = 'subprocess',
- capture_stderr = true,
- capture_stdout = true,
- playback_only = false,
- args = args,
- }, function(success, result, error)
- if not menu:is_alive() then return end
- local data = ensure_response_data(success, result, error, function(data)
- return type(data.file) == 'string'
- end)
- if not data then return end
- load_track('sub', data.file)
- menu:update_items({
- {
- title = t('Subtitles loaded & enabled'),
- bold = true,
- icon = 'check',
- selectable = false,
- },
- {
- title = t('Remaining downloads today: %s', data.remaining .. '/' .. data.total),
- italic = true,
- muted = true,
- icon = 'file_download',
- selectable = false,
- },
- {
- title = t('Resets in: %s', data.reset_time),
- italic = true,
- muted = true,
- icon = 'schedule',
- selectable = false,
- },
- })
- end)
- end
- ---@param query string
- ---@param page number|nil
- handle_search = function(query, page)
- if not menu:is_alive() then return end
- page = math.max(1, type(page) == 'number' and round(page) or 1)
- menu:update_items({{icon = 'spinner', align = 'center', selectable = false, muted = true}})
- local args = itable_join({config.ziggy_path, 'search-subtitles'}, credentials)
- local languages = itable_filter(get_languages(), function(lang) return lang:match('.json$') == nil end)
- args[#args + 1] = '--languages'
- args[#args + 1] = table.concat(table_keys(create_set(languages)), ',') -- deduplicates stuff like `en,eng,en`
- args[#args + 1] = '--page'
- args[#args + 1] = tostring(page)
- if file_path then
- args[#args + 1] = '--hash'
- args[#args + 1] = file_path
- end
- if query and #query > 0 then
- args[#args + 1] = '--query'
- args[#args + 1] = query
- end
- mp.command_native_async({
- name = 'subprocess',
- capture_stderr = true,
- capture_stdout = true,
- playback_only = false,
- args = args,
- }, function(success, result, error)
- if not menu:is_alive() then return end
- local data = ensure_response_data(success, result, error, function(data)
- return type(data.data) == 'table' and data.page and data.total_pages
- end)
- if not data then return end
- local subs = itable_filter(data.data, function(sub)
- return sub and sub.attributes and sub.attributes.release and type(sub.attributes.files) == 'table' and
- #sub.attributes.files > 0
- end)
- local items = itable_map(subs, function(sub)
- local hints = {sub.attributes.language}
- if sub.attributes.foreign_parts_only then hints[#hints + 1] = t('foreign parts only') end
- if sub.attributes.hearing_impaired then hints[#hints + 1] = t('hearing impaired') end
- return {
- title = sub.attributes.release,
- hint = table.concat(hints, ', '),
- value = {kind = 'file', id = sub.attributes.files[1].file_id},
- keep_open = true,
- }
- end)
- if #items == 0 then
- items = {
- {title = t('no results'), align = 'center', muted = true, italic = true, selectable = false},
- }
- end
- if data.page > 1 then
- items[#items + 1] = {
- title = t('Previous page'),
- align = 'center',
- bold = true,
- italic = true,
- icon = 'navigate_before',
- keep_open = true,
- value = {kind = 'page', query = query, page = data.page - 1},
- }
- end
- if data.page < data.total_pages then
- items[#items + 1] = {
- title = t('Next page'),
- align = 'center',
- bold = true,
- italic = true,
- icon = 'navigate_next',
- keep_open = true,
- value = {kind = 'page', query = query, page = data.page + 1},
- }
- end
- menu:update_items(items)
- end)
- end
- local initial_items = {
- {title = t('%s to search', 'ctrl+enter'), align = 'center', muted = true, italic = true, selectable = false},
- }
- menu = Menu:open(
- {
- type = menu_type,
- title = t('enter query'),
- items = initial_items,
- search_style = 'palette',
- on_search = handle_search,
- search_debounce = 'submit',
- search_suggestion = search_suggestion,
- },
- handle_select
- )
- end
|