はじめに

かなり久しぶりの更新になります. 私は元々 VSCode をエディタに使用していたのですが, プロジェクトメンバーが Nvim(NeoVim)を使いこなしているのを見て NVim を使ってみることにしました.

使ってみた感想としては, 全てをキーボードで完結できるので環境さえ整えてしまえば, かなり快適に開発できると思います.

前提として私の参加しているプロジェクトでは以下の技術スタックを使用しています.

  • Frontend
    • TypeScript
    • React
    • Tailwind CSS
  • Backend
    • TypeScript
    • NestJS
    • RestAPI & GraphQL
    • Prisma

私はバックエンドもフロントエンドも書くのですが,両方 TypeScript なので NVim の設定は共通です.

画面

設定ファイルの構成

私の設定ファイルは下記のような構成になっています. coreディレクトリにはプラグインに依存しない設定を書いています. この lua ファイルが読み込めさえすればプラグインが使用できない状況になったとしても最低限コーディングできます.

pluginsディレクトリにはパッケージマネージャーであるLazyを使用してplugins/configsにある各プラグインの設定を読み込んでいます. plugins/keymap.luaにはプラグイン依存のキーマップの設定を書いています.

shell
.
├── init.lua
└── lua
    ├── core
    │   ├── bootstrap.lua
    │   ├── config.lua
    │   ├── init.lua
    │   ├── keymap.lua
    │   └── utils.lua
    └── plugins
        ├── configs
        │   ├── bufferline.lua
        │   ├── cmp.lua
        │   ├── gitsigns.lua
        │   ├── lazy.lua
        │   ├── lualine.lua
        │   ├── luasnip.lua
        │   ├── mason-lspconfig.lua
        │   ├── mason.lua
        │   ├── nvim-tree.lua
        │   ├── nvterm.lua
        │   ├── telescope.lua
        │   └── treesitter.lua
        ├── init.lua
        └── keymap.lua

キーバインド

vim と言えばキーバンド. とはいえ私は標準のキーバインドに慣れているため, 設定は少なめです.

lua/core/keymap.lua
local M = {}

M.general = {
  i = {},
  n = {
    ["<ESC>"] = { ":noh <CR>", "clear highlights" },

    ["<c-h>"] = { "<c-w>h", "move to left window" },
    ["<c-j>"] = { "<c-w>j", "move to bottom window" },
    ["<c-k>"] = { "<c-w>k", "move to top window" },
    ["<c-l>"] = { "<c-w>l", "move to right window" },

    ["<TAB>"] = { ":bnext <CR>", "next buffer" },
    ["<S-TAB>"] = { ":bprevious <CR>", "previous buffer" },
    ["<leader>x"] = { ":bd! <CR>", "close current buffer" },
    ["<leader>X"] = { ":bufdo bd! <CR>", "close all buffers" },

    -- Netrw: deprecated
    -- ["<C-n>"] = { ":Lexplore <CR>", "toggle netrw" },
  },
  v = {},
  t = {
    ["<C-x>"] = { "<C-\\><C-n>", "exit terminal mode" },
  },
}

return M

キーバインドをテーブルで定義しています. 現在は活用できていませんが, 設定を構造化することで拡張性やメンテナンス性を高めることを期待しています.

以下はキーバインドのテーブルを展開して設定に適用するためのコードです.

lua/core/utils.lua
local M = {}

function M.load_keymaps(m)
  local keymaps = vim.api.nvim_set_keymap
  local default_opts = { noremap = true, silent = true }

  for scope, modes in pairs(m) do
    for mode, maps in pairs(modes) do
      for lhs, rhs in pairs(maps) do
        local opts = vim.tbl_extend("force", default_opts, { desc = rhs[2] })
        keymaps(mode, lhs, rhs[1], opts)
      end
    end
  end
end

return M

nvim_set_keymapの第 4 引数にdescを渡すことでキーバインドの説明を表示できるようになります. キーバインドを検索する際に便利なので, 設定することをお勧めします.

キーバインド

基本的な設定

プラグインに依存しない設定は/core/config.luaに記述し, /core/init.luaで読み込んでいます.

lua/core/config.lua
------------------------------------------------------------
-- general config
------------------------------------------------------------
local opt = vim.opt
local g = vim.g
local _border = "single"

-- language
vim.cmd('let $LANG="en_US.UTF-8"')

-- netrw
g.netrw_liststyle = 3
g.netrw_banner = 0
g.netrw_winsize = 20

-- status line
opt.showmode = false

-- clipboard
opt.clipboard = "unnamedplus"

-- indenting
opt.expandtab = true
opt.shiftwidth = 2
opt.smartindent = true
opt.tabstop = 2
opt.softtabstop = 2

-- search
opt.ignorecase = true
opt.smartcase = true

-- leader
g.mapleader = " "

-- scrolling
opt.scrolloff = 8
opt.sidescrolloff = 8

-- appearance
opt.shortmess:append("I")
opt.fillchars = { eob = " " }
opt.laststatus = 3
opt.termguicolors = true
vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
  vim.lsp.handlers.hover, { border = _border }
)
vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
  vim.lsp.handlers.signature_help, { border = _border }
)
vim.diagnostic.config({ float = { border = _border } })

-- environment
g.loaded_node_provider = 0

------------------------------------------------------------
-- autocmds
------------------------------------------------------------
local autocmd = vim.api.nvim_create_autocmd

-- disable auto commenting
autocmd("FileType", {
  pattern = "*",
  command = "setlocal formatoptions-=cro",
})

-- replace tabs with spaces
autocmd("BufWritePre", {
  pattern = "*",
  command = "retab",
})

-- remove trailing whitespace on save
autocmd("BufWritePre", {
  pattern = "*",
  command = ":%s/\\s\\+$//e",
})

-- remove duplicate blank lines on save
autocmd("BufWritePre", {
  pattern = "*",
  command = ":%s/\\n\\{3,}/\\r\\r/e",
})

プラグイン

プラグインは/plugins/init.luaで読み込んでいます. 以前は何でもかんでも突っ込んでいたのですが, 現在は必要最低限のプラグインしかしれていません.

  • mason : Launguage Server, Formatter, Linter などなどを管理する.
  • mason-lspconfig : LSP の設定と Mason でインストールしたパッケージを橋渡しする. Mason でインストールしただけで LSP が使えるようになるので楽.
  • nvim-cmp : Snippet, LSP, Buffer などの補完を可能にする.
  • gitsigns: Git 関連の操作を可能にする. hunk の操作などは CLI だと面倒なので, このプラグインが活躍する.
  • telescope: お馴染みの fazzy finder. ファイル検索だけでなく, 様々な検索ができる. かなり拡張性が高い.
  • nvim-surround : vim-surround の NeoVim 版. ()""などペアを簡単に挿入・変更・削除できる. キーバインドを覚えるのが少し大変だが, 覚えてしまえば打鍵量が格段に減る.

その他プラグインの一覧は以下の通りです.

プラグイン一覧

おわりに

VSCode ではなく Nvim を使用するメリットを感じられる程度には慣れてきたので, 今後もじっくりと育てていきたいと思っています.