This is a follow-up to the first post on Neovim and LaTeX and the second one.
If you write LaTeX in Neovim using the LazyVim distribution, you already get a fantastic editing experience thanks to VimTeX and texlab. But there’s one thing that often trips people up: how do you quickly wrap selected text in \emph{…}, \textbf{…}, or other LaTeX commands?
The answer is mini.surround — and with a small buffer-local configuration, it becomes a LaTeX wrapping powerhouse.
What mini.surround Gives You (Out of the Box)
LazyVim ships with the mini.surround extra, which you can enable in your lazy.lua (or wherever you manage extras):
|
1 2 |
{ import = "lazyvim.plugins.extras.coding.mini-surround" }, |
Once enabled, you get operations like:
| Keymap | Action |
|---|---|
gsa |
Add surrounding |
gsd |
Delete surrounding |
gsr |
Replace surrounding |
gsf |
Find surrounding |
For example, visual-select a word and press gsa" to wrap it in double quotes, or gsa) for parentheses. Great — but what about LaTeX-specific commands?
The Problem with LaTeX Commands
VimTeX gives you text objects, motions, completion, and much more — but it doesn’t ship a built-in “surround this selection with \emph{…}” feature. That’s where mini.surround‘s custom surroundings come in.
We can teach mini.surround to understand LaTeX commands by defining custom surroundings in a filetype-specific Lua file.
Setting Up Custom LaTeX Surroundings
Create (or edit) the file ~/.config/nvim/after/ftplugin/tex.lua. This file is automatically sourced by Neovim whenever you open a .tex file.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
-- TeX-only: mini.surround custom LaTeX commands local cfg = vim.b.minisurround_config or {} cfg.custom_surroundings = cfg.custom_surroundings or {} local function cmd_surround(cmd) return { input = { ("\\" .. cmd .. "{().-()}") }, output = { left = ("\\" .. cmd .. "{"), right = "}" }, } end -- LaTeX text commands cfg.custom_surroundings.e = cmd_surround("emph") -- \emph{...} cfg.custom_surroundings.b = cmd_surround("textbf") -- \textbf{...} cfg.custom_surroundings.i = cmd_surround("textit") -- \textit{...} cfg.custom_surroundings.t = cmd_surround("texttt") -- \texttt{...} cfg.custom_surroundings.u = cmd_surround("underline") -- \underline{...} cfg.custom_surroundings.s = cmd_surround("textsc") -- \textsc{...} -- TeX-style double quotes cfg.custom_surroundings.q = { input = { "``().-()''" }, output = { left = "``", right = "''" }, } vim.b.minisurround_config = cfg |
Why ftplugin? Files in this directory are loaded after all plugin configuration, and only for the matching filetype. This means our settings are buffer-local and don’t interfere with any other filetype.
How It Works
The cmd_surround helper function does two things:
input: A Lua pattern that matches\cmd{...}so thatgsdandgsrknow what to find and remove.output: The left (\cmd{) and right (}) strings used when adding a surrounding.
We then assign short, memorable single-character IDs to each command.
Using the Surroundings
Here’s what you can do in any .tex buffer:
Adding a Surrounding
Visual-select some text, then:
| Keymap | Result |
|---|---|
gsae |
\emph{selected text} |
gsab |
\textbf{selected text} |
gsai |
\textit{selected text} |
gsat |
\texttt{selected text} |
gsau |
\underline{selected text} |
gsas |
\textsc{selected text} |
gsaq |
(add opening and closing LaTeX quotations, i.e., two backticks and ”) |
You can also use gsa in normal mode with a motion: gsaeiw wraps the current word in \emph{…}.
Deleting a Surrounding
Place your cursor inside a command and press:
| Keymap | Action |
|---|---|
gsde |
Remove \emph{…}, leaving the inner text |
gsdb |
Remove \textbf{…} |
gsdi |
Remove \textit{…} |
Replacing a Surrounding
Want to change \emph{…} to \textbf{…}? No problem:
|
1 2 |
gsreb |
That’s gsr (replace surrounding), then e (find \emph{…}), then b (replace with \textbf{…}).
A Practical Example
Suppose you have this text:
|
1 |
This is very important information. |
You visual-select “very important” and press gsab. You get:
|
1 |
This is \textbf{very important} information. |
Now you decide that emphasis is better. With the cursor inside the \textbf{…}, press gsrbe:
|
1 |
This is \emph{very important} information. |
And if you want to remove the markup entirely: gsde:
|
1 |
This is very important information. |
Extending with Your Own Commands
The pattern is easy to extend. Just add more entries to cfg.custom_surroundings:
|
1 2 3 4 5 6 7 8 |
-- enquote (csquotes package) cfg.custom_surroundings.Q = cmd_surround("enquote") -- colored text cfg.custom_surroundings.r = { input = { "\\textcolor{[^}]*}{().-()}" }, output = { left = "\\textcolor{red}{", right = "}" }, } |
Pick single characters that are easy to remember and don’t conflict with built-in mini.surround ids (avoid (, ), [, ], {, }, ', ", `).
A Note on Limitations
The input patterns use Lua’s pattern matching, which cannot handle nested braces. For example, \emph{foo {bar} baz} may not be matched correctly by gsd or gsr. For the vast majority of LaTeX writing, this is not an issue — but it’s worth keeping in mind if you work heavily with nested commands.
Happy LaTeX writing! 🎓
Nice you really grab it under the hood !
Glad you like it! 🙂