@ -4,6 +4,7 @@ local log = ng_util.log
local trace = ng_util.trace
local empty = ng_util.empty
local warn = ng_util.warn
local vfn = vim.fn
_NG_Loaded = { }
_LoadedFiletypes = { }
@ -41,6 +42,7 @@ local disabled_ft = {
' windline ' ,
' notify ' ,
' nofile ' ,
' help ' ,
' ' ,
}
-- local cap = vim.lsp.protocol.make_client_capabilities()
@ -63,322 +65,45 @@ local luadevcfg = {
}
local luadev = { }
local user_luadev = _NgConfigValues.lsp [ ' lua-dev ' ]
if user_luadev then
luadev = vim.tbl_deep_extend ( ' force ' , luadev , user_luadev )
end
require ( ' navigator.lazyloader ' ) . load ( ' lua-dev.nvim ' , ' folke/lua-dev.nvim ' )
if _NgConfigValues.lsp_installer then
require ( ' navigator.lazyloader ' ) . load ( ' nvim-lsp-installer ' , ' williamboman/nvim-lsp-installer ' )
end
local ok , l = pcall ( require , ' lua-dev ' )
if ok and l then
luadev = l.setup ( luadevcfg )
end
local function add ( lib )
for _ , p in pairs ( vim.fn . expand ( lib , false , true ) ) do
p = vim.loop . fs_realpath ( p )
if p then
library [ p ] = true
end
end
end
-- add runtime
add ( ' $VIMRUNTIME ' )
-- add your config
-- local home = vim.fn.expand("$HOME")
add ( vim.fn . stdpath ( ' config ' ) )
-- add plugins it may be very slow to add all in path
-- if vim.fn.isdirectory(home .. "/.config/share/nvim/site/pack/packer") then
-- add(home .. "/.local/share/nvim/site/pack/packer/opt/*")
-- add(home .. "/.local/share/nvim/site/pack/packer/start/*")
-- end
library [ vim.fn . expand ( ' $VIMRUNTIME/lua ' ) ] = true
library [ vim.fn . expand ( ' $VIMRUNTIME/lua/vim ' ) ] = true
library [ vim.fn . expand ( ' $VIMRUNTIME/lua/vim/lsp ' ) ] = true
-- [vim.fn.expand("~/repos/nvim/lua")] = true
-- TODO remove onece PR #944 merged to lspconfig
local path_sep = require ( ' navigator.util ' ) . path_sep ( )
local strip_dir_pat = path_sep .. ' ([^ ' .. path_sep .. ' ]+)$ '
local strip_sep_pat = path_sep .. ' $ '
local dirname = function ( pathname )
if not pathname or # pathname == 0 then
return
end
local result = pathname : gsub ( strip_sep_pat , ' ' ) : gsub ( strip_dir_pat , ' ' )
if # result == 0 then
return ' / '
end
return result
if _NgConfigValues.mason then
require ( ' navigator.lazyloader ' ) . load ( ' mason.nvim ' , ' williamboman/mason.nvim ' )
require ( ' navigator.lazyloader ' ) . load ( ' mason-lspconfig.nvim ' , ' williamboman/mason-lspconfig.nvim ' )
end
-- TODO end
local setups = {
clojure_lsp = {
root_dir = function ( fname )
return util.root_pattern ( ' deps.edn ' , ' build.boot ' , ' project.clj ' , ' shadow-cljs.edn ' , ' bb.edn ' , ' .git ' ) ( fname )
or util.path . dirname ( fname )
end ,
on_attach = on_attach ,
filetypes = { ' clojure ' , ' edn ' } ,
message_level = vim.lsp . protocol.MessageType . error ,
cmd = { ' clojure-lsp ' } ,
} ,
elixirls = {
on_attach = on_attach ,
filetypes = { ' elixir ' , ' eelixir ' } ,
cmd = { ' elixir-ls ' } ,
message_level = vim.lsp . protocol.MessageType . error ,
settings = {
elixirLS = {
dialyzerEnabled = true ,
fetchDeps = false ,
} ,
} ,
root_dir = function ( fname )
return util.root_pattern ( ' mix.exs ' , ' .git ' ) ( fname ) or util.path . dirname ( fname )
end ,
} ,
gopls = {
on_attach = on_attach ,
-- capabilities = cap,
filetypes = { ' go ' , ' gomod ' , ' gohtmltmpl ' , ' gotexttmpl ' } ,
message_level = vim.lsp . protocol.MessageType . Error ,
cmd = {
' gopls ' , -- share the gopls instance if there is one already
' -remote=auto ' , --[[ debug options ]] --
-- "-logfile=auto",
-- "-debug=:0",
' -remote.debug=:0 ' ,
-- "-rpc.trace",
} ,
flags = { allow_incremental_sync = true , debounce_text_changes = 1000 } ,
settings = {
gopls = {
-- more settings: https://github.com/golang/tools/blob/master/gopls/doc/settings.md
-- flags = {allow_incremental_sync = true, debounce_text_changes = 500},
-- not supported
analyses = { unusedparams = true , unreachable = false } ,
codelenses = {
generate = true , -- show the `go generate` lens.
gc_details = true , -- // Show a code lens toggling the display of gc's choices.
test = true ,
tidy = true ,
} ,
usePlaceholders = true ,
completeUnimported = true ,
staticcheck = true ,
matcher = ' fuzzy ' ,
diagnosticsDelay = ' 500ms ' ,
experimentalWatchedFileDelay = ' 1000ms ' ,
symbolMatcher = ' fuzzy ' ,
gofumpt = false , -- true, -- turn on for new repos, gofmpt is good but also create code turmoils
buildFlags = { ' -tags ' , ' integration ' } ,
-- buildFlags = {"-tags", "functional"}
} ,
} ,
root_dir = function ( fname )
return util.root_pattern ( ' go.mod ' , ' .git ' ) ( fname ) or dirname ( fname ) -- util.path.dirname(fname)
end ,
} ,
clangd = {
flags = { allow_incremental_sync = true , debounce_text_changes = 500 } ,
cmd = {
' clangd ' ,
' --background-index ' ,
' --suggest-missing-includes ' ,
' --clang-tidy ' ,
' --header-insertion=iwyu ' ,
' --clang-tidy-checks=-*,llvm-*,clang-analyzer-* ' ,
' --cross-file-rename ' ,
} ,
filetypes = { ' c ' , ' cpp ' , ' objc ' , ' objcpp ' } ,
on_attach = function ( client , bufnr )
client.server_capabilities . documentFormattingProvider = client.server_capabilities . documentFormattingProvider
or true
on_attach ( client , bufnr )
end ,
} ,
rust_analyzer = {
root_dir = function ( fname )
return util.root_pattern ( ' Cargo.toml ' , ' rust-project.json ' , ' .git ' ) ( fname ) or util.path . dirname ( fname )
end ,
filetypes = { ' rust ' } ,
message_level = vim.lsp . protocol.MessageType . error ,
on_attach = on_attach ,
settings = {
[ ' rust-analyzer ' ] = {
assist = { importMergeBehavior = ' last ' , importPrefix = ' by_self ' } ,
cargo = { loadOutDirsFromCheck = true } ,
procMacro = { enable = true } ,
} ,
} ,
flags = { allow_incremental_sync = true , debounce_text_changes = 500 } ,
} ,
sqls = {
filetypes = { ' sql ' } ,
on_attach = function ( client , _ )
client.server_capabilities . executeCommandProvider = client.server_capabilities . documentFormattingProvider or true
highlight.diagnositc_config_sign ( )
require ( ' sqls ' ) . setup ( { picker = ' telescope ' } ) -- or default
end ,
flags = { allow_incremental_sync = true , debounce_text_changes = 500 } ,
settings = {
cmd = { ' sqls ' , ' -config ' , ' $HOME/.config/sqls/config.yml ' } ,
-- alterantively:
-- connections = {
-- {
-- driver = 'postgresql',
-- datasourcename = 'host=127.0.0.1 port=5432 user=postgres password=password dbname=user_db sslmode=disable',
-- },
-- },
} ,
} ,
sumneko_lua = {
cmd = { ' lua-language-server ' } ,
filetypes = { ' lua ' } ,
on_attach = on_attach ,
flags = { allow_incremental_sync = true , debounce_text_changes = 500 } ,
settings = {
Lua = {
runtime = {
-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
version = ' LuaJIT ' ,
} ,
diagnostics = {
enable = true ,
-- Get the language server to recognize the `vim` global
globals = { ' vim ' , ' describe ' , ' it ' , ' before_each ' , ' after_each ' , ' teardown ' , ' pending ' } ,
} ,
completion = { callSnippet = ' Both ' } ,
workspace = {
-- Make the server aware of Neovim runtime files
library = library ,
maxPreload = 2000 ,
preloadFileSize = 40000 ,
} ,
telemetry = { enable = false } ,
} ,
} ,
on_new_config = function ( cfg , root )
local libs = vim.tbl_deep_extend ( ' force ' , { } , library )
libs [ root ] = nil
cfg.settings . Lua.workspace . library = libs
return cfg
end ,
} ,
pyright = {
on_attach = on_attach ,
cmd = { ' pyright-langserver ' , ' --stdio ' } ,
filetypes = { ' python ' } ,
flags = { allow_incremental_sync = true , debounce_text_changes = 500 } ,
settings = {
python = {
formatting = { provider = ' black ' } ,
analysis = {
autoSearchPaths = true ,
useLibraryCodeForTypes = true ,
diagnosticMode = ' workspace ' ,
} ,
} ,
} ,
} ,
ccls = {
on_attach = on_attach ,
init_options = {
compilationDatabaseDirectory = ' build ' ,
root_dir = [[ util.root_pattern("compile_commands.json", "compile_flags.txt", "CMakeLists.txt", "Makefile", ".git") or util.path.dirname ]] ,
index = { threads = 2 } ,
clang = { excludeArgs = { ' -frounding-math ' } } ,
} ,
flags = { allow_incremental_sync = true } ,
} ,
jdtls = {
settings = {
java = { signatureHelp = { enabled = true } , contentProvider = { preferred = ' fernflower ' } } ,
} ,
} ,
omnisharp = {
cmd = { ' omnisharp ' , ' --languageserver ' , ' --hostPID ' , tostring ( vim.fn . getpid ( ) ) } ,
} ,
terraformls = {
filetypes = { ' terraform ' , ' tf ' } ,
} ,
sourcekit = {
cmd = { ' sourcekit-lsp ' } ,
filetypes = { ' swift ' } , -- This is recommended if you have separate settings for clangd.
} ,
}
setups.sumneko_lua = vim.tbl_deep_extend ( ' force ' , luadev , setups.sumneko_lua )
local servers = {
' angularls ' ,
' gopls ' ,
' tsserver ' ,
' flow ' ,
' bashls ' ,
' dockerls ' ,
' julials ' ,
' pylsp ' ,
' pyright ' ,
' jedi_language_server ' ,
' jdtls ' ,
' sumneko_lua ' ,
' vimls ' ,
' html ' ,
' jsonls ' ,
' solargraph ' ,
' cssls ' ,
' yamlls ' ,
' clangd ' ,
' ccls ' ,
' sqls ' ,
' denols ' ,
' graphql ' ,
' dartls ' ,
' dotls ' ,
' kotlin_language_server ' ,
' nimls ' ,
' intelephense ' ,
' vuels ' ,
' phpactor ' ,
' omnisharp ' ,
' r_language_server ' ,
' rust_analyzer ' ,
' terraformls ' ,
' svelte ' ,
' texlab ' ,
' clojure_lsp ' ,
' elixirls ' ,
' sourcekit ' ,
' fsautocomplete ' ,
' vls ' ,
' hls ' ,
' tflint ' ,
' terraform_lsp ' ,
}
local setups = require ( ' navigator.lspclient.clients_default ' ) . defaults ( )
local servers = require ( ' navigator.lspclient.servers ' )
local lsp_installer_servers = { }
local has_lspinst = false
local has_mason = false
if config.lsp_installer == true then
has_lspinst , _ = pcall ( require , ' nvim-lsp-installer ' )
if has_lspinst then
local srvs = require ( ' nvim-lsp-installer.servers ' ) . get_installed_servers ( )
log ( ' lsp_installered servers ' , srvs )
if # srvs > 0 then
lsp_installer_servers = srvs
end
has_lspinst , _ = pcall ( require , ' nvim-lsp-installer ' )
if has_lspinst then
local srvs = require ( ' nvim-lsp-installer.servers ' ) . get_installed_servers ( )
if # srvs > 0 then
lsp_installer_servers = srvs
end
end
has_mason , _ = pcall ( require , ' mason-lspconfig ' )
if has_mason then
local srvs = require ' mason-lspconfig ' . get_installed_servers ( )
if # srvs > 0 then
lsp_installer_servers = srvs
end
log ( lsp_installer_servers )
end
log ( " lsp_installer: " , lsp_installer_servers )
if config.lsp . disable_lsp == ' all ' then
config.lsp . disable_lsp = servers
end
@ -389,8 +114,8 @@ local ng_default_cfg = {
}
-- check and load based on file type
local function load_cfg ( ft , client , cfg , loaded )
log ( ft , client , loaded )
local function load_cfg ( ft , client , cfg , loaded , starting )
log ( ft , client , loaded , starting )
trace ( cfg )
if lspconfig [ client ] == nil then
log ( ' not supported by nvim ' , client )
@ -399,6 +124,7 @@ local function load_cfg(ft, client, cfg, loaded)
local lspft = lspconfig [ client ] . document_config.default_config . filetypes
local additional_ft = setups [ client ] and setups [ client ] . filetypes or { }
local bufnr = vim.api . nvim_get_current_buf ( )
local cmd = cfg.cmd
vim.list_extend ( lspft , additional_ft )
@ -414,19 +140,46 @@ local function load_cfg(ft, client, cfg, loaded)
end
trace ( ' lsp for client ' , client , cfg )
if cmd == nil or # cmd == 0 or v im. fn . executable( cmd [ 1 ] ) == 0 then
log ( ' lsp not installed for client ' , client , cmd )
if cmd == nil or # cmd == 0 or v fn.executable( cmd [ 1 ] ) == 0 then
log ( ' lsp not installed for client ' , client , cmd , " fallback " )
return
end
if _NG_Loaded == nil then
return log ( ' _NG_Loaded not set ' )
end
for k , c in pairs ( loaded ) do
if client == k then
-- loaded
log ( client , ' already been loaded for ' , ft , loaded , c )
if not _NG_Loaded [ bufnr ] or _NG_Loaded [ bufnr ] < 4 then
log ( ' doautocmd filetype ' )
vim.defer_fn ( function ( )
vim.cmd ( ' doautocmd FileType ' )
_NG_Loaded [ bufnr ] = ( _NG_Loaded [ bufnr ] or 0 ) + 1
end , 100 )
return
end
end
end
local clients = vim.lsp . buf_get_clients ( 0 )
for _ , c in pairs ( clients or { } ) do
log ( " lsp start up in progress client " , client , c.name )
if c.name == client then
_NG_Loaded [ bufnr ] = 100
return
end
end
if starting and ( starting.cnt or 0 ) > 0 then
log ( " lsp start up in progress " , starting )
return vim.defer_fn ( function ( )
load_cfg ( ft , client , cfg , loaded , { cnt = starting.cnt - 1 } )
end ,
200 )
end
if lspconfig [ client ] == nil then
error ( ' client ' .. client .. ' not supported ' )
return
@ -436,8 +189,27 @@ local function load_cfg(ft, client, cfg, loaded)
log ( ' lspconfig setup ' )
-- log(lspconfig.available_servers())
-- force reload with config
lspconfig [ client ] . setup ( cfg )
log ( client , ' loading for ' , ft )
-- lets have a guard here
if not _NG_Loaded [ client ] then
log ( client , ' loading for ' , ft , cfg )
log ( lspconfig [ client ] )
lspconfig [ client ] . setup ( cfg )
_NG_Loaded [ client ] = true
vim.defer_fn ( function ( )
log ( ' send filetype event ' )
vim.cmd ( [[doautocmd Filetype]] )
_NG_Loaded [ bufnr ] = ( _NG_Loaded [ bufnr ] or 0 ) + 1
end , 400 )
else
log ( ' send filetype event ' )
if not _NG_Loaded [ bufnr ] or _NG_Loaded [ bufnr ] < 4 then
log ( ' doautocmd filetype ' )
vim.defer_fn ( function ( )
vim.cmd ( ' doautocmd FileType ' )
_NG_Loaded [ bufnr ] = ( _NG_Loaded [ bufnr ] or 0 ) + 1
end , 100 )
end
end
end
-- need to verify the lsp server is up
end
@ -455,7 +227,7 @@ local function setup_fmt(client, enabled)
client.server_capabilities . documentFormattingProvider = false
else
client.server_capabilities . documentFormattingProvider = client.server_capabilities . documentFormattingProvider
or enabled
or enabled
end
end
@ -481,7 +253,7 @@ end
local loaded = { }
local function lsp_startup ( ft , retry , user_lsp_opts )
retry = retry or false
local path_sep = require ( ' navigator.util ' ) . path_sep ( )
local capabilities = update_capabilities ( )
for _ , lspclient in ipairs ( servers ) do
@ -551,7 +323,7 @@ local function lsp_startup(ft, retry, user_lsp_opts)
log ( lspclient )
-- if user provides override values
cfg.capabilities = capabilities
-- cfg.capabilities = capabilities
log ( lspclient , config.lsp . disable_format_cap )
if vim.tbl_contains ( config.lsp . disable_format_cap or { } , lspclient ) then
log ( ' fileformat disabled for ' , lspclient )
@ -635,16 +407,21 @@ local function lsp_startup(ft, retry, user_lsp_opts)
log ( ' lsp installer server config ' .. lspconfig [ lspclient ] . name , installer_cfg )
if installed and installer_cfg then
local paths = installer_cfg : get_default_options ( ) . cmd_env.PATH
local paths = installer_cfg : get_default_options ( ) . cmd_env and installer_cfg : get_default_options ( ) . cmd_env.PATH
if not paths then
-- for some reason lspinstaller does not install the binary, check default PATH
log ( ' lsp installer does not install the lsp in its path, fallback ' )
return load_cfg ( ft , lspclient , cfg , loaded )
end
paths = vim.split ( paths , ' : ' )
if vim.fn . empty ( cfg.cmd ) == 1 then
if v fn.empty( cfg.cmd ) == 1 then
cfg.cmd = { installer_cfg.name }
end
if v im. fn . executable( cfg.cmd [ 1 ] ) == 0 then
if v fn.executable( cfg.cmd [ 1 ] ) == 0 then
for _ , path in ipairs ( paths ) do
log ( path )
if v im. fn . isdirectory( path ) == 1 and string.find ( path , installer_cfg.root_dir ) then
if v fn.isdirectory( path ) == 1 and string.find ( path , installer_cfg.root_dir ) then
cfg.cmd [ 1 ] = path .. path_sep .. cfg.cmd [ 1 ]
log ( cfg.cmd )
break
@ -656,17 +433,49 @@ local function lsp_startup(ft, retry, user_lsp_opts)
end
end
end
if has_mason and _NgConfigValues.mason then
local servers = require ' mason-lspconfig ' . get_installed_servers ( )
if not vim.tbl_contains ( servers , lspconfig [ lspclient ] . name ) then
log ( ' mason server not installed ' , lspconfig [ lspclient ] . name )
-- return
end
local pkg_name = require " mason-lspconfig.mappings.server " . lspconfig_to_package [ lspconfig [ lspclient ] . name ]
local pkg = require " mason-registry " . get_package ( pkg_name )
log ( ' lsp installer server config ' .. lspconfig [ lspclient ] . name , pkg )
if pkg then
local path = pkg : get_install_path ( )
if not path then
-- for some reason lspinstaller does not install the binary, check default PATH
log ( ' lsp installer does not install the lsp in its path, fallback ' )
return load_cfg ( ft , lspclient , cfg , loaded )
end
cfg.cmd = cfg.cmd or { }
cfg.cmd [ 1 ] = table.concat ( { vfn.stdpath ( ' data ' ) , ' mason ' , ' bin ' , pkg.name } , path_sep )
if vfn.executable ( cfg.cmd [ 1 ] ) == 0 then
log ( ' failed to find cmd ' , cfg.cmd [ 1 ] , " fallback " )
return load_cfg ( ft , lspclient , cfg , loaded )
else
log ( ' cmd installed ' , cfg.cmd )
end
end
end
if vim.fn . executable ( cfg.cmd [ 1 ] ) == 0 then
if v fn.executable( cfg.cmd [ 1 ] ) == 0 then
log ( ' lsp server not installed in path ' .. lspclient .. vim.inspect ( cfg.cmd ) , vim.lsp . log_levels.WARN )
end
if _NG_Loaded [ lspclient ] then
log ( ' client loaded ? ' , lspclient )
log ( ' client loaded ? ' , lspclient , _NG_Loaded [ lspclient ] )
end
local starting = { }
if _NG_Loaded [ lspclient ] == true then
starting = { cnt = 1 }
end
load_cfg ( ft , lspclient , cfg , loaded )
_NG_Loaded [ lspclient ] = true
load_cfg( ft , lspclient , cfg , loaded , starting )
-- load_cfg(ft, lspclient, {}, loaded)
:: continue ::
end
@ -740,7 +549,7 @@ local function setup(user_opts, cnt)
local bufnr = user_opts.bufnr or vim.api . nvim_get_current_buf ( )
if ft == ' ' or ft == nil then
log ( ' nil filetype, callback ' )
local ext = v im. fn . expand( ' %:e ' )
local ext = v fn.expand( ' %:e ' )
if ext ~= ' ' then
cnt = cnt or 0
local opts = vim.deepcopy ( user_opts )
@ -820,7 +629,7 @@ local function setup(user_opts, cnt)
--- if code lens enabled
if _NgConfigValues.lsp . code_lens_action.enable then
require ( ' navigator.codelens ' ) . setup ( )
require ( ' navigator.codelens ' ) . setup ( bufnr )
end
-- _LoadedFiletypes[ft .. tostring(bufnr)] = true -- may prevent lsp config when reboot lsp
@ -835,23 +644,38 @@ local function on_filetype()
return
end
if uri == ' file:// ' or uri == ' file:/// ' then
log ( ' skip loading for ft ' , ft , uri )
trace ( ' skip loading for ft ' , ft , uri )
return
end
log ( _NG_Loaded )
if _NG_Loaded [ bufnr ] and type ( _NG_Loaded [ bufnr ] ) == ' number ' and _NG_Loaded [ bufnr ] > 1 then
log ( ' navigator was loaded for ft ' , ft , bufnr )
return
end
-- on_filetype should only be trigger only once for each bufnr
if _NG_Loaded [ bufnr ] ~= nil and type ( _NG_Loaded [ bufnr ] == ' number ' ) then
_NG_Loaded [ bufnr ] = _NG_Loaded [ bufnr ] + 1 -- do not hook and trigger filetype event multiple times
end
if _NG_Loaded [ bufnr ] == true then
_NG_Loaded [ bufnr ] = 1 -- record the count
end
-- as setup will send filetype event as well
log ( uri )
local wids = vim.fn . win_findbuf ( bufnr )
local wids = v fn.win_findbuf( bufnr )
if empty ( wids ) then
log ( ' buf not shown return ' )
end
setup ( { bufnr = bufnr } )
_NG_Loaded [ bufnr ] = 1
end
return {
setup = setup ,
get_cfg = get_cfg ,
lsp = servers ,
add_servers = add_servers ,
on_filetype = on_filetype ,
disabled_ft = disabled_ft ,