Design Principle
Lua owns all default values. The Go daemon receives the complete config via the CURSORTAB_CONFIG environment variable with all defaults already applied. Go structs should not have default values or use pointer types for optional fields - all fields are required and must be provided by Lua.
Lua-only vs Go fields: Some config sections are handled entirely in Lua and never sent to Go: enabled, keymaps, ui, blink. Conversely, Go's Config struct has auto-populated fields (ns_id, editor_version, editor_os) that Lua sets internally — these are not user-configurable and should not be added to default_config.
Files to Update
When modifying config options, update these locations:
1. Lua Side
lua/cursortab/config.lua
- Type annotation in
---@classblock (e.g.,---@field new_option type) - Default value in
default_configtable (required - Go expects all values) - Validation (if enum-like, add to
valid_*table and update error invalidate_config) - Unknown keys are automatically rejected by
validate_config_keys()— no update needed there unless changing the validation logic itself - If adding/modifying default values for highlight groups, update
config.setup_highlights()in config.lua
2. Go Side
server/main.go (only for fields consumed by the daemon — skip for Lua-only fields)
- Struct field with JSON tag in the appropriate config struct (
Config,ProviderConfig,BehaviorConfig, etc.) - No default values or optional fields - Lua provides the complete config
- Validation in
Config.Validate()method — enum checks use thevalidateEnum()helper with[]stringslices, numeric ranges use direct comparisons
server/logger/logger.go (for log levels only)
LogLevelconstants (LogLevelTrace,LogLevelDebug, etc.)String()method switch caseParseLogLevel()function switch case
3. Documentation
README.md
- Configuration example in the setup block
- Add comment showing valid values for enum options
doc/cursortab.txt
- Vim help file with same configuration example
- Keep in sync with README.md
Checklist
For enum-like options (e.g., log_level, provider.type):
- Add to Lua
valid_*table in config.lua (e.g.,valid_log_levels,valid_provider_types) - Update Lua error message in
validate_config()with new valid values - Add to Go
validateEnum()call inConfig.Validate()in main.go - Update README.md example/comments
- Update doc/cursortab.txt example/comments
For simple options:
- Add
---@fieldtype annotation in the appropriate---@classblock in config.lua - Add default value in
default_configtable in config.lua - Add struct field with JSON tag in main.go (in
Configor nested struct) — skip for Lua-only fields - Add validation in
Config.Validate()if needed (numeric ranges, path validation, etc.) - If
ui.jump.*, updateconfig.setup_highlights()in config.lua - Update README.md example
- Update doc/cursortab.txt example
For removing or renaming options:
- Add entry to
deprecated_mappingstable in config.lua (maps old flat key to new nested path, ornilif removed entirely) - For nested field renames, add entry to
nested_field_renamestable in config.lua - Remove old field from
default_config,---@classblocks, and Go structs - Update validation logic in both Lua and Go
- Update README.md and doc/cursortab.txt
Example: Adding a new enum value
When adding "trace" to log_level:
-- config.lua
local valid_log_levels = { trace = true, debug = true, info = true, warn = true, error = true }
-- In validate_config():
-- error: "Must be one of: trace, debug, info, warn, error"
// main.go - inside Config.Validate(), using validateEnum helper
if err := validateEnum(c.LogLevel, "log_level", []string{"trace", "debug", "info", "warn", "error"}); err != nil {
return err
}
// logger/logger.go - add constant, update String() and ParseLogLevel()
const LogLevelTrace LogLevel = iota
<!-- README.md and doc/cursortab.txt -->
log_level = "info", -- "trace", "debug", "info", "warn", "error"
Source: cursortab/cursortab.nvim — distributed by TomeVault.