How to Manage Snippets with UltiSnips in Neovim: A Complete Configuration Guide
UltiSnips in jdhao/nvim-config expands snippets with Ctrl‑J, jumps forward with the same key, jumps backward with Ctrl‑K, and scans both the bundled UltiSnips collection and a custom my_snippets/ directory for user definitions.
The jdhao/nvim-config repository provides a battle-tested setup for managing code snippets in Neovim using UltiSnips as the snippet engine. This configuration uses lazy.nvim for deferred loading and defines sensible global variables that control expansion triggers and search paths.
UltiSnips Plugin Configuration and Lazy Loading
The plugin specification lives in lua/plugin_specs.lua at lines 81‑99. UltiSnips is loaded lazily via the event = "InsertEnter" trigger, ensuring the plugin only activates when you actually need to insert text.
Inside the plugin spec, an init function executes vim.cmd to set global variables before the plugin loads:
init = function()
vim.cmd([[
let g:UltiSnipsExpandTrigger='<c-j>'
let g:UltiSnipsJumpForwardTrigger='<c-j>'
let g:UltiSnipsJumpBackwardTrigger='<c-k>'
let g:UltiSnipsEnableSnipMate=0
let g:UltiSnipsSnippetDirectories=['UltiSnips', 'my_snippets']
]])
end
These settings establish three critical behaviors:
<c-j>(Ctrl‑J) serves dual duty: it expands the snippet under the cursor and subsequently jumps to the next tabstop.<c-k>(Ctrl‑K) moves the cursor to the previous tabstop when you need to backtrack.- SnipMate compatibility is disabled (
g:UltiSnipsEnableSnipMate=0) to prevent conflicts with legacy snippet formats.
Snippet Directory Structure and Search Paths
The g:UltiSnipsSnippetDirectories variable tells UltiSnips exactly where to look for snippet definitions. The configuration specifies two locations:
UltiSnips— The bundled collection that ships with the plugin.my_snippets— A user-maintained directory at the repository root for personal snippets.
The repository includes several example files under my_snippets/:
my_snippets/python.snippets— Python-specific triggers.my_snippets/markdown.snippets— Markdown formatting shortcuts.my_snippets/cpp.snippets— C++ boilerplate snippets.
You can add new files following the pattern {filetype}.snippets to target specific languages.
Creating Custom Snippets with Proper Filetype Detection
To make editing snippet files easier, the configuration includes a filetype detector at ftdetect/snippets.vim. This autocommand recognizes *.snippets files and sets the snippets filetype, enabling syntax highlighting and filetype-specific commands when you edit your definitions.
To create a custom snippet, add a definition to my_snippets/{language}.snippets using UltiSnips syntax:
snippet def "Python function definition"
def ${1:function_name}(${2:args}):
${0:# body}
endsnippet
This defines a trigger word def with a description. Placeholders follow the ${number:default} syntax, where ${0} marks the final cursor position.
Daily Workflow: Expanding and Navigating Snippets
Once you enter Insert mode, lazy.nvim sources UltiSnips automatically. The engine scans the configured directories and indexes all available snippets for the current buffer’s filetype.
To use a snippet:
- Type the trigger word (e.g.,
def). - Press Ctrl‑J to expand the template.
- The cursor lands on the first placeholder (
${1}). - Press Ctrl‑J again to jump to
${2}, or Ctrl‑K to move back to the previous field.
The mirrored trigger for expansion and forward navigation reduces cognitive load—you only need to remember Ctrl‑J to advance through the snippet.
Summary
- Configuration location:
lua/plugin_specs.lualines 81‑99 defines UltiSnips settings via aninitfunction that sets global variables before the plugin loads. - Key mappings: Ctrl‑J expands snippets and jumps forward; Ctrl‑K jumps backward; SnipMate is explicitly disabled to avoid conflicts.
- Search paths: UltiSnips looks in both the bundled
UltiSnipsdirectory and the user-managedmy_snippets/folder. - Filetype support:
ftdetect/snippets.vimensures*.snippetsfiles receive proper syntax highlighting. - Creation workflow: Add
{filetype}.snippetsfiles tomy_snippets/, reload, and expand triggers with Ctrl‑J.
Frequently Asked Questions
How do I change the UltiSnips expansion trigger from Ctrl‑J to Tab?
Edit the plugin specification in lua/plugin_specs.lua and modify the g:UltiSnipsExpandTrigger variable. Change '<c-j>' to '<tab>' inside the init function’s vim.cmd block, then restart Neovim.
Where should I place my personal snippet files in this configuration?
Store all custom snippets in the my_snippets/ directory at the repository root. Create files named after the target filetype (e.g., rust.snippets, javascript.snippets) so UltiSnips automatically associates them with the correct buffers.
Why does UltiSnips use the same key for expanding and jumping forward?
The configuration intentionally sets g:UltiSnipsExpandTrigger and g:UltiSnipsJumpForwardTrigger both to '<c-j>'. This design creates a consistent workflow: pressing Ctrl‑J always moves you to the next logical step, whether that is expanding a new snippet or advancing to the next placeholder in an active one.
How do I disable the bundled UltiSnips snippets and use only my own?
Remove 'UltiSnips' from the g:UltiSnipsSnippetDirectories list in lua/plugin_specs.lua. Set the variable to only ['my_snippets'] so UltiSnips exclusively searches your custom directory and ignores the default collection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →