A neovim plugin to configure ccls language server and use its extensions.
ccls is a language server for c, cpp and variants that offers comparable
on-spec features as clangd along with a many extensions.
This plugin offers a tree-browser structure to parse the AST provided by ccls extensions and to quickly navigate to them.
These AST features include:
There are some additional functionalities, follow the README for them.
Features include:
ccls featureslspconfig or built-in vim.lsp.start()ccls LSP has many off-spec commands/calls. This plugin supports the following
The below functions return a quickfix list of items
$ccls/memberCalled via require("ccls").member(kind).
kind 4 = variables, 3 = functions, 2 = type
Individual member calls can also be made via
:CclsMember for Variables:CclsMemberFunction for functions:CclsMemberType for types$ccls/callCalled via require("ccls").call(callee).
true = outgoing calls, false = incoming calls
Can also be called via
:CclsIncomingCalls:CclsOutgoingCalls$ccls/inheritanceCalled via require("ccls").inheritance(derived)
derived true for derived classes, false for base classes
Can also be called via
:CclsBase:CclsDerived$ccls/varsCalled via :CclsVars kind or require("ccls").vars(kind).
This is similar to textDocument/references except it checks for the variable
type.
Kind values are 1 for all occurence of the variable type, 2 for defintion of
current variable and 3 for references without definition.
The following functions are hierarchical and return either a sidebar or a floating window
Each lua callback has a view option. View is a table with example {type = "float"} to use floating window.
For vim commands it can be passed via :CclsMemberHierarchy float
When omitted it uses a sidebar.
Inside the window, use maps:
o to open a node under cursor.c to close the node under cursorO to toggle node under cursorCR to jump to node under cursorq To quit window$ccls/member hierarchyCalled via require("ccls").memberHierarchy(kind, view).
kind 4 = variables, 3 = functions, 2 = type
individual member calls can also be made via
:CclsMemberHierarchy for Variables:CclsMemberFunction for functions:CclsMemberTyoe for types$ccls/call hierarchyCalled via require("ccls").callHierarchy(callee).
true = outgoing calls, false = incoming calls
Can also be called via
:CclsIncomingCallsHierarchy:CclsOutgoingCallsHierarchy$ccls/inheritance hierarchyCalled via require("ccls").inheritanceHierarchy(derived)
derived true for derived classes, false for base classes
Can also be called via
:CclsBaseHierarchy:CclsDerivedHierarchyCall require("ccls").setup(config) somewhere in your config
The default values are:
defaults = {
win_config = {
-- Sidebar configuration
sidebar = {
size = 50,
position = "topleft",
split = "vnew",
width = 50,
height = 20,
},
-- floating window configuration. check :help nvim_open_win for options
float = {
style = "minimal",
relative = "cursor",
width = 50,
height = 20,
row = 0,
col = 0,
border = "rounded",
},
},
filetypes = {"c", "cpp", "objc", "objcpp"},
-- Lsp is not setup by default to avoid overriding user's personal configurations.
-- Look ahead for instructions on using this plugin for ccls setup
lsp = {
codelens = {
enabled = false,
events = {"BufEnter", "BufWritePost"}
}
}
}
Any of the configuration options can be omitted.
win_config table accepts two keys:
-sidebar: split options
-float: same options supplied to nvim_open_win or other default floating
windows
By default, this plugin works on all filetypes accepted by ccls language
server. You can customize this by adding filetypes table to the config
require("ccls").setup({filetypes = {"c", "cpp", "opencl"}})
You can optionally setup LSP through the plugin. By default no setup calls are initiated.
There are two methods.
This requires that you have nvim-lspconfig plugin installed (and already loaded if lazy-loading). Pass the appropriate configurations like this.
local util = require "lspconfig.util"
local server_config = {
filetypes = { "c", "cpp", "objc", "objcpp", "opencl" },
root_dir = function(fname)
return util.root_pattern("compile_commands.json", "compile_flags.txt", ".git")(fname)
or util.find_git_ancestor(fname)
end,
init_options = { cache = {
directory = vim.env.XDG_CACHE_HOME .. "/ccls/",
-- or vim.fs.normalize "~/.cache/ccls" -- if on nvim 0.8 or higher
} },
--on_attach = require("my.attach").func,
--capabilities = my_caps_table_or_func
}
require("ccls").setup { lsp = { lspconfig = server_config } }
Any option omitted will use lspconfig defaults.
It is also possible to entirely use lspconfig defaults like this:
require("ccls").setup({lsp = {use_defaults = true}})
If using nvim 0.8, you can use vim.lsp.start() call instead which has the
benefit of reusing the same client on files within the same workspace.
To use that, pass this in your config, without supplying the keys use_defaults
or lspconfig.
Warning: Requires nvim 0.8
require("ccls").setup {
lsp = {
-- check :help vim.lsp.start for config options
server = {
name = "ccls", --String name
cmd = {"/usr/bin/ccls"}, -- point to your binary, has to be a table
args = {--[[Any args table]] },
offset_encoding = "utf-32", -- default value set by plugin
root_dir = vim.fs.dirname(vim.fs.find({ "compile_commands.json", ".git" }, { upward = true })[1]), -- or some other function that returns a string
--on_attach = your_func,
--capabilites = your_table/func
},
},
}
If neither use_defaults, lspconfig nor server are set,
then the plugin assumes you have setup ccls LSP elsewhere in your config.
This is the default behaviour.
ccls has minimal codelens capabilites. If you are not familiar with codenels, see Lsp spec
documentation.
According to ccls server capabilities tree, ccls supports resolveProvider
option of codelens.
To enable codelens, set lsp = { codelens = {enable = true}} in the config.
It is necessary to setup autocmds to refresh codelens. The default events are
BufEnter and BufWritePost. You can customize it this way:
require('ccls').setup({
lsp = {
codelens = {
enable = true,
events = {"BufWritePost", "InsertLeave"}
}
}
})
Note: Setting up codelens using this plugin requires neovim >= 0.8 as
LspAttach autocmd is only avaialble from version 0.8
If you wish to use clangd alongside ccls and want to avoid conflicting parallel requests, you can use the following table to disable specific capabilities.
Warning: Upstream (neovim) maintainers label the process of disabling capabilities as hacky. Until there is a mechanism in-place upstream that uses predicates to select clients for calls, this is the best solution.
This method uses both disabling certain capabilities and passing nil handlers
to others. This makes running two language servers more resource efficient.
Use only the following options. If you do not wish to disable said option, either set it to false or simply leave out that option.
require("ccls").setup {
lsp = {
disable_capabilities = {
completionProvider = true,
documentFormattingProvider = true,
documentRangeFormattingProvider = true,
documentHighlightProvider = true,
documentSymbolProvider = true,
workspaceSymbolProvider = true,
renameProvider = true,
hoverProvider = true,
codeActionProvider = true,
},
disable_diagnostics = true,
disable_signature = true,
},
}
Note: For these disabling mechanisms to be attached to the initiated/running ccls
instance, you will have to configure the server through the plugin either using
lsp = {lspconfig = {my_config_table}} or lsp={server={my_0.8.config}} as
descried earlier.
local filetypes = { "c", "cpp", "objc", "objcpp", "opencl" }
local server_config = {
filetypes = filetypes,
init_options = { cache = {
directory = vim.fs.normalize "~/.cache/ccls/",
} },
name = "ccls",
cmd = { "ccls" },
offset_encoding = "utf-32",
root_dir = vim.fs.dirname(
vim.fs.find({ "compile_commands.json", "compile_flags.txt", ".git" }, { upward = true })[1]
),
}
require("ccls").setup {
filetypes = filetypes,
lsp = {
server = server_config,
disable_capabilities = {
completionProvider = true,
documentFormattingProvider = true,
documentRangeFormattingProvider = true,
documentHighlightProvider = true,
documentSymbolProvider = true,
workspaceSymbolProvider = true,
renameProvider = true,
hoverProvider = true,
codeActionProvider = true,
},
disable_diagnostics = true,
disable_signature = true,
codelens = { enable = true }
},
}
As of now, the NodeTree filetype which renders a tree structure is a direct
lua rewrite of Martin Pilia's vim-yggdrasil. At some point in the future I
will rewrite the logic to utilize more lua-ecosystem features and make it
a general purpose Tree browser.
For now, it works exactly as intended but is not easy read. The code structure is as follows.
ccls/provider.lua contains functions to make LSP results compatible with
NodeTree.ccls/tree Folder has the luafied yggdrasil tree codeccls/tree/tree.lua has the Tree class.ccls/tree/node.lua has the node class reduced to a single node generator call
to avoid caching problems. Will be modularized when I rewrite the logic.ccls/tree/utils.lua has other function calls not part of tree or node class but necessaryOpen a floating preview window for node under the cursor from Sidebar
This will take some time. Need to figure out how to run a language server for testing. I will look through other plugins to see how they handle it. No promise on time.