nvimの開発環境構築メモ

VIMの開発環境を整えたかったので、その時のメモ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/13570fac-2ee1-87e2-43a7-08534d2ac6ac.png)

## Plugの導入

https://github.com/junegunn/vim-plug

## ターミナルの設定
テキストコピーを有効にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/f5cb6730-ae70-2af5-02e3-408e2c5390a6.png)

## NerdTree用のアイコン表示設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/9a57c9ad-1c94-c2b7-552f-d86599689580.png)

[Hack Nerdfont ダウンロード](https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/Hack.zip)

https://www.nerdfonts.com/font-downloads

## サーバー上で作業する場合
クリップボードにヤンクしたコードをコピーしたい時の準備
クライアントサイドでは不要
```
sudo apt-get install tmux
cat > ~/.tmux.conf
# クリップボード共有を有効化
set-option -g mouse on
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard -in"

# ステータスライン全体の背景を暗めに設定
set -g status-bg colour234
set -g status-fg white
```

## nvimの設定

```bash
mkdir -p ~/.config/nvim
cat > ~/.config/nvim/init.vim
以下の設定をコピペ
```

```vim
" ===== 基本設定 =====
set clipboard+=unnamedplus  " システムクリップボードを使用

" ===== 検索設定 =====
set ignorecase  " 大文字小文字を区別しない
set smartcase   " 検索文字に大文字がある場合は大文字小文字を区別
set incsearch   " インクリメンタルサーチ
set hlsearch    " 検索結果をハイライト

" ===== 表示設定 =====
set number      " 行番号を表示
set cursorline  " カーソル行をハイライト"
set wrap        " 長い行を折り返す
set textwidth=0 " 自動的な改行を無効化
set autowrite   " 自動保存
set showcmd     " 入力中のコマンドを表示
set showmode    " 現在のモードを表示
set showmatch   " 対応する括弧を表示
set expandtab   " タブをスペースに変換
set smartindent " スマートインデント
set autoindent      " 自動インデント
set shiftwidth=4    " インデント幅
set textwidth=78    " テキスト幅
set softtabstop=4   " ソフトタブストップ
set tabstop=4       " タブ幅
set fenc=UTF-8
set fencs=UTF-8,ISO-2022-JP,EUC-JP,CP932,SHIFT-JIS
set foldmethod=syntax  " 構文に基づいて折りたたみ
set foldlevelstart=0  " 99: 全て開いた状態 0: 全て閉じた状態"

" ===== バックアップとスワップファイルの設定 =====
set history=10000   " コマンドライン履歴を10000行保持
set nobackup        " バックアップファイルを作成しない
set nowritebackup   " ファイルの上書き前にバックアップを作成しない
set noswapfile      " スワップファイルを使用しない
set smarttab        " スマートタブ

" ===== プラグイン管理 =====
call plug#begin('~/.vim/plugged')

" Gitプラグイン
Plug 'tpope/vim-fugitive'
Plug 'airblade/vim-gitgutter'

" 自動ペアリングプラグイン
Plug 'windwp/nvim-autopairs'
Plug 'hrsh7th/nvim-cmp'
Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'hrsh7th/cmp-buffer'
Plug 'hrsh7th/cmp-path'
Plug 'hrsh7th/cmp-cmdline'

" 各種言語のシンタックスとインデントプラグイン
Plug 'sheerun/vim-polyglot'

" カラースキーム
Plug 'morhetz/gruvbox'

" Python固有のプラグイン
Plug 'hynek/vim-python-pep8-indent'

" 言語固有のプラグイン
Plug 'toyamarinyon/vim-swift'
Plug 'wuelnerdotexe/vim-astro'

" 強力な補完エンジン
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'neoclide/coc-git'

" リンティングと修正
Plug 'dense-analysis/ale'

" Tailwind CSS補完
Plug 'iamcco/coc-tailwindcss', {'do': 'yarn install --frozen-lockfile'}

" ファイルエクスプローラ
Plug 'preservim/nerdtree'

" ターミナル
Plug 'akinsho/toggleterm.nvim'

" JavaScript/JSXプラグイン
Plug 'mxw/vim-jsx'
Plug 'isRuslan/vim-es6'
Plug 'pangloss/vim-javascript'
Plug 'maxmellon/vim-jsx-pretty'
Plug 'alampros/vim-styled-jsx'
Plug 'moll/vim-node'

" ステータスライン
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'

" アイコン
Plug 'ryanoasis/vim-devicons'
Plug 'kristijanhusak/defx-icons'

" 自動保存
Plug 'Pocco81/auto-save.nvim'

Plug 'nvim-lua/plenary.nvim'
Plug 'nvim-treesitter/nvim-treesitter'
Plug 'hrsh7th/nvim-cmp' " Optional: For using slash commands and variables in the chat buffer
Plug 'nvim-telescope/telescope.nvim' " Optional: For working with files with slash commands
Plug 'stevearc/dressing.nvim' " Optional: Improves the default Neovim UI
Plug 'olimorris/codecompanion.nvim'

call plug#end()

" ===== プラグイン設定 =====

" Pydiction設定
let g:pydiction_location = '~/.vim/pydiction/complete-dict'
let g:PyFlakeOnWrite = 1
let g:PyFlakeCheckers = 'pep8,mccabe,pyflakes'
let g:PyFlakeDefaultComplexity=10
let g:PyFlakeRangeCommand = 'q'
let g:jedi#completions_command = "<C-k>"

" Astro設定
let g:astro_typescript = 'enable'

" Devicons設定
let g:webdevicons_enable_nerdtree = 1
let g:airline_powerline_fonts = 1
let g:airline#extensions#tabline#enabled = 1

" Copilot設定
let g:copilot_filetypes = { '*': v:true }

" JSX設定
let g:vim_jsx_pretty_highlight_close_tag = 1

" ===== ファイルタイプ固有の設定 =====
augroup FileTypeSpecificSettings
    autocmd!
    autocmd BufNewFile,BufRead *.jsx setlocal filetype=typescriptreact softtabstop=2 tabstop=2 shiftwidth=2
    autocmd BufNewFile,BufRead *.tsx,*.js,*ts,*.astro setlocal filetype=typescript softtabstop=2 tabstop=2 shiftwidth=2
    autocmd BufNewFile,BufRead *.vim setlocal filetype=vim softtabstop=2 tabstop=2 shiftwidth=2
    autocmd FileType python let b:did_ftplugin = 1
    autocmd FileType python setl colorcolumn=80 textwidth=78
augroup END

" ===== NERDTree設定 =====
let g:NERDTreeIgnore = ['\.pyc$', '\.pyo$', '__pycache__', '\.git$', '\.DS_Store$', '\.vscode$', '\.idea$', '\.venv$', '\.mypy_cache$', '\.pytest_cache$', '\.cache', '\.vscode', '\.idea', '\.venv', '\.mypy_cache', '\.pytest_cache', '\.svn']
let g:NERDTreeShowHidden = 1
let g:NERDTreeWinSize=35

" NERDTreeを自動的に開く
" autocmd vimenter * NERDTree | wincmd p

function! CloseVimIfLastWindow()
  " 現在のウィンドウが1つしかないかどうかを確認
  if winnr('$') == 1 && exists('b:NERDTree') && b:NERDTree.isTabTree()
    " NERDTree以外のバッファをフィルタリング
    let l:other_buffers = filter(range(1, bufnr('$')), 'buflisted(v:val) && getbufvar(v:val, "&filetype") != "nerdtree"')
    " NERDTree以外のバッファが1つだけ残っている場合、そのバッファが表示されていると仮定
    if len(l:other_buffers) == 1
      " 現在のバッファがNERDTreeではない場合、他のバッファに切り替えてから終了
      if &filetype != "nerdtree"
        execute 'buffer ' . l:other_buffers[0]
      endif
      " 現在のウィンドウを閉じる
      quit
    elseif len(l:other_buffers) == 0
      " NERDTree以外のバッファが存在しない場合にVimを終了
      quit
    endif
  endif
endfunction

" autocmdを使ってバッファの状態を監視し、条件が満たされたときに関数を実行
autocmd bufenter * call CloseVimIfLastWindow()

" バッファリストを取得する関数
function! GetBufferList()
  return filter(range(1, bufnr('$')), 'buflisted(v:val) && getbufvar(v:val, "&filetype") != "nerdtree"')
endfunction

" 指定されたバッファに移動する関数
function! SwitchToBuffer(buffer_number)
  execute 'buffer ' . a:buffer_number
endfunction

" NERDTreeの状態を更新する関数
function! UpdateNERDTree()
  if exists(':NERDTreeFind')
    NERDTreeFind
    wincmd p
  endif
endfunction

" NERDTreeが閉じられたときの処理
function! HandleNERDTreeClose()
  let l:buffer_list = GetBufferList()
  if len(l:buffer_list) >= 1
    call SwitchToBuffer(l:buffer_list[-1])
    call UpdateNERDTree()
  else
    quit
  endif
endfunction

" カスタムバッファクローズ処理
function! CustomBufferClose()
  let l:current_bufnr = bufnr('%')
  let l:buffer_list = GetBufferList()

  if &filetype == 'nerdtree'
    call HandleNERDTreeClose()
    return
  endif

  if len(l:buffer_list) > 1
    let l:current_index = index(l:buffer_list, l:current_bufnr)
    execute 'bdelete ' . l:current_bufnr

    if l:current_index == len(l:buffer_list) - 1
      call SwitchToBuffer(l:buffer_list[l:current_index - 1])
    else
      call SwitchToBuffer(l:buffer_list[l:current_index + 1])
    endif

    call UpdateNERDTree()
  else
    quit
  endif
endfunction

" NERDTreeが閉じられたときのイベントハンドラ
augroup NERDTreeCustom
  autocmd!
  autocmd BufDelete * if &filetype == 'nerdtree' | call HandleNERDTreeClose() | endif
augroup END

" :q コマンドのカスタマイズ
command! -bang -nargs=* QClose silent! call CustomBufferClose()
cnoreabbrev <expr> q getcmdtype() == ":" && getcmdline() == "q" ? "QClose" : "q"

" NERDTree表示設定
let g:NERDTreeShowLineNumbers=1
let g:NERDTreeDirArrows=1

" NERDTreeキーマッピング
let g:NERDTreeMapOpenVSplit = 'v'
nnoremap <silent> <C-n> :NERDTreeToggle<CR>
nnoremap <silent> <C-t> :NERDTreeFind<CR>

" バッファナビゲーション
nnoremap <silent> <C-{> :bprevious<CR>
nnoremap <silent> <C-}> :bnext<CR>


" 隠しファイルを表示
let NERDTreeShowHidden=1

" NERDTreeウィンドウサイズ
let NERDTreeWinSize=35

" 新しいタブでファイルを開く
let g:NERDTreeMapOpenInTab='t'

" Gitステータスインジケータ
let g:NERDTreeGitStatusIndicatorMapCustom = {
    \ 'Modified'  : '✹',
    \ 'Staged'    : '✔',
    \ 'Untracked' : '✭',
    \ 'Renamed'   : '➜',
    \ 'Unmerged'  : '═',
    \ 'Deleted'   : '✖',
    \ 'Dirty'     : '✗',
    \ 'Clean'     : '✔︎',
    \ 'Ignored'   : '☒',
    \ 'Unknown'   : '?'
    \ }

" Gitステータスを表示
let g:NERDTreeShowGitStatus = 1

" ===== Airline設定 =====
" Gitブランチを表示
let g:airline#extensions#branch#enabled = 1
let g:airline#extensions#branch#format = 0
let g:airline#extensions#hunks#enabled = 1

" バッファラインを有効化
let g:airline#extensions#tabline#enabled = 1
let g:airline#extensions#tabline#formatter = 'default'

" NERDTreeが開いている時でもバッファリストを表示
let g:airline#extensions#tabline#fnamemod = ':t'
let g:airline#extensions#tabline#show_buffers = 1

" ===== リンティング設定 =====
let g:ale_linters = {
    \ 'javascript': ['eslint'],
    \ 'typescript': ['tsserver', 'eslint'],
    \ 'python': ['flake8'],
    \ }
let g:ale_fixers = {
    \ 'python': ['autopep8'],
    \ 'javascript': ['prettier'],
    \ 'typescript': ['prettier'],
    \ }
" let g:syntastic_python_checkers = ['pyflakes', 'pep8']
let g:ale_python_flake8_executable = 'flake8'
let g:ale_python_autopep8_executable = 'autopep8'
let g:ale_fix_on_save = 1

" ===== リモート同期設定 =====
let g:sync_remote = 1
if g:sync_remote
"  autocmd BufWritePost ''
endif

" ===== フォントと色の設定 =====
" set guifont=DroidSansMono_Nerd_Font:h11
set background=dark
colorscheme habamax

" 補完メニューの色
highlight! CmpItemAbbr guifg=#ebdbb2 guibg=NONE
highlight! CmpItemKind guifg=#d79921 guibg=NONE
highlight! CmpItemMenu guifg=#83a598 guibg=NONE
highlight! CmpItemAbbrMatch guifg=#fe8019 guibg=NONE
highlight! CmpItemAbbrMatchFuzzy guifg=#fe8019 guibg=NONE

" ===== ファイルタイプ設定 =====
filetype indent on
filetype plugin indent on
filetype on
syntax on

" ===== ファイルタイプ別の設定 =====
" Vimスクリプトファイルの設定
autocmd FileType vim setlocal foldmethod=marker

" Pythonファイルの設定
autocmd FileType python setlocal
  \ tabstop=4
  \ softtabstop=4
  \ shiftwidth=4
  \ textwidth=79
  \ expandtab
  \ autoindent
  \ fileformat=unix
  \ foldmethod=indent

" JavaScript/TypeScript/JSX/TSXファイルの設定
autocmd FileType javascript,typescript,javascriptreact,typescriptreact setlocal
  \ tabstop=2
  \ softtabstop=2
  \ shiftwidth=2

" HTMLファイルの設定
autocmd FileType html setlocal
  \ tabstop=2
  \ softtabstop=2
  \ shiftwidth=2

" CSSファイルの設定
autocmd FileType css setlocal
  \ tabstop=2
  \ softtabstop=2
  \ shiftwidth=2

" ===== キーマッピング =====
" リーダーキーの設定
let mapleader = "\<Space>"

" ファイル保存のショートカット
nnoremap <leader>w :w<CR>

" ファイル終了のショートカット
nnoremap <leader>q :q<CR>

" 分割ウィンドウの移動
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l

" タブ操作
nnoremap <leader>tn :tabnew<CR>
nnoremap <leader>tc :tabclose<CR>
nnoremap <leader>to :tabonly<CR>

" ===== プラグイン固有の設定 =====
" NERDTreeの設定
nnoremap <leader>n :NERDTreeToggle<CR>
let g:NERDTreeIgnore = ['\.pyc$', '\.pyo$', '__pycache__']
let g:NERDTreeShowHidden = 1

" Fugitiveの設定
nnoremap <leader>gs :Git<CR>
nnoremap <leader>gc :Git commit<CR>
nnoremap <leader>gp :Git push<CR>

" fzfの設定
nnoremap <C-p> :Files<CR>
nnoremap <leader>b :Buffers<CR>

" ===== プラグイン設定 =====
let maplocalleader = ","

" ALE（非同期Lint Engine）の設定
let g:ale_linters = {
\   'python': ['flake8'],
\   'javascript': ['eslint'],
\   'typescript': ['tslint']
\}
let g:ale_fixers = {
\   '*': ['remove_trailing_lines', 'trim_whitespace'],
\   'python': ['black'],
\   'javascript': ['prettier'],
\   'typescript': ['prettier']
\}
let g:ale_fix_on_save = 1

" ステータスラインの設定
set laststatus=2
let g:airline_powerline_fonts = 1
let g:airline#extensions#tabline#enabled = 1

" ===== 自動コマンド =====
" ファイルを開いたときに、カーソルを最後の位置に移動
autocmd BufReadPost *
  \ if line("'\"") > 1 && line("'\"") <= line("$") |
  \   exe "normal! g`\"" |
  \ endif

" ファイルタイプごとのプラグイン・インデント設定を有効化
filetype plugin indent on

" シンタックスハイライトを有効化
syntax enable

" ===== lua設定 =====
" ===== ターミナル設定 =====
" Ctrl + バックスラッシュでターミナルを開閉
lua << EOF
-- ターミナル設定
require("toggleterm").setup {
  -- ターミナルのサイズ
  size = 60,
  -- ターミナルを開くキーマッピング
  open_mapping = [[<c-\>]],
  -- ファイルタイプごとの影の設定
  shade_filetypes = {},
  -- ターミナルに影をつける
  shade_terminals = true,
  -- 影の濃さ
  shading_factor = '2',
  -- 挿入モードで開始
  start_in_insert = true,
  -- 挿入モードのマッピングを有化
  insert_mappings = true,
  -- サイズを保持
  persist_size = true,
  -- ターミナルの方向
  direction = 'vertical',
}

-- 自動保存設定
require('auto-save').setup {
  -- 自動保存を有効化
  enabled = true,
  -- 自動保存をトリガーするイベント
  events = {"InsertLeave", "TextChanged"},
  -- 実行メッセージの設定
  execution_message = {
    message = function()
      return ("AutoSave: " .. vim.fn.strftime("%H:%M:%S") .. "に保存しました")
    end,
    dim = 0.18,
    cleaning_interval = 1250,
  },
  -- 自動保存の条件
  conditions = {
    exists = true,
    filename_is_not = {},
    filetype_is_not = {},
    modifiable = true
  },
  -- すべてのバッファを書き込むかどうか
  write_all_buffers = false,
  -- オン/オフコマンドを有効化
  on_off_commands = true,
  -- コマンドラインのクリーニング間隔
  clean_command_line_interval = 0,
  -- 遅延時間
  debounce_delay = 135
}

-- 補完設定
local cmp = require'cmp'

cmp.setup({
  -- スニペットエンジンの設定
  snippet = {
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body) -- vsnipユーザー向け
    end,
  },
  -- キーマッピングの設定
  mapping = {
    ['<C-b>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.close(),
    ['<CR>'] = cmp.mapping.confirm({ select = true }),
  },
  -- 補完ソースの設定
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'vsnip' }, -- vsnipユーザー向け
  }, {
    { name = 'buffer' },
  })
})

-- 検索コマンド用の補完ソース設定
cmp.setup.cmdline('/', {
  sources = {
    { name = 'buffer' }
  }
})

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

-- コマンドライン用の補完ソース設定
cmp.setup.cmdline(':', {
  sources = cmp.config.sources({
    { name = 'path' }
  }, {
    { name = 'cmdline' }
  })
})

-- 自動括弧補完の設定
-- 自動ペアリング設定
require('nvim-autopairs').setup()
local cmp = require('cmp')
local cmp_autopairs = require('nvim-autopairs.completion.cmp')
cmp.event:on(
  'confirm_done',
  cmp_autopairs.on_confirm_done()
  )

-- ===== コードコンパニオン設定 =====
local status, codecompanion = pcall(require, "codecompanion")
if not status then
  print("Error loading codecompanion")
  return
end

require("codecompanion").setup({

  strategies = {
    chat = {
      adapter = "openai", -- o1-miniをためす。
    },
    inline = {
      adapter = "openai", -- copilotにすると動かない
    },
    agent = {
      adapter = "openai",
    },
  },
  adapters = {
    ollama = function()
      return require("codecompanion.adapters").extend("ollama", {
        env = {
          url = "https://localhost:11434",
          api_key = "OLLAMA_API_KEY",
        },
        headers = {
          ["Content-Type"] = "application/json",
          ["Authorization"] = "Bearer ${api_key}",
        },
        parameters = {
          sync = true,
        },
      })
    end,
    deepseek = function()
      return require("codecompanion.adapters").extend("ollama", {
        schema = {
          model = {
            default = "deepseek-coder-v2:latest",
          },
          num_ctx = {
            default = 128000,
          },
          num_predict = {
            default = -1,
          },
        },
      })
    end,
    openai = function()
      return require("codecompanion.adapters").extend("openai", {
        env = {
          api_key = "sk-**"
        },
        schema = {
          model = {
            default = "o1-mini	",
          },
        },
      })
    end,
    haiku = function()
      return require("codecompanion.adapters").extend("anthropic", {
        env = {
          api_key = "--",
        },
        schema = {
          model = {
            default = "claude-3-haiku-20240307",
          },
        },
      })
    end,
    sonet = function()
      return require("codecompanion.adapters").extend("anthropic", {
        env = {
          api_key = "--",
        },
        schema = {
          model = {
            default = "claude-3-sonnet-20240229",
          },
        },
      })
    end,
  },
})
vim.api.nvim_set_keymap("n", "<C-a>", "<cmd>CodeCompanionActions<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("v", "<C-a>", "<cmd>CodeCompanionActions<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("n", "<LocalLeader>a", "<cmd>CodeCompanionToggle<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("v", "<LocalLeader>a", "<cmd>CodeCompanionToggle<cr>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("v", "ga", "<cmd>CodeCompanionAdd<cr>", { noremap = true, silent = true })
vim.cmd([[cab cc CodeCompanion]])
EOF
```