πΈ img-clip.nvim
Effortlessly embed images into any markup language, like LaTeX, Markdown or Typst.
Features
- Paste images directly from the system clipboard.
- Drag and drop images from your web browser or file explorer to embed them.
- Embed images as files, URLs, or directly as Base64.
- Configurable templates with cursor positioning and figure labels.
- Default templates for widely-used markup languages like LaTeX, Markdown and Typst.
- Cross-compatibility with Linux, Windows, and MacOS.
See these features in action in the demonstration section!
Requirements
- Linux: xclip (x11) or wl-clipboard (wayland).
- MacOS: pngpaste (optional, but recommended).
- Windows: No requirements.
β οΈ Run:checkhealth img-clip
after installation to ensure requirements are satisfied.
Installation
Install the plugin with your preferred package manager:
lazy.nvim
return {
"HakonHarnes/img-clip.nvim",
event = "BufEnter",
opts = {
-- add options here
-- or leave it empty to use the default settings
},
keys = {
-- suggested keymap
{ "<leader>p", "<cmd>PasteImage<cr>", desc = "Paste clipboard image" },
},
}
Usage
Commands
The plugin comes with the following commands:
PasteImage
Inserts the image from the clipboard into the document.
Consider binding PasteImage
to something like <leader>p
.
API
You can also use the Lua equivalent, which allows you to override your configuration by passing the options directly to the function:
require("img-clip").pasteImage({ use_absolute_path = false, file_name = "image.png" })
Configuration
Setup
The plugin comes with the following defaults:
{
default = {
debug = false, -- enable debug mode
dir_path = "assets", -- directory path to save images to, can be relative (cwd or current file) or absolute
file_name = "%Y-%m-%d-%H-%M-%S", -- file name format (see lua.org/pil/22.1.html)
url_encode_path = false, -- encode spaces and special characters in file path
use_absolute_path = false, -- expands dir_path to an absolute path
relative_to_current_file = false, -- make dir_path relative to current file rather than the cwd
relative_template_path = true, -- make file path in the template relative to current file rather than the cwd
prompt_for_file_name = true, -- ask user for file name before saving, leave empty to use default
show_dir_path_in_prompt = false, -- show dir_path in prompt when prompting for file name
use_cursor_in_template = true, -- jump to cursor position in template after pasting
insert_mode_after_paste = true, -- enter insert mode after pasting the markup code
embed_image_as_base64 = false, -- paste image as base64 string instead of saving to file
max_base64_size = 10, -- max size of base64 string in KB
template = "$FILE_PATH", -- default template
drag_and_drop = {
enabled = true, -- enable drag and drop mode
insert_mode = false, -- enable drag and drop in insert mode
copy_images = false, -- copy images instead of using the original file
download_images = true, -- download images and save them to dir_path instead of using the URL
},
},
-- filetype specific options
-- any options that are passed here will override the default config
-- for instance, setting use_absolute_path = true for markdown will
-- only enable that for this particular filetype
-- the key (e.g. "markdown") is the filetype (output of "set filetype?")
filetypes = {
markdown = {
url_encode_path = true,
template = "![$CURSOR]($FILE_PATH)",
drag_and_drop = {
download_images = false,
},
},
html = {
template = '<img src="$FILE_PATH" alt="$CURSOR">',
},
tex = {
relative_template_path = false,
template = [[
\begin{figure}[h]
\centering
\includegraphics[width=0.8\textwidth]{$FILE_PATH}
\caption{$CURSOR}
\label{fig:$LABEL}
\end{figure}
]],
},
typst = {
template = [[
#figure(
image("$FILE_PATH", width: 80%),
caption: [$CURSOR],
) <fig-$LABEL>
]],
},
rst = {
template = [[
.. image:: $FILE_PATH
:alt: $CURSOR
:width: 80%
]],
},
asciidoc = {
template = 'image::$FILE_PATH[width=80%, alt="$CURSOR"]',
},
org = {
template = [=[
#+BEGIN_FIGURE
[[file:$FILE_PATH]]
#+CAPTION: $CURSOR
#+NAME: fig:$LABEL
#+END_FIGURE
]=],
},
},
-- override options for specific files, dirs or custom triggers
files = {}, -- file specific options (e.g. "main.md" or "/path/to/main.md")
dirs = {}, -- dir specific options (e.g. "project" or "/home/user/project")
custom = {}, -- custom options enabled with the trigger option
}
Options
Option values can be configured as either static values (e.g. "assets"), or by dynamically generating them through functions.
For instance, to set the dir_path
to match the name of the currently opened file:
dir_path = function()
return vim.fn.expand("%:t:r")
end,
Filetypes
Filetype specific options will override the default (or global) configuration. Any option can be specified for a specific filetype. For instance, if you only want to use absolute file paths for LaTeX, then:
filetypes = {
tex = {
use_absolute_path = true
}
}
Filetype specific options are determined by the filetype (see :help filetype
).
You can override settings for any filetype by specifying it as the key in your configuration:
filetypes = {
<filetype> = { -- obtained from "set filetype?"
-- add options here
}
}
Overriding options for specific files, directories or custom triggers
Options can be overridden for specific files, directories or based on custom conditions. This means that you can have different options for different projects, or even different files within the same project.
For files and directories, you can specify settings that apply to only a specific file or directory using its absolute path (e.g. /home/user/project/README.md
).
You can also specify a general file or directory name (e.g. README.md
) which will apply the settings to any README.md
file.
For custom options, you can specify a trigger function that returns a boolean value that is used to enable it.
The plugin evaluates the options in the following order:
- Custom options
- File specific options
- Directory specific options
- Filetype specific options
- Default options
Example configuration:
-- file specific options
files = {
["/path/to/specific/file.md"] = {
template = "Custom template for this file",
},
["README.md"] = {
template = "Custom template for README.md files",
},
},
-- directory specific options
dirs = {
["/path/to/project"] = {
template = "Project specific template",
},
},
-- custom options
custom = {
{
trigger = function() -- returns true to enable
return vim.fn.strftime("%A") == "Monday"
end,
template = "Template for Mondays only",
},
}
The options can be nested arbitrarily deep:
dirs = {
["/home/user/markdown"] = {
template = "template for this project",
filetypes = { -- filetype options nested inside dirs
markdown = {
template = "markdown template"
}
},
files = { -- file options nested inside dirs
["readme.md"] = {
dir_path = "images"
},
},
},
}
.img-clip.lua
file
Project-specific settings with the Project-specific settings can be specified in a .img-clip.lua
file in the root of your project.
The plugin will automatically load this file and use it to override the default settings.
If multiple files are found, the closest one to the current file (in any parent directory) will be used.
The .img-clip.lua
should return a Lua table containing the options (similar to opts
in lazy.nvim):
return {
-- add options here
}
Example:
return {
default = {
template = "default template"
},
filetypes = {
markdown = {
template = "markdown template"
}
},
}
Templates
Templates in the plugin use placeholders that are dynamically replaced with the correct values at runtime. For available placeholders, see the following table and the demonstration:
Placeholder | Description | Example |
---|---|---|
$FILE_NAME |
File name, including its extension. | image.png |
$FILE_NAME_NO_EXT |
File name, excluding its extension. | image |
$FILE_PATH |
File path. | /path/to/image.png |
$LABEL |
Figure label, generated from the file name, converted to lower-case and with spaces replaced by dashes. | the-image (from the image.png ) |
$CURSOR |
Indicates where the cursor will be placed after insertion if use_cursor_in_template is true. |
Templates can also be defined using functions with the above placeholders available as function parameters:
template = function(context)
return "![" .. context.cursor .. "](" .. context.file_path .. ")"
end
Drag and drop
The drag and drop feature enables users to drag images from the web browser or file explorer into the terminal to automatically embed them, in normal mode.
It can be optionally enabled in insert mode using the drag_and_drop.insert_mode
option.
For drag and drop to work properly, the following is required by the terminal emulator:
- The terminal emulator must paste the file path or URL to the image when it is dropped into the terminal.
- The text must be inserted in bracketed paste mode, which allows Neovim to differentiate pasted text from typed-in text.
This is required because the drag and drop feature is implemented by overriding
vim.paste()
.
A list of terminal emulators and their capabilities is given below.
Terminal | X11 | Wayland | MacOS | Windows | ||||
---|---|---|---|---|---|---|---|---|
File | URL | File | URL | File | URL | File | URL | |
Kitty | β | β | β | β | β | β | β | β |
Konsole | β | β | βοΈ | βοΈ | β | β | β | β |
Alacritty | β | β | β | β | β | β | β | β |
Wezterm | β | β | βοΈ | βοΈ | β | β | β | β |
Foot | β | β | β | β | β | β | β | β |
Terminal.app | β | β | β | β | β | β | β | β |
iTerm.app | β | β | β | β | β | β | β | β |
Hyper | β | β | βοΈ | βοΈ | β | β | β | β |
XTerm | β | β | β | β | β | β | β | β |
Windows Terminal | β | β | β | β | β | β | β | β |
PowerShell | β | β | β | β | β | β | β | β |
Cmder | β | β | β | β | β | β | β | β |
ConEmu | β | β | β | β | β | β | β | β |
π‘ If you're having issues on Windows, try changing the default shell to
powershell
orpwsh
. See:h shell-powershell
.
β οΈ MacOS URLs only work in Safari.