# Paste link to image in clipboard when editing Markdown

I have an image on the clipboard and I want to paste it into my document as the text [](img/image1.png).

Instead of first copying it to that directory, I want to do it with a single p key press in Vim. So it hooks p, checks if I am editing a Markdown file, saves the image from the clipboard to the location img/image1.png, and inserts [](img/image1.png) into the file.

Is this possible with Vim?

I use macOS and terminal Vim.

• For clarity, is this "I have copied an image directly as if in, say, word" or "I have copied an image file in finder"? (I don't actually know if they are different...) – Joe May 27 at 10:12

The usual terminal command for accessing the clipboard on Mac is pbpaste. However, this doesn't work for image data, so we have to resort to AppleScript:

nnoremap <silent> <buffer> p :call MarkdownClipboardImage()<cr>

function! MarkdownClipboardImage() abort
" Create img directory if it doesn't exist
let img_dir = getcwd() . '/img'
if !isdirectory(img_dir)
silent call mkdir(img_dir)
endif

" First find out what filename to use
let index = 1
let file_path = img_dir . "/image" . index . ".png"
let index = index + 1
let file_path = img_dir . "/image" . index . ".png"
endwhile

let clip_command = 'osascript'
let clip_command .= ' -e "set png_data to the clipboard as «class PNGf»"'
let clip_command .= ' -e "set referenceNumber to open for access POSIX path of'
let clip_command .= ' (POSIX file \"' . file_path . '\") with write permission"'
let clip_command .= ' -e "write png_data to referenceNumber"'

silent call system(clip_command)
if v:shell_error == 1
normal! p
else
execute "normal! i[](" . file_path . ")"
endif
endfunction


Add the code above to ~/.vim/after/ftplugin/markdown.vim.

This saves the clipboard data to the file "img/image1.png" if it contains PNG data. It then just uses the :normal! to insert the desired markdown link.

If the clipboard doesn't contain PNG data (or if we're not in a Markdown file), it performs a regular p command.

If I were using this, I'd tidy it up to support further image types and to leave the cursor in a more helpful position, but this works well as a proof of concept.

(I'd also use a <leader>p mapping as suggested by Karl Yngve Lervåg — this seems like functionality I'd prefer to invoke specifically — but that's not what you asked for).

• :help :map-<buffer>
• :help system()
• :help v:shell_error
• :help :normal
• AppleScript documentation
• Nice, I've updated my answer with a link to yours, since your solution actually answers the MacOS requirement. – Karl Yngve Lervåg Nov 8 '17 at 7:37
• @KarlYngveLervåg Thanks, that's very kind of you. I wouldn't even have written my answer if it wasn't for yours: you inspired me to find a native Mac solution! – Rich Nov 8 '17 at 9:41

The following solution should work on most Linux systems. It requires a relatively updated xclip tool. For a solution that works on MacOS/OSX, see @Rich's answer.

I've proposed a solution that maps <leader>p instead of remapping p, since I think this is a much saner approach. The function SaveFile will inspect the clipboard with xclip. If there is an image file, it will be saved to img/imageN.<ext>, unless the file already exists. It will also populate the * register with the [](img/...) text and then paste it.

nnoremap <silent> <leader>p :call SaveFile()<cr>

function! SaveFile() abort
let targets = filter(
\ systemlist('xclip -selection clipboard -t TARGETS -o'),
\ 'v:val =~# ''image''')
if empty(targets) | return | endif

let outdir = expand('%:p:h') . '/img'
if !isdirectory(outdir)
call mkdir(outdir)
endif

let mimetype = targets[0]
let extension = split(mimetype, '/')[-1]
let tmpfile = outdir . '/savefile_tmp.' . extension
call system(printf('xclip -selection clipboard -t %s -o > %s',
\ mimetype, tmpfile))

let cnt = 0
let filename = outdir . '/image' . cnt . '.' . extension
call system('diff ' . tmpfile . ' ' . filename)
if !v:shell_error
call delete(tmpfile)
break
endif

let cnt += 1
let filename = outdir . '/image' . cnt . '.' . extension
endwhile


• My guess is that you have an old version of xclip. You should try to do the xclip commands in your terminal. – Karl Yngve Lervåg Nov 12 '17 at 10:05