diff --git a/lib/livebook_web/live/path_select_component.ex b/lib/livebook_web/live/path_select_component.ex index 4f4d57328..052f343b8 100644 --- a/lib/livebook_web/live/path_select_component.ex +++ b/lib/livebook_web/live/path_select_component.ex @@ -17,7 +17,23 @@ defmodule LivebookWeb.PathSelectComponent do @impl true def mount(socket) do inner_block = Map.get(socket.assigns, :inner_block, nil) - {:ok, assign(socket, inner_block: inner_block)} + {:ok, assign(socket, inner_block: inner_block, current_dir: nil)} + end + + @impl true + def update(assigns, socket) do + %{assigns: assigns} = socket = assign(socket, assigns) + {dir, basename} = split_path(assigns.path) + dir = Path.expand(dir) + + files = + if assigns.current_dir != dir do + list_files(dir, assigns.extnames, assigns.running_paths) + else + assigns.files + end + + {:ok, assign(socket, files: annotate_matching(files, basename), current_dir: dir)} end @impl true @@ -50,8 +66,16 @@ defmodule LivebookWeb.PathSelectComponent do <% end %>
+ <%= if highlighting?(@files) do %> +
+ <%= for file <- @files, file.highlighted != "" do %> + <%= render_file(file, @phx_target) %> + <% end %> +
+ <% end %> +
- <%= for file <- list_matching_files(@path, @extnames, @running_paths) do %> + <%= for file <- @files, file.highlighted == "" do %> <%= render_file(file, @phx_target) %> <% end %>
@@ -60,6 +84,10 @@ defmodule LivebookWeb.PathSelectComponent do """ end + defp highlighting?(files) do + Enum.any?(files, & &1.highlighted != "") + end + defp render_file(file, phx_target) do icon = case file do @@ -92,18 +120,17 @@ defmodule LivebookWeb.PathSelectComponent do """ end - defp list_matching_files(path, extnames, running_paths) do - # Note: to provide an intuitive behavior when typing the path - # we enter a new directory when it has a trailing slash, - # so given "/foo/bar" we list files in "foo" and given "/foo/bar/ - # we list files in "bar". - # - # The basename is kinda like search within the current directory, - # so we highlight files starting with that string. - - {dir, basename} = split_path(path) - dir = Path.expand(dir) + defp annotate_matching(files, prefix) do + for %{name: name} = file <- files do + if String.starts_with?(name, prefix) do + %{file | highlighted: prefix, unhighlighted: String.replace_prefix(name, prefix, "")} + else + %{file | highlighted: "", unhighlighted: name} + end + end + end + defp list_files(dir, extnames, running_paths) do if File.exists?(dir) do file_names = case File.ls(dir) do @@ -114,36 +141,34 @@ defmodule LivebookWeb.PathSelectComponent do file_infos = file_names |> Enum.map(fn name -> - file_info(dir, name, basename, running_paths) + file_info(name, Path.join(dir, name), running_paths) end) |> Enum.filter(fn file -> not hidden?(file.name) and (file.is_dir or valid_extension?(file.name, extnames)) end) + parent = Path.dirname(dir) + file_infos = - if Path.dirname(dir) == dir do + if parent == dir do file_infos else - parent_dir = file_info(dir, "..", basename, running_paths) - [parent_dir | file_infos] + [file_info("..", parent, running_paths) | file_infos] end - Enum.sort_by(file_infos, fn file -> - {-String.length(file.highlighted), !file.is_dir, file.name} - end) + Enum.sort_by(file_infos, fn file -> {!file.is_dir, file.name} end) else [] end end - defp file_info(dir, name, filter, running_paths) do - path = Path.join(dir, name) |> Path.expand() + defp file_info(name, path, running_paths) do is_dir = File.dir?(path) %{ name: name, - highlighted: if(String.starts_with?(name, filter), do: filter, else: ""), - unhighlighted: String.replace_prefix(name, filter, ""), + highlighted: "", + unhighlighted: name, path: if(is_dir, do: ensure_trailing_slash(path), else: path), is_dir: is_dir, is_running: path in running_paths @@ -158,6 +183,13 @@ defmodule LivebookWeb.PathSelectComponent do Path.extname(filename) in extnames end + # Note: to provide an intuitive behavior when typing the path + # we enter a new directory when it has a trailing slash, + # so given "/foo/bar" we list files in "foo" and given "/foo/bar/ + # we list files in "bar". + # + # The basename is kinda like search within the current directory, + # so we highlight files starting with that string. defp split_path(path) do if String.ends_with?(path, "/") do {path, ""}