Fork me on GitHub
#babashka
<
2020-11-17
>
walterl00:11:10

What's the babashka.process way to do echo -e "foo\nbar\nbaz" | fzf, such that fzf uses the piped input, and can still read input from the keyboard? If :in is :inherit-ed, input can't come from somewhere else; if :in is not :inherit-ed, fzf can't read from the keyboard

walterl01:11:21

It dawned on me that I don't really know how the above command's stdin is managed anyway. I hypothesized that stdin is read from the pipe (the echo command), the pipe is then closed, and then (re-?)connected to the tty. That appears to be wrong, because /proc/<pid>/fd/0 was clearly pointing at a pipe, after the command was accepting input from the keyboard. So now I'm thinking that stdin is read from the pipe only, and keyboard input is then read directly from /dev/tty. But if that's the case, why can't fzf read from /dev/tty when stdin isn't inherited from bb?

russmatney02:11:32

I just found https://junegunn.kr/2016/02/using-fzf-in-your-program, and the clojure example worked for me when run with bb - could probably be ported to a more terse bb/process function

russmatney02:11:55

(require '[ :as io])
(import 'java.lang.ProcessBuilder$Redirect)

(defmacro with-filter
  [command & forms]
  `(let [sh#  (or (System/getenv "SHELL") "sh")
         pb#  (doto (ProcessBuilder. [sh# "-c" ~command])
                (.redirectError
                  (ProcessBuilder$Redirect/to (io/file "/dev/tty"))))
         p#   (.start pb#)
         in#  (io/reader (.getInputStream p#))
         out# (io/writer (.getOutputStream p#))]
     (binding [*out* out#]
       (try [email protected] (.close out#) (catch Exception e#)))
     (take-while identity (repeatedly #(.readLine in#)))))

(with-filter "fzf -m"
  (dotimes [n 50]
    (println n)
    (Thread/sleep 5)))

borkdude09:11:47

Nice! I changed the dotimes part with (println (slurp *in*)) so you can pipe a file to this script

borkdude09:11:38

@UJY23QLS1 Using babashka.process:

(require '[babashka.process :as p])

(defn fzf [s]
  (let [proc (p/process ["fzf" "-m"]
                        {:in s :err :inherit
                         :out :string})]
    (:out @proc)))

(fzf (slurp *in*))

walterl14:11:49

Thanks @UGNFWPPGF, @U04V15CAJ! It looks like it's the :err :inherit that makes the difference. It kinda makes sense that it should be inherited, but I'm still not all that clear on why it has this effect. Do you have more insight?

walterl14:11:07

Either way, I'm very glad that I can now use fzf from bb! \o/

borkdude14:11:21

@UJY23QLS1 :err :inherit only influences the output, so the rendering in the screen that fzf does

borkdude14:11:58

From Java you can only send one inputstream. But I think fzf will consume it until EOF and then maybe it will switch to reading the keyboard. This is my guess.

walterl14:11:36

It seems like there's more than that going on, though: if you don't include :err :inherit, fzf understandably won't output anything, but it will also not respond to any input.

borkdude14:11:15

@UJY23QLS1 it does, at least on my machine. just press a couple of up arrows and then hit enter

borkdude14:11:17

$ cat src/babashka/main.clj | bb examples/fzf.clj " (:require\n"

borkdude14:11:27

^ I commented out :err :inherit there

walterl14:11:03

Interesting! On my Ubuntu 18.04 it definitely doesn't respond

walterl14:11:18

Not even ^c

walterl14:11:27

I have to kill bb

borkdude14:11:49

Maybe that's an old version of fzf?

borkdude14:11:00

$ fzf --version
0.24.3 (brew)

walterl14:11:23

fzf --version
0.24.1 (d4c9db0)

walterl14:11:04

Does the same with 0.24.3 o_O

walterl14:11:27

I'm testing with this:

(require '[babashka.process :as p])

(defn fzf [s]
  (let [proc (p/check (p/process ["fzf" #_"-m"]
                                 {:in s #_#_:err :inherit
                                  :out :string}))]
    (:out proc)))

; (fzf (slurp *in*))
(fzf "foo\nbar\nbaz")

borkdude14:11:40

let me try without -m

borkdude14:11:00

still works for me

borkdude14:11:12

ctrl-c also works

borkdude14:11:32

which bb version is this? 0.2.3?

borkdude14:11:18

maybe a subtle difference between linux and macos then

walterl14:11:39

Yeah. That's what's so interesting πŸ™‚

borkdude14:11:57

could be an implementation difference inside fzf

borkdude14:11:08

maybe in linux it blocks on writing to stderr first

borkdude14:11:28

what do you get for :err :string ?

borkdude14:11:50

so:

(defn fzf [s]
  (let [proc (p/process ["fzf" #_"-m"]
                        {:in s :err :string ;; inherit
                         :out :string})]
    (:out @proc)))

walterl14:11:13

I know the library fzf uses that provides is_tty() has different low level (termios) implementations for the different OSs. Maybe it stems from there...?

walterl14:11:57

For :err :string I get no output but input works!

borkdude14:11:19

then I think my guess was right

borkdude14:11:35

that or maybe the buffer for stderr is bigger in macos

🀷 1
walterl14:11:47

Thanks for helping flesh this out, though. And many thanks for bb πŸ™‚babashka

πŸ‘ 1
borkdude14:11:55

It's quite a cool tool, this fzf

truestory 1
walterl15:11:51

It's a game changer, yes. I resisted at first, but it quickly convinced me. As a vim user, it has also become indispensable to navigation https://github.com/junegunn/fzf.vim#commands

russmatney16:11:39

@UJY23QLS1 if you use rofi, the integration there is nice too - something like

(require '[babashka.process :refer [process]])

(defn rofi [s]
  (let [proc (process
               ["rofi" "-i" "-dmenu" "-mesg" "Select" "-sync" "-p" "*"]
               {:in  s :err :inherit
                :out :string})]
    (:out @proc)))

(rofi (slurp *in*))

walterl16:11:30

TIL: rofi πŸ‘

borkdude15:11:15

file walker with babashka.fs:

user=> (fs/walk-file-tree {:pre-visit-dir (fn [dir attrs] (prn '-> (str (fs/relativize dir))) (if (fs/hidden? dir) :terminate :continue))})
-> ""
-> "test"
-> "test/babashka"
-> ".cpcache"

πŸš€ 7
borkdude15:11:08

this stops at the first hidden directory, pretty useless, just a demo ;)

borkdude16:11:51

of course, the multi-arg counter part to io/file:

user=> (fs/path "foo/bar" "baz")
#object[sun.nio.fs.UnixPath 0x21baa903 "foo/bar/baz"]

πŸ™Œ 1
nha16:11:05

Thread/sleep does not do anything on babashka?

borkdude16:11:50

@nha why do you think that? it should work:

$ time bb -e '(Thread/sleep 5000)'
bb -e '(Thread/sleep 5000)'   0.01s  user 0.01s system 0% cpu 5.028 total

nha16:11:38

gah you’re right I forgot it was ms not seconds πŸ˜…

πŸ‘ 1
borkdude22:11:12

Thanks for posting! I'll add it to the bb readme

nate22:11:29

oh cool, thank you!

borkdude23:11:24

Excellent explanation of the i/o flags :) Going to watch the rest tomorrow.

nate14:11:15

Thank you! I'm quite fond of them. They're key to one-liners being so easy to write.

borkdude14:11:22

Some people love them, others find them confusing. I'm contemplating an alternative here: https://github.com/borkdude/babashka/issues/613 Just an idea so far.

borkdude14:11:47

One downsize of using flags is that these things don't work well with full blown scripts

borkdude14:11:55

So it would be nice if we could have something that worked in both cases

borkdude14:11:49

I made a separate doc page for the io flags now, so people know how to port it to script usage: https://github.com/borkdude/babashka/blob/master/doc/io-flags.md

borkdude20:11:34

I now finished watching your presentation. Really good. Posted it to Reddit too :)

nate21:11:28

awesome, thank you!

nate21:11:32

glad you liked it