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 *map
ings 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.
: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:
:read
,: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:
<C-r><register>
inserts content of <register>
,=<expression>
puts the result of the evaluation of <expression>
into register =
,system('<external command>')
captures the output of <external command>
,->trim()
removes the EOL, if any.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