Neovim

GitHub概要

neovim/neovim

Vim-fork focused on extensibility and usability

ホームページ:https://neovim.io
スター91,915
ウォッチ984
フォーク6,251
作成日:2014年1月31日
言語:Vim Script
ライセンス:Other

トピックス

apicluaneovimnvimtext-editorvim

スター履歴

neovim/neovim Star History
データ取得日時: 2025/8/13 01:43

開発ツール

Neovim

概要

NeovimはVimの現代的なフォークとして開発されたテキストエディタです。Vimの哲学を継承しながら、Luaスクリプト、内蔵LSP、Tree-sitterなどの現代的機能を追加し、より拡張性と保守性を向上させています。

詳細

Neovimは2014年にThiago de Arruda氏によってVimのフォークとして開始されました。Vimの100%近くをBram Moolenaar氏1人で維持している状況と比較し、Neovimははるかに広い貢献者ベースを持ち、活発な開発コミュニティによって支えられています。

Neovimの最大の革新は、Luaをファーストクラスのスクリプト言語として導入したことです。従来のVimscriptに加えてLuaによる設定とプラグイン開発が可能になり、より高速で保守性の高い拡張機能を作成できます。内蔵LSP(Language Server Protocol)サポート、Tree-sitterによる高度なシンタックスハイライト、非同期処理の改善などが主要な特徴です。

「ヘッドレス」コアエディタ設計により、他のプログラムとの統合が容易になり、IDEやブラウザとの連携も可能です。Vimとの後方互換性を保ちながら、現代的な開発環境に適応した機能を提供しています。ターミナルやSSH経由のリモートサーバーでの作業にも対応し、モーダル編集による効率的なワークフローを実現します。

メリット・デメリット

メリット

  • 現代的なアーキテクチャ: Luaスクリプト、内蔵LSP、Tree-sitterサポート
  • 活発な開発コミュニティ: Vimより広い貢献者ベースによる継続的な改善
  • 高性能: 最適化された軽量設計により高速で効率的な動作
  • 優れた拡張性: LuaとVimscriptの両方による柔軟なカスタマイズ
  • ヘッドレス設計: 他のプログラムやIDEとの統合が容易
  • Vim互換性: Vimで学んだ知識をそのまま活用可能
  • リモート作業対応: ターミナルベースでSSH環境に最適

デメリット

  • 急峻な学習曲線: モーダル編集の習得が困難
  • プラグイン依存: 高度な機能には多くのプラグインが必要
  • 混乱する文書: LuaとVimscriptの混在により情報が分散
  • 設定の複雑さ: 最適な環境構築には深い知識が必要
  • レガシー機能: Vimからの多くの古い機能を継承
  • ターミナル制限: GUIでの機能が限定的
  • 初期設定: 基本的な使用にも相当な設定が必要

主要リンク

書き方の例

init.lua基本設定

-- ~/.config/nvim/init.lua - Lua設定
-- 基本設定
vim.opt.number = true              -- 行番号表示
vim.opt.relativenumber = true      -- 相対行番号表示
vim.opt.mouse = 'a'               -- マウス有効
vim.opt.ignorecase = true         -- 大文字小文字を無視
vim.opt.smartcase = true          -- 大文字が含まれる場合は区別
vim.opt.hlsearch = false          -- 検索ハイライト無効
vim.opt.wrap = false              -- 行の折り返し無効
vim.opt.breakindent = true        -- インデント継続
vim.opt.tabstop = 4               -- タブ幅
vim.opt.shiftwidth = 4            -- シフト幅
vim.opt.expandtab = true          -- タブをスペースに変換

-- 見た目の設定
vim.opt.termguicolors = true      -- True colorサポート
vim.opt.signcolumn = 'yes'        -- サインカラム常時表示
vim.opt.cmdheight = 1             -- コマンド行の高さ
vim.opt.scrolloff = 8             -- スクロールオフセット
vim.opt.sidescrolloff = 8         -- 横スクロールオフセット

-- ファイル設定
vim.opt.backup = false            -- バックアップ無効
vim.opt.writebackup = false       -- 書き込み時バックアップ無効
vim.opt.undofile = true           -- アンドーファイル有効
vim.opt.swapfile = false          -- スワップファイル無効

-- キーマップ設定
vim.g.mapleader = ' '             -- リーダーキーをスペースに設定

-- 基本的なキーマップ
local keymap = vim.keymap.set
keymap('n', '<leader>w', ':w<CR>', { desc = 'Save file' })
keymap('n', '<leader>q', ':q<CR>', { desc = 'Quit' })
keymap('n', '<leader>h', ':nohlsearch<CR>', { desc = 'Clear search highlight' })
keymap('n', '<C-h>', '<C-w>h', { desc = 'Move to left window' })
keymap('n', '<C-j>', '<C-w>j', { desc = 'Move to bottom window' })
keymap('n', '<C-k>', '<C-w>k', { desc = 'Move to top window' })
keymap('n', '<C-l>', '<C-w>l', { desc = 'Move to right window' })

-- ビジュアルモードでの移動
keymap('v', 'J', ":m '>+1<CR>gv=gv", { desc = 'Move selected lines down' })
keymap('v', 'K', ":m '<-2<CR>gv=gv", { desc = 'Move selected lines up' })

-- 検索時の中央表示
keymap('n', 'n', 'nzzzv')
keymap('n', 'N', 'Nzzzv')

-- システムクリップボード
keymap('n', '<leader>y', '"+y', { desc = 'Yank to system clipboard' })
keymap('v', '<leader>y', '"+y', { desc = 'Yank to system clipboard' })
keymap('n', '<leader>p', '"+p', { desc = 'Paste from system clipboard' })

プラグイン管理(lazy.nvim)

-- ~/.config/nvim/lua/plugins/init.lua
return {
  -- カラースキーム
  {
    'folke/tokyonight.nvim',
    lazy = false,
    priority = 1000,
    config = function()
      vim.cmd([[colorscheme tokyonight]])
    end,
  },

  -- ファイルエクスプローラー
  {
    'nvim-tree/nvim-tree.lua',
    dependencies = { 'nvim-tree/nvim-web-devicons' },
    config = function()
      require('nvim-tree').setup{}
      vim.keymap.set('n', '<leader>e', ':NvimTreeToggle<CR>', { desc = 'Toggle file explorer' })
    end,
  },

  -- ファジーファインダー
  {
    'nvim-telescope/telescope.nvim',
    dependencies = { 'nvim-lua/plenary.nvim' },
    config = function()
      local telescope = require('telescope')
      telescope.setup{}
      
      local builtin = require('telescope.builtin')
      vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Find files' })
      vim.keymap.set('n', '<leader>fg', builtin.live_grep, { desc = 'Live grep' })
      vim.keymap.set('n', '<leader>fb', builtin.buffers, { desc = 'Find buffers' })
      vim.keymap.set('n', '<leader>fh', builtin.help_tags, { desc = 'Help tags' })
    end,
  },

  -- LSP設定
  {
    'neovim/nvim-lspconfig',
    dependencies = {
      'williamboman/mason.nvim',
      'williamboman/mason-lspconfig.nvim',
    },
    config = function()
      require('mason').setup()
      require('mason-lspconfig').setup{
        ensure_installed = { 'lua_ls', 'pyright', 'tsserver' }
      }
      
      local lspconfig = require('lspconfig')
      
      -- Lua LSP
      lspconfig.lua_ls.setup{
        settings = {
          Lua = {
            diagnostics = {
              globals = {'vim'}
            }
          }
        }
      }
      
      -- Python LSP
      lspconfig.pyright.setup{}
      
      -- TypeScript LSP
      lspconfig.tsserver.setup{}
      
      -- LSPキーマップ
      vim.keymap.set('n', 'gd', vim.lsp.buf.definition, { desc = 'Go to definition' })
      vim.keymap.set('n', 'K', vim.lsp.buf.hover, { desc = 'Hover documentation' })
      vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, { desc = 'Go to implementation' })
      vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, { desc = 'Rename' })
      vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, { desc = 'Code action' })
    end,
  },

  -- 補完エンジン
  {
    'hrsh7th/nvim-cmp',
    dependencies = {
      'hrsh7th/cmp-nvim-lsp',
      'hrsh7th/cmp-buffer',
      'hrsh7th/cmp-path',
      'hrsh7th/cmp-cmdline',
      'L3MON4D3/LuaSnip',
      'saadparwaiz1/cmp_luasnip',
    },
    config = function()
      local cmp = require('cmp')
      local luasnip = require('luasnip')
      
      cmp.setup{
        snippet = {
          expand = function(args)
            luasnip.lsp_expand(args.body)
          end,
        },
        mapping = cmp.mapping.preset.insert({
          ['<C-d>'] = cmp.mapping.scroll_docs(-4),
          ['<C-f>'] = cmp.mapping.scroll_docs(4),
          ['<C-Space>'] = cmp.mapping.complete(),
          ['<C-e>'] = cmp.mapping.abort(),
          ['<CR>'] = cmp.mapping.confirm({ select = true }),
          ['<Tab>'] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_next_item()
            elseif luasnip.expand_or_jumpable() then
              luasnip.expand_or_jump()
            else
              fallback()
            end
          end, { 'i', 's' }),
        }),
        sources = cmp.config.sources({
          { name = 'nvim_lsp' },
          { name = 'luasnip' },
        }, {
          { name = 'buffer' },
        })
      }
    end,
  },

  -- Tree-sitter
  {
    'nvim-treesitter/nvim-treesitter',
    build = ':TSUpdate',
    config = function()
      require('nvim-treesitter.configs').setup{
        ensure_installed = {
          'lua', 'python', 'javascript', 'typescript', 'html', 'css'
        },
        highlight = {
          enable = true,
        },
        indent = {
          enable = true,
        },
      }
    end,
  },

  -- ステータスライン
  {
    'nvim-lualine/lualine.nvim',
    dependencies = { 'nvim-tree/nvim-web-devicons' },
    config = function()
      require('lualine').setup{
        options = {
          theme = 'tokyonight'
        }
      }
    end,
  },
}

カスタム関数とコマンド

-- ~/.config/nvim/lua/utils.lua
local M = {}

-- 末尾の空白を削除する関数
function M.trim_whitespace()
  local save_cursor = vim.fn.getpos('.')
  vim.cmd([[%s/\s\+$//e]])
  vim.fn.setpos('.', save_cursor)
end

-- 現在の行を複製する関数
function M.duplicate_line()
  local line = vim.fn.getline('.')
  vim.fn.append('.', line)
end

-- プロジェクトルートを取得する関数
function M.get_project_root()
  local markers = {'.git', 'package.json', 'Cargo.toml', 'pyproject.toml'}
  local current_file = vim.fn.expand('%:p:h')
  
  for _, marker in ipairs(markers) do
    local root = vim.fn.finddir(marker, current_file .. ';')
    if root ~= '' then
      return vim.fn.fnamemodify(root, ':h')
    end
  end
  
  return current_file
end

-- ファイルを新しい名前で保存する関数
function M.save_as()
  local current_file = vim.fn.expand('%:t')
  local new_name = vim.fn.input('New filename: ', current_file)
  if new_name ~= '' and new_name ~= current_file then
    vim.cmd('saveas ' .. new_name)
  end
end

-- 設定ファイルを素早く編集する関数
function M.edit_config()
  vim.cmd('edit ~/.config/nvim/init.lua')
end

-- コマンドの登録
vim.api.nvim_create_user_command('TrimWhitespace', M.trim_whitespace, {})
vim.api.nvim_create_user_command('DuplicateLine', M.duplicate_line, {})
vim.api.nvim_create_user_command('SaveAs', M.save_as, {})
vim.api.nvim_create_user_command('EditConfig', M.edit_config, {})

-- キーマップの設定
vim.keymap.set('n', '<leader>tw', M.trim_whitespace, { desc = 'Trim whitespace' })
vim.keymap.set('n', '<leader>dl', M.duplicate_line, { desc = 'Duplicate line' })
vim.keymap.set('n', '<leader>sa', M.save_as, { desc = 'Save as' })
vim.keymap.set('n', '<leader>ec', M.edit_config, { desc = 'Edit config' })

return M

オートコマンドとイベント

-- ~/.config/nvim/lua/autocmds.lua
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd

-- 一般的なオートコマンド
local general = augroup('General', { clear = true })

autocmd('TextYankPost', {
  group = general,
  pattern = '*',
  callback = function()
    vim.highlight.on_yank({ higroup = 'Visual', timeout = 200 })
  end,
  desc = 'Highlight yanked text',
})

autocmd('BufReadPost', {
  group = general,
  pattern = '*',
  callback = function()
    local mark = vim.api.nvim_buf_get_mark(0, '"')
    local lcount = vim.api.nvim_buf_line_count(0)
    if mark[1] > 0 and mark[1] <= lcount then
      pcall(vim.api.nvim_win_set_cursor, 0, mark)
    end
  end,
  desc = 'Restore cursor position',
})

-- ファイルタイプ別設定
local filetypes = augroup('FileTypes', { clear = true })

autocmd('FileType', {
  group = filetypes,
  pattern = { 'python' },
  callback = function()
    vim.opt_local.tabstop = 4
    vim.opt_local.shiftwidth = 4
    vim.opt_local.expandtab = true
  end,
})

autocmd('FileType', {
  group = filetypes,
  pattern = { 'javascript', 'typescript', 'html', 'css', 'json' },
  callback = function()
    vim.opt_local.tabstop = 2
    vim.opt_local.shiftwidth = 2
    vim.opt_local.expandtab = true
  end,
})

autocmd('FileType', {
  group = filetypes,
  pattern = { 'lua' },
  callback = function()
    vim.opt_local.tabstop = 2
    vim.opt_local.shiftwidth = 2
    vim.opt_local.expandtab = true
  end,
})

-- 保存時の処理
local save_group = augroup('SaveGroup', { clear = true })

autocmd('BufWritePre', {
  group = save_group,
  pattern = { '*.py', '*.js', '*.ts', '*.lua', '*.html', '*.css' },
  callback = function()
    require('utils').trim_whitespace()
  end,
  desc = 'Trim whitespace on save',
})

-- LSP設定
local lsp_group = augroup('LspGroup', { clear = true })

autocmd('LspAttach', {
  group = lsp_group,
  callback = function(ev)
    -- LSPアタッチ時のキーマップ設定
    local opts = { buffer = ev.buf }
    vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
    vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
    vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
    vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
    vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
    vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
    vim.keymap.set('n', '<leader>f', function()
      vim.lsp.buf.format { async = true }
    end, opts)
  end,
})