Easily read your project's local settings files and merge them into your Neovim 0.11+ native LSP configuration.
This plugin makes it easy to reuse settings your team already committed to version control for VS Code by
providing an API to merge the relevant settings from VS Code's settings schema into the LSP settings table you pass
to vim.lsp.config() (or any way you configure LSP).
vim.lsp.config() API).vscode/settings.jsoncodesettings.jsonlspsettings.jsonIf you are only using the API, you do not need to call .setup() unless you with to use non-default config.
You must call .setup() for some features like jsonls, lua_ls, and jsonc filetype integrations to work,
or to configure codesettings.nvim itself with local files.
return {
'mrjones2014/codesettings.nvim',
-- these are the default settings just set `opts = {}` to use defaults
opts = {
---Look for these config files
config_file_paths = { '.vscode/settings.json', 'codesettings.json', 'lspsettings.json' },
---Integrate with jsonls to provide LSP completion for LSP settings based on schemas
jsonls_integration = true,
---Set up library paths for lua_ls automatically to pick up the generated type
---annotations provided by codesettings.nvim; to enable for only your nvim config,
---you can also do something like:
---lua_ls_integration = function()
--- return vim.uv.cwd() == ('%s/.config/nvim'):format(vim.env.HOME)
---end,
---This integration also works for emmylua_ls
lua_ls_integration = true,
---Set filetype to jsonc when opening a file specified by `config_file_paths`,
---make sure you have the jsonc tree-sitter parser installed for highlighting
jsonc_filetype = true,
---Enable live reloading of settings when config files change; for servers that support it,
---this is done via the `workspace/didChangeConfiguration` notification, otherwise the
---server is restarted
live_reload = false,
---Provide your own root dir; can be a string or function returning a string.
---It should be/return the full absolute path to the root directory.
---If not set, defaults to `require('codesettings.util').get_root()`
root_dir = nil,
--- How to merge lists; 'append' (default), 'prepend' or 'replace'
merge_lists = 'append',
},
-- I recommend loading on these filetype so that the
-- jsonls integration, lua_ls integration, and jsonc filetype setup works
ft = { 'json', 'jsonc', 'lua' },
}
codesettings.nvim can also be specified in the local JSON configuration files by using a top-level codesettings
object key, and these override the global plugin configuration. For example:
{
"codesettings.merge_lists": "replace",
}
Recommended setup: If you don't use before_init for anything else, you can use it as a global hook
to look for local config files for all LSPs:
vim.lsp.config('*', {
before_init = function(_, config)
local codesettings = require('codesettings')
codesettings.with_local_settings(config.name, config)
end,
})
Alternatively, you can configure it on a per-server basis.
-- you can also still use `before_init` here
-- if you want codesettings to be `require`d
-- lazily
local codesettings = require('codesettings')
vim.lsp.config(
'yamlls',
codesettings.with_local_settings('yamlls', {
settings = {
yaml = {
validate = true,
schemaStore = { enable = true },
},
},
}, {
-- you can also pass custom merge opts on a per-server basis
list_behavior = 'replace',
})
)
-- or from a config file under `/lsp/rust-analyzer.lua` in your config directory.
-- if you use rustaceanvim to configure rust-analyzer, see the `rustaceanvim` section below
return codesettings.with_local_settings('rust-analyzer', {
settings = {
-- ...
},
})
The before_init global hook does not work if you use rustaceanvim
to configure rust-analyzer, however you can still use codesettings.nvim to merge local settings.
rustaceanvim loads VS Code settings by default, but your global settings override the local ones; codesettings.nvim
does the opposite. Here's how I configure rustaceanvim in my own setup:
return {
'mrcjkb/rustaceanvim',
ft = 'rust',
version = '^6',
dependencies = { 'mrjones2014/codesettings.nvim' },
init = function()
vim.g.rustaceanvim = {
-- the rest of your settings go here...
-- I want VS Code settings to override my settings,
-- not the other way around, so use codesettings.nvim
-- instead of rustaceanvim's built-in vscode settings loader
load_vscode_settings = false,
-- the global hook doesn't work when configuring rust-analyzer with rustaceanvim
settings = function(_, settings)
-- Note the exact way this is invoked to work with rustaceanvim:
-- - passed in settings are wrapped like `{ settings = settings }`
-- - the returned value is the `.settings` subtable
return require('codesettings').with_local_settings('rust-analyzer', { settings = settings }).settings
end,
default_settings = {
['rust-analyzer'] = {
-- your global LSP settings go here
},
},
}
end,
}
jsonc filetype for local config fileslive_reload = true)codesettings.nvim plugin itself in local config JSON filesjsonls integration for schema-based completion of LSP settings in JSON(C) configuration files
lua_ls integration
codesettings.json looks like:{
"Lua": {
"runtime.version": "LuaJIT",
"workspace": {
"library": ["${3rd}/luassert/library", "${addons}/busted/library"],
"checkThirdParty": false,
},
"diagnostics.globals": ["vim", "setup", "teardown"],
},
}
To get autocomplete in Lua files, either set config.lua_ls_integration = true, or (for lua_ls only, not emmylua_ls) use ---@module 'codesettings' which will tell lua_ls as though codesettings
has been required, then you will have access to ---@type lsp.server_name generated type annotations.
-- for example, for lua_ls
vim.lsp.config('lua_ls', {
-- this '@module' annotation makes lua_ls import the library from codesettings,
-- where the annotations come from; this isn't needed if you use `lua_ls_integration = true`
-- and `codesettings.nvim` is loaded
---@module 'codesettings'
-- then you will have access to the generated type annotations
---@type lsp.lua_ls
settings = {},
})
:Codesettings show - show the resolved LSP config for each active LSP client; note that this only shows active clients:Codesettings local - show the resolved local config found in local config files in your project:Codesettings files - show the config files found in your project:Codesettings edit - edit or create a local config file based on your configured config file paths:Codesettings health - check plugin health (alias for :checkhealth codesettings)require('codesettings').setup(opts?: CodesettingsConfig)
codesettings.nvim itself with local files.require('codesettings').with_local_settings(lsp_name: string, config: table, opts: CodesettingsConfigOverrides?): table
config.settings. Returns the merged config.config table. This is necessary for some workflows to ensure the vim.lsp module sees the updated settings.require('codesettings').local_settings(opts: CodesettingsConfigOverrides?): Settings
Settings object.Settings object provides some methods like:Settings:schema(lsp_name) - Filter the settings down to only the keys that match the relevant schema e.g. settings:schema('eslint')Settings:merge(settings, key, opts) - merge another Settings object into this one, optionally specify a sub-key to merge, and control merge behavior with the 2nd and 3rd parameter, respectively (e.g. { merge_lists = 'replace' })Settings:get(key) - returns the value at the specified key; supports dot-separated key paths like Settings:get('some.sub.property')Settings:get_subtable(key) - like Settings:get(key), but returns a Settings object if the path is a table, otherwise nilSettings:clear() - remove all valuesSettings:set(key, value) - supports dot-separated key paths like some.sub.propertyExample using local_settings() directly:
local codesettings = require('codesettings')
local eslint_settings = c.local_settings()
:schema('eslint')
:merge({
eslint = {
codeAction = {
disableRuleComment = {
enable = true,
location = 'sameLine',
},
},
},
})
:get('eslint.codeAction') -- get the codeAction subtable
vim.fs.root to search upwards with markers based on your configured config file paths, as well as .git and .jj (for Jujutsu repos)config_file_paths under your project root and uses any that existFollows the semantics of vim.tbl_deep_extend('force', your_config, local_config), essentially:
config is the base; values from the settings file override or extend it within config.settingscodesettings.nvim provides a fluent ConfigBuilder API that lets you override plugin options for a single load of local settings, without affecting the
global configuration. This is useful, for example, for multi-root projects where you might have a separate instance of the LSP server per-root.
vim.lsp.config('rust_analyzer', {
before_init = function(_, config)
local c = require('codesettings')
c
-- starts from the plugin's global config as a base
.loader()
-- override the root directory from the LSP config, which might be a sub-root
:root_dir(config.root_dir)
-- merge local settings according to the configuration specified
-- by this `ConfigBuilder`
:with_local_settings(
config.name,
config
)
end,
})
See codesettings.config.builder for the full available API and which settings can be overridden.
codesettings.nvim allows for custom post-processing of your local config files. Extensions can be registered globally,
or through the ConfigBuilder for one-shot loaders. Extensions can be registered directly, or via a string which will be
required. No extensions are registered by default.
local SomeExtension = require('some-3rdparty-extension')
require('codesettings').setup({
loader_extensions = { SomeExtension, 'another-3rdparty-extension' },
})
-- or for one-shot loaders
require('codesettings')
.loader()
:loader_extensions({ SomeExtension, 'another-3rdparty-extension' })
:with_local_settings('lua_ls', {
-- ...
})
codesettings.nvim provides the following built-in loader extensions that can be enabled in your plugin config
using their module path:
codesettings.extensions.env$ENV_VAR, ${ENV_VAR}, and even ${ENV_VAR:-/some/default/path} syntax.codesettings.extensions.neoconf.neoconf.json files.config_file_paths option.The extension API expects extensions to be modules that provide at least one of two API functions. The types that describe an extension are:
---@class CodesettingsLoaderExtensionContext
---@field parent table? The immediate parent table/list of this node
---@field path string[] Full path from the root to this node
---@field key string|integer The key/index of this node in the parent
---@field list_idx integer? Index if parent is a list
---@class CodesettingsLoaderExtension
---Optional visitor for non-leaf nodes (tables or lists). Return a control code and optional replacement value.
---Note that the replacement value is only used if the control code is `REPLACE`.
---@field object (fun(node:any, ctx:CodesettingsLoaderExtensionContext): CodesettingsLoaderExtensionControl, any?)?
---Optional visitor for leaf nodes. Return a control code and optional replacement value.
---Note that the replacement value is only used if the control code is `REPLACE`.
---@field leaf (fun(value:any, ctx:CodesettingsLoaderExtensionContext): CodesettingsLoaderExtensionControl, any?)?
---@enum CodesettingsLoaderExtensionControl
M.Control = {
---Continue recursion (for objects) or leave leaf unchanged
CONTINUE = 'continue',
---Skip recursion (objects only)
SKIP = 'skip',
---Replace this node/leaf with provided replacement value (can be nil)
REPLACE = 'replace',
}
Extensions support both simple table style extensions, as well as stateful method style extensions;
they will work whether your functions need to be called like extension.leaf(value, ctx) or
extension:leaf(value, ctx).
See codesettings.extensions.env for a simple example extension.
Some very basic performance benchmarks are available at ./bench/report.md.
They are quite minimal, but are useful as a baseline to flag and issues when implementing new functionality. They are updated by CI
after every merge to master so they are run on GitHub Actions runners and will likely have much better performance
on the machine you actually run Neovim on.
This project would not exist without the hard work of some other open source projects!
jsonc files