fix: file menu not updating active item and other menu issues

- Preselecting previous folder wasn't working sometimes.
- `join_path()` didn't account for drive letters already having a separator.

Also refactors how `open_file_navigation_menu()` stores menu items value. As a side effect this is now a lot faster as we don't have to serialize each item's path into meta table, but instead we now store just a path string and handle it as needed.

closes #355
This commit is contained in:
tomasklaen
2022-10-31 11:10:25 +01:00
parent e8060c74ad
commit 5f5bf4b842
4 changed files with 59 additions and 58 deletions

View File

@@ -900,7 +900,7 @@ mp.add_key_binding(nil, 'open-file', function()
-- Update active file in directory navigation menu -- Update active file in directory navigation menu
local function handle_file_loaded() local function handle_file_loaded()
if Menu:is_open('open-file') then if Menu:is_open('open-file') then
Elements.menu:activate_value(normalize_path(mp.get_property_native('path'))) Elements.menu:activate_one_value(normalize_path(mp.get_property_native('path')))
end end
end end

View File

@@ -351,7 +351,7 @@ end
---@param menu? MenuStack ---@param menu? MenuStack
function Menu:select_value(value, menu) function Menu:select_value(value, menu)
menu = menu or self.current menu = menu or self.current
local index = itable_find(menu.items, function(_, item) return item.value == value end) local index = itable_find(menu.items, function(item) return item.value == value end)
self:select_index(index, 5) self:select_index(index, 5)
end end
@@ -372,7 +372,7 @@ end
---@param index? integer ---@param index? integer
---@param menu? MenuStack ---@param menu? MenuStack
function Menu:activate_unique_index(index, menu) function Menu:activate_one_index(index, menu)
self:deactivate_items(menu) self:deactivate_items(menu)
self:activate_index(index, menu) self:activate_index(index, menu)
end end
@@ -381,16 +381,16 @@ end
---@param menu? MenuStack ---@param menu? MenuStack
function Menu:activate_value(value, menu) function Menu:activate_value(value, menu)
menu = menu or self.current menu = menu or self.current
local index = itable_find(menu.items, function(_, item) return item.value == value end) local index = itable_find(menu.items, function(item) return item.value == value end)
self:activate_index(index, menu) self:activate_index(index, menu)
end end
---@param value? any ---@param value? any
---@param menu? MenuStack ---@param menu? MenuStack
function Menu:activate_unique_value(value, menu) function Menu:activate_one_value(value, menu)
menu = menu or self.current menu = menu or self.current
local index = itable_find(menu.items, function(_, item) return item.value == value end) local index = itable_find(menu.items, function(item) return item.value == value end)
self:activate_unique_index(index, menu) self:activate_one_index(index, menu)
end end
---@param id string ---@param id string
@@ -420,7 +420,7 @@ end
---@param menu? MenuStack ---@param menu? MenuStack
function Menu:delete_value(value, menu) function Menu:delete_value(value, menu)
menu = menu or self.current menu = menu or self.current
local index = itable_find(menu.items, function(_, item) return item.value == value end) local index = itable_find(menu.items, function(item) return item.value == value end)
self:delete_index(index) self:delete_index(index)
end end

View File

@@ -159,6 +159,7 @@ function open_file_navigation_menu(directory_path, handle_select, opts)
local files, directories = read_directory(directory.path, opts.allowed_types) local files, directories = read_directory(directory.path, opts.allowed_types)
local is_root = not directory.dirname local is_root = not directory.dirname
local path_separator = path_separator(directory.path)
if not files or not directories then return end if not files or not directories then return end
@@ -171,77 +172,72 @@ function open_file_navigation_menu(directory_path, handle_select, opts)
if is_root then if is_root then
if state.os == 'windows' then if state.os == 'windows' then
items[#items + 1] = { items[#items + 1] = {title = '..', hint = 'Drives', value = '{drives}', separator = true}
title = '..', hint = 'Drives', value = {is_drives = true, is_to_parent = true}, separator = true,
}
end end
else else
local serialized = serialize_path(directory.dirname) items[#items + 1] = {title = '..', hint = 'parent dir', value = directory.dirname, separator = true}
serialized.is_directory = true
serialized.is_to_parent = true
items[#items + 1] = {title = '..', hint = 'parent dir', value = serialized, separator = true}
end end
local items_start_index = #items + 1 local selected_index = #items + 1
local path_separator = path_separator(directory.path)
for _, dir in ipairs(directories) do for _, dir in ipairs(directories) do
local serialized = serialize_path(join_path(directory.path, dir)) items[#items + 1] = {title = dir, value = join_path(directory.path, dir), hint = path_separator}
if serialized then
serialized.is_directory = true
items[#items + 1] = {title = serialized.basename, value = serialized, hint = path_separator}
end
end end
for _, file in ipairs(files) do for _, file in ipairs(files) do
local serialized = serialize_path(join_path(directory.path, file)) items[#items + 1] = {title = file, value = join_path(directory.path, file)}
if serialized then
serialized.is_file = true
items[#items + 1] = {title = serialized.basename, value = serialized}
end
end end
for index, item in ipairs(items) do for index, item in ipairs(items) do
if not item.value.is_to_parent then if not item.value.is_to_parent and opts.active_path == item.value then
if index == items_start_index then item.selected = true end
if opts.active_path == item.value.path then
item.active = true item.active = true
if not opts.selected_path then item.selected = true end if not opts.selected_path then selected_index = index end
end end
if opts.selected_path == item.value.path then item.selected = true end if opts.selected_path == item.value then selected_index = index end
end
end end
local menu_data = { local menu_data = {
type = opts.type, title = opts.title or directory.basename .. path_separator, items = items, type = opts.type, title = opts.title or directory.basename .. path_separator, items = items,
on_open = opts.on_open, on_close = opts.on_close, selected_index = selected_index,
} }
local menu_options = {on_open = opts.on_open, on_close = opts.on_close}
return Menu:open(menu_data, function(path) return Menu:open(menu_data, function(path)
local is_drives = path == '{drives}'
local is_to_parent = is_drives or #path < #directory_path
local inheritable_options = { local inheritable_options = {
type = opts.type, title = opts.title, allowed_types = opts.allowed_types, active_path = opts.active_path, type = opts.type, title = opts.title, allowed_types = opts.allowed_types, active_path = opts.active_path,
} }
if path.is_drives then if is_drives then
open_drives_menu(function(drive_path) open_drives_menu(function(drive_path)
open_file_navigation_menu(drive_path, handle_select, inheritable_options) open_file_navigation_menu(drive_path, handle_select, inheritable_options)
end, {type = inheritable_options.type, title = inheritable_options.title, selected_path = directory.path}) end, {
type = inheritable_options.type, title = inheritable_options.title, selected_path = directory.path,
on_open = opts.on_open, on_close = opts.on_close,
})
return return
end end
if path.is_directory then 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 then
-- Preselect directory we are coming from -- Preselect directory we are coming from
if path.is_to_parent then if is_to_parent then
inheritable_options.selected_path = directory.path inheritable_options.selected_path = directory.path
end end
open_file_navigation_menu(path.path, handle_select, inheritable_options) open_file_navigation_menu(path, handle_select, inheritable_options)
else else
handle_select(path.path) handle_select(path)
end end
end) end, menu_options)
end end
-- Opens a file navigation menu with Windows drives as items. -- Opens a file navigation menu with Windows drives as items.
@@ -255,7 +251,7 @@ function open_drives_menu(handle_select, opts)
playback_only = false, playback_only = false,
args = {'wmic', 'logicaldisk', 'get', 'name', '/value'}, args = {'wmic', 'logicaldisk', 'get', 'name', '/value'},
}) })
local items = {} local items, selected_index = {}, 1
if process.status == 0 then if process.status == 0 then
for _, value in ipairs(split(process.stdout, '\n')) do for _, value in ipairs(split(process.stdout, '\n')) do
@@ -263,15 +259,17 @@ function open_drives_menu(handle_select, opts)
if drive then if drive then
local drive_path = normalize_path(drive) local drive_path = normalize_path(drive)
items[#items + 1] = { items[#items + 1] = {
title = drive, hint = 'Drive', value = drive_path, title = drive, hint = 'drive', value = drive_path, active = opts.active_path == drive_path,
selected = opts.selected_path == drive_path,
active = opts.active_path == drive_path,
} }
if opts.selected_path == drive_path then selected_index = #items end
end end
end end
else else
msg.error(process.stderr) msg.error(process.stderr)
end end
return Menu:open({type = opts.type, title = opts.title or 'Drives', items = items}, handle_select) return Menu:open(
{type = opts.type, title = opts.title or 'Drives', items = items, selected_index = selected_index},
handle_select
)
end end

View File

@@ -161,7 +161,10 @@ end)()
---@param p2 string ---@param p2 string
---@return string ---@return string
function join_path(p1, p2) function join_path(p1, p2)
return p1 .. path_separator(p1) .. p2 local p1, separator = trim_trailing_separator(p1)
-- Prevents joining drive letters with a redundant separator (`C:\\foo`),
-- as `trim_trailing_separator()` doesn't trim separators from drive letters.
return p1:sub(#p1) == separator and p1 .. p2 or p1 .. separator.. p2
end end
-- Check if path is absolute. -- Check if path is absolute.
@@ -183,17 +186,17 @@ end
-- Remove trailing slashes/backslashes. -- Remove trailing slashes/backslashes.
---@param path string ---@param path string
---@return string ---@return string path, string trimmed_separator_type
function trim_trailing_separator(path) function trim_trailing_separator(path)
path = trim_end(path, path_separator(path)) local separator = path_separator(path)
path = trim_end(path, separator)
if state.os == 'windows' then if state.os == 'windows' then
-- Drive letters on windows need trailing backslash -- Drive letters on windows need trailing backslash
if path:sub(#path) == ':' then return path .. '\\' end if path:sub(#path) == ':' then path = path .. '\\' end
return path
else else
if path == '' then return '/' end if path == '' then path = '/' end
return path
end end
return path, separator
end end
-- Ensures path is absolute, remove trailing slashes/backslashes. -- Ensures path is absolute, remove trailing slashes/backslashes.
@@ -202,8 +205,8 @@ end
---@return string ---@return string
function normalize_path_lite(path) function normalize_path_lite(path)
if not path or is_protocol(path) then return path end if not path or is_protocol(path) then return path end
path = ensure_absolute(path) path = trim_trailing_separator(ensure_absolute(path))
return trim_trailing_separator(path) return path
end end
-- Ensures path is absolute, remove trailing slashes/backslashes, normalization of path separators and deduplication. -- Ensures path is absolute, remove trailing slashes/backslashes, normalization of path separators and deduplication.