💤

クソでかinit.lua

2023/01/06に公開

init.luaがそれなりに充実してきたので整理も兼ねて紹介記事を書こう!と思いついたので実行していきたいと思います。

この記事の対象としては1年ほど前の自分、つまりコーディング未経験者を想定しています。

全体像

最新版は上記のリンクから。この記事の内容は基本的に更新しないと思います。

init.lua
if vim.fn.expand '%:p' == '' then
	vim.cmd [[e $MYVIMRC]]
end
vim.cmd 'colo catppuccin'

local p = vim.opt -- d: variables
p.list = true
p.listchars = {
	tab = '│ ',
}
p.pumblend = 22
p.relativenumber = true
p.number = true
p.autowriteall = true
p.termguicolors = true
p.clipboard:append { 'unnamedplus' }
p.autochdir = true
p.laststatus = 0
p.cmdheight = 0 --until `folke/noice` recovered

local aucmd = vim.api.nvim_create_autocmd -- d: autocmd
-- d: Just using `set fo-=cro` won't work since many filetypes set/expand `formatoption`
aucmd('filetype', {
	callback = function()
		p.fo = { j = true }
		p.softtabstop = 3
		p.tabstop = 3
		p.shiftwidth = 3
	end,
})

local usrcmd = vim.api.nvim_create_user_command
usrcmd('Make', function(opts)
	local cmd = '<cr> '
	local ft = vim.bo.filetype
	if ft == 'rust' then
		cmd = '!cargo '
	elseif ft == 'lua' or ft == 'cpp' or ft == 'c' then
		cmd = '!make '
	end
	vim.cmd(cmd .. opts.args)
end, {
	nargs = '*',
})

local map = vim.keymap.set -- d: keymap
map('n', '<esc>', '<cmd>noh<cr>') -- <esc> to noh
map('i', '<c-[>', '<c-[><cmd>update | lua vim.lsp.buf.format{async=true}<cr>')
map({ 'n', 'v' }, '$', '^') -- swap $ & ^
map({ 'n', 'v' }, '^', '$')
map({ 'n', 'v' }, ',', '@:') --repeat previous command
map('i', '<c-n>', '<down>') --emacs keybind
map('i', '<c-p>', '<up>')
map('i', '<c-b>', '<left>')
map('i', '<c-f>', '<right>')
map('i', '<c-a>', '<home>')
map('i', '<c-e>', '<end>')
map('i', '<c-d>', '<del>')
map('i', '<c-k>', '<right><c-c>v$hs')
map('i', '<c-u>', '<c-c>v^s')
map('i', '<a-d>', '<right><c-c>ves')
map('i', '<a-f>', '<c-right>')
map('i', '<a-b>', '<c-left>')
map({ 'n', 'v' }, '<tab>', require('todo-comments').jump_next)
map({ 'n', 'v' }, '<s-tab>', require('todo-comments').jump_prev)
map('n', '<cr>', ':Make ') -- execute shell command
map('n', '<s-cr>', ':!')
map({ 'i', 'n', 'v' }, '<a-left>', '<c-w><') -- change window size
map({ 'i', 'n', 'v' }, '<a-down>', '<c-w>+')
map({ 'i', 'n', 'v' }, '<a-up>', '<c-w>-')
map({ 'i', 'n', 'v' }, '<a-right>', '<c-w>>')
map('n', 't', require('telescope.builtin').builtin) -- Telescope
map('n', '<space>o', require('telescope.builtin').lsp_document_symbols)
map('n', '<space>d', require('telescope.builtin').diagnostics)
map('n', '<space>b', require('telescope.builtin').buffers)
map('n', '<space>e', require('telescope').extensions.file_browser.file_browser)
map('n', '<space>f', require('telescope').extensions.frecency.frecency)
map('n', '<space>c', '<cmd>TodoTelescope<cr>')
map('n', '<space>n', require('telescope').extensions.notify.notify)
map({ 'n', 'v' }, '<space>a', '<cmd>Lspsaga code_action<cr>')
map('n', '<space>j', '<cmd>Lspsaga lsp_finder<cr>') --`j` stands for jump
map('n', '<space>r', '<cmd>Lspsaga rename<cr>')
map('n', '<space>h', '<cmd>Lspsaga hover_doc<cr>')
map('n', '<c-j>', '<cmd>Lspsaga diagnostic_jump_next<cr>')
map('n', '<c-k>', '<cmd>Lspsaga diagnostic_jump_prev<cr>')

require('packer').startup(function(use) -- d: package
	use 'wbthomason/packer.nvim' -- NOTE: required
	use 'nvim-lua/plenary.nvim'
	use 'kkharji/sqlite.lua'
	use 'MunifTanjim/nui.nvim'
	use 'nvim-tree/nvim-web-devicons' -- NOTE: appearance
	use {
		'amdt/sunset',
		config = function()
			vim.g.sunset_latitude = 35.02
			vim.g.sunset_longitude = 135.78
		end,
	}
	use {
		'catppuccin/nvim',
		as = 'catppuccin',
		config = function()
			require('catppuccin').setup {
				background = {
					dark = 'frappe',
				},
				dim_inactive = {
					enabled = true,
				},
			}
		end,
	}
	use {
		'nvim-treesitter/nvim-treesitter',
		run = ':TSUpdate',
		config = function()
			require('nvim-treesitter.configs').setup {
				ensure_installed = { 'bash', 'markdown_inline' },
				auto_install = true,
				highlight = {
					enable = true,
					additional_vim_regex_highlighting = false,
				},
			}
		end,
	}
	use {
		'rcarriga/nvim-notify',
		config = function()
			vim.notify = require 'notify'
			vim.notify_once = require 'notify'
		end,
	} -- NOTE: UI
	use {
		'folke/todo-comments.nvim',
		config = function()
			require('todo-comments').setup {
				keywords = {
					FIX = { alt = { 'e' } }, -- e: `e` stands for error
					TODO = {
						color = 'hint',
						alt = { 'q' }, -- q: `q` stands for question
					},
					HACK = {
						color = 'doc',
						alt = { 'a' }, -- a: `a` stands for attention
					},
					WARN = { alt = { 'x' } }, -- x: `x` is abbreviation of `XXX`
					PERF = {
						color = 'cmt',
						alt = { 'p' }, -- p: `p` stands for performance
					},
					NOTE = {
						color = 'info',
						alt = { 'd' }, -- d: `d` stands for description
					},
					TEST = { alt = { 't', 'PASS', 'FAIL' } }, -- t: `t` stands for test
				},
				colors = {
					cmt = { 'Comment' },
					doc = { 'SpecialComment' },
					todo = { 'Todo' },
				},
			}
		end,
	}
	--[[	
	use {
		'folke/noice.nvim',
		config = function()
			require('noice').setup {
				presets = {
					bottom_search = true,
				},
			}
		end,
	}
]]
	use {
		'windwp/nvim-autopairs',
		config = function() -- NOTE: Input Helper
			require('nvim-autopairs').setup {
				map_c_h = true,
			}
		end,
	}
	use {
		'nvim-telescope/telescope.nvim',
		tag = '0.1.0',
		config = function() -- NOTE: Fuzzy Search
			require('telescope').setup {
				extensions = {
					file_browser = {
						hidden = true,
						hide_parent_dir = true,
					},
				},
			}
			require('telescope').load_extension 'frecency'
			require('telescope').load_extension 'file_browser'
		end,
	}
	use 'nvim-telescope/telescope-frecency.nvim'
	use 'nvim-telescope/telescope-file-browser.nvim'
	use {
		'williamboman/mason.nvim',
		config = function() -- NOTE: lsp
			require('mason').setup()
		end,
	}
	use {
		'williamboman/mason-lspconfig.nvim',
		config = function()
			require('mason-lspconfig').setup {
				ensure_installed = {
					'bashls',
					'sumneko_lua',
					'rust_analyzer@nightly',
				},
			}
		end,
	}
	use {
		'neovim/nvim-lspconfig',
		config = function()
			local capabilities = require('cmp_nvim_lsp').default_capabilities()

			-- d: rust_analyzer
			require('lspconfig').rust_analyzer.setup {
				capabilities = capabilities,
				settings = {
					['rust-analyzer'] = {
						hover = {
							actions = {
								reference = {
									enable = true,
								},
							},
						},
						inlayHints = {
							closingBraceHints = {
								minLines = 0,
							},
							lifetimeElisionHints = {
								enable = 'always',
								useParameterNames = true,
							},
							maxLength = 0,
							typeHints = {
								hideNamedConstructor = false,
							},
						},
						lens = {
							implementations = {
								enable = false,
							},
						},
						rustfmt = {
							rangeFormatting = {
								enable = true,
							},
						},
						semanticHighlighting = {
							operator = {
								specialization = {
									enable = true,
								},
							},
						},
						typing = {
							autoClosingAngleBrackets = {
								enable = true,
							},
						},
						workspace = {
							symbol = {
								search = {
									kind = 'all_symbols',
								},
							},
						},
					},
				},
			}

			-- d: lua
			require('lspconfig').sumneko_lua.setup {
				capabilities = capabilities,
				settings = {
					Lua = {
						runtime = {
							version = 'LuaJIT',
						},
						diagnostics = {
							globals = { 'vim' },
						},
						workspace = {
							library = vim.api.nvim_get_runtime_file('', true),
							checkThirdParty = false,
						},
						telemetry = {
							enable = false,
						},
					},
				},
			}

			-- d: clangd
			require('lspconfig').clangd.setup {
				capabilities = capabilities,
			}
		end,
	}
	use {
		'glepnir/lspsaga.nvim',
		branch = 'main',
		config = function()
			require('lspsaga').init_lsp_saga {
				saga_winblend = 20,
				max_preview_lines = 10,
				code_action_lightbulb = {
					enable = false,
				},
				finder_action_keys = {
					open = '<cr>',
					vsplit = '<c-v>',
					split = '<c-x>',
				},
				definition_action_keys = {
					edit = '<cr>',
					vsplit = '<c-v>',
					split = '<c-x>',
					tabe = 't',
				},
			}
		end,
	}
	use {
		'jose-elias-alvarez/null-ls.nvim',
		config = function()
			local nls = require 'null-ls'
			nls.setup {
				sources = {
					nls.builtins.formatting.dprint.with { filetypes = { 'markdown', 'json', 'toml' } },
					nls.builtins.formatting.stylua,
				},
			}
		end,
	}
	use {
		'hrsh7th/nvim-cmp',
		config = function() -- NOTE: cmp
			local luasnip = require 'luasnip'
			local cmp = require 'cmp'
			cmp.setup {
				snippet = {
					expand = function(args)
						luasnip.lsp_expand(args.body)
					end,
				},
				window = {
					completion = cmp.config.window.bordered(),
					documentation = cmp.config.window.bordered(),
				},
				mapping = cmp.mapping.preset.insert {
					['<a-k>'] = cmp.mapping.scroll_docs(-10),
					['<a-j>'] = cmp.mapping.scroll_docs(10),
					['<c-c>'] = cmp.mapping.abort(),
					['<tab>'] = cmp.mapping(function(fallback)
						if cmp.visible() then
							cmp.confirm {
								behavior = cmp.ConfirmBehavior.Insert,
								select = true,
							}
						else
							fallback()
						end
					end, { 'i', 's', 'c' }),
					['<s-tab>'] = cmp.mapping(function(fallback)
						if luasnip.expand_or_jumpable() then
							luasnip.expand_or_jump()
						else
							fallback()
						end
					end, { 'i', 's', 'c' }),
					['<c-n>'] = cmp.mapping(function(fallback)
						if cmp.visible() then
							cmp.select_next_item()
						else
							fallback()
						end
					end, { 'i', 's', 'c' }),
					['<c-p>'] = cmp.mapping(function(fallback)
						if cmp.visible() then
							cmp.select_prev_item()
						else
							fallback()
						end
					end, { 'i', 's', 'c' }),
				},
				sources = {
					{ name = 'luasnip' },
					{ name = 'nvim_lsp' },
					{ name = 'nvim_lua' },
					{ name = 'nvim_lsp_signature_help' },
					{ name = 'buffer' },
				},
			}

			cmp.setup.cmdline('/', {
				sources = {
					{ name = 'buffer' },
				},
			})

			cmp.setup.cmdline(':', {
				sources = {
					{ name = 'path' },
					{ name = 'cmdline' },
					{ name = 'buffer' },
				},
			})
		end,
	}
	use 'hrsh7th/cmp-nvim-lsp'
	use 'hrsh7th/cmp-nvim-lua'
	use 'hrsh7th/cmp-nvim-lsp-signature-help'
	use 'hrsh7th/cmp-buffer'
	use 'hrsh7th/cmp-path'
	use 'hrsh7th/cmp-cmdline'
	use 'saadparwaiz1/cmp_luasnip'
	use 'L3MON4D3/LuaSnip'
end)

init.luaは分割しておくと管理しやすいよ!って各所でいわれています。おっしゃる通りだと思います。

が、自分はあちこちファイルを行き来するのが面倒なのでやっていません←

デフォルトファイル

if vim.fn.expand '%:p' == '' then
   vim.cmd [[e $MYVIMRC]]
end

開くファイルを指定せずにnvimした場合inil.luaを開く様にしています。
こうすることでどこからでも楽にinit.luaできます。
尚、この方法で開いた場合一度:eするまでハイライトが死んでます😢 有識者助けて

オプション

local p = vim.opt -- d: variables
p.list = true
p.listchars = {
   tab = '│ ',
}
p.pumblend = 22
p.relativenumber = true
p.number = true
p.autowriteall = true
p.termguicolors = true
p.clipboard:append { 'unnamedplus' }
p.autochdir = true
p.laststatus = 0
p.cmdheight = 0 --until `folke/noice` recovered

p.list=true

としておくとlistcharと呼ばれる文字たち(tab, eol など)を目立たせることができます。

p.listchars={..}

でどの様に目立たせるかを指定できます。
自分はインデントをspaceではなくtabにしているのでこの設定をしておくことでindentline系のプラグインを入れなくとも同じことを再現できます。

  • なんなら大体のindentline系のプラグインは代替インデント幅が4文字以外だと表示がおかしくなります。(自分はインデント3文字にしているので致命的)
  • この設定ではそのデメリットもないのでこの形に落ち着いています。

p.pumblend=22

ではpop up menu、要はfloating windowの背景透過率を指定します。0で不透明、100で透明

p.relativenumber=true

と指定することで左端の行番号をカーソルのある行からのオフセットとして表示できます。

  • コードが長くなって行数が3桁とかになってくると数行移動するのにいちいち121Gなどとするのが面倒に感じていました。
  • オフセットが分かっていれば6k10jと脳死でできるので思いのほか重宝しています。(地味にGよりk、jの方が押しやすいってのもあるかも)

p.number=true

も同時に指定してやるとカーソルのある行だけ、絶対行数が表示されます。
これをfalseにすると、カーソルのある行は0と表示されます。

p.autowriteall=true

としておくと別のバッファにいく時やneovimから出た時に自動で変更を保存してくれます。
自分の場合、基本neovim開きっぱなし、fuzzy_finderで頻繁にあちこち行ったり来たりしてるのでこれがないとストレスフル⚰️

  • p.awa=trueとしても同じ。\(awa)/

p.termguicolors=true

みなさんご存知。これmacのデフォルトのterminalでは効かないんですよねぇ 流石に時代錯誤な気がします

p.clipboard:append { 'unnamedplus' }

としておくとシステムのclipboardと連携してくれます。これもみなさんご存知ですね。luaでどう書けばいいのか地味に困る。ドキュメント読もう

p.autochdir=true

によりneovim上でのカレントディレクトリをファイルの変更に合わせて追尾してくれます。
自分は:!<s-cr>に割り当てて多用しているのですがその中で簡単なgit操作などを行うのでこの設定をしておかないと⚰️

p.laststatus=0

でステータスラインを非表示にできます。尚、完全に非表示にできるわけではなくwindowを縦に分割すると顔を出します。

p.cmdheight=0

とすることで必要のない時にcmdlineを隠すことができます。

  • 蛇足

neovimではvimの基本的なUIもユーザーがカスタマイズできる様にしよう!という動きの元、
vimのcコードをひっくり返してapiの開発が進んでいます。
folke/noiceも同じ目的を持ったプラグインです。
ですがこのapiは目下開発中なので当然予期しないバグなどが有り得ます(@nightly)
neovim --HEADを使用している場合、neovim側のバグによりnoice.nvimを使ってcmdlineを開こうとするとクラッシュします。
なのでこのバグが修正されるまではnoice.nvimを使わずこのオプションを設定しています。

AutoCmd

local aucmd = vim.api.nvim_create_autocmd -- d: autocmd
-- d: Just using `set fo-=cro` won't work since many filetypes set/expand `formatoption`
aucmd('filetype', {
	callback = function()
		p.fo = { j = true }
		p.softtabstop = 3
		p.tabstop = 3
		p.shiftwidth = 3
	end,
})

vimのautocmdって、なんか適切にaugroupでまとめないとうまく働かない、みたいなのよく聞くけど
ぶっちゃけゼェんぜん理解出来てないです。でも今のままで問題ないので放置してます。

このautocmdは任意のfiletypeを対象にしています。

p.softtabstop = 3
p.tabstop = 3
p.shiftwidth = 3

これらをわざわざautocmdで設定しているのは、普通に書くと外部ツール(具体的にはrust-analyzer)の影響で正しく反映されなかったからです。
同じ記述を2箇所に書くのも野暮ったいのでまとめて任意のftに対して設定することでまとめています。

UserCmd

local usrcmd = vim.api.nvim_create_user_command
usrcmd('Make', function(opts)
	local cmd = '<cr> '
	local ft = vim.bo.filetype
	if ft == 'rust' then
		cmd = '!cargo '
	elseif ft == 'lua' or ft == 'cpp' or ft == 'c' then
		cmd = '!make '
	end
	vim.cmd(cmd .. opts.args)
end, {
	nargs = '*',
})

(Neo)vimにある程度詳しい方なら、これ:makeじゃあかんの? となると思います。
自分も初めは :makeうまいこと使えば便利やん。使お と思っていましたが、幾つかの点に引っかかっていました。

  • コマンドの実行が失敗した場合、デフォルトだとエラーメッセージが書かれたtmpファイルが自動で開かれる。これが鬱陶しい。
  • p.shellpipeをエラーファイルが作られないように(例 p.shellpipe='echo Executing...')設定してやればtmpファイルは作られないが、今度は エラーしたのにエラーファイルが作られない というエラーメッセが表示される。🥹

他にも細かい不満点はありましたが、それらは適切に設定してやると回避できました。

これら二つの問題を回避するためMakeコマンドを作りました。nargs='*'としてやることで任意個の引数を受け取れます。これを<cr>にマッピングして即座に呼び出せる様にしています。

Key Mapping

local map = vim.keymap.set -- d: keymap
map('n', '<esc>', '<cmd>noh<cr>') -- <esc> to noh
map('i', '<c-[>', '<c-[><cmd>update | lua vim.lsp.buf.format{async=true}<cr>')
map({ 'n', 'v' }, '$', '^') -- swap $ & ^
map({ 'n', 'v' }, '^', '$')
map({ 'n', 'v' }, ',', '@:') --repeat previous command
map('i', '<c-n>', '<down>') --emacs keybind
map('i', '<c-p>', '<up>')
map('i', '<c-b>', '<left>')
map('i', '<c-f>', '<right>')
map('i', '<c-a>', '<home>')
map('i', '<c-e>', '<end>')
map('i', '<c-d>', '<del>')
map('i', '<c-k>', '<right><c-c>v$hs')
map('i', '<c-u>', '<c-c>v^s')
map('i', '<a-d>', '<right><c-c>ves')
map('i', '<a-f>', '<c-right>')
map('i', '<a-b>', '<c-left>')
map({ 'n', 'v' }, '<tab>', require('todo-comments').jump_next)
map({ 'n', 'v' }, '<s-tab>', require('todo-comments').jump_prev)
map('n', '<cr>', ':Make ') -- execute shell command
map('n', '<s-cr>', ':!')
map({ 'i', 'n', 'v' }, '<a-left>', '<c-w><') -- change window size
map({ 'i', 'n', 'v' }, '<a-down>', '<c-w>+')
map({ 'i', 'n', 'v' }, '<a-up>', '<c-w>-')
map({ 'i', 'n', 'v' }, '<a-right>', '<c-w>>')
map('n', 't', require('telescope.builtin').builtin) -- Telescope
map('n', '<space>o', require('telescope.builtin').lsp_document_symbols)
map('n', '<space>d', require('telescope.builtin').diagnostics)
map('n', '<space>b', require('telescope.builtin').buffers)
map('n', '<space>e', require('telescope').extensions.file_browser.file_browser)
map('n', '<space>f', require('telescope').extensions.frecency.frecency)
map('n', '<space>c', '<cmd>TodoTelescope<cr>')
map('n', '<space>n', require('telescope').extensions.notify.notify)
map({ 'n', 'v' }, '<space>a', '<cmd>Lspsaga code_action<cr>')
map('n', '<space>j', '<cmd>Lspsaga lsp_finder<cr>') --`j` stands for jump
map('n', '<space>r', '<cmd>Lspsaga rename<cr>')
map('n', '<space>h', '<cmd>Lspsaga hover_doc<cr>')
map('n', '<c-j>', '<cmd>Lspsaga diagnostic_jump_next<cr>')
map('n', '<c-k>', '<cmd>Lspsaga diagnostic_jump_prev<cr>')

全部話してるとキリがないのでかいつまんで行きます。

map('n', '<esc>', '<cmd>noh<cr>')

<esc>に検索時のハイライト消しを割り当てています。中身のないコピー記事いろいろな記事でなぜか<esc><esc>に割り当てられているやつですね!

map('i', '<c-[>', '<c-[><cmd>update | lua vim.lsp.buf.format{async=true}<cr>')

おそらく世のコーダーに呆れられることをしています。<c-[>==<esc>と思ってください。

  • 他にも<c-m>==<cr>,<c-i>==<tab>だったりします

insert modeで<c-[>と入力するとまず<c-[>が送られnormal modeに入ります。次に:updateが実行されバッファに変更があった場合保存されます。最後にlua vim.lsp.buf.format{async=true}が実行され、コードがフォーマットされます。formatterが見つからない場合エラーメッセが表示されます。いつかきちんと書き直したいです。

map({ 'n', 'v' }, '$', '^') & map({ 'n', 'v' }, '^', '$')

デフォルトでは行末移動は$(shift+4) 行頭移動は^(shift+6)なのですが、
行頭移動は左側へ、行末移動は右側へ行くのにキーボード上では逆になっていて毎回混乱していました。なのでより直感的にするためにこれらのkeyを交換しています。

map({ 'n', 'v' }, ',', '@:')

とすることで直前のコマンドを繰り返すことができます。vimでは.に直前の文字列操作を繰り返す機能が割り当てられています。dot repeatってやつですね。コマンドに対してもそれと同じ感覚の機能が欲しかったのでこのマップにしています。

map('i', '<a-d>', '<right><c-c>ves')

ターミナル上ではoption+d(mac)で単語削除できます。
vimでは<a or m-ナントカ>とすることで修飾キーとしてoption(mac)を使えます。
他OSのaltに当たるやつ。

<a-f> <a-b>についても同様。

map({ 'i', 'n', 'v' }, '<a-left>', '<c-w><')

<c-w>系のキーマップはwindowの操作、移動に主に使われています。
コメントにも書いてある通り<c-w>< <c-w>+ <c-w>- <c-w>>はウィンドウのサイズを調整するのに使われます。正直デフォルトのままでも使いやすいんですが、
サイズ調整だけは連打できないのが致命的に辛いのでマッピングを作っています。
ちなみに<a-left>のleftとは左矢印キーのことです。

telescope & lsp系キーマップ

プラグインのとこでまとめて紹介します。

プラグイン

長いので端折って紹介していきます。大分厳選しているはずなんですけどね..

ライブラリ系

見た目系(?)

エディタの見た目(主に色)をいい感じにしてくれる奴らです。個人的に色ってUIとはまたなんか違うよなと思うので分けていますがそんなに深い意味はありません。

amdt/sunset

use {
	'amdt/sunset',
	config = function()
		vim.g.sunset_latitude = 35.02
		vim.g.sunset_longitude = 135.78
	end,
}

緯度と経度を指定してやるとその場所の日の出、日の入りに合わせて自動でbackgroundをスイッチしてくれます。つまり、これの設定を安易に晒すということは自分の住所を晒すということです。まぁダミーかもしれんけど

実は使ってるcolorscheme(とターミナルエミュレータ)によってはこのプラグインは必要ありません。
いつからかneovimはターミナルの背景色を判定し、それに合わせてbackgroundのlight darkを設定してくれる様になりました。
更に、warpなど、システムの外観に合わせて自動でターミナルのlight,darkを切り替えてくれるターミナルエミュレータitermにこの設定ないの未だに信じられない を使っている場合は何もせずともこのプラグインと同じ恩恵を得ることができます。(なんならより柔軟に設定できる)

余談ですが、これが原因で一時期itermからwarpに移行していた時期がありました。ですが、色表示がおかしい(今は改善されています)、補完はwarp-builtinよりiterm+figの方が優秀、itermの方がカスタマイズ性が高い、などの理由(figの存在が一番大きい)により結局itermに落ち着いています。itermではcmd+shift+oでspotlightの様なものが開くのでそこからcolorschemeを切り替えると便利です。

catppuccin/nvim

use {
	'catppuccin/nvim',
	as = 'catppuccin',
	config = function()
		require('catppuccin').setup {
			background = {
				dark = 'frappe',
			},
			dim_inactive = {
				enabled = true,
			},
		}
	end,
}

colorschemeは語り出すと止まらないのですが、個人的にdark themeよりlight themeの方が好きなためdark themeが蔓延っている浮世に憂いが止まりません。
しかし、夜中は背景が暗い方が見やすいため基本的にはlight, dark両方に対応しているcolorschemeしか使っていません。
このcolorschemeはカッコよさと見やすさが両立されていてかつlight themeもちゃんと腰を入れて作っている稀有なプラグインです。他に良さげなcolorschemeがある場合はぜひ教えてください🙏🏻

その他

UI系

rcarriga/nvim-notify

use {
	'rcarriga/nvim-notify',
	-- noice.nvimが正常に使える場合、config部分は要りません
	config = function()
		vim.notify = require 'notify'
		vim.notify_once = require 'notify'
	end,
}

neovimの通知をmacのシステム通知みたくできます

folke/todo-comments.nvim

use {
	'folke/todo-comments.nvim',
	config = function()
		require('todo-comments').setup {
			keywords = {
				FIX = { alt = { 'e' } }, -- e: `e` stands for error
				TODO = {
					color = 'hint',
					alt = { 'q' }, -- q: `q` stands for question
				},
				HACK = {
					color = 'doc',
					alt = { 'a' }, -- a: `a` stands for attention
				},
				WARN = { alt = { 'x' } }, -- x: `x` is abbreviation of `XXX`
				PERF = {
					color = 'cmt',
					alt = { 'p' }, -- p: `p` stands for performance
				},
				NOTE = {
					color = 'info',
					alt = { 'd' }, -- d: `d` stands for description
				},
				TEST = { alt = { 't', 'PASS', 'FAIL' } }, -- t: `t` stands for test
			},
			colors = {
				cmt = { 'Comment' },
				doc = { 'SpecialComment' },
				todo = { 'Todo' },
			},
		}
	end,
}

vimはデフォルトでコメント内で特定のキーワードを特別なハイライトをしてくれます。このプラグインの提供する機能も基本的に同じですが、builtinは若干物足りないのと、こっちの方がカスタマイズしやすいのでこっちを使っています。require('todo-comments').jump_prevnext()でtodo-comment間を行き来できます。自分はこれを<tab> <s-tab>に割り当てています。便利。

map('n', '<space>c', '<cmd>TodoTelescope<cr>')

また、<space>cで現在のバッファのtodo-commentたちを一覧できる様にしています。(要telescope)

個人的にかなりおすすめのプラグインです。

folke/noice.nvim

use {
	'folke/noice.nvim',
	config = function()
		require('noice').setup {
			presets = {
				bottom_search = true,
			},
		}
	end,
}

オプションのところでも述べたようにこのプラグインは現在(2023/1/6)neovim側のバグが原因でうまく動作しません。

このプラグインを使うとneovimのUIをいい感じに乗っ取ってくれます。また、個人的に重宝しているのは、notifyと連携することでvimから送られる通知の履歴を簡単に見返せる機能です。

更にtelescopeと連携することで、通知の履歴をfuzzy_searchできます。ネ申
自分はmap('n', '<space>n', require('telescope').extensions.notify.notify) とマッピングすることで<space>nでいつでも呼び出せる様にしています。

入力補助系

windwp/nvim-autopairs

use {
	'windwp/nvim-autopairs',
	config = function()
		require('nvim-autopairs').setup {
			map_c_h = true,
		}
	end,
}

みんな何かしら入れてる勝手にカッコ閉じてくれるタイプのやつですね。builtinのlsp使っている人は大体これ(偏見)

FuzzyFinder系

FuzzyFinder系というよりtelescope系

telescopeの存在でvimからneovimに移行する決心がつきました。このプラグインを使ったことない方にはぜひ試して欲しいですし、他のff系プラグインを使っている方にも一度は触れて欲しいです。エコシステムが豊かでneovimとの相性も良いのでできることが一気に広がります。

nvim-telescope/telescope.nvim

use {
	'nvim-telescope/telescope.nvim',
	tag = '0.1.0',
	config = function()
		require('telescope').setup {
			extensions = {
				file_browser = {
					hidden = true,
					hide_parent_dir = true,
				},
			},
		}
		require('telescope').load_extension 'frecency'
		require('telescope').load_extension 'file_browser'
	end,
}

これがないと始まらない。neovimのプラグインで一番おすすめです。
↓はbuiltinのpickerでよく使うもの

map('n', 't', require('telescope.builtin').builtin)
map('n', '<space>o', require('telescope.builtin').lsp_document_symbols)
map('n', '<space>d', require('telescope.builtin').diagnostics)
map('n', '<space>b', require('telescope.builtin').buffers)

nvim-telescope/telescope-frecency.nvim

use 'nvim-telescope/telescope-frecency.nvim'
-- まっぴんぐ
map('n', '<space>f', require('telescope').extensions.frecency.frecency)

builtinのpickerになってないのが不思議。

nvim-telescope/telescope-file-browser.nvim

use 'nvim-telescope/telescope-file-browser.nvim'
--まっぴんぐ
map('n', <space>e',require('telescope').extensions.file_browser.file_browser)

ファイラー系プラグインって地味にたくさんありますが、telescopeのいつものUIでブラウズするのがなんだかんだ一番楽なのでこれを使っています。似た様な理由でvim時代はcoc-explorerを使っていました。マッピングが<space>eになってるのはその名残です。(explorer)

LSP系

今やすっかり人権装備となったlsp

neovim/nvim-lspconfig

use {
	'neovim/nvim-lspconfig',
	config = function()
		local capabilities = require('cmp_nvim_lsp').default_capabilities()
                                                                          
		-- d: rust_analyzer
		require('lspconfig').rust_analyzer.setup {
			capabilities = capabilities,
			settings = {
				['rust-analyzer'] = {
					hover = {
						actions = {
							reference = {
								enable = true,
							},
						},
					},
					inlayHints = {
						closingBraceHints = {
							minLines = 0,
						},
						lifetimeElisionHints = {
							enable = 'always',
							useParameterNames = true,
						},
						maxLength = 0,
						typeHints = {
							hideNamedConstructor = false,
						},
					},
					lens = {
						implementations = {
							enable = false,
						},
					},
					rustfmt = {
						rangeFormatting = {
							enable = true,
						},
					},
					semanticHighlighting = {
						operator = {
							specialization = {
								enable = true,
							},
						},
					},
					typing = {
						autoClosingAngleBrackets = {
							enable = true,
						},
					},
					workspace = {
						symbol = {
							search = {
								kind = 'all_symbols',
							},
						},
					},
				},
			},
		}
                                                                          
		-- d: lua
		require('lspconfig').sumneko_lua.setup {
			capabilities = capabilities,
			settings = {
				Lua = {
					runtime = {
						version = 'LuaJIT',
					},
					diagnostics = {
						globals = { 'vim' },
					},
					workspace = {
						library = vim.api.nvim_get_runtime_file('', true),
						checkThirdParty = false,
					},
					telemetry = {
						enable = false,
					},
				},
			},
		}
                                                                          
		-- d: clangd
		require('lspconfig').clangd.setup {
			capabilities = capabilities,
		}
	end,
}

InlayHintやSemanticTokenなどの機能は未実装なもののtelescopeとの兼ね合いでbuiltinを使っています。rust-analyzerの設定はcoc時代のものです。それからいじってないので古臭いことやってるかもしれません。

luaの設定のこの部分

diagnostics = {
	globals = { 'vim' },
},

を取り除くとlspがvim.optなどのvimをグローバルな変数だと理解してくれずにエラーを出す様になります。

glepnir/lspsaga.nvim

use {
	'glepnir/lspsaga.nvim',
	branch = 'main',
	config = function()
		require('lspsaga').init_lsp_saga {
			saga_winblend = 20,
			max_preview_lines = 10,
			code_action_lightbulb = {
				enable = false,
			},
			finder_action_keys = {
				open = '<cr>',
				vsplit = '<c-v>',
				split = '<c-x>',
			},
			definition_action_keys = {
				edit = '<cr>',
				vsplit = '<c-v>',
				split = '<c-x>',
				tabe = 't',
			},
		}
	end,
}
--まっぴんぐ
map({ 'n', 'v' }, '<space>a', '<cmd>Lspsaga code_action<cr>')
map('n', '<space>j', '<cmd>Lspsaga lsp_finder<cr>') --`j` stands for jump
map('n', '<space>r', '<cmd>Lspsaga rename<cr>')
map('n', '<space>h', '<cmd>Lspsaga hover_doc<cr>')
map('n', '<c-j>', '<cmd>Lspsaga diagnostic_jump_next<cr>')
map('n', '<c-k>', '<cmd>Lspsaga diagnostic_jump_prev<cr>')

<space>jLspsaga lsp_finderを呼び出すために入れています。その他のマッピングはbuiltinのapiを用いて

map({ 'n', 'v' }, '<space>a', vim.lsp.code_action)
map('n', '<space>r', vim.lsp.buf.rename)
map('n', '<space>h', vim.lsp.buf.hover)
map('n', '<c-j>', vim.diagnostic.goto_next)
map('n', '<c-k>', vim.diagnostic.goto_prev)

再現できます。Lspsagaを使った方がUIが見やすいです。

その他

  • mason.nvim: プラグインマネージャヤーみたいなやつ
  • mason-lspconfig.nvim: プラグインマネージャヤーみたいなやつ2
  • null-ls.nvim: lspと同じ機能を提供する外部ツールをlspとして扱うことができるものです

補完系

hrsh7th/nvim-cmp

use {
	'hrsh7th/nvim-cmp',
	config = function()
		local luasnip = require 'luasnip'
		local cmp = require 'cmp'
		cmp.setup {
			snippet = {
				expand = function(args)
					luasnip.lsp_expand(args.body)
				end,
			},
			window = {
				completion = cmp.config.window.bordered(),
				documentation = cmp.config.window.bordered(),
			},
			mapping = cmp.mapping.preset.insert {
				['<a-k>'] = cmp.mapping.scroll_docs(-10),
				['<a-j>'] = cmp.mapping.scroll_docs(10),
				['<c-c>'] = cmp.mapping.abort(),
				['<tab>'] = cmp.mapping(function(fallback)
					if cmp.visible() then
						cmp.confirm {
							behavior = cmp.ConfirmBehavior.Insert,
							select = true,
						}
					else
						fallback()
					end
				end, { 'i', 's', 'c' }),
				['<s-tab>'] = cmp.mapping(function(fallback)
					if luasnip.expand_or_jumpable() then
						luasnip.expand_or_jump()
					else
						fallback()
					end
				end, { 'i', 's', 'c' }),
				['<c-n>'] = cmp.mapping(function(fallback)
					if cmp.visible() then
						cmp.select_next_item()
					else
						fallback()
					end
				end, { 'i', 's', 'c' }),
				['<c-p>'] = cmp.mapping(function(fallback)
					if cmp.visible() then
						cmp.select_prev_item()
					else
						fallback()
					end
				end, { 'i', 's', 'c' }),
			},
			sources = {
				{ name = 'luasnip' },
				{ name = 'nvim_lsp' },
				{ name = 'nvim_lua' },
				{ name = 'nvim_lsp_signature_help' },
				{ name = 'buffer' },
			},
		}
                                                            
		cmp.setup.cmdline('/', {
			sources = {
				{ name = 'buffer' },
			},
		})
                                                            
		cmp.setup.cmdline(':', {
			sources = {
				{ name = 'path' },
				{ name = 'cmdline' },
				{ name = 'buffer' },
			},
		})
	end,
}

lspconfigでおすすめされてたので脳死で使ってます拡張性が高そうなので使っています。

cmp.setupに渡されるtableのsourcesの部分では複数の補完ソースを指定できますが、この時、ソースの並びがそのまま補完の優先度になります。自分の場合

sources = {
	{ name = 'luasnip' },--priority 1
	{ name = 'nvim_lsp' },--priority 2
	{ name = 'nvim_lua' },--priority 3
	{ name = 'nvim_lsp_signature_help' },--priority 4
	{ name = 'buffer' },--priority 5
},

となっています

その他

  • cmp-nvim-lsp: lspが送ってくる補完ソース
  • cmp-nvim-lua: init.luaを書く時とかに重宝します
  • cmp-nvim-lsp-signature-help: 関数の引数の補完がいい感じになります
  • cmp-buffer: 現在のバッファの単語を補完に追加してくれます
  • cmp-path: cmdline等でpathを入力したい場面で補完を提供してくれます
  • cmp-cmdline: vimのコマンドなどを補完してくれます。builtinの補完との違いで一番大きいと感じるのはfuzzy_searchできる点です。地味に革命
  • cmp_luasnip: なんか必要らしいlspが送ってくるコードのスニペットに対応するためにスニペットプラグインが必要らしく、これはスニペットプラグインと補完プラグインを繋ぐ役割を持っている。らしいです
  • LuaSnip: 高機能なスニペットプラグインだそうです。全然使いこなせていない..

Discussion