coc.nvim -> nvim-lsp移行プロジェクト
追記:以下の動機で移行を考え始めたけどそもそも競合せず使える
skkeletonを使うためにddc.vimを入れたらcoc.nvimと競合したっぽい
skkeletonの入力体験が良いことがわかったので、これを機にcoc.nvimからddc.vim x nvim-lspに全面移行したい
こんな感じで設定はしていたんだけどな…cocがddcを邪魔しなくなるのは良かったんだけどcocが出なくなってしまったのよね
参考になる記事
READMEを参考にして最低限の設定はこんな感じ
" 読み込み(vim-plug)
call plug#begin(stdpath('config') . '/plugged')
Plug 'neovim/nvim-lspconfig'
Plug 'williamboman/nvim-lsp-installer'
call plug#end()
lua << EOF
-- READMEを参考
local nvim_lsp = require('lspconfig')
-- language serverがバッファにアタッチされたときに実行する関数
local on_attach = function(client, bufnr)
local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end
-- <c-x><c-o>による補完
buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')
-- map用オプション
local opts = { noremap=true, silent=true }
-- `vim.lsp.*`関数のマッピング(多いので省略)
buf_set_keymap('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', opts)
buf_set_keymap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opts)
end
-- lsp_installerの設定
local lsp_installer = require("nvim-lsp-installer")
-- デフォルトアイコンが見分けつかないので設定
-- その他のデフォルト設定は[READMEを参照](https://github.com/williamboman/nvim-lsp-installer#default-configuration)
lsp_installer.settings({
ui = {
icons = {
server_installed = "✓",
server_pending = "➜",
server_uninstalled = "✗"
}
}
})
-- サーバー起動時に自動でon_attachをアタッチする
lsp_installer.on_server_ready(function(server)
local opts = {}
opts.on_attach = on_attach
server:setup(opts)
vim.cmd [[ do User LspAttachBuffers ]]
end)
EOF
これで:LspInstall {server-name}
でlanguage serverをインストールできるようになる
個人的に重要なポイント Denoプロジェクトではdenolsを有効にし、tsserverとeslintを停止する
lsp_installer.on_server_ready
に設定を追加
lsp_installer.on_server_ready(function(server)
local opts = {}
opts.on_attach = on_attach
+ if server.name == 'tsserver' or server.name == 'eslint' then
+ opts.root_dir = nvim_lsp.util.root_pattern("package.json")
+ elseif server.name == 'denols' then
+ opts.root_dir = nvim_lsp.util.root_pattern("deno.json", "deno.jsonc", "deps.ts")
+ opts.init_options = { lint = true, unstable = true, }
+ end
server:setup(opts)
vim.cmd [[ do User LspAttachBuffers ]]
end)
ddcの追加
Plug 'vim-denops/denops.vim'
Plug 'Shougo/ddc.vim'
Plug 'Shougo/ddc-around'
Plug 'Shougo/ddc-matcher_head'
Plug 'Shougo/ddc-matcher_length'
Plug 'Shougo/ddc-sorter_rank'
Plug 'Shougo/ddc-nvim-lsp'
Plug 'matsui54/denops-popup-preview.vim'
Plug 'ray-x/lsp_signature.nvim'
" 略
" lspを表示だけするならaroundは必須ではないけど基礎的なsourceなので記載
call ddc#custom#patch_global('sources', ['nvim-lsp', 'around'])
call ddc#custom#patch_global('sourceOptions', #{
\ _: #{
\ ignoreCase: v:true,
\ matchers: ['matcher_head'],
\ sorters: ['sorter_rank'],
\ },
\ around: #{
\ mark: 'A',
\ matchers: ['matcher_head', 'matcher_length'],
\ },
\ nvim-lsp: #{
\ mark: 'lsp',
\ forceCompletionPattern: '\.\w*|:\w*|->\w*',
\ },
\ })
" ちょっとここの設定の意味はよくわからない
call ddc#custom#patch_global('sourceParams', #{
\ nvim-lsp: #{ maxSize: 500, kindLabels: #{ Class: 'c' } },
\ })
" 明示的に起動が必要(忘れがち)
call ddc#enable()
call popup_preview#enable()
" <TAB>/<S-TAB> completion.
inoremap <silent><expr> <TAB>
\ pumvisible() ? '<C-n>' :
\ (col('.') <= 1 <Bar><Bar> getline('.')[col('.') - 2] =~# '\s') ?
\ '<TAB>' : ddc#map#manual_complete()
inoremap <expr><S-TAB> pumvisible() ? '<C-p>' : '<C-h>'
lua require("lsp_signature").setup()
これで補完が出るはず
設定して早々ではあるけどddc-fuzzyへ移行しよう
プラグインを読み込んで、patch_global('sourceOptions'...)
をddc-fuzzyを使うように修正、キーマップをpumを使うように修正
Plug 'Shougo/pum.vim'
Plug 'tani/ddc-fuzzy'
" 略
call ddc#custom#patch_global('completionMenu', 'pum.vim')
call ddc#custom#patch_global('sourceOptions', #{
\ _: #{
\ ignoreCase: v:true,
\ matchers: ['matcher_fuzzy'],
\ sorters: ['sorter_fuzzy'],
\ converters: ['converter_fuzzy']
\ },
\ around: #{ mark: 'A' },
\ nvim-lsp: #{
\ mark: 'lsp',
\ forceCompletionPattern: '\.\w*|:\w*|->\w*',
\ },
\ })
" <TAB>/<S-TAB> completion. ここはddcのtab補完設定なのでpum.vimを使うなら不要
" inoremap <silent><expr> <TAB>
" \ pumvisible() ? '<C-n>' :
" \ (col('.') <= 1 <Bar><Bar> getline('.')[col('.') - 2] =~# '\s') ?
" \ '<TAB>' : ddc#map#manual_complete()
" inoremap <expr><S-TAB> pumvisible() ? '<C-p>' : '<C-h>'
inoremap <Tab> <Cmd>call pum#map#insert_relative(+1)<CR>
inoremap <S-Tab> <Cmd>call pum#map#insert_relative(-1)<CR>
inoremap <C-n> <Cmd>call pum#map#insert_relative(+1)<CR>
inoremap <C-p> <Cmd>call pum#map#insert_relative(-1)<CR>
inoremap <C-y> <Cmd>call pum#map#confirm()<CR>
inoremap <C-e> <Cmd>call pum#map#cancel()<CR>
inoremap <PageDown> <Cmd>call pum#map#insert_relative_page(+1)<CR>
inoremap <PageUp> <Cmd>call pum#map#insert_relative_page(-1)<CR>
snippetの設定
各サーバーにcapabilitiesを設定する
Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'
" 略
" ddcのsourceにvsnip追加
call ddc#custom#patch_global('sources', ['nvim-lsp', 'vsnip', 'around'])
call ddc#custom#patch_global('sourceOptions', #{
\ " ...
\ vsnip: #{
\ mark: 'VS',
\ dup: v:true,
\ },
\ " ...
\ })
" Tab / S-Tab / C-n / C-pのマッピングを変更、autocmd追加
imap <silent><expr> <TAB> pum#visible() ? '<Cmd>call pum#map#insert_relative(+1)<CR>' : vsnip#jumpable(+1) ? '<Plug>(vsnip-jump-next)' : '<TAB>'
imap <silent><expr> <S-TAB> pum#visible() ? '<Cmd>call pum#map#insert_relative(-1)<CR>' : vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-TAB>'
imap <silent><expr> <C-n> (pum#visible() ? '' : '<Cmd>call ddc#map#manual_complete()<CR>') . '<Cmd>call pum#map#select_relative(+1)<CR>'
imap <silent><expr> <C-p> (pum#visible() ? '' : '<Cmd>call ddc#map#manual_complete()<CR>') . '<Cmd>call pum#map#select_relative(-1)<CR>'
autocmd User PumCompleteDone call vsnip_integ#on_complete_done(g:pum#completed_item)
lua << EOF
local nvim_lsp = require('lspconfig')
local on_attach = function(client, bufnr)
-- 略
end
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true
local lsp_installer = require("nvim-lsp-installer")
-- iconとかの設定は省略
lsp_installer.on_server_ready(function(server)
local opts = {}
opts.on_attach = on_attach
opts.capabilities = capabilities
-- tsserverとかdenolsとかの部分は省略
server:setup(opts)
vim.cmd [[ do User LspAttachBuffers ]]
end)
EOF
gamoutatsumiさんのdofilesを参考にした
これでこの記事のAuto snippet的なのができるようになると思ったんだけど…動かないな
先にcmdlineのコンプリートを設定
ddcのドキュメントにpum.vimを使ってコマンドラインの補完をやるサンプルが載っている
あと例によって前出のdotfilesも参考にする
Plug 'Shougo/neco-vim'
Plug 'Shougo/ddc-cmdline-history'
" 略
cnoremap <expr> <TAB> pum#visible() ? '<Cmd>call pum#map#insert_relative(+1)<CR>' : ddc#map#manual_complete()
cnoremap <expr> <S-TAB> pum#visible() ? '<Cmd>call pum#map#insert_relative(-1)<CR>' : ddc#map#manual_complete()
cnoremap <expr> <C-n> pum#visible() ? '<Cmd>call pum#map#insert_relative(+1)<CR>' : '<C-n>'
cnoremap <expr> <C-p> pum#visible() ? '<Cmd>call pum#map#insert_relative(-1)<CR>' : '<C-p>'
cnoremap <expr> <CR> pum#visible() ? '<Cmd>call pum#map#confirm()<CR>' : '<CR>'
" cnoremap <C-y> <Cmd>call pum#map#confirm()<CR>
" cnoremap <C-e> <Cmd>call pum#map#cancel()<CR>
nnoremap : <Cmd>call CommandlinePre()<CR>:
function! CommandlinePre() abort
" Overwrite sources
let s:prev_buffer_config = ddc#custom#get_buffer()
call ddc#custom#patch_buffer('sources', ['necovim', 'cmdline-history'])
call ddc#custom#patch_buffer('autoCompleteEvents', ['CmdlineChanged'])
call ddc#custom#patch_buffer('sourceOptions', #{
\ _: #{
\ ignoreCase: v:true,
\ matchers: ['matcher_fuzzy'],
\ sorters: ['sorter_fuzzy'],
\ converters: ['converter_fuzzy']
\ },
\ necovim: #{ mark: 'neco' },
\ cmdline-history: #{ mark: 'hist' },
\ })
autocmd CmdlineLeave ++once call CommandlinePost()
" Enable command line completion
call ddc#enable_cmdline_completion()
endfunction
function! CommandlinePost() abort
" Restore sources
call ddc#custom#set_buffer(s:prev_buffer_config)
endfunction
Deno/Nodeの出し分けの箇所、修正
前述の設定だとtsserver
は起動しないものの、起動失敗メッセージが出てしまっていた
root_dir
はあくまでプロジェクトルートを定義するものであり、起動可否はautostart
に真偽値で指定する必要がある
したがって、root_dir
で指定されたファイルが見つかった場合のみサーバーを起動させる設定を書く
root_dir
がどのように実行されているかはnvim-lspのコードを参考にした
local nvim_lsp = require("lspconfig")
local lsp_installer = require("nvim-lsp-installer")
function detected_root_dir(root_dir)
return not(not(root_dir(vim.api.nvim_buf_get_name(0), vim.api.nvim_get_current_buf())))
end
lsp_installer.on_server_ready(function(server)
local opts = {}
opts.on_attach = on_attach
opts.capabilities = capabilities
if server.name == 'tsserver' or server.name == 'eslint' then
local root_dir = nvim_lsp.util.root_pattern("package.json", "node_modules")
opts.root_dir = root_dir
opts.autostart = detected_root_dir(root_dir)
elseif server.name == 'denols' then
local root_dir = nvim_lsp.util.root_pattern("deno.json", "deno.jsonc", "deps.ts")
opts.root_dir = root_dir
opts.autostart = detected_root_dir(root_dir)
opts.init_options = { lint = true, unstable = true, }
end
server:setup(opts)
vim.cmd [[ do User LspAttachBuffers ]]
end)
nvim_lsp.util.root_pattern()
は指定のファイルが存在すればそのパスを、なければnil
を返すが、nvim-lsp内部ではautostart==true
で判断しているので、not(not())
で囲んで明示的にtrue/false
に変換する必要がある
これで.ts
.tsx
などを開いたとき、以下のように選択的にサーバーを立ち上げることができる
-
"package.json", "node_modules"
のあるディレクトリ以下ではtsserver
とeslint
が立ち上がる -
"deno.json", "deno.jsonc", "deps.ts"
のあるディレクトリ以下ではdenols
が立ち上がる
条件に使っているファイルが両方存在してしまうとどちらのサーバーも起動してしまうが…一旦考えないこととする
on-attachするとdenolsでマッピングがつかない?気がしたので外に出した
nnoremap <expr> K '<Cmd>' . (['vim','help']->index(&filetype) >= 0 ? 'help ' . expand('<cword>') : 'lua vim.lsp.buf.hover()') . '<CR>'
nnoremap gD <cmd>lua vim.lsp.buf.declaration()<CR>
nnoremap gd <cmd>lua vim.lsp.buf.definition()<CR>
nnoremap gi <cmd>lua vim.lsp.buf.implementation()<CR>
nnoremap gr <cmd>lua vim.lsp.buf.references()<CR>
nnoremap <C-k> <cmd>lua vim.lsp.buf.signature_help()<CR>
nnoremap <space>wa <cmd>lua vim.lsp.buf.add_workspace_folder()<CR>
nnoremap <space>wr <cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>
nnoremap <space>wl <cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>
nnoremap <space>D <cmd>lua vim.lsp.buf.type_definition()<CR>
nnoremap <space>rn <cmd>lua vim.lsp.buf.rename()<CR>
nnoremap <space>ca <cmd>lua vim.lsp.buf.code_action()<CR>
nnoremap <space>e <cmd>lua vim.diagnostic.open_float()<CR>
nnoremap [g <cmd>lua vim.diagnostic.goto_prev()<CR>
nnoremap ]g <cmd>lua vim.diagnostic.goto_next()<CR>
nnoremap sl <cmd>lua vim.diagnostic.setloclist()<CR>
nnoremap <space>p <cmd>lua vim.lsp.buf.formatting()<CR>
あとvim/helpファイルではK
が強制的にヘルプを見るよう修正(vimlではhelp参照にならないっぽい)
vim-jpで質問したところvsnipの設定の問題とかではなさそう
言語サーバーによって対応している機能は異なるため一律にスニペットが使えるというわけではない
tsではlspがスニペットを出してくれないのでfriendly-snippetsを使うのが良さそう
読み込めばスニペットを出してくれるのでvsnip→ddcと連携させてスニペットを使える
なんだかんだ言ってcoc.nvimとddc x skkeletonの共存できた
参考:
設定:
imap <C-j> <Plug>(skkeleton-enable)
cmap <C-j> <Plug>(skkeleton-enable)
call ddc#custom#patch_global('sources', ['skkeleton'])
call ddc#custom#patch_global('sourceOptions', #{
\ skkeleton: #{
\ matchers: ['skkeleton'],
\ minAutoCompleteLength: 1,
\ },
\ })
call skkeleton#config(#{
\ eggLikeNewline: v:true,
\ globalJisyo: "path/to/jisyo",
\ })
function s:enable_ddc() abort
let b:coc_suggest_disable = v:true
call ddc#custom#patch_global('autoCompleteEvents',
\ ['TextChangedI', 'TextChangedP', 'CmdlineChanged'])
endfunction
function s:disable_ddc() abort
let b:coc_suggest_disable = v:false
call ddc#custom#patch_global('autoCompleteEvents', [])
endfunction
" initialize
call <sid>disable_ddc()
augroup skkeleton
autocmd!
autocmd User skkeleton-enable-pre call <sid>enable_ddc()
autocmd User skkeleton-disable-pre call <sid>disable_ddc()
augroup END
単に2つのオートコンプリーターをトグルすれば良い
- skkeletonが有効になったとき→cocを無効化、ddcを有効化
- skkeletonが無効になったとき→cocを有効化、ddcを無効化
cocは専用の変数があるのでそれにv:true
/ v:false
を入れればOK
ddcはそういった設定がないっぽいが、autoCompleteEvents
の対象を空配列にすることで「どのイベントが起きても補完しない」状態になり、実質disableにできる
汎用的にやるならddc#custom#get_global()
とかを使って設定を取り出したほうが良いが今回はskkeletonオンリーなので決め打ちでやってみた
あと設定上insert開始時には必ずddcは無効状態なのでInsertEnter
イベントの設定は不要