Neovimはじめてみた

2024/11/01に公開

はじめに

9月の末ごろからWezTermを触り始めて、LazyGitなどを入れてみると、ターミナルで全て完結できるのがいいなと思い始めました。そこで、いっそのことneovimを使ってコーディングもターミナル上で完結しようと思いました。

環境

実行環境は以下の通りです。

  • macOS Sequoia 15.0
  • WezTerm (zsh)
  • neovim: v0.10.1
    • プラグインマネージャー: lazy.nvim

現在のコーディング環境

現在のコーディング環境は以下のようになっています。

項目 現在
Terminal WezTerm
IDE JetBrains系 + VSCode
Gitクライアント Fork
Databaseクライアント DataGrip

TerminalにはWezTermを使っています。使い始めたのは最近ですが設定をテキストファイルで管理できる点がとても良いです。以前はiTerm2を使っており、ストレスはなかったのですが今ではWezTermの方が良いと思っています。

コーディングで主に使っているのはJetBrainsのIDE( PyCharm, WebStorm, PhpStorm )です。JetBrainsのIDEは、デフォルトで各種言語の補完が効き、プラグインを追加しなくてもストレスなくコーディングできるのが良い点です。またどちらかというと、JetBrainsにプラグインの追加はしたくないです。プラグインの追加はGUIベースになるため、管理が煩雑になるのと異なる環境間での共有が難しいためです。もちろん、設定のexport機能を使えばいいのですが、設定を変更するたびにexportしてimportしてというのは煩雑なためです。

VSCodeは、csvやjsonなどのテキストファイルを開いて確認・編集するときに使っています。VSCodeに関しても、JetBrainsのIDEと同様の理由でプラグインの追加はしたくないです。プラグインの追加をしていないためコーディングには使えず、利用頻度は低めです。Curosrなどの生成AIを組み込んだVSCodeベースのエディタも出てきていますがほとんど使っていないです。

コーディング以外のアプリケーションとして、GitクライアントにはForkを利用しています。名前がGitの機能のforkと被っているので検索するのが大変ですが、シンプルな見た目とgit-flowの機能がデフォルトで利用できて便利なため利用しています。DatabaseクライアントにはJetBrainsのDataGripを利用しています。PyCharmやWebStormからでもほぼ同様のDatabaseクライアントが利用できるのですが、Database接続専用のクライアントがあるとシンプルになるのでDataGripを利用しています。

これからのコーディング環境

これからのコーディング環境を以下に示します。

項目 現在 これから
Terminal WezTerm 同じ
IDE JetBrains系 + VSCode neovim (+ plugins)
Gitクライアント Fork lazygit
Databaseクライアント DataGrip 同じ

vimに対しては操作が難しいそうと言う心理的なハードルがあったため、今まで使ったことがありませんでした。しかし、~/.config以下で設定およびプラグインを管理できるのが私にとって魅力的に感じたため、操作が難しいそうと言う心理的なハードルは気にならなくなりました。加えてターミナルだけで完結し、ホームポジションからほとんど手を動かす必要がないため、マウス操作が必要になるJetBrainsのIDEより早く操作ができるようになるかなと思っています。

Forkは、ターミナル上でTUIで操作できるlazygitを代替にする予定です。一方DataGripを代替する予定はないです。TUIベースのデータベースクライアントのツールはあるのですが、利用を見送っています。

プラグインなどの詳細は後述しますが、現在とこれからのアプリケーションで利用する機能の対応付けは以下の通りです。

機能 現在 これから
コマンドの実行 WezTerm WezTerm
補完・エラー機能 IDE neovim (+ LSP)
関数の定義ジャンプ IDE neovim (+ LSP)
誤字検出 IDE neovim (+ LSP)
リポジトリ内全体での文字列検索 IDE neovim (+ telescope)
行ごとのGit Blame IDE neovim (+ git-blame.nvim)
コンフリクトの解消 IDE neovim (+ diffview.nvim)
GitFlowに従ったブランチ管理 Fork lazygit
行ごとのコミット、プッシュ機能 Fork lazygit
スタッシュ Fork lazygit
データベース操作 DataGrip DataGrip

NeoVimについて

NeoVimとは

https://neovim.io/
公式サイトによると

hyperextensible Vim-based text editor

拡張可能なVimベースのテキストエディタのことです。

https://github.com/neovim/neovim

開発しているGitHubによると

Neovim is a project that seeks to aggressively refactor Vim in order to:

  • Simplify maintenance and encourage contributions
  • Split the work between multiple developers
  • Enable advanced UIs without modifications to the core
  • Maximize extensibility

メンテナンスを簡素化し、開発しやすくし、高度なUIを提供し、拡張しやすくするためにvimをリファクタすることを目指すプロジェクトのことです。

インストール

neovimはbrewでインストールできます。

$ brew install neovim

インストール後、$ nvimでneovimを開くことができます。

キーマップの表記について

neovimでは<C-@><S-@>などの表記を見かけます。これらはキーマッピングのことで<C-@>の時はCtrlと別のキー(@)を、<S-@>の時はShiftと別のキー(@)を押下することを意味しています。詳しくは以下の記事をご覧ください。

https://kannokanno.hatenablog.com/entry/2013/05/05/130219

また、そのほかにも<Leader>という表記も見かけます。これはユーザーが自由に割り当てれる複数入力の開始キーのことです。既存の<C-@>などと被らないため自由度が高いです。私は<Leader>にスペースキーに割り当てています。

https://vim.blue/leader-key/

基本操作

Vimには以下のモードが存在します。

  • Normal mode
  • Insert mode
  • Command mode
  • Visual mode

それぞれどのようなモードで、どのように遷移し、そのモードで打てるキーマップを説明します。

Normal mode

Normal modeはneovimを開いた直後のモードです。キーを押下することでカーソル移動やコピーなどができます。

Normal mode: 移動

キーマップ 操作
h 左移動
j 下移動
k 上移動
l 右移動
e 英単語のスペース区切り前などに移動
$ 末尾に移動
0 先頭に移動
{ 段落ごとに上に移動
} 段落ごとに下に移動
[[ セクションごとに上に移動
]] セクションごとに下に移動
<C-b> ページを上に移動(1ページずつ)
<C-f> ページを下に移動(1ページずつ)
<C-u> ページを上に移動(半ページずつ)
<C-d> ページを下に移動(半ページずつ)
<C-y> ページを上に送る(1行ずつ)
<C-e> ページを下に送る(1行ずつ)
gg ファイルの先頭に移動
G ファイルの末尾に移動

Normal mode: 移動して入力モードへ移行

キーマップ 操作
a カーソルの右側から入力を開始
A 行の末尾に移動して入力を開始
i カーソルの左側から入力を開始
I 行の先頭に移動して入力を開始
o 改行して入力を開始
O 下に改行をして上に入力を開始
C カーソルのある位置から末尾までを削除して入力を開始
c0 カーソルのある位置から先頭まで削除をして入力を開始
cc カーソルのある行を削除して入力を開始

Normal mode: コピー

ここでは、Normal modeのみで完結するコピーのコマンドのみを載せていますが、実際は後述するVisual modeでコピー範囲を選択してなどの方法をよく使います。

キーマップ 操作
yy 無名レジスタに行コピー
dd 行を切り取り
p 無名レジスタの内容を貼り付け
x カーソル上の文字を削除(Deleteキーと同じ)

Normal mode: 検索

キーマップ 操作
/検索したい文字列 文字の検索
n 次の検索結果に移動
N 前の検索結果に移動

Normal mode: その他

キーマップ 操作
u 直前の入力を削除(Ctrl + Zと同じ挙動)

Insert mode

Insert modeは文字を入力するモードです。普段のテキストエディタと同じ感覚で使うことができます。<esc>を入力することでNormal modeに戻ることができます。ただ、頻繁に<esc>を入力するのは大変なので別のキーにマッピングするのが便利です。次の記事でもあるようにjjkkに割り当てると、カーソル移動時にモードが切り替わるので操作しやすくなります。

https://qiita.com/RepublicOfKorokke/items/0a267b53156cd8a64a59

上記の設定をする場合、以下のように追記します。

init.lua
vim.keymap.set("i", "jj", "<esc>")
vim.keymap.set("i", "kk", "<esc>")

Command mode

Command modeは様々なコマンドを実行するモードです。Normal modeから:を入力して切り替えることができます。<esc>で再びNormal modeに戻ることができます。

Command modeでよく利用するコマンドは以下の通りです。

キーマップ 操作
:u 直前の操作を取り消す
:q 終了する
:q! 保存しないで終了する
:w 保存する

Visual mode

Visual modeはテキストの領域選択が出来るモードです。Normal modeから以下のいずれかの入力でVisual modeに切り替えることができます。

キーマップ 操作
v vを押したところから選択ができる
V Vを押した行全体を選択できる
<C-v> 矩形選択が出来る

上記のコマンドで範囲選択をした後に以下のコマンドを実行することで、操作ができます。

キーマップ 操作
d 選択範囲の切り取り
y 選択範囲のコピー
<S->> インデントを下げる
<S-<> インデントを上げる

またクリップボードを利用したコピーをするために以下のキーマップを設定しています。

キーマップ 操作
<leader>y 行をクリップボードにコピー
<leader>d 行をクリップボードにコピーし、選択範囲を切り取る
<leader>p クリップボードの内容を貼り付け

状態遷移

それぞれのモードを状態遷移図で表すと以下のようになります。a,i,oなどでInsert modeに、v,VなどでVisual modeに、:でCommand modeになります。そして<esc>でNormal modeになります。

そのほか

Normal modeでのカーソルが太く、どこを基準としているかわかりにくい場合があります。


カーソルが四角になっていて、どこを基準としているのわかりづらい


カーソルを変更してInsert modeと同じにするとわかりやすい

上の画像のような変更をするために、以下の内容を追記します。

~/.config/nvim/lua/plugins/init.lua
-- cursor
vim.opt.guicursor = "n-i:ver25"

プラグイン

vimでコーディングする際にはプラグインの追加が必須になるかと思います。そして複数のプラグインを導入・管理するために、まずはプラグインマネージャーを導入します。色々ありますが、lazynvimを導入します。

https://github.com/folke/lazy.nvim

公式の設定にあるように以下の設定を追記していきます。

~/.config/nvim/init.lua
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  local lazyrepo = "https://github.com/folke/lazy.nvim.git"
  local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
  if vim.v.shell_error ~= 0 then
    vim.api.nvim_echo({
      { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
      { out, "WarningMsg" },
      { "\nPress any key to exit..." },
    }, true, {})
    vim.fn.getchar()
    os.exit(1)
  end
end
vim.opt.rtp:prepend(lazypath)

-- Make sure to setup `mapleader` and `maplocalleader` before
-- loading lazy.nvim so that mappings are correct.
-- This is also a good place to setup other settings (vim.opt)
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"

-- Setup lazy.nvim
require("lazy").setup({
  spec = {
    -- add your plugins here
  },
  -- Configure any other settings here. See the documentation for more details.
  -- colorscheme that will be used when installing plugins.
  install = { colorscheme = { "habamax" } },
  -- automatically check for plugin updates
  checker = { enabled = true },
})

このファイルの、specにプラグインを追記していくことで複数のプラグインを管理・インストールできます。ただ、このファイルに追加し続けるとinit.luaが非常に大きくなり管理しづらくなります。 そのため lazy.nvimではプラグインを管理するファイルを分割することができます。

~/.config/nvim/init.lua
  spec = {
+    { { import = "plugins" } }
  },

こうすることで、~/.config/nvim/lua/plugins/*.luaで作成したプラグインを読み込めます。ファイル作成後にneovimを開くと、lazy.nvimによってプラグインのインストールが始まります。


nvim起動時のlazy.nvimの画面

上記の画面は:Lazyコマンドで呼び出せます。lazy.nvimのそのほかの操作については以下の記事を参照してください。

https://zenn.dev/siteyo/articles/980b6205e93914

plugins: ファイラー

ファイラーにはnvim-treeをインストールします。


nvim起動時のnvim-treeの画面

以下のようにキーマップを設定しています。

キーマップ 操作
<C-n> NvimTreeを表示・非表示にする
<C-m> NvimTreeにフォーカスする
nvim-treeの設定
~/.config/nvim/lua/plugins/nvim-tree.lua
-- open File Tree when open
local function open_nvim_tree()
    require("nvim-tree.api").tree.open()
end

vim.api.nvim_create_autocmd({ "VimEnter" }, { callback = open_nvim_tree })

return {
  "nvim-tree/nvim-tree.lua",
  version = "*",
  lazy = false,
  dependencies = {
    "nvim-tree/nvim-web-devicons",
  },
  keys = {
    {mode = "n", "<C-n>", "<cmd>NvimTreeToggle<CR>", desc = "NvimTreeをトグルする"},
    {mode = "n", "<C-m>", "<cmd>NvimTreeFocus<CR>", desc = "NvimTreeにフォーカス"},
  },
  config = function()
    require("nvim-tree").setup {
      git = {
        enable = true,
        ignore = true,
      }
    }
  end,
}

plugins: bufferをタブ表示

neovimで開いたファイルを上部にタブとして表示するプラグインです。

bufferlineの設定
~/.config/nvim/lua/plugins/bufferline.lua
return {
  'akinsho/bufferline.nvim',
  version = "*",
  dependencies = 'nvim-tree/nvim-web-devicons',
  config = function ()
    vim.opt.termguicolors = true
    require("bufferline").setup{}
  end
}


bufferlineによるタブの表示(垢枠内)

nvimのbufferの切り替えは<C-h><C-l>で移動できるように設定しています。ちなみに、プラグインのbufferlineと<C-h><C-l>は関係なく動きます。

~/.config/nvim/init.lua
(〜前略〜)
-- ファイル切り替え
vim.keymap.set("n", "<C-h>", "<cmd>bprev<CR>")
vim.keymap.set("n", "<C-l>", "<cmd>bnext<CR>")
キーマップ 操作
<C-h> 左のバッファに移動
<C-l> 右のバッファに移動

plugins: ファジーファインダー

https://zenn.dev/yutakatay/articles/vim-fuzzy-finder
https://github.com/nvim-telescope/telescope.nvim

ファジーファインダーとは、あいまい検索ができるツールのことで、ファイルの文字列やファイル名を検索できます。ファジーファインダーにはtelescope.nvimを利用します。


telescopeの文字列検索の画面

検索には様々なモードがあり、以下のように設定しています。

キーマップ 操作
<leader>ff ファイル名を検索
<leader>fg プロジェクト内の文字列を検索
<leader>fh neovimのヘルプタグを検索
telescope.nvimの設定
~/.config/nvim/lua/plugins/telescope.lua
return {
  'nvim-telescope/telescope.nvim', tag = '0.1.8',
  dependencies = { 'nvim-lua/plenary.nvim' },
  config = function()
    local builtin = require('telescope.builtin')
    vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Telescope find files' })
    vim.keymap.set('n', '<leader>fg', builtin.live_grep, { desc = 'Telescope live grep' })
    vim.keymap.set('n', '<leader>fh', builtin.help_tags, { desc = 'Telescope help tags' })
  end
}

live_grep機能を利用するためには、ripgrepをインストールする必要があります。brewでインストールします。

$ brew install ripgrep

plugins: Git

エディタの行番号の左側に、ファイルに変更・追記がある行を表示するようにします。

https://github.com/lewis6991/gitsigns.nvim


gitsignsでエディタの左側に各行のgitの状態を表示(赤枠内)

gitsigns.nvimの設定
~/.config/nvim/lua/plugins/gitsigns.lua
return {
  "lewis6991/gitsigns.nvim",
  config = function()
    require('gitsigns').setup {
      signs = {
        add          = { text = '┃' },
        change       = { text = '┃' },
        delete       = { text = '_' },
        topdelete    = { text = '‾' },
        changedelete = { text = '~' },
        untracked    = { text = '┆' },
      },
      signs_staged = {
        add          = { text = '┃' },
        change       = { text = '┃' },
        delete       = { text = '_' },
        topdelete    = { text = '‾' },
        changedelete = { text = '~' },
        untracked    = { text = '┆' },
      },
      signs_staged_enable = true,
      signcolumn = true,  -- Toggle with `:Gitsigns toggle_signs`
      numhl      = false, -- Toggle with `:Gitsigns toggle_numhl`
      linehl     = false, -- Toggle with `:Gitsigns toggle_linehl`
      word_diff  = false, -- Toggle with `:Gitsigns toggle_word_diff`
      watch_gitdir = {
        follow_files = true
      },
      auto_attach = true,
      attach_to_untracked = false,
      current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame`
      current_line_blame_opts = {
        virt_text = true,
        virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align'
        delay = 1000,
        ignore_whitespace = false,
        virt_text_priority = 100,
        use_focus = true,
      },
      current_line_blame_formatter = '<author>, <author_time:%R> - <summary>',
      sign_priority = 6,
      update_debounce = 100,
      status_formatter = nil, -- Use default
      max_file_length = 40000, -- Disable if file is longer than this (in lines)
      preview_config = {
        -- Options passed to nvim_open_win
        border = 'single',
        style = 'minimal',
        relative = 'cursor',
        row = 0,
        col = 1
      },
    }
  end
}

plugins: Git Blame

git-blameはカーソルがある行のgitの編集者・編集日時などステータスを表示するプラグインです。

https://github.com/f-person/git-blame.nvim


git-blameでカーソル行のステータスを表示している

git-blame.nvimの設定
~/.config/nvim/lua/plugins/coding-gitblame.lua
return {
  "f-person/git-blame.nvim",
  -- load the plugin at startup
  event = "VeryLazy",
  -- Because of the keys part, you will be lazy loading this plugin.
  -- The plugin will only load once one of the keys is used.
  -- If you want to load the plugin at startup, add something like event = "VeryLazy",
  -- or lazy = false. One of both options will work.
  opts = {
      -- your configuration comes here
      -- for example
      enabled = true,  -- if you want to enable the plugin
      message_template = " <author>, <date> : <summary>", -- template for the blame message, check the Message template section for more options
      date_format = "%m-%d-%Y %H:%M:%S", -- template for the date, check Date format section for more options
      virtual_text_column = 1,  -- virtual text start column, check Start virtual text at column section for more options
  },
}

plugins: コンフリクトの解消・diffの確認

gitのコンフリクト解消にはdiffview.nvimを使います。

https://github.com/sindrets/diffview.nvim


コンフリクトが発生している画面

上記のようなコンフリクトがある状態で<leader>hdを押下すると以下のコンフリクト解消画面になります。コンフリクト解消のためのコマンドだけ載せておきます。コマンド実行の結果などは隠しておくので適宜確認してください。


コンフリクト解消画面

キーマップ 操作
<leader>hd コンフリクト解消画面の表示
<leader>hc コンフリクト解消画面を閉じる
<leader>co マージ先の変更を取り込む
<leader>ct マージ元の変更を取り込む
<leader>cb 両方のコンフリクトを取り込まない(まず選ばない)
<leader>ca 両方のコンフリクトを取り込む(まず選ばない)
コンフリクト解消画面での、各種コマンドの実行結果


<leader>coを押下した時:左側(マージ先)の変更を取り込む


<leader>ctを押下した時:右側(マージ元)の変更を取り込む


<leader>cbを押下した時:両方のコンフリクトを取り込まない


<leader>caを押下した時:両方のコンフリクトを取り込む

:h diffview-merge-toolの説明

READMEにはコンフリクト解消に関することはあまり書かれてないのですが、:h diffview-merge-toolを実行すると説明を見ることができます。説明の一部を載せます。

        You can jump between conflict markers with `]x` and `[x`. This works
        from the file panel as well.

         Additionally there are mappings for operating directly on the conflict
        markers:
          • `<leader>co`: Choose the OURS version of the conflict.
          • `<leader>ct`: Choose the THEIRS version of the conflict.
          • `<leader>cb`: Choose the BASE version of the conflict.
          • `<leader>ca`: Choose all versions of the conflict (effectively
            just deletes the markers, leaving all the content).
          • `dx`: Choose none of the versions of the conflict (delete the
            conflict region).

そのほかにも<leader>hfで単一のファイルの変更履歴を追えるようにすることもできます。画面下部がgitの履歴で、上部のパネルの左が編集前、右が編集後になっています。単一のファイルに注目して編集履歴を追えるのは便利かなと思います。


単一のファイルに注目して変更履歴を追う

diffview.nvimの設定
~/.config/nvim/lua/plugins/coding-diffview.lua
return {
  "sindrets/diffview.nvim",
  config = function ()
    require("diffview").setup()
  end,
  lazy = false,
  keys = {
    {mode = "n", "<leader>hh", "<cmd>DiffviewOpen HEAD~1<CR>", desc = "1つ前とのdiff"},
    {mode = "n", "<leader>hf", "<cmd>DiffviewFileHistory %<CR>", desc = "ファイルの変更履歴"},
    {mode = "n", "<leader>hc", "<cmd>DiffviewClose<CR>", desc = "diffの画面閉じる"},
    {mode = "n", "<leader>hd", "<cmd>Diffview<CR>", desc = "コンフリクト解消画面表示"},
  },
}

plugins: neovimの前面にターミナルを表示

https://github.com/akinsho/toggleterm.nvim

toggletermをインストールすると、nvimを開きながらターミナルなどを開くことができます。設定をdirection = "float"にすると、<leader>ttで前面にターミナルを出すことができます。また<leader>lglazygitを出し、git操作が可能です。qで前面に出したlazygitを閉じることができます。


lazygitをfloatでneovimの前面に表示した状態

toggleterm.nvimの設定
~/.config/nvim/lua/plugins/toggleterm.lua
return {
  'akinsho/toggleterm.nvim',
  version = "*",
  config = function()
    require("toggleterm").setup{
      -- size can be a number or function which is passed the current terminal
      size = 20,
      open_mapping = [[<leader>tt]], -- or { [[<c-\>]], [[<c-¥>]] } if you also use a Japanese keyboard.
      hide_numbers = true, -- hide the number column in toggleterm buffers
      shade_filetypes = {},
      autochdir = false, -- when neovim changes it current directory the terminal will change it's own when next it's opened
      shade_terminals = true, -- NOTE: this option takes priority over highlights specified so if you specify Normal highlights you should set this to false
      start_in_insert = true,
      insert_mappings = true, -- whether or not the open mapping applies in insert mode
      terminal_mappings = true, -- whether or not the open mapping applies in the opened terminals
      persist_size = true,
      persist_mode = true, -- if set to true (default) the previous terminal mode will be remembered
      direction = 'float',
      close_on_exit = true, -- close the terminal window when the process exits
      clear_env = false, -- use only environmental variables from `env`, passed to jobstart()
       -- Change the default shell. Can be a string or a function returning a string
      shell = vim.o.shell,
      auto_scroll = true, -- automatically scroll to the bottom on terminal output
      -- This field is only relevant if direction is set to 'float'
      float_opts = {
        border = 'curved',
        winblend = 3,
        title_pos = 'center',
      },
      winbar = {
        enabled = false,
        name_formatter = function(term) --  term: Terminal
          return term.name
        end
      },
    }
    -- ここでlazygitを開く設定を追加している
    local Terminal  = require('toggleterm.terminal').Terminal
    local lazygit = Terminal:new({ cmd = "lazygit", hidden = true })

    function _lazygit_toggle()
      lazygit:toggle()
    end

    vim.api.nvim_set_keymap("n", "<leader>lg", "<cmd>lua _lazygit_toggle()<CR>", {noremap = true, silent = true})

  end
}

キーマップ 操作
<leader>tt ターミナルを表示する
<leader>lg lazygitを表示する

plugins: コメントアウト

コメントアウトのトグルにはComment.nvimを使います。

https://github.com/numToStr/Comment.nvim

comment.nvimの設定
~/.config/nvim/lua/plugins/comment.lua
return {
    'numToStr/Comment.nvim',
}
キーマップ 操作
gcc Normal modeにて単一の行のコメントアウトをトグル
gc Visual modeにて複数行のコメントアウトをトグル

plugins: LSP

コーディングする際に必須となる機能を追加します。LSPとはLanguage Server Protocolの略称で、コーディング支援をエディタ・言語に依存しない形に切り出して広く使いやすいようにした仕組みです。詳しくは以下の記事を参照してください。

https://zenn.dev/vim_jp/articles/40b2c348386f88

以下の記事を参考に導入して、合計4つのプラグインを入れます。

https://zenn.dev/botamotch/articles/21073d78bc68bf

LSP.1: LSP管理

LSPの設定とサーバー管理のためのプラグインを3つ入れます。

https://github.com/neovim/nvim-lspconfig
https://github.com/williamboman/mason.nvim?tab=readme-ov-file
https://github.com/williamboman/mason-lspconfig.nvim

私は、主にPythonTypeScriptを使うのでそれらのLSPサーバーを設定します。これらの設定をすることで、定義ジャンプや変数の定義のコメントを見れるようになります。加えて、誤字も確認できるようにtypos_lspという設定も追加しています。

LSP周りの3つの設定ファイル

以下の3つのファイルを起きます。nvim-lspconfig.luamason-nvim.luaは設定を読み込むためだけに利用しています。

~/.config/nvim/lua/plugins/nvim-lspconfig.lua
return {
  "neovim/nvim-lspconfig",
  version = "*",
  lazy = false,
  config = function()
    local lspconfig = require('lspconfig')
  end,
}
~/.config/nvim/lua/plugins/mason-nvim.lua
return {
  "williamboman/mason.nvim",
  version = "*",
  lazy = false,
  config = function()
    require("mason").setup()
  end,
}
~/.config/nvim/lua/plugins/nvim-lspconfig.lua
return {
  "williamboman/mason-lspconfig.nvim",
  version = "*",
  lazy = false,
  config = function()
    local lsp_servers = { "lua_ls", "pyright", "ruff", "ts_ls", "html", "yamlls", "jsonls" }
    local diagnostics = { "typos_lsp" }
    require("mason-lspconfig").setup {
      ensure_installed = vim.tbl_flatten({ lsp_servers, diagnostics }),
    }
    require("mason-lspconfig").setup_handlers {
      function (server_name)
        local nvim_lsp = require("lspconfig")
        require("lspconfig").typos_lsp.setup {}

        require("lspconfig").pyright.setup {
          root_dir = nvim_lsp.util.root_pattern(".venv"),
          -- cmd = { "bash", "-c", "source ./.venv/bin/activate"},
          settings = {
            python = {
              -- 仮想環境のルートパス
              venvPath = ".",
              -- 仮想環境のフォルダ名
              -- venv = ".venv",
              pythonPath = "./.venv/bin/python",
              -- analysis = {
              --   extraPaths = {"."},
              --   autoSearchPaths = true,
              --   useLibraryCodeForTypes = true
              -- }
            }
          }
        }
      end,
    }
    -- カーソル下の変数の情報
    vim.keymap.set('n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>')
    -- 定義ジャンプ
    vim.keymap.set('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>')
    -- 定義ジャンプ後に下のファイルに戻る
    vim.keymap.set('n', 'gt', '<C-t>')
    -- 改行やインデントなどのフォーマット
    vim.keymap.set('n', 'gf', '<cmd>lua vim.lsp.buf.formatting()<CR>')
    -- カーソル下の変数をコード内で参照している箇所
    vim.keymap.set('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>')
    -- 変数名のリネーム
    vim.keymap.set('n', 'gn', '<cmd>lua vim.lsp.buf.rename()<CR>')

  end,
}
キーマップ 操作
K カーソル下の変数などの定義を表示
gd 定義先へジャンプ
gt ジャンプした定義先から戻る


Kでカーソル下の定義を表示した画面

LSP.2: 補完

次に、コーディングしながら補完候補を出すためのプラグインを入れます。

https://github.com/hrsh7th/nvim-cmp

nvim-cmpの設定

公式サイトにはなかったため、lazy.nvimでの設定は次のサイトを参考にしました。
https://www.reddit.com/r/neovim/comments/1124tv8/help_setting_up_nvimcmp_with_lazynvim/?rdt=41409

~/.config/nvim/lua/plugins/lsp-nvim-cmp.lua
return {
	"hrsh7th/nvim-cmp",
	dependencies = {
		"hrsh7th/cmp-nvim-lsp",
		"hrsh7th/cmp-nvim-lua",
		"hrsh7th/cmp-buffer",
		"hrsh7th/cmp-path",
		"hrsh7th/cmp-cmdline",
		"saadparwaiz1/cmp_luasnip",
		"L3MON4D3/LuaSnip",
	},
  config = function ()
    local cmp = require("cmp")
    local types = require('cmp.types')
	  vim.opt.completeopt = { "menu", "menuone", "noselect" }
    cmp.setup({
      
      snippet = {
        expand = function(args)
          require("luasnip").lsp_expand(args.body) -- For `luasnip` users.
        end,
      },
      window = {
        -- completion = cmp.config.window.bordered(),
        -- documentation = cmp.config.window.bordered(),
      },
      mapping = cmp.mapping.preset.insert({
        -- <C-n>: down, <C-p>: up
        ['<Tab>'] = cmp.mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Insert }),
        ['<S-Tab>'] = cmp.mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Insert }),
        ["<C-b>"] = cmp.mapping.scroll_docs(-4),
        ["<C-f>"] = cmp.mapping.scroll_docs(4),
        ["<C-l>"] = cmp.mapping.complete(),
        ["<C-e>"] = cmp.mapping.abort(),
        ["<CR>"] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
      }),
      sources = cmp.config.sources({
        { name = "nvim_lsp" },
        { name = "nvim_lua" },
        -- { name = "luasnip" }, -- For luasnip users.
        -- { name = "orgmode" },
      }, {
        { name = "buffer" },
        { name = "path" },
      }),
      })
  end
}

<Tab>でも候補を選べるようにキーマップを設定しました。

キーマップ 操作
<C-p> 上の補完候補に移動(デフォルト)
<S-Tab> 上の補完候補に移動
<C-n> 下の補完候補に移動(デフォルト)
<Tab> 下の補完候補に移動
<C-b> 補完候補のドキュメントを上にスクロール(ドキュメントが短い場合は効かないように見える)
<C-f> 補完候補のドキュメントを下にスクロール(ドキュメントが短い場合は効かないように見える)
<C-l> 補完候補が出てない時に出すようにする
<C-e> 補完候補を閉じる
<CR> 補完候補から選択


nvim-compで補完が動いている様子

plugins: インデントの色付け

インデントに色をつけるプラグインです。注意点は言語ごとに設定が必要なところです。ただ、luaだけは何もせずにインデントに色がつきました。

https://github.com/lukas-reineke/indent-blankline.nvim


インデントに色がついている様子(Python)


インデントに色がついている様子(TypeScript)


インデントに色がついている様子(Lua)

indent-blankline.nvimの設定
~/.config/nvim/lua/plugins/coding-indent.lua
return {
  'lukas-reineke/indent-blankline.nvim',
  dependencies = {
    'HiPhish/rainbow-delimiters.nvim',
    'nvim-treesitter/nvim-treesitter',
  },
  config = function()
    require('nvim-treesitter.configs').setup({
      ensure_installed = { "python", "typescript" },
      highlight = {
        enable = true,
      }
    })
    local highlight = {
      "RainbowRed",
      "RainbowYellow",
      "RainbowBlue",
      "RainbowOrange",
      "RainbowGreen",
      "RainbowViolet",
      "RainbowCyan",
    }
    local hooks = require("ibl.hooks")
    -- create the highlight groups in the highlight setup hook, so they are reset
    -- every time the colorscheme changes
    hooks.register(hooks.type.HIGHLIGHT_SETUP, function()
      vim.api.nvim_set_hl(0, "RainbowRed", { fg = "#E06C75" })
      vim.api.nvim_set_hl(0, "RainbowYellow", { fg = "#E5C07B" })
      vim.api.nvim_set_hl(0, "RainbowBlue", { fg = "#61AFEF" })
      vim.api.nvim_set_hl(0, "RainbowOrange", { fg = "#D19A66" })
      vim.api.nvim_set_hl(0, "RainbowGreen", { fg = "#98C379" })
      vim.api.nvim_set_hl(0, "RainbowViolet", { fg = "#C678DD" })
      vim.api.nvim_set_hl(0, "RainbowCyan", { fg = "#56B6C2" })
    end)
    vim.g.rainbow_delimiters = { highlight = highlight }
    require("ibl").setup { scope = { highlight = highlight } }

    hooks.register(hooks.type.SCOPE_HIGHLIGHT, hooks.builtin.scope_highlight_from_extmark)
  end,
}

plugins: テーマ(colorscheme)

neovimの見た目もプラグインで変えることができます。「neovim colorscheme」などと検索するとトレンドなどを載せているサイトが出てきます。その中から以下のcatppuccinを選びました。見た目が柔らかい感じがするので良さげです。本当はJetBrainsに近いdarculaテーマを選びたかったのですが、あまり良さそうなのがなかったので一旦諦めました。

https://github.com/catppuccin/catppuccin

catppuccinの設定
~/.config/nvim/lua/plugins/colorscheme-catppuccin.lua
return {
  "catppuccin/nvim",
  name = "catppuccin",
  priority = 1000,
  -- enabled = false,
  config = function()
    require("catppuccin").setup({
      flavour = "frappe",
      integrations = {
       gitsigns = true,
       nvimtree = true,
      }
    })
    vim.cmd.colorscheme("catppuccin")
  end
}

plugins: IMEの設定

以下の記事を頼りにIMEを自動で切り替えるプラグインを入れます。これを入れてjjkkではなく<C-g>でInsert modeを抜けるようにすると便利です。

https://zenn.dev/tunacan/articles/c6a899a7dc2b67

事前にim-selectのインストールが必要です。

$ brew tap daipeihust/tap
$ brew install im-select

https://github.com/keaising/im-select.nvim

im-selectの設定
~/.config/nvim/lua/plugins/util-im-select.lua
return {
  "keaising/im-select.nvim",
  config = function()
    require("im_select").setup({
      -- デフォルトのIME
      default_im_select  = "com.apple.keylayout.ABC",

      -- 以下のイベント時に、デフォルトのIMEになる
      set_default_events = {"VimEnter", "InsertEnter", "InsertLeave"},

      -- 以下のイベント時に、前回使われていたIMEになる(無効にしている)
      set_previous_events = {},
    })
  end,
}

おわりに

これでひとまずneovimの環境構築は終わりです。構築しつつ触ってみて感じたデメリットとメリットと所感をまとめます。

デメリット

デメリットは以下の3点を感じました。

1つ目は、neovimとプラグインの開発が今なお活発であるため、環境構築が大変な点です。プラグインなどの概念に慣れてしまえば大変では無いのですが、初学者からすると「あの機能も・この機能も必要なのか・・・」のようにIDEとneovimに関するプラグインへのある程度の理解が必要なため大変だと思います。

2つ目は、neovimに関するコミュニティ・技術書・記事もたくさんあるのですが、欲しい情報にアクセスするのがプログラミング言語に比べると慣れるまで大変という点です。特にプラグインのインストールに関して、READMEに書いてあるインストール方法をそのままコピペでは利用できない場合もあり、「自分の使っているプラグインマネージャーだとどう書くんだ・・・?」となることが多々ありました。

3つ目に、よく言われている操作がわかりづらいと言う点です。ただ、これは他の2つに比べると慣れの問題だと思うので、触っていくとデメリットには感じないようになりました。どちらかというとneovimに慣れてしまうとターミナルなどの操作においてjkを押しそうになってしまうところが大変です。zshにvimモードなるものがあるらしいので気になっています。

メリット

メリットは以下の4点を感じました。

1つ目は、設定をdotfileで管理できる点です。これは私がneovimを使っていて1番気に入っている点です。WezTermstarshipなど、ターミナル関連のアプリケーションは~/.config以下で設定を管理できることが多いです。これらをdotfileと言うGitリポジトリで、管理して育てていくのがとてもエンジニアっぽくていいなと思っています。

2つ目は、コマンドの操作やコーディングをキーボードのみでおおよそ完結できる点です。今まで私はMacでJetBrainsとターミナルとGitクライアントなどをそれぞれフルスクリーンで表示しつつ、画面を左右に切り替えながら(=トラックパッドを3本指で左右にスワイプしながら)使っていました。それらの作業がターミナル1つで完結するのはとても良いなと思います。ただし、ブラウザなど他のアプリを使っているので、全ての作業がターミナルだけで完結するわけでは無いです。

3つ目は、IDEの仕組みを知れたり、IDEの便利さを改めて感じれた点です。ファイルのタブや曖昧検索なども単一の機能として独立した機能として存在していて、それらがうまく組み合わさっていることで、普段使っているIDEは動いているんだなと改めて感じました。加えて、普段何気なく使っているIDEの機能である補完に関して、LSPと言うプロトコルがあってその上で動いていることを知れたのはよかったです。

4つ目は、vimが怖く無くなった点です。今までターミナルでのエディタはnanoしか使っておらずたまに意図せずvimの利用を迫られた時にドギマギして:wqなどを押してなんとかしていました。その漠然としたvimへの恐怖感は無くなったかなと思います。

所感

ここまで書いておいてなんなのですが、neovimは手放しに他人におすすめはできないと思いました。私にとってneovimを使うメリットはデメリットを超えていますが、それが当てはまらない場合もあるからです。neovimは操作にある程度の慣れが必要で、そもそも環境を作るのにも一苦労します。加えて設定をテキストファイルで管理できることに楽しみを覚えない場合には、JetBrainsや他のIDEを使ったほうが良いかと思います。ただ、慣れるとコーディングの大体の操作がターミナルだけで完結するのでとても良いと思います。

あと、最初に使うエディタではないと思いました。特定の言語に精通し、自分に合ったJetBrainsなどのIDE/エディタを見つけた上で、そのエディタに使いたい機能が無い場合や特定のやりたいことができない場合に、neovimに移るのが良いと思いました。最初にJetBrainsなどのIDEを使うと、IDEの補完機能への解像度や、知らない機能がある便利さに気付ける点において良いなと思っています。

2024年9月の末頃からneovimを触り始めて、3日ほどでhjklなどの基本操作は慣れました。neovimの環境は1日1~2時間程度で作っていき、トータル40~50時間ほど(実期間は1ヶ月くらい・・・)で最低限の機能は持たせられたかなという感じです。ただ、よくよく考えると最低限使える機能を持たせるのに1ヶ月もかかるって今の世の中だと中々無いと思うのですが、その大変さも相まってneovimのことがより好きになりました。

プラグインを随時追加していきつつ、これからも使っていきます!

参考

https://qiita.com/naomaruru/items/95e2ed811b8b8c8bb053
https://qiita.com/RepublicOfKorokke/items/0a267b53156cd8a64a59
https://zenn.dev/peloeil/articles/a9c579ba24b225
https://qiita.com/0829/items/c4ba0e7aa274c2d0b34f
https://qiita.com/mutsuki15/items/4abce9d107304d3dd120
https://zenn.dev/yutakatay/articles/vim-fuzzy-finder
https://qiita.com/hachi8833/items/7beeee825c11f7437f54
https://zenn.dev/vim_jp/articles/43d021f461f3a4
https://qiita.com/ysmb-wtsg/items/79be0d97711eb49f5e82#4-completion
https://zenn.dev/botamotch/articles/4ef893e0d4cd40
https://qiita.com/naoki96/items/14cbdf69bb2176f45c90
https://github.com/jackMort/ChatGPT.nvim?tab=readme-ov-file
https://kamino.hatenablog.com/entry/iterm2_neovim
https://namileriblog.com/mac/lazy_nvim_lsp/#i-19
https://zenn.dev/misora/articles/d0e8c244f2f4db
https://zenn.dev/futsuuu/articles/3b74a8acec166e
https://zenn.dev/sa2knight/articles/e0a1b2ee30e9ec22dea9
https://zenn.dev/paiza/articles/9ca689a0365b05
https://zenn.dev/charlie/articles/421bd209099ea5
https://github.com/miiton/Cica
https://qiita.com/uhooi/items/95435fdec0f090f7b3ce#nvim-cmp

GitHubで編集を提案

Discussion