今夜はminiとsnacksで決まり!

この​記事はVim駅伝2025年2月26日(水)の​記事です。
前回の​記事は​ mityu さんの​「:QuickRun で​ Vim9 script の​実行を​簡単に​する​プラグイン作った」と​いう​記事でした。
次回の​記事は​ 2月28日(金) に​投稿される​予定です。

はじめに

最近、​巷で​話題に​なっている​Neovimプラグインと​いえば?​そう、​「folke/snacks.nvim」​(以降、​snacks.nvim)です。
色々(30個、​2025/02/025現在)な​プラグインが​一つの​レポジトリに​集っている​最早スナックではなく、​ご馳走プラグインです。​folkewareらしい​統一感の​ある、​リッチな​UIが​提供されています。​folkeさんの​馬力には​驚かされます。
そう​いえば、​私の​好きな​プラグインの​中にも​似たような​コンセプトの​プラグインが​あります。​それは、​「echasnovski/mini.nvim」​(以降、​mini.nvim)です。​こちらももは​やminiではなく、​maximumプラグインなのですが、​snacks.nvimと​違う​点も​あります。​それは、​シンプルな​UIが​提供されている​ことと、​mini.hogeと​いう​名前で​レポジトリが​別れていて、​個別で​インストールできると​いう​点です。

snacks.nvimが​出た​時に​思いました。​これを​組み合わせたら​初心者パックを​組めるんじゃないか?と。

と​いう​ことで​今回は、​snacks.nvimと​mini.nvimを​組み合わせた​初心者パックを​紹介します。

上記プラグインに​加えて、​以下​4つの​プラグインを​導入します。

記事の​対象

初心者パックを​謳うのであれば、​詳細に​説明するのが​当然かと​思います。​時間が​足りず説明が​不足しています。​その​分ソースコードに​たくさんの​コメントを​書いているので、​そこから​読み取ってください。

公開後の​追記

流石に​文字だけだと​分かりにくいので​gifを​急いで​用意します

追記​おわり

全体​ソース

この​構成で​フロントエンド開発を​してみましたが、​問題なく​使えそうでした。
https://gist.github.com/staticWagomU/41dc7dc75343874f1e78445144041331

コピペして​使ってみてください。

ソースコード
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
vim.loader.enable()

-- mini.nvimの​セットアップ
local path_package = vim.fn.stdpath('data') .. '/site'
local mini_path = path_package .. '/pack/deps/start/mini.nvim'
if not vim.loop.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')
end

-- mini.depsの​セットアップ
require('mini.deps').setup({ path = { package = path_package } })

local add = MiniDeps.add
local now, later = MiniDeps.now, MiniDeps.later

vim.diagnostic.config({
	-- signcolumnに​表示される​シンボルを​設定
	signs = {
		text = {
			[vim.diagnostic.severity.ERROR] = "🚒",
			[vim.diagnostic.severity.WARN] = "🚧",
			[vim.diagnostic.severity.INFO] = "👀",
			[vim.diagnostic.severity.HINT] = "🦒",
		},
	},
	severity_sort = true,
	-- 末尾に​表示される​virtual_textを​非表示
	virtual_text = false,
	-- 最近​追加された​builtinの​virtual_linesを​表示
	virtual_lines = {
		only_current_line = true,
		format = function(diagnostic)
			return string.format('%s (%s: %s)', diagnostic.message, diagnostic.source, diagnostic.code)
		end,

	}
})

-- clipboardの​設定
vim.opt.clipboard = 'unnamedplus,unnamed'
-- インデントの​設定
vim.opt.shiftwidth = 2
vim.opt.softtabstop = 2
vim.opt.tabstop = 2
-- statuslineの​設定
vim.opt.laststatus = 3
vim.opt.cmdheight = 0

-- 行番号の​設定
vim.opt.number = false

now(function()
	-- シンタックスハイライトに​使用
	add {
		source = 'nvim-treesitter/nvim-treesitter',
		checkout = 'master',
		monitor = 'main',
		hooks = {
			post_checkout = function()
				vim.cmd('TSUpdate')
			end,
		},
	}

	---@diagnostic disable-next-line: missing-fields
	require('nvim-treesitter.configs').setup {
		auto_install = true,
		ensure_installed = { 'lua', 'vimdoc' },
		highlight = { enable = true },
		sync_install = true,
	}
end)

now(function()
	require('mini.notify').setup()
	-- 標準の​notifyを​上書き
	vim.notify = require('mini.notify').make_notify()
end)

now(function()
	-- 画面下部に​表示される​ステータスラインの​設定
	require('mini.statusline').setup()
end)

now(function()
	-- 色々な​追加設定
	require('mini.extra').setup()
end)

now(function()
	-- mini.nvimが​良い​感じに​初期設定してくれる
	require('mini.basics').setup()
end)

later(function()
	-- [  ]始まりの​キーマッピングを​設定
	-- bufferの​トグルなど
	require('mini.bracketed').setup()
end)

later(function()
	-- signcolumnや​行番号列に​gitの​差分に​基づいて​色が​付く
	require('mini.diff').setup()
end)

later(function()
	-- :Git statusなどの​コマンドを​実行できる
	require('mini.git').setup()
end)

now(function()
	-- 󰈤 などの​アイコンを​いい​感じに​設定してくれる
	-- nvim_web_deviconsを​入れなくても​mini.iconsで​賄うことができる
	require('mini.icons').setup()
	MiniIcons.mock_nvim_web_devicons()
	MiniDeps.later(MiniIcons.tweak_lsp_kind)
end)

now(function()
	-- (等を​入力すると​ペアに​なる​)等が​自動的に​入力される
	-- 括弧忘れなどが​なくなって​便利
	require('mini.pairs').setup()
end)

now(function()
	-- 便利関数が​入っている
	require('mini.misc').setup()
	-- ファイルを​開き直した​ときに​以前​いた​カーソル位置に​戻ってくれる
	MiniMisc.setup_restore_cursor({
		center = false,
	})

	-- split等で​画面分割している​ときに​:Zoomコマンドを​実行すると
	-- フォーカスしている​バッファが​全面に​表示される
	-- 再度、​:Zoomコマンドを​実行すると​元に​戻る
	-- いい​機能
	vim.api.nvim_create_user_command('Zoom', function()
		MiniMisc.zoom(0, {})
	end, { desc = '' })
end)

now(function()
	-- ダブルクオーテーションを​シングルクオートに​書き換えたり
	-- 消したり、​追加したりできる
	-- これに​慣れると​vimから​離れられなくなります
	require('mini.surround').setup()
end)

later(function()
	-- カーソル位置に​ある​単語が​ハイライトされる
	-- 時々役に​立つ
	require('mini.cursorword').setup { delay = 200 }
end)

later(function()
	-- インデントが​分かりやすくなる
	require('mini.indentscope').setup { delay = 200 }
end)

later(function()
	-- tabに​アイコンが​付いたりと​分かりやすくなる
	require('mini.tabline').setup()
end)

later(function()
	require('mini.files').setup()
	vim.keymap.set('n', '<Leader>E', function()
		MiniFiles.open(vim.api.nvim_buf_get_name(0))
	end)
	vim.api.nvim_create_autocmd("User", {
		pattern = "MiniFilesActionRename",
		callback = function(event)
			Snacks.rename.on_rename_file(event.data.from, event.data.to)
		end,
	})
end)

now(function()
	-- テキストオブジェクトを​作成できる
	local ai = require('mini.ai')
	ai.setup({
		custom_textobjects = {
			-- viBと​する​ことで​バッファ全体を​選択できたり
			B = MiniExtra.gen_ai_spec.buffer(),
			I = MiniExtra.gen_ai_spec.indent(),
			-- yiLで​行コピーが​できる
			L = MiniExtra.gen_ai_spec.line(),
			-- treesitterと​連携させる​ことで​さらに​色々な​ことができる
			F = ai.gen_spec.treesitter({ a = '@function.outer', i = '@function.inner' }),
			i = ai.gen_spec.treesitter({ a = '@conditional.outer', i = '@conditional.inner' }),
		},
	})
end)

later(function()
	-- mini.nvim版 which-key
	local miniclue = require('mini.clue')
	miniclue.setup({
		clues = {
			miniclue.gen_clues.builtin_completion(),
			miniclue.gen_clues.g(),
			miniclue.gen_clues.marks(),
			miniclue.gen_clues.registers(),
			miniclue.gen_clues.windows({ submode_resize = true }),
			miniclue.gen_clues.z(),
		},
		triggers = {
			{ mode = 'n', keys = '<Leader>' }, -- Leader triggers
			{ mode = 'x', keys = '<Leader>' },
			{ mode = 'n', keys = [[\]] },   -- mini.basics
			{ mode = 'n', keys = '[' },     -- mini.bracketed
			{ mode = 'n', keys = ']' },
			{ mode = 'x', keys = '[' },
			{ mode = 'x', keys = ']' },
			{ mode = 'n', keys = 'g' }, -- `g` key
			{ mode = 'x', keys = 'g' },
			{ mode = 'i', keys = '<C-r>' },
			{ mode = 'c', keys = '<C-r>' },
			{ mode = 'n', keys = '<C-w>' }, -- Window commands
			{ mode = 'n', keys = 'z' },  -- `z` key
			{ mode = 'x', keys = 'z' },
			{ mode = 'n', keys = '<leader>l' },
		},
		window = { config = { border = 'single' } },
	})
end)

now(function()
	add('https://github.com/folke/snacks.nvim')
	require('snacks').setup({
		indent = {
			enabled = true,
			indent = { enabled = true },
			scope = { enabled = false },
			animate = { enabled = false },
		},
		statuscolumn = { enabled = true },
		picker = {
			layout = { preset = 'ivy' },
		},
		bigfile = { enabled = true },
	})

	-- ファイルピッカーを​開く
	vim.keymap.set('n', '<Leader>e', Snacks.picker.files, { desc = 'open file' })
	-- バッファーを​全部​削除する
	vim.keymap.set('n', '<Leader>bd', Snacks.bufdelete.delete, { desc = 'delete buffer' })
	-- ターミナルを​トグルする
	vim.keymap.set('n', '<Leader><Leader>', Snacks.terminal.toggle, { desc = 'toggle terminal' })
	vim.keymap.set('t', '<Leader><Leader>', Snacks.terminal.toggle, { desc = 'toggle terminal' })
end)



later(function()
	-- 入力補完ウィンドウを​出してくれる
	require('mini.completion').setup {
		window = {
			info = { border = 'single' },
			signature = { border = 'single' },
		},
		lsp_completion = {
			source_func = 'completefunc',
			auto_setup = true,
			process_items = function(items, base)
				-- Don't show 'Text' and 'Snippet' suggestions
				items = vim.tbl_filter(function(x)
					return x.kind ~= 1 and x.kind ~= 15
				end, items)
				return MiniCompletion.default_process_items(items, base)
			end,
		},
	}
	if vim.fn.has('nvim-0.11') == 1 then
		---@diagnostic disable-next-line: undefined-field
		vim.opt.completeopt:append('fuzzy') -- Use fuzzy matching for built-in completion
	end
end)

later(function()
	add {
		source = 'neovim/nvim-lspconfig',
		depends = {
			'williamboman/mason.nvim',
			'williamboman/mason-lspconfig.nvim',
		},
	}
	require('mason').setup()
	require('mason-lspconfig').setup({
		ensure_installed = {
			'astro',
			'lua_ls',
			'ts_ls',
		},
		automatic_installation = true,
	})

	local lspconfig = require('lspconfig')
	local capabilities = vim.lsp.protocol.make_client_capabilities()

	require('mason-lspconfig').setup_handlers {
		function(server_name)
			lspconfig[server_name].setup {
				capabilities = capabilities,
			}
		end,
		['lua_ls'] = function()
			lspconfig['lua_ls'].setup {
				capabilities = capabilities,
				settings = {
					Lua = {
						runtime = {
							version = 'LuaJIT',
							pathStrict = true,
							path = { '?.lua', '?/init.lua' },
						},
						completion = { callSnippet = 'Both' },
						diagnostics = { globals = { 'vim' } },
						telemetry = { enable = false },
						workspace = {
							library = vim.list_extend(vim.api.nvim_get_runtime_file('lua', true), {
								'${3rd}/luv/library',
								'${3rd}/busted/library',
								'${3rd}/luassert/library',
								vim.api.nvim_get_runtime_file('', true),
							}),
							checkThirdParty = 'Disable',
						},
					},
				},
			}
		end,
	}

	vim.keymap.set('n', 'K', '<Cmd>lua vim.lsp.buf.hover()<Cr>', { desc = 'Hover' })
	vim.keymap.set('n', '<Leader>lf', '<Cmd>lua vim.lsp.buf.format()<Cr>', { desc = 'format code' })
	vim.keymap.set('n', '<Leader>lr', Snacks.picker.lsp_references, { desc = 'references' })
	vim.keymap.set('n', '<Leader>ld', Snacks.picker.lsp_definitions, { desc = 'go to definition' })
	vim.keymap.set('n', '<Leader>lD', Snacks.picker.lsp_declarations, { desc = 'go to declaration' })
	vim.keymap.set('n', '<Leader>li', Snacks.picker.lsp_implementations, { desc = 'go to implementation' })
	vim.keymap.set('n', '<Leader>lt', Snacks.picker.lsp_type_definitions, { desc = 'go to type definition' })
	vim.keymap.set('n', '<Leader>ln', '<Cmd>lua vim.lsp.buf.rename()<Cr>', { desc = 'rename symbol' })
	vim.keymap.set('n', '<Leader>lc', '<Cmd>lua vim.lsp.buf.code_action()<Cr>', { desc = 'code action' })
	vim.keymap.set('n', '<Leader>lI', '<Cmd>lua vim.lsp.buf.incoming_calls()<Cr>', { desc = 'incoming calls' })
	vim.keymap.set('n', '<Leader>lo', '<Cmd>lua vim.lsp.buf.outgoing_calls()<Cr>', { desc = 'outgoing calls' })
	vim.keymap.set('n', '<Leader>le', '<Cmd>lua vim.diagnostic.open_float()<Cr>', { desc = 'open diagnostics' })
	vim.keymap.set('n', ']l', '<Cmd>lua vim.diagnostic.goto_next()<Cr>', { desc = 'go to next diagnostics' })
	vim.keymap.set('n', '[l', '<Cmd>lua vim.diagnostic.goto_prev()<Cr>', { desc = 'go to previous diagnostics' })
end)

vim.cmd.colorscheme('minicyan')

解説

輪ごむの​お気に​入りポイントは、​一部の​lsp関連の​操作を​snacks.nvimの​pickerに​委ねている​ところです。

1234567891011121314
	vim.keymap.set('n', 'K', '<Cmd>lua vim.lsp.buf.hover()<Cr>', { desc = 'Hover' })
	vim.keymap.set('n', '<Leader>lf', '<Cmd>lua vim.lsp.buf.format()<Cr>', { desc = 'format code' })
	vim.keymap.set('n', '<Leader>lr', Snacks.picker.lsp_references, { desc = 'references' })
	vim.keymap.set('n', '<Leader>ld', Snacks.picker.lsp_definitions, { desc = 'go to definition' })
	vim.keymap.set('n', '<Leader>lD', Snacks.picker.lsp_declarations, { desc = 'go to declaration' })
	vim.keymap.set('n', '<Leader>li', Snacks.picker.lsp_implementations, { desc = 'go to implementation' })
	vim.keymap.set('n', '<Leader>lt', Snacks.picker.lsp_type_definitions, { desc = 'go to type definition' })
	vim.keymap.set('n', '<Leader>ln', '<Cmd>lua vim.lsp.buf.rename()<Cr>', { desc = 'rename symbol' })
	vim.keymap.set('n', '<Leader>lc', '<Cmd>lua vim.lsp.buf.code_action()<Cr>', { desc = 'code action' })
	vim.keymap.set('n', '<Leader>lI', '<Cmd>lua vim.lsp.buf.incoming_calls()<Cr>', { desc = 'incoming calls' })
	vim.keymap.set('n', '<Leader>lo', '<Cmd>lua vim.lsp.buf.outgoing_calls()<Cr>', { desc = 'outgoing calls' })
	vim.keymap.set('n', '<Leader>le', '<Cmd>lua vim.diagnostic.open_float()<Cr>', { desc = 'open diagnostics' })
	vim.keymap.set('n', ']l', '<Cmd>lua vim.diagnostic.goto_next()<Cr>', { desc = 'go to next diagnostics' })
	vim.keymap.set('n', '[l', '<Cmd>lua vim.diagnostic.goto_prev()<Cr>', { desc = 'go to previous diagnostics' })

おわりに

うん、​いい​感じです。