vimviediting

Vi(m): How to read file into exact possition in buffer


I need to read content of another file into exact position I am at in the editing buffer in Vi(m). The :r/path/to/file (or the :r!some -command) inserts new line before the content. As an example consider editing a YAML file like this:

---
some:
  structured:
    content:
      metadata:
        uuid: <Esc>:r/proc/sys/kernel/random/uuid<Enter>i
_b5806d69-1612-44aa-a30c-9b4312a99b5c

What I need is to achieve is to make Vi(m) basically like this instead:

some:
  structured:
    content:
      metadata:
        uuid: <Esc>:r/proc/sys/kernel/random/uuid<Enter>ib5806d69-1612-44aa-a30c-9b4312a99b5c_

(I denoted final cursor position with underscore _ character.)

Another example could be:

Editing some text and now I want `<Esc>:r!date -R<Enter>i` right in place of the command and continue to type uninterrupted._

I hope the issue is clear. I use the :r[!]... command many times a day and the extra new line inserted before the content read in the buffer is extremely annoying and disrupting for you have to move around, delete the extra new line etc. Does some way exist that would be as easy to run as is the :r[!]... command and without plugins (just using *mapings for example, because I generally use Vi(m) without any extras or tweaks)? All ideas are welcome.


Some ideas... Perhaps normal mode mapping could be the most useful because then it could be q-recorded and replayed at, say, all places where needed in the buffer or (not sure) just run and repeated with . normal mode command too. The issue is that I do not generally know how to get prompt for a command from within a mapping. If this was possible then it would be fairly easy to workaround.


Solution

  • :help :read makes it pretty clear that the new text will be inserted on a new line below the current/given one:

    Insert the file [name] (default: current file) below the cursor.

    There is no way to change that behavior, so you are left with two options:

    1. somehow work around the quirks of :read,
    2. look for an alternative to :read.

    Here is how a quick and dirty implementation of #1 could look, from insert mode:

    <CR>                        " split the line at cursor
    <Esc>                       " leave insert mode
    :-read /path/to/file<CR>    " insert content of file below line above
                                " leaving cursor on first inserted line
    -                           " move cursor up
    v']+                        " visually select until line below last inserted line
    J                           " join
    

    That's not a lot of work, but that's a kind of work I don't really like and a kind I wouldn't want to have to do often. Too pedestrian for my taste.

    #2 is a good opportunity to dive into vimscript. Here is how it would look, again from insert mode:

    <C-r>=system('cat /path/to/file')->trim()<CR>
    

    where:

    Which is a very different way of interacting with both your text and your editor. Much more high-level and deliberate. I don't know about you but I find the idea of editing (at least some of) my stuff programmatically quite appealing.

    Of course, you could generalize both solutions and turn them into mappings. Speaking of mappings, here is an insert mode one that does just what you want, prompt and all:

    inoremap <expr> <key> input('Cmd: ')->system()->trim()
    

    See :help 'shellredir' if stderr is worrying you… or you could simply append 2>/dev/null to the output of input().

    For manually replacing a visually selected external command with its output, you can do something like this, from visual mode:

    c<C-r>=getreg('"')->system()->trim()<CR>
    

    which is easy to turn into a mapping:

    xnoremap <key> c<C-r>=getreg('"')->system()->trim()<CR><Esc>
    

    Reference:

    :help :read
    :help :range
    :help '[
    :help J
    :help i_ctrl-r
    :help registers
    :help system()
    :help trim()
    :help :map-expression
    :help input()
    :help getreg()
    :help v_c