ccls.nvim
ccls.nvim copied to clipboard
Neovim plugin for ccls language server. Leverages off-spec extensions to LSP client with AST browser
ccls.nvim
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:
- member functions/variables of an object
- base and derived hierarchy of a class
- call hierarchy for a function
- sturcts and variables of the same type in the project
There are some additional functionalities, follow the README for them.
-
ccls extensions
-
Quickfix
-
$ccls/member
-
$ccls/call
-
$ccls/inheritance
-
$ccls/vars
-
-
Sidebar or float
-
$ccls/member
hierarchy -
$ccls/call
hierarchy -
$ccls/inheritance
hierarchy
-
-
Quickfix
-
Configuration
- Window configuration
- Filetypes
-
Lsp
- Using lspcofing
- Using direct call
- Codelens
- Coexistence with clangd
- NodeTree
-
TODO
- Preview
- Tests
Features include:
- Most off-spec
ccls
features - Setup lsp either via
lspconfig
or built-invim.lsp.start()
- Use treesitter to highlight NodeTree window
- Update tagstack on jump to new node
- Setup codelens autocmds
ccls extensions
ccls
LSP has many off-spec commands/calls. This plugin supports the following
Quickfix
The below functions return a quickfix list of items
$ccls/member
Called 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/call
Called via require("ccls").call(callee)
.
true = outgoing calls, false = incoming calls
Can also be called via
-
:CclsIncomingCalls
-
:CclsOutgoingCalls
$ccls/inheritance
Called via require("ccls").inheritance(derived)
derived true
for derived classes, false
for base classes
Can also be called via
-
:CclsBase
-
:CclsDerived
$ccls/vars
Called 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.
Sidebar or float
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 cursor -
O
to toggle node under cursor -
CR
to jump to node under cursor -
q
To quit window
$ccls/member
hierarchy
Called 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
hierarchy
Called via require("ccls").callHierarchy(callee)
.
true = outgoing calls, false = incoming calls
Can also be called via
-
:CclsIncomingCallsHierarchy
-
:CclsOutgoingCallsHierarchy
$ccls/inheritance
hierarchy
Called via require("ccls").inheritanceHierarchy(derived)
derived true
for derived classes, false
for base classes
Can also be called via
-
:CclsBaseHierarchy
-
:CclsDerivedHierarchy
Configuration
Call require("ccls").setup(config)
somewhere in your config
The default values are:
Code
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.
Window configuration
win_config
table accepts two keys:
-sidebar
: split options
-float
: same options supplied to nvim_open_win
or other default floating
windows
Filetypes
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"}})
Lsp
You can optionally setup LSP through the plugin. By default no setup calls are initiated.
There are two methods.
Using lspcofing
This requires that you have nvim-lspconfig plugin installed (and already loaded if lazy-loading). Pass the appropriate configurations like this.
Code
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}})
Using direct call
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
Code
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.
Codelens
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
Coexistence with clangd
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.
Code
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.
Here is a complete setup example from my config (using nvim 0.8 features)
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 }
},
}
Notes
NodeTree
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 luafiedyggdrasil
tree code-
ccls/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 oftree
ornode
class but necessary
-
TODO
Preview
Open a floating preview window for node under the cursor from Sidebar
Tests
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.
Credits
- MaskRay Thank you for creating the LSP!
- vim-ccls for inspiration and speicifc ideas on translating LSP data into tree-like structure.
- vim-yggdrasil The entire tree-browser part of the code is a lua rewrite of this plugin.