Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lua/codesettings/build/cli.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ local build_targets = {
annotations = require('codesettings.build.annotations'),
config = require('codesettings.build.config-schema'),
doc = require('codesettings.build.doc'),
terminals = require('codesettings.build.terminal-objects'),
}

-- Define build order and dependencies
local build_order = { 'schemas', 'annotations', 'config', 'doc' }
local build_order = { 'schemas', 'terminals', 'annotations', 'config', 'doc' }

local build_dependencies = {
terminals = { 'schemas' },
annotations = { 'schemas' },
doc = { 'schemas' },
}
Expand Down
59 changes: 59 additions & 0 deletions lua/codesettings/build/schemas.lua
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,65 @@ function M.get_schemas()
return ret
end

--- Collect all terminal object property paths from a schema.
--- Terminal objects are properties with type="object" but no "properties" field,
--- meaning their keys are arbitrary user data and should not be dot-expanded.
---@param schema table JSON schema
---@param prefix string? current property path prefix
---@param terminals table<string, boolean> table to collect terminal paths
local function collect_terminal_objects(schema, prefix, terminals)
if type(schema) ~= 'table' then
return
end

local props = schema.properties
if type(props) ~= 'table' then
return
end

for name, def in pairs(props) do
if type(def) == 'table' then
local full_path = prefix and (prefix .. '.' .. name) or name

-- Check if this is a terminal object:
-- - type is "object"
-- - no "properties" field (free-form dictionary)
if def.type == 'object' and not def.properties then
terminals[full_path] = true
end

-- Recurse if it has nested properties
if def.properties then
collect_terminal_objects(def, full_path, terminals)
end
end
end
end

--- Generate a lookup table of all terminal object paths across all LSP schemas.
--- Returns a table mapping property paths to true for paths that should not have
--- their keys dot-expanded (e.g., "yaml.schemas").
---@return table<string, boolean> terminal object paths
function M.generate_terminal_objects_cache()
local schemas = M.get_schemas()
local terminals = {}

for _, schema_meta in pairs(schemas) do
if Util.exists(schema_meta.settings_file) then
local ok, data = pcall(vim.fn.readfile, schema_meta.settings_file)
if ok and type(data) == 'table' then
local json_str = table.concat(data, '\n')
local ok2, schema_json = pcall(vim.fn.json_decode, json_str)
if ok2 and type(schema_json) == 'table' then
collect_terminal_objects(schema_json, nil, terminals)
end
end
end
end

return terminals
end

--- A map of LSP server schemas that require special handling for non-common configuration layouts.
local SpecialCases = {
nixd = function(json)
Expand Down
43 changes: 43 additions & 0 deletions lua/codesettings/build/terminal-objects.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- Build script to generate cache of terminal object paths
local Schemas = require('codesettings.build.schemas')
local Util = require('codesettings.util')

local relpath = 'lua/codesettings/generated/terminal-objects.lua'

local M = {}

function M.build()
if #arg == 0 then
error('This function is part of a build tool and should not be called directly!')
end

print('Generating terminal objects cache...')
local terminals = Schemas.generate_terminal_objects_cache()

-- Generate Lua file with the cache
local lines = {
'-- stylua: ignore',
'-- This file contains a lookup table of property paths that represent',
'-- terminal objects (type=object with no properties field).',
'-- Keys inside these objects should not be dot-expanded.',
'',
'---@type table<string, boolean>',
'return {',
}

local paths = vim.tbl_keys(terminals)
table.sort(paths)

for _, path in ipairs(paths) do
table.insert(lines, string.format(' [%q] = true,', path))
end

table.insert(lines, '}')
table.insert(lines, '')

Util.write_file(Util.path(relpath), table.concat(lines, '\n'))

print(string.format('Generated %s with %d terminal object paths', relpath, #paths))
end

return M
64 changes: 64 additions & 0 deletions lua/codesettings/generated/terminal-objects.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- stylua: ignore
-- This file contains a lookup table of property paths that represent
-- terminal objects (type=object with no properties field).
-- Keys inside these objects should not be dot-expanded.

---@type table<string, boolean>
return {
["Lua.format.defaultConfig"] = true,
["Lua.nameStyle.config"] = true,
["Lua.runtime.special"] = true,
["ada.scenarioVariables"] = true,
["csharp.debug.sourceFileMap"] = true,
["dart.customDevTools.env"] = true,
["dart.env"] = true,
["dart.getDartSdkCommand.env"] = true,
["dart.getFlutterSdkCommand.env"] = true,
["dart.mcpServerTools"] = true,
["deno.env"] = true,
["deno.suggest.imports.hosts"] = true,
["dotnet.unitTestDebuggingOptions.sourceFileMap"] = true,
["dotnet.unitTestDebuggingOptions.sourceLinkOptions"] = true,
["elixirLS.envVariables"] = true,
["eslint.codeActionsOnSave.options"] = true,
["eslint.options"] = true,
["gopls.env"] = true,
["gopls.semanticTokenModifiers"] = true,
["gopls.semanticTokenTypes"] = true,
["haskell.serverEnvironment"] = true,
["haskell.toolchain"] = true,
["java.project.referencedLibraries.sources"] = true,
["ltex.bibtex.fields"] = true,
["ltex.latex.commands"] = true,
["ltex.latex.environments"] = true,
["ltex.markdown.nodes"] = true,
["luau-lsp.fflags.override"] = true,
["luau-lsp.require.directoryAliases"] = true,
["luau-lsp.require.fileAliases"] = true,
["luau-lsp.types.definitionFiles"] = true,
["perl.env"] = true,
["perlnavigator.perlEnv"] = true,
["powershell.powerShellAdditionalExePaths"] = true,
["rust-analyzer.cargo.extraEnv"] = true,
["rust-analyzer.check.extraEnv"] = true,
["rust-analyzer.completion.snippets.custom"] = true,
["rust-analyzer.debug.engineSettings"] = true,
["rust-analyzer.diagnostics.remapPrefix"] = true,
["rust-analyzer.lru.query.capacities"] = true,
["rust-analyzer.procMacro.ignored"] = true,
["rust.rust-analyzer"] = true,
["sonarlint.analyzerProperties"] = true,
["sonarlint.rules"] = true,
["stylelintplus.config"] = true,
["stylelintplus.configOverrides"] = true,
["svelte.plugin.svelte.compilerWarnings"] = true,
["swift.excludePathsFromActivation"] = true,
["swift.pluginPermissions"] = true,
["swift.swiftEnvironmentVariables"] = true,
["swift.testEnvironmentVariables"] = true,
["tailwindCSS.includeLanguages"] = true,
["tinymist.preview.sysInputs"] = true,
["vetur.format.defaultFormatterOptions.js-beautify-html"] = true,
["vetur.grammar.customBlocks"] = true,
["yaml.schemas"] = true,
}
19 changes: 16 additions & 3 deletions lua/codesettings/settings.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local Util = require('codesettings.util')
local Extensions = require('codesettings.extensions')
local TerminalObjects = require('codesettings.generated.terminal-objects')

local M = {}

Expand Down Expand Up @@ -59,21 +60,33 @@ end

---Expand a table with dotted keys into a nested table structure
---@param tbl table the table to expand
---@param current_path string? internal recursion use: current property path for tracking terminal objects
---@return table expanded the expanded table
function M.expand(tbl)
function M.expand(tbl, current_path)
if type(tbl) ~= 'table' then
return tbl
end

-- Check if we're inside a terminal object (free-form dictionary)
-- If so, do not expand dotted keys in this table
local is_terminal = current_path and TerminalObjects[current_path]

local out = {}
for key, value in pairs(tbl) do
local v = value

-- Build the path for this key
local key_path = current_path and (current_path .. '.' .. key) or key

-- Recurse into map-like tables, but never into JSON Schema "properties" tables.
if is_map(v) and key ~= 'properties' then
v = M.expand(v)
v = M.expand(v, key_path)
end

if type(key) == 'string' and key:find('%.') then
-- Only expand dotted keys if:
-- 1. The key contains a dot
-- 2. We're NOT inside a terminal object
if type(key) == 'string' and key:find('%.') and not is_terminal then
local parts = {}
for part in key:gmatch('[^.]+') do
parts[#parts + 1] = part
Expand Down
1 change: 1 addition & 0 deletions schemas/dartls.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,7 @@
"type": "boolean"
},
"default": {
"analyze_files": false,
"run_tests": false
},
"markdownDescription": "A map of MCP tool names to booleans to enable/disable specific tools from the Dart MCP server. Tools set to `false` will be excluded (if supported). By default, tools that overlap with built-in VS Code functionality will be excluded.",
Expand Down
3 changes: 2 additions & 1 deletion schemas/eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@
"jsonc",
"css",
"glimmer-js",
"glimmer-ts"
"glimmer-ts",
"svelte"
],
"description": "An array of language ids for which the extension should probe if support is installed.",
"items": {
Expand Down
18 changes: 18 additions & 0 deletions schemas/intelephense.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,24 @@
"scope": "window",
"type": "boolean"
},
"intelephense.inlayHint.parameterNames": {
"default": true,
"description": "Will show inlay hints for call argument parameter names if named arguments are not already in use.",
"scope": "window",
"type": "boolean"
},
"intelephense.inlayHint.parameterTypes": {
"default": true,
"description": "Will show inlay hints for anonymous function declaration parameter types if not already declared.",
"scope": "window",
"type": "boolean"
},
"intelephense.inlayHint.returnTypes": {
"default": true,
"description": "Will show an inlay hint for call declaration return type if not already declared.",
"scope": "window",
"type": "boolean"
},
"intelephense.licenceKey": {
"description": "DEPRECATED. Don't use this. Go to command palette and search for enter licence key.",
"scope": "application",
Expand Down
87 changes: 83 additions & 4 deletions spec/settings_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ local Settings = require('codesettings.settings')

describe('Settings.path()', function()
it('returns empty table for nil/empty', function()
assert.same({}, Settings.path(nil))
assert.same({}, Settings.path(nil)) ---@diagnostic disable-line: param-type-mismatch
assert.same({}, Settings.path(''))
end)

Expand All @@ -13,7 +13,7 @@ describe('Settings.path()', function()
end)

it('wraps non-string keys', function()
assert.same({ 123 }, Settings.path(123))
assert.same({ 123 }, Settings.path(123)) ---@diagnostic disable-line: param-type-mismatch
end)
end)

Expand Down Expand Up @@ -63,7 +63,7 @@ end)

describe('Settings.expand()', function()
it('passes non-table through', function()
assert.equal(5, Settings.expand(5))
assert.equal(5, Settings.expand(5)) ---@diagnostic disable-line: param-type-mismatch
end)

it('expands dotted keys into nested tables', function()
Expand Down Expand Up @@ -208,7 +208,7 @@ end)

describe('Settings.expand()', function()
it('passes non-table through', function()
assert.equal(5, Settings.expand(5))
assert.equal(5, Settings.expand(5)) ---@diagnostic disable-line: param-type-mismatch
end)

it('expands dotted keys into nested tables', function()
Expand Down Expand Up @@ -277,4 +277,83 @@ describe('Settings.expand()', function()
},
}, expanded)
end)

it('does not expand keys inside terminal objects (e.g., yaml.schemas)', function()
-- yaml.schemas is a terminal object (type=object, no properties)
-- Keys inside it should not be dot-expanded
local expanded = Settings.expand({
['yaml.schemas'] = {
['./some/relative/schema.json'] = 'relpath/*',
['https://example.com/schema.json'] = 'config/*.yaml',
['../parent/schema.json'] = 'schemas/*',
},
})
assert.same({
yaml = {
schemas = {
['./some/relative/schema.json'] = 'relpath/*',
['https://example.com/schema.json'] = 'config/*.yaml',
['../parent/schema.json'] = 'schemas/*',
},
},
}, expanded)
end)

it('does not expand keys inside terminal objects when already nested', function()
local expanded = Settings.expand({
yaml = {
schemas = {
['./some/relative/schema.json'] = 'relpath/*',
},
},
})
assert.same({
yaml = {
schemas = {
['./some/relative/schema.json'] = 'relpath/*',
},
},
}, expanded)
end)

it('still expands dotted keys in non-terminal nested objects', function()
-- Regular nested objects should still have their dotted keys expanded
local expanded = Settings.expand({
yaml = {
format = {
['some.nested.key'] = true,
},
},
})
assert.same({
yaml = {
format = {
some = {
nested = {
key = true,
},
},
},
},
}, expanded)
end)

it('handles terminal objects with multiple levels of nesting', function()
local expanded = Settings.expand({
['yaml.schemas'] = {
['file.with.dots.json'] = 'pattern',
},
['yaml.format.enable'] = true,
})
assert.same({
yaml = {
schemas = {
['file.with.dots.json'] = 'pattern',
},
format = {
enable = true,
},
},
}, expanded)
end)
end)