IEx path autocompletion

34 views
Skip to first unread message

Frank Hunleth

unread,
Dec 3, 2020, 10:43:48 AM12/3/20
to elixir-l...@googlegroups.com
Hi!

I have been experimenting with file path autocompletion in IEx. We
were wanting this feature for Nerves, but there's nothing
Nerves-specific about it. With some work, I could turn this into a PR
to core Elixir if desired.

I was asked to give a feature overview here and start the discussion.

Here's how it works at a high level:

1. If tab was typed while the user is in a string, run the path
completion code; else run the existing IEx completion code.
2. Append a "*" to the string fragment and call Path.wildcard. (Before
doing this, the string is rejected as a possible path if it has
wildcard characters in it)
3. Process the Path.wildcard results into a hint and/or possible
completions and return.

There are edge conditions in all of the steps above, but that's the gist.

My current implementation is in the Toolshed library for convenience
with Nerves. The autocomplete code is completely separate from
anything else in there. See
https://github.com/fhunleth/toolshed/blob/main/lib/toolshed/autocomplete.ex.
The tests may be helpful too:
https://github.com/fhunleth/toolshed/blob/main/test/toolshed/autocomplete_test.exs.

Here's how to try it out:

1. Clone Toolshed
2. iex -S mix
3. Run `Toolshed.Autocomplete.set_expand_fun()` at the prompt
4. Type `File.read("lib/<tab>` or whatever you'd like.

I haven't tested on Windows or over Erlang distribution at all, so
those use cases may need fixing. It currently only works with Elixir
strings.

We're currently exercising path autocompletion with Nerves. It seems
to be working well, and my plan is to keep updating it in Toolshed as
we find improvements. If feedback is positive for core Elixir, I'll
start the PR going. I would also be happy to pull it out of Toolshed
if core isn't the right place and non-Nerves users would like to use
it without the overhead.

Thanks!
Frank

José Valim

unread,
Dec 3, 2020, 11:23:32 AM12/3/20
to elixir-l...@googlegroups.com
Thanks Frank, this sounds good to me. Perhaps the only change I would do is to use FIle.ls instead of Path.wildcard. Basically split anything after the last "/", ls the directory, and then do String.starts_with?(results, last). It will also protect us from wildcard characters and so on. What do you think? Would you like to send a PR?

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CA%2B-urNQKCUGDT_wY8-c5Zvy7rE%2BF9ov7vYDiXwOCR5F8bUH-hA%40mail.gmail.com.

Frank Hunleth

unread,
Dec 3, 2020, 11:47:02 AM12/3/20
to elixir-l...@googlegroups.com
On Thu, Dec 3, 2020 at 11:23 AM José Valim <jose....@dashbit.co> wrote:
>
> Thanks Frank, this sounds good to me. Perhaps the only change I would do is to use FIle.ls instead of Path.wildcard. Basically split anything after the last "/", ls the directory, and then do String.starts_with?(results, last). It will also protect us from wildcard characters and so on. What do you think? Would you like to send a PR?

Funny enough, that was my first try. It had two issues: 1. I was
concerned that tab completion in a directory with tons of files would
result in large lists being passed around and that Path.wildcard could
filter earlier, and 2. there was an annoying edge case (with handling
dots, I think) and Path.wildcard was particularly convenient for
working around that. At any rate, I'll look again for the PR, since I
agree that filtering wildcards doesn't seem right.

Yes, I'll send a PR. I'm feeling slow given that a lot is going on
this week, but I'm on it! (And I'll collect and incorporate any other
comments posted here too)

Thanks!
Frank

José Valim

unread,
Dec 3, 2020, 11:56:03 AM12/3/20
to elixir-l...@googlegroups.com
About 1, Path.wildcard uses File.ls underneath, so it would be the same.

About 2, Path.wildcard ignores all dots by default after reading them from File.ls. We probably want something like this: unless the user explicitly starts the last segment with a dot, we ignore dots.

Both of these in practice means something like this:

with {:ok, files} <- FIle.ls(dir) do
  if last == "" do
    Enum.reject(files, &String.starts_with?(&1, "."))
  else
    Enum.filter(files, &String.starts_with?(&1, last))
  end
end

Thoughts?

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

Frank Hunleth

unread,
Dec 3, 2020, 12:24:26 PM12/3/20
to elixir-l...@googlegroups.com
On Thu, Dec 3, 2020 at 11:56 AM José Valim <jose....@dashbit.co> wrote:
>
> About 1, Path.wildcard uses File.ls underneath, so it would be the same.
>
> About 2, Path.wildcard ignores all dots by default after reading them from File.ls. We probably want something like this: unless the user explicitly starts the last segment with a dot, we ignore dots.
>
> Both of these in practice means something like this:
>
> with {:ok, files} <- FIle.ls(dir) do
> if last == "" do
> Enum.reject(files, &String.starts_with?(&1, "."))
> else
> Enum.filter(files, &String.starts_with?(&1, last))
> end
> end
>
> Thoughts?

Totally convinced. I'll switch it out. Thanks for the detailed explanation.

Frank
Reply all
Reply to author
Forward
0 new messages