5

Often I find myself in the shell, wanting to insert a filename into a lengthy command.

I'd like to hit a shortcut, and use Netrw or Nerdtree to browse for a file or directory, and have it pasted in place into the command I’m building.

So if I was in a folder, and wanted to compare 2 files:

  • I'd type diff

  • then I'd like to hit my shortcut, and visually browse using Netrw or Nerdtree, and select a file.

  • That filename is now added to my diff command... e.g. diff /tmp/file1.txt

  • I hit the shortcut again, and browse for the second file, /super/ez/another/file/somewhere/else/compare.txt

  • this then adds the second file to my diff command, so I now have:

      diff /tmp/file1.txt /super/ez/another/file/somewhere/else/compare.txt
    

Is this possible using any tool? I like Netrw in Vim, but am willing to try new things!

Note that I'm not interested in Tab completion of filenames using globbing. That works sometimes, but not all the time. I like Netrw and would love to figure out how to use it to select a filename by browsing.

2 Answers 2

4

Zsh allows you to bind a shell function to a key and also has a builtin to put text into your command line. So you can do something like this (in zsh):

# define a function that does the work
function my_browser_function () {
  local result
  result=$( some command that returns the filename )
  # print -z $result (see comments)
  LBUFFER+=$result
}
# turn the function into a widget
zle -N my_browser_function
# bind it to some key ("CTRL-X I" for example)
bindkey '\Cxi' my_browser_function

Now you just have to get a command working that lets you browse the filesystem in the way you want and prints the result on stdout.

EDIT: Here is a very basic version of the above function that uses vim:

function my_browser_function () {
  local file=$(mktemp)
  vim -c "map <C-A> :cal system('echo '.expand('%').'/'.getline('.').'>$file')<bar>q<cr>" .
  LBUFFER+=$(<$file)
  rm $file
}

You can navigate in vim/netrw as usual and then use <C-A> (Ctrl-A) to Accept the file/directory name.

Obviously this is only a very basic function. If you want more functionallity you also might write a vim autoload function and call that from the vim command line with -c.

4
  • 2
    You don't want to use print -z here (stricktly speaking print -rz --) which is more to simulate key presses (might call other zle widgets for instance). Use LBUFFER+=$result (or LBUFFER+=${(qqq)result} to insert it quoted). Dec 8, 2014 at 11:30
  • cool! this is definitely half of it... now to figure out a way to get Netrw to select a file and output it to stdout.... I can go vi . and browse using Netrw to the file. But then I have to edit it and hit a keystroke to copy the name to the clipboard. Must... eliminate... these... extra... keystrokes!
    – Brad Parks
    Dec 8, 2014 at 12:49
  • I've found vifm, which seems to do the "browsing" part - super nice... but when I try and run it using a shortcut via zsh or tmux, I get a screen dump of what appears to be escape characters... any ideas on what this would happen?
    – Brad Parks
    Dec 9, 2014 at 20:39
  • Another file manager which might be interesting for this (like vimfm) is ranger. I also added a better shell function that really uses vim above.
    – Lucas
    Dec 10, 2014 at 23:45
3
+100

2022 version of the answer

Vifm v0.8 or later provides a better way of implementing this via --choose-files option which makes this independent of the location of configuration directory.

zsh version

function insert_files() {
    local file
    while read file; do
        LBUFFER+="$(printf %q "$file") "
    done <<< "$(vifm --choose-files - < /dev/tty)"

    zle reset-prompt
}
zle -N insert_files
bindkey '^t' insert_files

bash version

function _insert_files() {
    local file
    local files
    while read -r file; do
        files="$files$(printf %q "$file") "
    done <<< "$(vifm --choose-files - < /dev/tty)"

    local prefix=${READLINE_LINE::READLINE_POINT}
    local suffix=${READLINE_LINE:READLINE_POINT}
    READLINE_LINE=${prefix}${files}${suffix}
    READLINE_POINT=$(( READLINE_POINT + ${#files} ))
}
bind -x '"\C-T": _insert_files'

Original answer (2014)

You basically already know the answer from here. It's quite easy to put this all together like this:

function insert_files() {
    vifm -f < /dev/tty > /dev/tty

    while read l; do
        LBUFFER+="'$l' "
    done < ~/.vifm/vimfiles

    zle reset-prompt
}
zle -N insert_files
bindkey '^t' insert_files

I'm not a zsh-user, so I stole structure of the code from answer by Lucas. As for bash, I was looking once for a way to process output of external command inside shortcut, but didn't find one.

Demonstration

P.S. My code contains quite primitive escaping, you might want to improve that and maybe change the way spacing is added between arguments.

2
  • This did not work for Mac, could someone help in this regard? Nov 27, 2022 at 9:29
  • 1
    Added updated version of the answer which works better.
    – xaizek
    Nov 30, 2022 at 11:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .