Skip to content
61 changes: 61 additions & 0 deletions st3/sublime_lib/settings_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,33 @@ def _on_settings_changed(self) -> None:


class GlobalSettingsListener(BaseSettingsListener, sublime_plugin.EventListener):
"""
A subclass of :class:`sublime_plugin.EventListener`
that also listens for changes in a global settings object.

Subclasses must set the `SETTINGS_NAME` class variable to the name of the global settings.

Example usage:

.. code-block:: python
from sublime_lib import GlobalSettingsListener, on_setting_changed

class JsCustomConfigurationsListener(GlobalSettingsListener)
SETTINGS_NAME = 'JS Custom.sublime-settings'

@on_setting_changed('configurations')
def configurations_changed(self, new_configuration, old_configuration):
if self.settings.get('auto_build', False):
sublime.active_window().run_command('build_js_custom_syntaxes')
"""
SETTINGS_NAME = ''

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(sublime.load_settings(self.SETTINGS_NAME), *args, **kwargs)

def _on_settings_changed(self) -> None:
# Necessary because Sublime keeps EventListener objects alive unnecessarily.
# See https://github.com/sublimehq/sublime_text/issues/4078.
if self in sublime_plugin.all_callbacks['on_new']:
super()._on_settings_changed()
else:
Expand All @@ -68,6 +89,31 @@ def on_new(self, view: sublime.View) -> None:


class ViewSettingsListener(BaseSettingsListener, sublime_plugin.ViewEventListener):
"""
A subclass of :class:`sublime_plugin.ViewEventListener`
that also listens for changes in the view settings.

Example:

.. code-block:: python
from sublime_lib import ViewSettingsListener, on_setting_changed

class TabSizeListener(ViewSettingsListener):
@classmethod
def is_applicable(cls, settings):
return settings.get('translate_tabs_to_spaces', False)

@classmethod
def applies_to_primary_view_only(cls):
return True

@on_setting_changed('tab_size')
def tab_size_changed(self, new_value, old_value):
self.view.run_command('resize_existing_tabs', {
'new_size': new_value,
'old_size': old_value,
})
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
view = args[0]
assert isinstance(view, sublime.View)
Expand All @@ -81,6 +127,21 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
def on_setting_changed(
selector: Union[str, Callable[[sublime.Settings], Selected]]
) -> Callable[[OnChangeListener], OnChangeListener]:
"""
A decorator function to mark a method
of a :class:`GlobalSettingsListener` or :class:`ViewSettingsListener`
as a setting change handler.

The handler is not called every time the `settings` object changes,
but only when the value derived using the `selector` argument changes.
If `selector` is callable, then the derived value is ``selector(self)``.
If `selector` is a :class:`str`,
then the derived value is ``self.get(selector, None)``.
Otherwise, the derived value is ``projection(self, selector)``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the "selector" concept being reused now, it might be sensible to add a section to our docs with more details and examples.


The handler should accept two arguments:
the new derived value and the previous derived value.
"""
def decorator(function: OnChangeListener) -> OnChangeListener:
setattr(function, OPTIONS_ATTRIBUTE, OnChangedOptions(get_selector(selector)))
return function
Expand Down