diff --git a/README.md b/README.md index 8f53ea2..deaa947 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ to look for local config files for all LSPs: vim.lsp.config('*', { before_init = function(_, config) local codesettings = require('codesettings') - config = codesettings.with_local_settings(config.name, config) + codesettings.with_local_settings(config.name, config) end, }) ``` @@ -208,6 +208,7 @@ vim.lsp.config('lua_ls', { - `require('codesettings').with_local_settings(lsp_name: string, config: table, opts: CodesettingsConfigOverrides?): table` - Loads settings from the configured files, extracts relevant settings for the given LSP based on its schema, and deep-merges into `config.settings`. Returns the merged config. + - This **mutates the input `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` - Loads and parses the settings file(s) for the current project. Returns a `Settings` object. @@ -439,4 +440,3 @@ This project would not exist without the hard work of some other open source pro - [x] [yamlls](https://github.com/redhat-developer/vscode-yaml/tree/master/package.json) - [x] [zeta_note](https://github.com/artempyanykh/zeta-note-vscode/tree/main/package.json) - [x] [zls](https://github.com/zigtools/zls-vscode/tree/master/package.json) - diff --git a/lua/codesettings/init.lua b/lua/codesettings/init.lua index 9d6601c..c68b6c7 100644 --- a/lua/codesettings/init.lua +++ b/lua/codesettings/init.lua @@ -46,9 +46,9 @@ function M.local_settings(opts) return Settings.load_all(opts) end ----Load settings from VS Code settings.json file +---Load settings from VS Code settings.json file. This mutates the given LSP config. ---@param lsp_name string the name of the LSP, like 'rust-analyzer' or 'tsserver' ----@param config table the LSP config to merge the vscode settings into +---@param config vim.lsp.Config|vim.lsp.ClientConfig the LSP config to merge the vscode settings into ---@param opts CodesettingsConfigOverrides? optional config overrides for this load ---@return table config the merged config function M.with_local_settings(lsp_name, config, opts) diff --git a/lua/codesettings/settings.lua b/lua/codesettings/settings.lua index c79a646..804fd64 100644 --- a/lua/codesettings/settings.lua +++ b/lua/codesettings/settings.lua @@ -241,8 +241,17 @@ function Settings:merge(settings, key, config) settings = M.new(settings) end if key then - local value = Util.merge(Util.merge({}, self:get(key) or {}, config), settings._settings, config) - self:set(key, value) + local existing = self:get(key) + if existing then + -- Mutate the existing table in place to preserve references + local value = Util.merge(existing, settings._settings, config) + -- technically `Util.merge` will mutate the value, + -- but set here to be explicit + self:set(key, value) + else + -- No existing value, safe to set directly + self:set(key, settings._settings) + end else self._settings = Util.merge(self._settings, settings._settings, config) end diff --git a/lua/codesettings/util.lua b/lua/codesettings/util.lua index 721d16f..ef9de70 100644 --- a/lua/codesettings/util.lua +++ b/lua/codesettings/util.lua @@ -118,7 +118,10 @@ function M.merge(a, b, config) if type(v) ~= 'table' then return false end - if vim.islist(v) then + -- NB: vim.islist({}) returns true for empty tables, but we want to treat + -- empty tables as mergeable maps, not lists so that an empty settings table + -- gets mutated rather than replaced. + if vim.islist(v) and #v > 0 then return false end return true diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index f749f0c..7520332 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -109,6 +109,24 @@ describe('Settings:merge()', function() A:merge(B) assert.equal(2, A:get('opt.value')) end) + + it('works with an empty settings = {} table', function() + local config = { + settings = {}, + } + + local A = Settings.new(config) + local B = Settings.new() + B:set('settings.gopls.buildFlags', { '-tags=bsd' }) + A:merge(B) + assert.same({ + settings = { + gopls = { + buildFlags = { '-tags=bsd' }, + }, + }, + }, config) + end) end) describe('Settings:clear()', function() diff --git a/spec/util_spec.lua b/spec/util_spec.lua index 66ed12c..a3d1694 100644 --- a/spec/util_spec.lua +++ b/spec/util_spec.lua @@ -10,12 +10,12 @@ describe('util.merge - scalar merge', function() assert.equal(2, merged) end) - it('nil right side keeps left if right nil (expected: becomes nil because b takes precedence)', function() + it('empty table on right side preserves left values', function() local a = { value = 5 } - local b = { value = nil } + local b = {} -- { value = nil } is essentially equivalent to {} local merged = Util.merge(a, b) - -- Current contract: b takes precedence even if nil - assert.is_nil(merged.value) + -- Empty tables are now treated as maps and merged, so left side is preserved + assert.equal(5, merged.value) end) end)