Topper Harley
Topper Harley

Reputation: 12384

Replace visual selection with command output

I would like to replace parts of one line with the result of the selection being piped into a command.

For example:

echo "hello $(echo "world" | base64)" | vim -

This will open a buffer with hello d29ybGQK in it. Now press wvw to visually select d29ybGQK.

Then I attempted :!base64 -d and I expected the buffer to contain hello world, which did not happen. Indeed, the whole line was piped into the command, and the whole line was replaced.

Is it possible to replace only the visual selection, and have only that selection piped into the command?

I also attempted c<c-r>=system('base64 -d') but that did not send the visual selection to the command's stdin.

Upvotes: 6

Views: 1530

Answers (2)

sidyll
sidyll

Reputation: 59307

Filtering with ! is always line-wise. Your solution with c and the expression register is an excellent way to solve this. You only forgot to pass the input to system(), which is its second optional argument.

Since you just changed the text selected, it went into the " register automatically. All you need to do is to grab it back and pass it to system with getreg():

c<C-R>=system('base64 -D', getreg('"'))

Note that base64 may echo a newline at the end. If you want to remove it, either wrap the whole thing in trim(), a new function in Vim 8, or use [:-2]:

c<C-R>=trim(system('base64 -D', getreg('"')))
c<C-R>=system('base64 -D', getreg('"'))[:-2]

This is a shorthand for [0:-2], meaning grab everything from character 0 to second-last in the resulting string.

Consider creating a visual map if you use it often:

vnoremap <leader>d c<C-R>=system('base64 -D', getreg('"'))[:-2]<CR>

Upvotes: 12

Ingo Karkat
Ingo Karkat

Reputation: 172758

For historical reasons, the Ex commands are inherently line-based; venerable vi also didn't have visual mode yet. That limitation includes filtering through an external command with :range!; it will always filter complete lines.

manual solution

For simple input like in your example, it's probably easiest to temporarily split the line, filter, and then reassemble. For example:

:substitute/ /\r/ | execute '.!base64 -d' | -1join

plugin solution

For a universal solution, you need to use or implement a plugin that grabs the selected text, filters it (probably through system()), and then replaces the selection with the result.

My SubstituteExpression plugin has a {Visual}g= mapping that can filter through Vimscript expressions, Vim functions and commands, and external commands.

express.vim by Tom McDonald offers an almost identical implementation. It also allows on-the-fly creation of operators via :MapExpress and :MapSubpress, something for which I would use my TextTransform plugin, which you need to install as a dependency, anyway. My plugin offers more advanced (cross-mode) repeats, and the :Ex-command expression variant, but has two large dependencies you also need to install.

Upvotes: 1

Related Questions