-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Beta-testing 'mini.snippets' #1428
Comments
Yay!! Christmas present from the mini.nvim santa has dropped. As usual awesome work @echasnovski. I had few doubts
add("rafamadriz/friendly-snippets")
require("mini.snippets").setup() |
No, not yet. I hope this to get resolved next (#886).
No, 'mini.snippets' by default doesn't load any snippets. This has to be done explicitly by the user (similar to 'mini.hipatterns'). The setup from Quickstart should work. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
So, basically gen_loader.from_lang() does the job of loading the snippets right? |
Correct. The way it works is designed after 'rafamadriz/friendly-snippets'. Plus it is a reasonable way to organize per language snippets anyway. |
Yes, they do not. Because #886 is still not resolved. Edit: 'mini.snippets' is expected to be used with manual expansion ( During work on #886 I plan to provide a way to define 'mini.snippets' as default snippet expand/insert for snippets from LSP completions and have a way for 'mini.snippets' to provide its suggestions for 'mini.completion' (as a dummy LSP server). These are distinct features which come from the fact that 'mini.snippets' is both snippet manager (find/match snippet) and expand provider (start snippet session, jump between tabstops, etc.). |
Ohh ok my bad, did not think these two were dependent for working. Overall, the module looks great. I just tried writing basic boiler plate code and I write it bonkers fast now. Thank you once again @echasnovski . |
I find "c-n" does not work given the config, it delete all the chars before cursor pos, after I expand and then use "right" to move one char, "c-n" works. local root = vim.fn.fnamemodify("./.repro", ":p")
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"--single-branch",
"https://github.com/folke/lazy.nvim.git",
lazypath,
})
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
"folke/tokyonight.nvim",
-- do not remove the colorscheme!
{
"echasnovski/mini.snippets",
version = false,
config = function()
local gen_loader = require("mini.snippets").gen_loader
require("mini.snippets").setup({
snippets = {
-- Load custom file with global snippets first (adjust for Windows)
gen_loader.from_file("~/.config/nvim/snippets/global.json"),
-- Load snippets based on current language by reading files from
-- "snippets/" subdirectories from 'runtimepath' directories.
gen_loader.from_lang(),
},
-- Module mappings. Use `''` (empty string) to disable one.
mappings = {
-- Expand snippet at cursor position. Created globally in Insert mode.
expand = "<C-7>",
-- Interact with default `expand.insert` session.
-- Created for the duration of active session(s)
jump_next = "<C-n>",
jump_prev = "<C-p>",
stop = "<C-c>",
},
-- Functions describing snippet expansion. If `nil`, default values
-- are `MiniSnippets.default_<field>()`.
expand = {
-- Resolve raw config snippets at context
prepare = nil,
-- Match resolved snippets at cursor position
match = nil,
-- Possibly choose among matched snippets
select = nil,
-- Insert selected snippet
insert = nil,
},
})
end,
},
-- add any other pugins here
}
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
vim.cmd([[colorscheme tokyonight]]) global.json {
"Basic": {
"prefix": "ba",
"body": "T1=$1 T2=$2 T0=$0"
},
} iShot_2024-12-24_00.00.29.mp4 |
Yeah, unfortunately, I'll take a closer look to try and tackle this case, but I have a feeling that this will just be a documented known limitation. Thanks for the feedback! |
I tried to remap |
That's already what those mapping do (but only for the duration of snippet session). The issue here is that 'mini.snippets' sues You can see how that looks with the following:
At the moment, I think that making |
Oh, thanks for explanation -- I see many hard coded motions, there are even hard coded keymaps! |
Stay in insert mode is superior, no longer need to escape select mode when jump to previous tabstop! I'm looking to write a cmp-minisnippets. |
I notice one very strange thing when using cmp, some chars are swallowed when expanding: iShot_2024-12-24_08.10.18.mp4local root = vim.fn.fnamemodify("./.repro", ":p")
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"--single-branch",
"https://github.com/folke/lazy.nvim.git",
lazypath,
})
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
-- do not remove the colorscheme!
"folke/tokyonight.nvim",
"neovim/nvim-lspconfig",
{
"hrsh7th/nvim-cmp",
lazy = false,
dependencies = {
"hrsh7th/cmp-nvim-lsp",
},
config = function(_, opts)
local cmp = require("cmp")
require("cmp").setup({
mapping = cmp.mapping.preset.insert({
["<cr>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.confirm()
end
end, { "i", "c", "s" }),
}),
completion = {
completeopt = "menu,menuone,noinsert",
},
snippet = {
expand = function(args)
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body })
end,
},
sources = require("cmp").config.sources({
{ name = "nvim_lsp" },
}, {}),
})
end,
},
{
"echasnovski/mini.snippets",
version = false,
config = function()
local gen_loader = require("mini.snippets").gen_loader
require("mini.snippets").setup({
snippets = {
-- Load custom file with global snippets first (adjust for Windows)
gen_loader.from_file("~/.config/nvim/snippets/global.json"),
-- Load snippets based on current language by reading files from
-- "snippets/" subdirectories from 'runtimepath' directories.
gen_loader.from_lang(),
},
})
end,
},
-- add any other pugins here
}
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
require("lspconfig").lua_ls.setup({
settings = {
capabilities = require("cmp_nvim_lsp").default_capabilities(),
Lua = {
runtime = {
version = "LuaJIT",
},
workspace = {
library = {
"/usr/local/share/nvim/runtime",
},
},
completion = {
callSnippet = "Replace",
},
},
},
})
vim.cmd([[colorscheme tokyonight]]) |
Hmmm... I have doubts that this is from 'mini.snippets' as it during initial insert it doesn't remove any text. But the fact that |
I debug to find cmp removing the pair before snippet expanding, needs more investment. The whole process is following,
cmp stage before { "vim.schedule()" } -- This is before apply text edit
function#function#if completion_item.textEdit: {
_index = 1,
newText = "",
range = {
["end"] = {
character = 15,
line = 765
},
start = {
character = 13,
line = 765
}
}
}
cmp stage after { "vim.schedule(" } -- This is after appying notice the range is [13,15) which stands for
To my understanding, the fix would be suspending completion request to stop step 2 from firing when first expanding. |
Thanks for such a deep investigation! From the looks of it, is it safe to say that the issue is on 'nvim-cmp' side? The reason it gets noticeable with 'mini.snippets' and not If that's the case, could you, maybe, create an issue in 'hrsh7th/nvim-cmp' with reproduction steps? |
Sure, I’m thinking how cmp can get the information to not send request, currently it checks if current mode is insert mode to decide that. Or, cmp needs to update outdated lsp response in some way, either lua_ls/cmp is to be blame… |
So I indeed tried to work around it, but there is no real solution here. For at least two reasons (in addition to dealing with some quirks):
So I indeed opted for a separate note about not using |
Hello @echasnovski, When a snippet is inserted and the user presses To reproduce, see the following init.lua--[[
Use:
mkdir ~/.config/repro
cd ~/.config/repro
touch init.lua
add the contents of this file to init.lua
NVIM_APPNAME=repro nvim init.lua
Remove:
rm -rf ~/.local/share/repro ~/.local/state/repro ~/.local/cache/repro
rm -rf ~/.config/repro
--]]
--[[
Steps to reproduce:
1. Navigate to one line above the line containing function clone
2. Insert mode, type "for" and "<c-j>": The "for" snippet is expanded
3. Type "esc" and "3dd"
4. Now the final tabstop symbol is visible as the first character on the line
containing function clone
5. I cannot remove the symbol. Reloading the buffer also does not remove the symbol
6. Restart nvim: The symbol is no longer present
--]]
local function clone(path_to_site)
local mini_path = path_to_site .. "pack/deps/start/mini.nvim"
if not vim.uv.fs_stat(mini_path) then
vim.cmd('echo "Installing `mini.nvim`" | redraw')
local clone_cmd =
{ "git", "clone", "--filter=blob:none", "https://github.com/echasnovski/mini.nvim", mini_path }
vim.fn.system(clone_cmd)
vim.cmd("packadd mini.nvim | helptags ALL")
vim.cmd('echo "Installed `mini.nvim`" | redraw')
end
end
local path_to_site = vim.fn.stdpath("data") .. "/site/"
clone(path_to_site)
local MiniDeps = require("mini.deps")
MiniDeps.setup({ path = { package = path_to_site } })
local add, now = MiniDeps.add, MiniDeps.now
now(function()
add("rafamadriz/friendly-snippets")
vim.cmd("colorscheme randomhue")
require("mini.basics").setup() -- sensible defaults
local gen_loader = require("mini.snippets").gen_loader
require("mini.snippets").setup({
snippets = {
gen_loader.from_lang(),
},
})
local mini_pick = require("mini.pick")
mini_pick.setup()
vim.ui.select = mini_pick.ui_select
end) |
It does not "restart" because snippet session never stopped. Snippet session stops only in two ways: manually or automatically. Exiting into Normal mode will stop session only if current tabstop is final, otherwise it is treated as an "exit for quick text edit and then go back to editing in snippet session".
The answer is simple: don't do that. I explored the possibility of stopping session whenever any tabstop extmark is invalidated, but that looked impossible because I want users to be able to delete tabstop text completely (i.e. for it to become empty string in order to be visualized as inline virtual text).
The important thing to understand here is that "stop snippet session" and "exit into Normal mode" are independent actions (if current tabstop is not final). If you want to stop the session whenever you press
Yes, you can: enter Insert mode and press |
Thank you! As always, your answer provides a better understanding of the plugin. I really like mini.snippets! In my config the plugin has been integrated with nvim-cmp to expand For the moment I use |
Here are several options:
|
Hi, Would it be possible to add a keymap, such as This functionality would streamline workflows for users who rely on choice-based snippets. |
Thanks for the effort! Left couple of review comments which I think are relevant. |
In the help, regarding "supertab", you write:
The word "cleaner" triggered me to think about concrete drawbacks. In my config I can combine combinations of Apart from not being ubiqitous and thus a little harder to navigate, I found one concrete drawback: Pressing tab at the start of the line or inside an empty tabstop inserts a real "tab" instead of opening the somewhat expected The suggested implementation of supertab in the help: -- ...
local snippets = require('mini.snippets')
local match_strict = function(snippets)
-- Do not match with whitespace to cursor's left
return snippets.default_match(snippets, { pattern_fuzzy = '%S+' })
end
-- ... This introduces warning: "Redefined local snippets" and error I would suggest the following: -- ...
local snippets = require('mini.snippets')
local match_strict = function(candidate_snippets)
-- Do not match with whitespace to cursor's left
return snippets.default_match(candidate_snippets, { pattern_fuzzy = '%S+' })
end
-- ... Would you be willing to accept a PR adding the candidate_snippets fragment to the help? |
This is a result
That's my oversight of not sticking exactly to the example in the tests. I am currently working on the module, so will fix it myself (with you as co-author, of course :) ). Thanks for noticing! Edit: db8790f |
I intend to write a cmp resource: cmp_mini_snippets. While investigating I saw that |
That's great! Let me know if you need any clarifications and/or 'mini.snippets' adjustment. It would also be best to possibly move that discussion somewhere else. Do you plan to have separate repo for that from the start? If yes, then maybe there; if not, then in separate discussion here.
Ah, you mean |
Very much WIP: https://github.com/abeldekat/cmp-mini-snippets |
I've made a simple one, mainly to integrate my pr. The main branch has incorrect indent for multiline ghost text. It does not do any smart-loading or caching, I feel this is unnecessary, seems mini.snippets already handles it quit well;) It looks like this iShot_2025-01-01_00.19.38.mp4 |
@krovuxdev, I believe the issue should now be resolved: all choices are shown after jumping and deleting all tabstop text. There is also now more info about interaction with choices. |
Hi @echasnovski, Thank you very much! It worked, but I noticed a small problem. completeopt.mp4 |
As far as I can tell, the menu appears only after the initial insert and all other times it works as expected, correct? The reason for that is because Other than that, the general direction of not showing completion menu by adjusting 'completeopt' is indeed the suggested approach here. Couple of questions/suggestions, though:
|
@echasnovski this is a first snippet plugin in any editor I've tried that works exactly how I want: sometimes during snippet session I have to check something, like open vertical split etc, and continue with snippet after. Very huge thanks for that! I have only one suggestion: the way of adding custom variables is not very obvious and documented. This is what I'm doing now (with lazy.nvim): {
opts = function()
local plugin = require("mini.snippets")
return {
expand = {
insert = function(snippet, opts)
return plugin.default_insert(snippet, vim.tbl_deep_extend("force", opts or {}, {
lookup = {
["GIT_USERNAME"] = vim.fn.system("git config user.name")
}
}))
end
}
}
end
} This works. But I guess, such case would be very frequent so ideally, I would like to have nice shortcuts just to insert such variables. Do you think it makes sense to expose it to configuration? |
Adding an example with exactly this approach, which is indeed a suggested one, to variable lookup is planned (didn't realize I missed it). Regarding this exact |
@echasnovski you are right about environment variable in this case. I just wanted to make some simple illustration without huge piles of specific code |
I don't think it is a huge pile. The So it would be something like this: -- Use evnironment variables with value is same for all snippet sessions
vim.loop.os_setenv('USERNAME', 'user')
-- Compute custom lookup for variables with dynamic values
local insert_with_lookup = function(snippet)
local lookup = { TM_SELECTED_TEXT = vim.fn.getreg('*') }
return MiniSnippets.default_insert(snippet, { lookup = lookup })
end
require('mini.snippets').setup({
-- ... Set up snippets ...
expand = { insert = insert_with_lookup },
}) |
Seems I've found a bug. Consider a following snippet for Python: return {
["'try/except' clause"] = {
prefix = "te",
body = {
"try:",
"\t${0:$TM_SELECTED_TEXT}",
"except Exception:",
"\tpass",
},
},
} The problem is that in case of multiline I've seen something relevant here: hrsh7th/vim-vsnip#86 Hope it helps 🙏 |
Hmmm... My initial impression is that the current behavior is correct. Resolving variables is best understood as a straightforward replace. In this case it is replaced with Tweaking indent during variable expansion also opens a big Pandora box. For example, the case of several such variables together. Like in case of The other technical reason is that indents in 'mini.snippets' respect comment leaders which might introduce problems. I'll think about the concise way to possibly adjust for that, but at the moment I am a bit skeptical. |
Hello. Thank you very much for the plugin. I think this is exactly what I wanted. I tried to scoop the thread and see if this was mentioned something. Apologies if I duplicated the reports.
Here is a short demo vid: Kooha-2025-01-03-18-15-19.mp4And the snippet used in the video: "function": {
"prefix": "fun",
"body": [
"function (${2:arg})${3: use ($$4)} {",
"\t$0",
"}"
]
}, PS: I just realized that I could just add a second snippet just for the ( Thank you! |
🎉
I also encountered it, but I am afraid the current approach of "remove placeholder after typing at its start" is the best design here. Mostly because placeholders (in my opinion) should contain "valid" text in the context of the snippet (for programming languages - something that syntactically correct, for example). Or more strictly they are "leave it as is or replace" type of text. So using
It is highlighted. Depending on the tabstop and session progression, tabstops are highlighted with one of five highlight groups. Those by default are colored underdouble which is not really seen in your screencast. Check out demo to see how they can be seen. Or define are more visible highlight groups for your terminal/color scheme. Here is a crude quick example: vim.api.nvim_set_hl(0, 'MiniSnippetsCurrent', { bg = 'Yellow' })
vim.api.nvim_set_hl(0, 'MiniSnippetsCurrentReplace', { bg = 'Red' })
vim.api.nvim_set_hl(0, 'MiniSnippetsFinal', { bg = 'Green' })
vim.api.nvim_set_hl(0, 'MiniSnippetsUnvisited', { bg = 'Cyan' })
vim.api.nvim_set_hl(0, 'MiniSnippetsVisited', { bg = 'Blue' }) |
Yeah, I think you're right on this one. It hit me once I started thinking in possible workarounds for my issues.
Oh, got it. So it's more an issue of my color theme. Thank you! |
@9seconds, after quick testing I agree that having some kind of indent adjustments in case of expanding variables is more expected. Probably, nested placeholders also (like in
Also, this is now documented. |
I submitted a PR in blink.cmp. For caching, I saw the context you mentioned earlier. I copied |
Thank you very much, it worked with
I was trying to prevent text from being overwritten or reset when using I noticed that when I change the choices, there are three options like "one", "two", "three". Sometimes, when I press |
Please, don't do that. There is a documented way of getting default context.
Yes, because 'vim.b.minisnippets_config` can contain buffer-local snippets. And that should be respected. But that is not the biggest issue here as users can use their own context values for a more granular evaluation of loaders. So this type of caching can be actively bad. |
@echasnovski, I have a question: is there a way to do something like But neither of the two options works, only lookup = {
GIT_USERNAME = vim.fn.system("git config user.name"), -- works
-- ["GIT:USERNAME"] = vim.fn.system("git config user.name"), doesn't work
GIT = {
USERNAME = vim.fn.system("git config user.name"),
},
}, global.json "name": {
"prefix": "git_username",
"body": [
"user: ${GIT:USERNAME}"
],
"description": " git username"
} (I like the structure where |
No, it is not possible. Variable names must match the Nested lookup is also not allowed, as documented. |
Variables now should preserve relative indent on The I postponed the same change for linked tabstops (like |
I've pushed an updated for linked tabstops to preserve relative indent. It is easier to demonstrate what it means: minisnippets_relative-indent-in-linked-tabstops.mp4I have a bad feeling that this might not account for some weird edge cases or that it is not wanted as a feature. But it seems natural to handle tabstop text in the same way as variable's text (which is indeed useful). Thanks again, @9seconds, for the general idea of "preserve relative indent". |
Please leave your feedback about new mini.snippets module here. Feel free to either add new comment or positively upvote existing one.
Some things I am interested to find out (obviously, besides bugs):
Thanks!
The text was updated successfully, but these errors were encountered: