3

The terminal emulator is handy!

However, using it to open a file you wish to edit (while remaining in vim) is troublesome because simply doing vim myfile.txt will spawn a 'vim within vim inception' type situation.

Is there a way to short-circuit this behavior so as to open a given file in a new buffer of the existing vim instance?

A nice side effect to potential solution is that it would effectively give you an alternate way to navigate to and open files (ie- alternative to a vim specific tree or file browser plugin)

Derrick
  • 131
  • 4
  • Ups! I just now saw that you tagged it with neovim. Not sure if this would work there. I might have to delete it / move it somewhere more suited. – Moba Jun 29 '22 at 12:05
  • No worries! I appreciate the detailed advice and your approach there looks solid. Even in vanilla vim I see however the solution is not necessarily a quick one-liner so I may take some time on the weekend to experiment with your help there. Thanks again. – Derrick Jun 29 '22 at 21:47
  • 1
    I have no experience with neovim and use the original Vim only on Windows, where the +clientserver feature is always enabled. There in a terminal inside Vim the environment variable VIM_SERVERNAME is defined, so with vim --servername %VIM_SERVERNAME% --remote myfile.txt I was able to open myfile.txt in the original Vim instance (both console and GUI). – Jürgen Krämer Jun 30 '22 at 06:12
  • @JürgenKrämer Indeed. I tried that at first, but had issues with switching windows. Testing now I am not able to reproduce, - so not sure what it was. If it works even 95% of the time it would be a lot more simple – Moba Jun 30 '22 at 20:33
  • 1

1 Answers1

3

Sorry, I did not see the tag before I wrote and posted. This is for Vim and I have to figure out what to do with it. Delete / move / …. My bad. No idea if this would work in neovim.


One could use the :h clientserver.

Tried this first but had some issues where it did not switch window before opening resulting it opening in the terminal window. Tested again after comment by Mr. Krämer and not able to reproduce, so likely a very rare glitch.

Example:

vim --servername "$VIM_SERVERNAME" --remote-send '<C-w><C-w>' --remote a*

Simply add it as an alias or the like:

alias svim="vim --servername \"\$VIM_SERVERNAME\" \
      --remote-send '<C-w><C-w>' --remote"
svim foo b*

It requires the Vim session to be run as server. If not one can become by

:call remote_startserver('foo')


Another option that is easier to get smooth is the :h terminal-api. (Considering edit above, perhaps not :)

It gives the possibility to send commands back to Vim.

Besides the already supported drop one can also call functions. The function name has to start with Tapi_ and the terminal started with ++api=Tapi_function_name.

On the shell / terminal side one can then print a special sequence to send data to the Vim instance that opened it.

<Esc>]51; + data + <07>

The data is JSON and for terminal API calls using call it has the format:

["call", "Tapi_function_name", DATA] where DATA can be anything from a number, string, to an JSON array or object.

Modified another script I used to test for this.

Basics

Send a reply in the format:

{
        "split": 0,
        "stay" : 1,
        "files": ["file_A", "file_B", "file_C"]
}
  • split: always split (0 = false, 1 = true)
  • stay : stay in terminal (0 = false, 1 = true)

On the Vim side:

fun! Tapi_File_open(term_bufnr, args)
        " Escape the file-names and create a string "
        " we can use with for example :next"
        let files = join(map(a:args['files'], 'fnameescape(v:val)'), ' ')
        " Window ID's "
        let win_term = win_getid(winnr())
        let win_use = win_getid(winnr('#'))
    if win_use == 0 || win_term == win_use || a:args['split']
            split
    else
            call win_gotoid(win_use)
    endif
    exe &quot;:next &quot; . files
    if a:args['stay']
            call win_gotoid(win_term)
    endif

endfun

" Custom command to open a terminal with the API enabled." command! FileTerm :terminal ++api=Tapi_File_open

On the Terminal side

One could use Vim in Ex mode to generate the data. Something like:

let TAPI_FUNCTION = 'Tapi_Files_open'
let f = map(argv(), '"\"".fnameescape(fnamemodify(v:val, ":p"))."\""')
let reply = [
        \ "call",
        \ TAPI_FUNCTION,
        \ #{
                \ files: f,
                \ split: 0,
                \ goto: 0
        \}
\ ]
exe "set t_ts=\<Esc>]51; t_fs=\x07"
let &titlestring = json_encode(reply)
redraw
qa

Instead of using titlestring it would likely be better to use a temporary file that one sourced after. That way one can use -s as well.

And call it by some alias or small script that in turn sources it:

vim -E -c "source argv2json_cmd.vim" -- file1 file2 file3 ...


Using bash

This is a crudebash script. The help section should be explanatory.

./svim --vvv -g *.txt

NB! NB!

=============

StackExchange destroys tabs in code. In bash script below there are required TAB's in the print_help() section. To copy the script click edit on this answer and copy the text from the raw text.


#! /bin/bash -

stay=1 split=0 eopt=0 verbose=0 v_files=()

print_help() { cat<<-EOH >&2 USAGE: $0 [OPTIONS] <FILES>

  Open file(s) from terminal in Vim where
  terminal has been started with:
    ++api=Tapi_File_open

OPTIONS
  -h --help   This help
  -n --stay   Stay in terminal window
  -g --goto   Goto opened document(s)
  -s --split  Split
     --vv     Print information in terminal
     --vvv    Print information + JSON arg
     --       End of options
EOH

}

for arg in "$@"; do # Options section if [ $eopt -eq 0 ] && [ "${arg:0:1}" = "-" ]; then case "$arg" in -h|--help) print_help; exit ;; -n|--stay) stay=1 ;; -g|--goto) stay=0 ;; -s|--split) split=1 ;; --vv) verbose=1 ;; --vvv) verbose=2 ;; --) eopt=1 ;; esac else # Escape backslash then double quote v_files+=( "$(printf %s "$(realpath -- "$arg")" |
sed 's/\/\\/g;s/"/\"/g')" ) fi done

Join file-names into string

files=$(printf '"%s",' "${v_files[@]}")

Verbose

if [ $verbose -gt 0 ]; then printf '\033[1;34mOpen files in vim (stay: %d, split: %d)\033[0m\n'
"$stay" "$split" >&2 fi

Extra verbose: print JSON data

if [ $verbose -gt 1 ]; then printf '\n\033[0;36m{"stay": %d, "split": %d, "files": [%s]}\033[0m\n\n'
"$stay" "$split" "$files" >&2 fi

Call Vim by printing special sequence

printf '\033]51;['
'"call", "Tapi_File_open",'
'{"stay": %d, "split": %d, "files": [%s]}'
']\007'
"$stay" "$split" "$files"

Moba
  • 390
  • 2
  • 6