mirror of
https://github.com/nix-community/nixvim.git
synced 2026-09-25 16:50:23 -04:00
modules/lsp: add native LSP feature toggles
Expose `completion`, `semanticTokens`, `documentColor`, `linkedEditingRange`, `onTypeFormatting`, and `inlineCompletion`. Each feature uses `enable` to opt into Nixvim management and `activate` as the value passed to `vim.lsp.<feature>.enable()`. `activate` defaults to true, preserving the existing `inlayHints` and `codelens` output. Set `enable = true` and `activate = false` to disable semantic tokens or document color, which Neovim enables by default. A shared table generates the global feature options and Lua. `documentColor` also accepts `settings` for its `opts` argument. `enabledByDefault` limits disable guidance to Neovim defaults, while `settingsExample` carries the document color example. Because `vim.lsp.completion.enable()` takes a client id and buffer, completion runs from `LspAttach` after `onAttach`. Neovim snapshots `completionProvider.triggerCharacters` when completion is enabled, so users must extend that list first. See `:h lsp-autocompletion`. `supports_method` receives `bufnr` so dynamic registration checks the attached buffer. User `onAttach` code runs in a function so `return` cannot skip Nixvim setup. `client/unregisterCapability` is not handled, so withdrawn support leaves completion state until the client detaches. If `onAttach` calls `completion.enable()` first, its buffer options remain because Neovim keeps the options from the first call.
This commit is contained in:
+87
-25
@@ -1,6 +1,89 @@
|
||||
{ lib, config, ... }:
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.lsp;
|
||||
opts = options.lsp;
|
||||
|
||||
features = {
|
||||
inlayHints.luaName = "inlay_hint";
|
||||
codelens.luaName = "codelens";
|
||||
semanticTokens = {
|
||||
luaName = "semantic_tokens";
|
||||
enabledByDefault = true;
|
||||
};
|
||||
documentColor = {
|
||||
luaName = "document_color";
|
||||
enabledByDefault = true;
|
||||
hasSettings = true;
|
||||
settingsExample = {
|
||||
style = "virtual";
|
||||
};
|
||||
};
|
||||
linkedEditingRange.luaName = "linked_editing_range";
|
||||
onTypeFormatting.luaName = "on_type_formatting";
|
||||
inlineCompletion.luaName = "inline_completion";
|
||||
};
|
||||
|
||||
mkFeatureOptions =
|
||||
name:
|
||||
{
|
||||
luaName,
|
||||
enabledByDefault ? false,
|
||||
hasSettings ? false,
|
||||
settingsExample ? null,
|
||||
}:
|
||||
{
|
||||
enable = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether Nixvim manages `vim.lsp.${luaName}`.
|
||||
|
||||
See [`:h lsp-${luaName}`](https://neovim.io/doc/user/lsp/#lsp-${luaName})
|
||||
'';
|
||||
};
|
||||
|
||||
activate = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = ''
|
||||
Value passed to `vim.lsp.${luaName}.enable()`.
|
||||
''
|
||||
+ lib.optionalString enabledByDefault ''
|
||||
|
||||
Neovim enables this feature by default. To disable it, set
|
||||
`${opts.${name}.enable}` to `true` and this option to `false`.
|
||||
'';
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs hasSettings {
|
||||
settings = lib.nixvim.mkSettingsOption {
|
||||
description = ''
|
||||
Options passed as the `opts` argument to
|
||||
`vim.lsp.${luaName}.enable()`. Nixvim passes `nil` for `filter`. Use
|
||||
`${opts.luaConfig}.content` to filter by buffer or client.
|
||||
'';
|
||||
example = settingsExample;
|
||||
};
|
||||
};
|
||||
|
||||
mkFeatureLua =
|
||||
name:
|
||||
{
|
||||
luaName,
|
||||
...
|
||||
}:
|
||||
let
|
||||
featureCfg = cfg.${name};
|
||||
# The `opts` parameter follows `filter`, so pass `nil` as the second argument.
|
||||
optsArg = lib.optionalString (
|
||||
featureCfg ? settings && featureCfg.settings != { }
|
||||
) ", nil, ${lib.nixvim.toLuaObject featureCfg.settings}";
|
||||
in
|
||||
lib.mkIf featureCfg.enable "vim.lsp.${luaName}.enable(${lib.boolToString featureCfg.activate}${optsArg})";
|
||||
in
|
||||
{
|
||||
options.lsp = {
|
||||
@@ -11,26 +94,8 @@ in
|
||||
Lua code configuring LSP.
|
||||
'';
|
||||
};
|
||||
|
||||
inlayHints = {
|
||||
enable = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether to enable inlay hints globally.
|
||||
|
||||
See [`:h lsp-inlay_hint`](https://neovim.io/doc/user/lsp/#lsp-inlay_hint)
|
||||
'';
|
||||
};
|
||||
};
|
||||
codelens = {
|
||||
enable = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether to enable codelens globally.
|
||||
|
||||
See [`:h lsp-codelens`](https://neovim.io/doc/user/lsp/#lsp-codelens)
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// lib.mapAttrs mkFeatureOptions features;
|
||||
|
||||
imports = [
|
||||
./servers
|
||||
@@ -39,10 +104,7 @@ in
|
||||
];
|
||||
|
||||
config = {
|
||||
lsp.luaConfig.content = lib.mkMerge [
|
||||
(lib.mkIf cfg.inlayHints.enable "vim.lsp.inlay_hint.enable(true)")
|
||||
(lib.mkIf cfg.codelens.enable "vim.lsp.codelens.enable(true)")
|
||||
];
|
||||
lsp.luaConfig.content = lib.mkMerge (lib.mapAttrsToList mkFeatureLua features);
|
||||
|
||||
extraConfigLua = lib.mkIf (cfg.luaConfig.content != "") ''
|
||||
-- LSP {{{
|
||||
|
||||
@@ -1,9 +1,69 @@
|
||||
{ lib, config, ... }:
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.lsp;
|
||||
opts = options.lsp;
|
||||
|
||||
# Isolate user code so `return` cannot skip the completion setup that follows.
|
||||
userOnAttachLua = lib.optionalString (cfg.onAttach != "") ''
|
||||
local function __nixvim_user_on_attach()
|
||||
${cfg.onAttach}
|
||||
end
|
||||
|
||||
__nixvim_user_on_attach()
|
||||
'';
|
||||
|
||||
# Completion needs a client and buffer, so configure it per attached buffer.
|
||||
# Neovim snapshots `completionProvider.triggerCharacters` when completion is
|
||||
# enabled. Run this after `onAttach` so users can extend the list first. See
|
||||
# `:h lsp-attach` and `:h lsp-autocompletion`.
|
||||
# Pass `bufnr` because dynamic registration can target a non-current buffer.
|
||||
completionLua = lib.optionalString cfg.completion.enable ''
|
||||
if client:supports_method('textDocument/completion', bufnr) then
|
||||
vim.lsp.completion.enable(${lib.boolToString cfg.completion.activate}, client.id, bufnr${
|
||||
lib.optionalString (
|
||||
cfg.completion.settings != { }
|
||||
) ", ${lib.nixvim.toLuaObject cfg.completion.settings}"
|
||||
})
|
||||
end
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.lsp = {
|
||||
completion = {
|
||||
enable = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether Nixvim manages `vim.lsp.completion` for attached clients that
|
||||
support `textDocument/completion`.
|
||||
|
||||
See [`:h lsp-completion`](https://neovim.io/doc/user/lsp/#lsp-completion)
|
||||
'';
|
||||
};
|
||||
|
||||
activate = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = ''
|
||||
Value passed to `vim.lsp.completion.enable()`. To disable completion,
|
||||
set `${opts.completion.enable}` to `true` and this option to `false`.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.nixvim.mkSettingsOption {
|
||||
description = ''
|
||||
Options passed to `vim.lsp.completion.enable()`.
|
||||
'';
|
||||
example = {
|
||||
autotrigger = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
onAttach = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
description = ''
|
||||
@@ -22,7 +82,7 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.onAttach != "") {
|
||||
config = lib.mkIf (cfg.onAttach != "" || cfg.completion.enable) {
|
||||
autoGroups.nixvim_lsp_on_attach.clear = false;
|
||||
|
||||
autoCmd = [
|
||||
@@ -61,7 +121,8 @@ in
|
||||
|
||||
extraConfigLua = ''
|
||||
local function __nixvim_lsp_on_attach(client, bufnr, event)
|
||||
${cfg.onAttach}
|
||||
${userOnAttachLua}
|
||||
${completionLua}
|
||||
end
|
||||
|
||||
vim.lsp.handlers["client/registerCapability"] = (function(overridden)
|
||||
|
||||
@@ -1,5 +1,131 @@
|
||||
{ pkgs }:
|
||||
{
|
||||
feature-enable-only =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
globalLua = config.lsp.luaConfig.content;
|
||||
expectedLuaNames = {
|
||||
inlayHints = "inlay_hint";
|
||||
codelens = "codelens";
|
||||
semanticTokens = "semantic_tokens";
|
||||
documentColor = "document_color";
|
||||
linkedEditingRange = "linked_editing_range";
|
||||
onTypeFormatting = "on_type_formatting";
|
||||
inlineCompletion = "inline_completion";
|
||||
};
|
||||
in
|
||||
{
|
||||
lsp = lib.mapAttrs (_: _: { enable = true; }) expectedLuaNames;
|
||||
|
||||
assertions = lib.mapAttrsToList (option: luaName: {
|
||||
assertion = lib.hasInfix "vim.lsp.${luaName}.enable(true)" globalLua;
|
||||
message = "Expected `lsp.${option}.enable` to emit `vim.lsp.${luaName}.enable(true)`.";
|
||||
}) expectedLuaNames;
|
||||
};
|
||||
|
||||
feature-forced-off =
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
lsp.semanticTokens = {
|
||||
enable = true;
|
||||
activate = false;
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.hasInfix "vim.lsp.semantic_tokens.enable(false)" config.lsp.luaConfig.content;
|
||||
message = "Expected `activate = false` to emit a disabling call.";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
feature-disabled-emits-nothing =
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
lsp.documentColor = {
|
||||
enable = false;
|
||||
settings.style = "virtual";
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = !lib.hasInfix "vim.lsp.document_color" config.lsp.luaConfig.content;
|
||||
message = "Expected no document color Lua while `enable` is off.";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
document-color-settings =
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
lsp.documentColor = {
|
||||
enable = true;
|
||||
settings.style = "virtual";
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.hasInfix ''vim.lsp.document_color.enable(true, nil, { style = "virtual" })'' config.lsp.luaConfig.content;
|
||||
message = "Expected document color settings as the third argument.";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
completion-survives-onattach-return =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
attachLua = config.extraConfigLua;
|
||||
in
|
||||
{
|
||||
lsp = {
|
||||
completion.enable = true;
|
||||
onAttach = ''
|
||||
if client.name ~= "lua_ls" then
|
||||
return
|
||||
end
|
||||
'';
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.hasInfix "__nixvim_user_on_attach()" attachLua;
|
||||
message = "Expected the user's `onAttach` body to run in its own function.";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
let
|
||||
afterUserCall = lib.last (lib.splitString "__nixvim_user_on_attach()" attachLua);
|
||||
in
|
||||
lib.hasInfix "vim.lsp.completion.enable" afterUserCall;
|
||||
message = "Expected the completion call to follow the user's `onAttach`, outside it.";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
completion-activate =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
attachLua = config.extraConfigLua;
|
||||
in
|
||||
{
|
||||
lsp.completion = {
|
||||
enable = true;
|
||||
activate = false;
|
||||
settings.autotrigger = true;
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.hasInfix "vim.lsp.completion.enable(false, client.id, bufnr, { autotrigger = true })" attachLua;
|
||||
message = "Expected `activate` and `settings` to reach the completion call.";
|
||||
}
|
||||
{
|
||||
assertion = lib.hasInfix "client:supports_method('textDocument/completion', bufnr)" attachLua;
|
||||
message = "Expected the capability check to pass `bufnr`.";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
example = {
|
||||
lsp.servers = {
|
||||
"*".config = {
|
||||
|
||||
Reference in New Issue
Block a user