diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..cc1923a --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.8 diff --git a/dependencies.json b/dependencies.json deleted file mode 100644 index cf228af..0000000 --- a/dependencies.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "*": { - "*": [ - "sublime_lib" - ] - } -} diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..08d23d8 --- /dev/null +++ b/settings.py @@ -0,0 +1,89 @@ +from typing import Any, List +import sublime + + +class TrailingSpacesSettings: + SETTINGS_FILENAME = 'trailing_spaces.sublime-settings' + + def __init__(self): + self._settings = sublime.Settings(0) + + def load(self) -> None: + self._settings = sublime.load_settings(self.SETTINGS_FILENAME) + + def save(self) -> None: + sublime.save_settings(self.SETTINGS_FILENAME) + + def _get(self, key: str, value_type: Any) -> Any: + value = self._settings.get(key) + if not isinstance(value, value_type): + raise Exception(f'Invalid value for setting "{key}". Expected "{value_type}", got "{type(value)}') + return value + + def _set(self, key: str, value: Any, value_type: Any) -> None: + if not isinstance(value, value_type): + raise Exception(f'Invalid value when setting "{key}". Expected "{value_type}", got "{type(value)}') + self._settings.set(key, value) + + # -- Getters and setters for supported options --------------------------------------------------------------------- + + @property + def enabled(self) -> bool: + return self._get('enabled', bool) + + @property + def file_max_size(self) -> int: + return self._get('file_max_size', int) + + @property + def highlight_color(self) -> str: + return self._get('highlight_color', str) + + @highlight_color.setter + def highlight_color(self, value: str) -> None: + self._set('highlight_color', value, str) + + @property + def include_current_line(self) -> bool: + return self._get('include_current_line', bool) + + @property + def include_empty_lines(self) -> bool: + return self._get('include_empty_lines', bool) + + @property + def modified_lines_only(self) -> bool: + return self._get('modified_lines_only', bool) + + @modified_lines_only.setter + def modified_lines_only(self, value: bool) -> None: + self._set('modified_lines_only', value, bool) + + @property + def non_visible_highlighting(self) -> int: + return self._get('non_visible_highlighting', int) + + @property + def regexp(self) -> str: + return self._get('regexp', str) + + @property + def save_after_trim(self) -> bool: + return self._get('save_after_trim', bool) + + @property + def scope_ignore(self) -> List[str]: + return self._get('scope_ignore', list) + + @property + def syntax_ignore(self) -> List[str]: + value = self._settings.get('syntax_ignore') + return value if isinstance(value, list) else [] + + @property + def trim_on_save(self) -> bool: + return self._get('trim_on_save', bool) + + @property + def update_interval(self) -> int: + return self._get('update_interval', int) diff --git a/trailing_spaces.py b/trailing_spaces.py index 7d467ae..9aeb81a 100644 --- a/trailing_spaces.py +++ b/trailing_spaces.py @@ -8,53 +8,37 @@ @since: 2011-02-25 ''' -import sublime -import sublime_plugin -import difflib +from .settings import TrailingSpacesSettings +from os.path import isfile import codecs +import difflib import re - -from os.path import isfile -from collections import ChainMap -from sublime_lib import NamedSettingsDict - -SETTINGS_FILENAME = 'trailing_spaces.sublime-settings' -# Only need defaults for settings that are not exposed in default settings. -DEFAULT_SETTINGS = { - 'syntax_ignore': [], -} +import sublime +import sublime_plugin # dictionary of currently active view ids and last visible regions active_views = {} -current_highlight_color = None +current_highlight_color = '' on_disk = None # Highlight color as defined in settings. Plugin mutates that setting when disabled so # that has to be stored. -INITIAL_HIGHLIGHT_COLOR = None +INITIAL_HIGHLIGHT_COLOR = '' HIGHLIGHT_REGION_KEY = 'TrailingSpacesHighlightedRegions' -settings = None -named_settings = None +settings = TrailingSpacesSettings() def plugin_loaded(): - global settings, named_settings, current_highlight_color, INITIAL_HIGHLIGHT_COLOR - - # A settings layer that handles settings with the `trailing_spaces_` prefix (now deprecated). - class DeprecatedSettingsDict(NamedSettingsDict): - def __getitem__(self, key): - return super().__getitem__('trailing_spaces_%s' % key) + global current_highlight_color, INITIAL_HIGHLIGHT_COLOR - deprecated_settings = DeprecatedSettingsDict(SETTINGS_FILENAME) - named_settings = NamedSettingsDict(SETTINGS_FILENAME) - settings = ChainMap(deprecated_settings, named_settings, DEFAULT_SETTINGS) + settings.load() - current_highlight_color = settings['highlight_color'] + current_highlight_color = settings.highlight_color INITIAL_HIGHLIGHT_COLOR = current_highlight_color - if not settings['enabled']: + if not settings.enabled: current_highlight_color = "" - if settings['highlight_color'] != current_highlight_color: - named_settings.save() + if settings.highlight_color != current_highlight_color: + settings.save() # Private: Makes sure all timers are stopped. @@ -98,16 +82,16 @@ def view_find_all_in_regions(view, regions, regex): # Returns both the list of regions which map to trailing spaces and the list of # regions which are to be highlighted, as a list [matched, highlightable]. def find_trailing_spaces(view, scan_only_visible=True): - include_empty_lines = settings['include_empty_lines'] - include_current_line = settings['include_current_line'] - regexp = settings['regexp'] + "$" + include_empty_lines = settings.include_empty_lines + include_current_line = settings.include_current_line + regexp = settings.regexp + "$" if not include_empty_lines: regexp = "(?<=\\S)%s$" % regexp trailing_regions = [] - non_visible_highlighting = settings['non_visible_highlighting'] + non_visible_highlighting = settings.non_visible_highlighting if scan_only_visible: # find all matches in the currently visible region plus a little before and after @@ -120,7 +104,7 @@ def find_trailing_spaces(view, scan_only_visible=True): else: trailing_regions = view.find_all(regexp) - ignored_scopes = ",".join(settings['scope_ignore']) + ignored_scopes = ",".join(settings.scope_ignore) # filter out ignored scopes trailing_regions = [ region for region in trailing_regions @@ -176,7 +160,7 @@ def ignore_view(view): if not view_syntax or view_settings.get('is_widget'): return False - for syntax_ignore in settings['syntax_ignore']: + for syntax_ignore in settings.syntax_ignore: if syntax_ignore in view_syntax: return True @@ -189,7 +173,7 @@ def ignore_view(view): # # Returns True or False. def max_size_exceeded(view): - return view.size() > settings['file_max_size'] + return view.size() > settings.file_max_size # Private: Highlights specified regions as trailing spaces. @@ -222,7 +206,7 @@ def toggle_highlighting(view): # If performing live, highlighted trailing regions must be updated # internally. - if not settings['enabled']: + if not settings.enabled: (matched, highlightable) = find_trailing_spaces(view) highlight_trailing_spaces_regions(view, highlightable) @@ -306,7 +290,7 @@ def find_regions_to_delete(view): (regions, highlightable) = find_trailing_spaces(view, scan_only_visible=False) # Filtering is required in case triming is restricted to dirty regions only. - if settings['modified_lines_only']: + if settings.modified_lines_only: modified_lines = get_modified_lines(view) # If there are no dirty lines, don't do nothing. @@ -378,8 +362,8 @@ def run(self): return state = toggle_highlighting(view) - named_settings['highlight_color'] = current_highlight_color - named_settings.save() + settings.highlight_color = current_highlight_color + settings.save() sublime.status_message('Highlighting of trailing spaces is %s' % state) def is_checked(self): @@ -389,47 +373,47 @@ def is_checked(self): # Public: Toggles "Modified Lines Only" mode on or off. class ToggleTrailingSpacesModifiedLinesOnlyCommand(sublime_plugin.WindowCommand): def run(self): - was_on = settings['modified_lines_only'] - named_settings['modified_lines_only'] = not was_on - named_settings.save() + was_on = settings.modified_lines_only + settings.modified_lines_only = not was_on + settings.save() message = "Let's trim trailing spaces everywhere" if was_on \ else "Let's trim trailing spaces only on modified lines" sublime.status_message(message) def is_checked(self): - return settings['modified_lines_only'] + return settings.modified_lines_only # Public: Matches and highlights trailing spaces on key events, according to the # current settings. class TrailingSpacesListener(sublime_plugin.EventListener): def on_modified_async(self, view): - if settings['enabled']: + if settings.enabled: match_trailing_spaces(view) def on_selection_modified_async(self, view): - if settings['enabled']: + if settings.enabled: match_trailing_spaces(view) def on_activated_async(self, view): - if settings['modified_lines_only']: + if settings.modified_lines_only: self.freeze_last_version(view) - if settings['enabled']: + if settings.enabled: match_trailing_spaces(view) # continuously watch view for changes to the visible region - if not view.id() in active_views: + if view.id() not in active_views: # track active_views[view.id()] = view.visible_region() self.update_on_region_change(view) def on_pre_save(self, view): - if settings['modified_lines_only']: + if settings.modified_lines_only: self.freeze_last_version(view) - if settings['trim_on_save']: + if settings.trim_on_save: view.run_command("delete_trailing_spaces") def on_close(self, view): @@ -449,9 +433,8 @@ def update_on_region_change(self, view): active_views[view.id()] = view.visible_region() # continue only if the view is still active - if settings['enabled'] and view.id() in active_views: - sublime.set_timeout_async(lambda: self.update_on_region_change(view), - settings['update_interval']) + if settings.enabled and view.id() in active_views: + sublime.set_timeout_async(lambda: self.update_on_region_change(view), settings.update_interval) # Toggling messes with what is red from the disk, and it breaks the diff # used when modified_lines_only is true. Honestly, I don't know why (yet). @@ -522,7 +505,7 @@ def run(self, edit): deleted = delete_trailing_regions(self.view, edit) if deleted: - if settings['save_after_trim'] and not settings['trim_on_save']: + if settings.save_after_trim and not settings.trim_on_save: sublime.set_timeout(lambda: self.save(self.view), 10) msg_parts = {"nbRegions": deleted,