TL;DR

  • Neovim + LazyVim でファイルツリー・タブ・LSP・ファジー検索が最初から揃う
  • neo-tree をカスタマイズすると、ツリーでカーソル移動するだけでプレビュー表示される VS Code 的な体験が得られる
  • microhelix も試したが、プロジェクト単位のコード閲覧には Neovim が一番完成度が高い

背景

ターミナルで VS Code のようにコードを閲覧・編集したかった。いくつかのツールを試した結果:

ツール 評価 理由
bat ファイル単体なら良い プロジェクト単位の操作ができない
micro 編集は直感的 filemanager プラグインの完成度が低い
helix LSP内蔵で設定不要 ファイルツリーが未実装
Neovim + LazyVim 採用 ファイルツリー・タブ・LSP が全部入り

セットアップ

インストール

brew install neovim ripgrep fd

LazyVim の導入

# 既存設定があればバックアップ
mv ~/.config/nvim ~/.config/nvim.bak 2>/dev/null

# LazyVim starter をクローン
git clone https://github.com/LazyVim/starter ~/.config/nvim

nvim で起動するとプラグインが自動インストールされる。

基本操作

ナビゲーション

キー 操作
Space リーダーメニュー(VS Code の Cmd+Shift+P 的)
Space + e ファイルツリー表示
Space + f + f ファイル検索(Cmd+P 的)
Space + s + g 全文検索(Cmd+Shift+F 的)
Ctrl + h 左ウィンドウ(ツリー)にフォーカス
Ctrl + l 右ウィンドウ(エディタ)にフォーカス

エディタ操作

キー 操作
i 編集モードに入る
Esc or Ctrl+c 編集モードを抜ける
Ctrl+f / Ctrl+b 1ページ下/上
Ctrl+d / Ctrl+u 半ページ下/上
g + d 定義へジャンプ
K ホバー情報
:wqa 全保存して終了
:qa 全終了(保存なし)

neo-tree カスタマイズ: VS Code 的プレビュー体験

デフォルトの neo-tree は Space + e でツリーが開閉するだけ。以下をカスタマイズした:

  1. 起動時にツリーを常時表示
  2. ツリーでカーソル移動するだけでファイル内容をプレビュー
  3. プレビュー用バッファを1つだけ使い回す(タブが増殖しない)
  4. Enter で正式にファイルを開く(プレビューから確定)
  5. Enter でフォルダも展開/折りたたみ可能

設定ファイル

~/.config/nvim/lua/plugins/neo-tree.lua:

local preview_buf = nil

return {
  {
    "nvim-neo-tree/neo-tree.nvim",
    opts = {
      close_if_last_window = false,
      filesystem = {
        follow_current_file = { enabled = true },
        use_libuv_file_watcher = true,
      },
      window = {
        mappings = {
          ["<cr>"] = function(state)
            local node = state.tree:get_node()
            if node.type == "file" then
              preview_buf = nil
              vim.cmd("wincmd l")
              vim.cmd("edit " .. vim.fn.fnameescape(node.path))
            elseif node.type == "directory" then
              require("neo-tree.sources.filesystem.commands").toggle_node(state)
            end
          end,
        },
      },
    },
    init = function()
      -- 起動時にツリーを自動表示
      vim.api.nvim_create_autocmd("VimEnter", {
        callback = function()
          vim.cmd("Neotree show")
        end,
      })

      -- ツリー内でカーソル移動したらプレビュー表示
      vim.api.nvim_create_autocmd("CursorMoved", {
        pattern = "neo-tree*",
        callback = function()
          local ok, manager = pcall(require, "neo-tree.sources.manager")
          if not ok then return end
          local state = manager.get_state("filesystem")
          if not state or not state.tree then return end
          local node = state.tree:get_node()
          if not node or node.type ~= "file" then return end

          local cur_win = vim.api.nvim_get_current_win()
          local target_win = nil
          for _, win in ipairs(vim.api.nvim_list_wins()) do
            if win ~= cur_win then
              target_win = win
              break
            end
          end
          if not target_win then return end

          local cur_buf = vim.api.nvim_win_get_buf(target_win)
          local cur_name = vim.api.nvim_buf_get_name(cur_buf)
          if cur_name == node.path then return end

          if preview_buf and vim.api.nvim_buf_is_valid(preview_buf)
            and preview_buf ~= cur_buf then
            pcall(vim.api.nvim_buf_delete, preview_buf, { force = true })
          end

          vim.api.nvim_win_call(target_win, function()
            vim.cmd("edit " .. vim.fn.fnameescape(node.path))
          end)
          preview_buf = vim.api.nvim_win_get_buf(target_win)
        end,
      })
    end,
  },
}

ポイント

  • preview_buf 変数で前回のプレビューバッファを追跡し、新しいファイルを開く前に削除する。これでタブが無限に増えない
  • CursorMoved autocmd で neo-tree バッファ内のカーソル移動を検知。after_render イベントではカーソル移動を拾えない
  • Enter は node.type で分岐。ファイルなら右ペインで開き、ディレクトリなら展開/折りたたみ

まとめ

Vim のモーダル操作は慣れが必要だが、LazyVim + neo-tree カスタマイズで「ツリーからファイルを選んでプレビュー → Enter で確定」という VS Code 的なワークフローはターミナル内で実現できる。