gokcehan / lf

Terminal file manager
MIT License
7.68k stars 326 forks source link

How do I execute scripts that take a choice via stdin without needing to press <enter>? #1616

Open UtkarshVerma opened 7 months ago

UtkarshVerma commented 7 months ago

I am trying to create a trash command that mimics the built-in delete command by doing the following:

So far, I have written this:

# Move the file/directory to trash.
cmd trash %{{
    set -f

    if ! command -v trash-put >/dev/null 2>&1; then 
        echo "trash-cli is not installed."
        exit 1
    fi

    file_count=$(echo "$fx" | wc -l)
    if [ $file_count -eq 1 ]; then
        printf "trash '%s'?" $fx
    else
        printf "trash %s items?" $file_count
    fi
    printf " [y/N] "

    choice=$(/usr/bin/env bash -c 'read -n 1 choice; echo $choice')

    # Clear the prompt.
    echo

    if [ $choice != "y" ]; then
        exit
    fi

    trash-put $fx
}}

The script behaves well if I execute in my dash shell interactively. However, the input requires me to press <enter> if I invoke the command via lf. What could be the issue here?

Secondly, is there any way I can suppress the ">" symbol that appears for shell-pipe commands for this command specifically. It's not a high priority thing, but would be nice to know if I could.

image

Thanks.

MahouShoujoMivutilde commented 7 months ago

Replace %{{ with ${{. Why do you even want a shell-pipe for such an interactive use case?

Btw, you could just read -r -n 1 choice instead of choice=$(.....).

UtkarshVerma commented 7 months ago

I want the script output to be shown at the bottom. Using ${{ completely occupies the whole screen. Also, I am using the choice=$(...) because /bin/sh points to dash which strictly conforms to the POSIX standard.

In POSIX, read -n 1 does not exist.

DusanLesan commented 7 months ago

I have a weird idea that could be a last resort if nobody has better ideas:

cmd delete :{{
    map y send_key_y
    map n send_key_n
    trashing_listener
}}

cmd send_key_y ${{
    pipe="/tmp/my_pipe_$id"
    echo 'y' > "$pipe"
}}

cmd send_key_n ${{
    pipe="/tmp/my_pipe_$id"
    echo 'n' > "$pipe"
}}

cmd trashing_listener &{{
    pipe="/tmp/my_pipe_$id"
    mkfifo "$pipe"

    cleanup() {
        rm -f "$pipe"
        lf -remote "send $id remap"
        lf -remote "send $id echo"
        exit 0
    }

    if ! command -v trash-put >/dev/null 2>&1; then
        lf -remote "send $id echo \"trash-cli is not installed.\""
        # exit 1
    fi

    file_count=$(echo "$fx" | wc -l)
    if [ $file_count -eq 1 ]; then
        lf -remote "send $id echo \"trash $fx? [y/N]\""
    else
        lf -remote "send $id echo \"trash $file_count items? [y/N]\""
    fi

    while true; do
        if read -r msg < "$pipe"; then
            if [ "$msg" = "y" ]; then
                notify-send "trash-put $fx"
            fi
            cleanup
        fi
    done
}}

cmd remap :{{
    map y copy
}}

delete calls a process that will listen for input and remaps y and n to provide that input trashing_listener creates a loop that listens for y remap is supposed to return key mappings to the original state

randoragon commented 4 months ago

Years ago in 2020 when I was configuring lf, I wanted the exact same thing you want. Back then, it was achievable by setting the shell to zsh and running read -q inside %{{ ... }}. See my dotfiles: https://github.com/randoragon/dotfiles/blob/cfcc10ff35e2f8d4633b035aa0bcf9bc7787c641/lf/.config/lf/lfrc#L34

However, a few years ago, I don't remember when, it stopped to work. Now read -q hangs inside %{{ }}, and only works properly inside ${{ }} which, as you pointed out, is inconsistent with :delete, because it occupies the entire screen.

I think this may be a bug, but I've been too lazy to investigate or open my own ticket :P

DusanLesan commented 3 months ago

@UtkarshVerma @randoragon If you are willing to deal with tmux flow, you can try to do it like:

cmd trash ${{
    split_height=$(( $(echo "$fx" | wc -l) + 2 ))
    [ $split_height -gt $lf_height ] && split_height=$lf_height
    tmux split-window -l $split_height '
        echo -n "'"$fx"'\n\nDelete files? [y/n] "
        stty -icanon -echo
        choice=$(dd bs=1 count=1 2>/dev/null)
        if [[ "$choice" == "y" ]]; then
            while IFS= read -r file; do
                notify-send "Moving file to trash..." ">$file<"
            done <<< "'"$fx"'"
            lf -remote "send '$id' load"
        fi
    '
}}