Skip to content

Easier/better settings chaining #126

@Thom1729

Description

@Thom1729

Inspired by this forum thread.

Some packages want to allow package settings to be overridden per view, per window, and/or per project. We recommend using a ChainMap of SettingsDicts for this. However, this only works if all of the Settings objects use the same keys. In practice, packages should and often do cordon off the package-specific parts of view, window, and project settings. This is usually done in one of two ways:

  • Prefixing the key (e.g. my_setting in the global package settings, Example Package-my_setting in the view settings).
  • Putting all package settings under a struct (e.g. Example Packagemy_setting in the project settings).

Even with SettingsDict and ChainMap, this requires boilerplate to implement. Consider the following code sample from ESLint-Formatter:

  @staticmethod
  def get_pref(key):
    global_settings = sublime.load_settings(SETTINGS_FILE)
    value = global_settings.get(key)

    # Load active project settings
    project_settings = sublime.active_window().active_view().settings()

    # Overwrite global config value if it's defined
    if project_settings.has(PROJECT_NAME):
      value = project_settings.get(PROJECT_NAME).get(key, value)

    return value

This only handles two levels (package and project), is read-only, and relies on active_window(). but it's still a fair bit of code. Adding window and view settings would add yet more complication.

It would seem beneficial for sublime_lib to provide an abstraction for this pattern that is robust, full-featured, and easy to use. The simplest approach (in terms of implementation) would be to define simple dictionary decorators and rely on ChainMap:

settings = ChainMap(
    PrefixedDict(SettingsDict(view.settings()), 'MySettings-'),
    PrefixedDict(SettingsDict(window.settings()), 'MySettings-'),
    NamedSettingsDict('MySettings')
)

This is still a fair bit of boilerplate, though. In addition, the ChainMap is missing the subscribe functionality that SettingsDict provides; a plugin would have to subscribe to each individually and manually check whether the value has changed.

A more usable solution would be a single constructor:

settings = SettingsChain('MySettings', window)
settings = SettingsChain('MySettings', view) # includes window implicitly?

This is a bit more opinionated than the user building their own stack piece-by-piece, but it's a lot more concise and would encourage consistency among packages that used it.

Questions:

  • Does this generally sound like a reasonable feature to provide?
  • What's the right balance of convenience without confusion?
  • How should we handle modifying the chain? We could follow the ChainMap convention of modifying the top dict, or we could require that the user specify, e.g. my_chain.view[key] = value.
  • What do we do about project data? It's not a real Settings, so even if we shimmed it there's no way to subscribe to changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions