This document is intended for the authors of front-end clients.

xi-core supports two mechanisms for handling persistent user preferences: file-based and RPC-based (unmanaged). The file-based system is similar to that employed by editors such as vim or Sublime Text; the RPC system is an alternative provided for front-ends that wish to manage preferences on their own, or which cannot support the file-based approach for platform reasons.

Clients which wish to use the file-based mechanism must explicitly opt-in by including "config_dir": "$CONFIG_PATH" in the params of the client_started RPC, where $CONFIG_PATH is a path to a directory that will contain config files.

If using file-based config, xi-core is responsible for watching those files for changes, and will reload them appropriately.

File based config

xi-core user config files are TOML files with the .xiconfig file extension. These files should exist in the root of the user’s config directory. General preferences should be in a file named preferences.xiconfig, and language/syntax-specifc preferences should be given the lowercase language name and the extension, for instance yaml.xiconfig, cpp.xiconfig, and markdown.xiconfig. Each file represents to what we call a ‘config domain’, and the keys & values in the file constitute a ‘config table’.

Config table format

Internally, all config tables are represented as JSON objects; all keys must be strings, and values must be objects, arrays, strings, or bools. Null values are not allowed.

When we load config files, we convert from TOML to JSON. The TOML ‘Datetime’ type is converted to a string.

Defaults

xi-core includes a number of default config tables. In the source, these are included as TOML files (see /rust/core-lib/assets/). At compile time these are baked into the binary.

Config Domains

We refer to a particular group of config settings as a ‘config domain’. A domain may contain both default and user settings; user settings always override default settings for that given domain. Not all domains are persistent; for instance there may be a ‘user override’ domain for each active view, which stores settings specific to that view, and which is forgotten when that view is closed. This would be used, for instance, if the user has manually changed an individual view’s indentation settings.

Generating the view’s config table

Each view has its own config table, generated by merging the tables from various domains relevant to that view. Those tables are merged in a predetermined order; here in order of application (reverse priority):

  1. General config, including platform-specific overrides
  2. Syntax config
  3. User Overrides

When a config changes, either because a file is modified or an RPC is received, then the config_changed notification is sent to the client for each affected view. If a change does not affect any views (for instance if rust.xiconfig is modified, but no Rust files are open) then no notification is sent.

RPC based config

Configs can be set or modified with the modify_user_config RPC notification. This notification has two paramaters, domain and changes. domain can be either the string "general", for the general user preferences domain, or else an object with a single key, which should be either "syntax" or "user_override"; the value in this case should be either a syntax name (identical to the file names, minus the extension, used for file-based config) or a view identifier.

Note it is an error to modify one of the ‘general’ or ‘syntax’ domains over RPC if the client has opted into the file-based config mechanism.

Examples

If a client is not opting in to file-based config (and is persisting preferences through another mechanism) than it should send those preferences immediately after launch, and before opening any views:

// send the user's general preferences
{
    "method": "modify_user_config",
    "params": {
        "domain": "general",
        "changes": {
            "font_face": "Monaco",
            "font_size": 18.0,
            "translate_tabs_to_spaces": false
        }
    }
}
// and their markdown-specific preferences
{
    "method": "modify_user_config",
    "params": {
        "domain": { "syntax": "markdown" },
        "changes": {
            "font_face": "Chalkboard"
        }
    }
}

Regardless of whether the file-based config system is being used, non-persistent view-specific settings can only be modified over RPC. For instance, if the same user as above wanted to specifically use four-space indentation for a view, the client would send the following RPC:

// send the user's general preferences
{
    "method": "modify_user_config",
    "params": {
        "domain": { "user_override": "view-id-1" },
        "changes": {
            "translate_tabs_to_spaces": true,
            "tab_size": 4
        }
    }
}

Null values

Null values are allowed in tables sent over RPC; these are interpreted as the deletion of any existing value for that key in the given domain.

Validation

Whenever a config table is modified, either through the RPC mechanism or by editing a file, the updated table is passed through a validator. If the table is invalid (for instance if it contains unrecognized keys) then an error is reported and the new table is ignored.