#how to integrate fzf with Elixir Script

18 messages · Page 1 of 1 (latest)

steep tiger
#

I tried using fzf, a command-line fuzzy finder, within my Elixir script. Here's the code I used:

defmodule FzfExample do
  def select_from_list(list) do
    {selected, 0} = System.cmd("fzf", [], input: Enum.join(list, "\n"))
    String.trim(selected)
  end
end

IO.puts("Select an item:")
items = ["apple", "banana", "cherry", "date"]
selected_item = FzfExample.select_from_list(items)
IO.puts("You selected: #{selected_item}")

However, when I run the script, I get this error:

Select an item:                                                  ** (ArgumentError) invalid option :input with value "apple\nbanana\ncherry\ndate"                                                     (elixir 1.15.4) lib/system.ex:1175: System.cmd_opts/4            (elixir 1.15.4) lib/system.ex:1100: System.do_cmd/3
    fzf.exs:4: FzfExample.select_from_list/1
    fzf.exs:11: (file)
exotic oriole
#

System.cmd does not seem to have an option input

steep tiger
# exotic oriole `System.cmd` does not seem to have an option `input`

Thanks @exotic oriole!
I fixed that.

I switched from cmd/3 to shell/2 because of this:

If you desire to execute a trusted command inside a shell, with pipes, redirecting and so on, please check shell/2.

I tried this:

defmodule FzfExample do
  def select_from_list(list) do
    command = "echo '#{Enum.join(list, "\n")}' | fzf"
    {result, _exit_code} = System.shell(command)
    String.trim(result)
  end
end

IO.puts("Select an item:")
items = ["apple", "banana", "cherry", "date"]
selected_item = FzfExample.select_from_list(items)
IO.puts("You selected: #{selected_item}")

But I get the error:

Select an item:
Failed to open /dev/tty                         You selected:

The only thing I've got to work is this, but it lacks interactivity.

defmodule FzfExample do
  def select_from_list(list) do
    IO.puts("Type to filter (press Enter when done):")
    user_input = IO.gets("> ") |> String.trim()

    command = "echo '#{Enum.join(list, "\n")}' | fzf -f '#{user_input}'"
    {result, _exit_code} = System.shell(command)
    String.trim(result)
  end
end

IO.puts("Select an item:")
items = ["apple", "banana", "cherry", "date"]
selected_item = FzfExample.select_from_list(items)
IO.puts("You selected: #{selected_item}")
exotic oriole
#

you want fzf to open a prompt and the user to input to that?

steep tiger
exotic oriole
#

hmm, not sure how to do that, but I don't think it works through the typical System functions

steep tiger
# exotic oriole hmm, not sure how to do that, but I don't think it works through the typical Sys...

🥲 that's a shame. I guess it's not possible in elixir. Thanks for your help though Nicd!

It's straightforward in ruby. I guess IO.popen has a capability shell/2 doesn't?

def select_from_list(items)
  IO.popen('fzf', 'r+') do |fzf|
    fzf.puts(items)
    fzf.close_write
    return fzf.read.strip
  end
end

items = ["apple", "banana", "cherry", "date"]
puts "Select an item:"
selected_item = select_from_list(items)
puts "You selected: #{selected_item}"
exotic oriole
#

it might be doable with Port

#

but I haven't done such a thing before

steep tiger
#

Tried to use ports:

defmodule FzfExample do
  def select_from_list(items) do
    port = Port.open({:spawn, "fzf"}, [:binary])

    Enum.each(items, fn item ->
      Port.command(port, "#{item}\n")
    end)

    receive do
      {^port, {:data, result}} -> String.trim(result)
    end
  end
end

IO.puts("Select an item:")
items = ["apple", "banana", "cherry", "date"]
selected_item = FzfExample.select_from_list(items)
IO.puts("You selected: #{selected_item}")

Result:

Select an item:
Failed to open /dev/tty

Seems like Elixir isn't capable of this, so far what I understand from the port docs is that they are made for message passing.

wraith crystal
#

There’s some option you have to pass to give it a tty

exotic oriole
#

though it's an old discussion

steep tiger
#

Thanks @exotic oriole!
This helps a lot!

This is one of the places where things that are easy in other languages are fairly complex in Elixir.

Found this on the thread. Now I finally understand the meme.

steep tiger
velvet patio
#

@steep tiger I'm not sure 100% what you're trying to accomplish and whether this fits that, but usually a more idiomatic way would be compose rather than embed, by having your script take input from stdin (via a pipe), so that using it looks something like fzf --args | your-script

steep tiger