Skip to content

Commit a0fcf9a

Browse files
authored
Refactor: Create strongly typed class for options handling (#158)
1 parent 5695f73 commit a0fcf9a

File tree

4 files changed

+129
-63
lines changed

4 files changed

+129
-63
lines changed

.python-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.8

dependencies.json

-7
This file was deleted.

settings.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from typing import Any, List
2+
import sublime
3+
4+
5+
class TrailingSpacesSettings:
6+
SETTINGS_FILENAME = 'trailing_spaces.sublime-settings'
7+
8+
def __init__(self):
9+
self._settings = sublime.Settings(0)
10+
11+
def load(self) -> None:
12+
self._settings = sublime.load_settings(self.SETTINGS_FILENAME)
13+
14+
def save(self) -> None:
15+
sublime.save_settings(self.SETTINGS_FILENAME)
16+
17+
def _get(self, key: str, value_type: Any) -> Any:
18+
value = self._settings.get(key)
19+
if not isinstance(value, value_type):
20+
raise Exception(f'Invalid value for setting "{key}". Expected "{value_type}", got "{type(value)}')
21+
return value
22+
23+
def _set(self, key: str, value: Any, value_type: Any) -> None:
24+
if not isinstance(value, value_type):
25+
raise Exception(f'Invalid value when setting "{key}". Expected "{value_type}", got "{type(value)}')
26+
self._settings.set(key, value)
27+
28+
# -- Getters and setters for supported options ---------------------------------------------------------------------
29+
30+
@property
31+
def enabled(self) -> bool:
32+
return self._get('enabled', bool)
33+
34+
@property
35+
def file_max_size(self) -> int:
36+
return self._get('file_max_size', int)
37+
38+
@property
39+
def highlight_color(self) -> str:
40+
return self._get('highlight_color', str)
41+
42+
@highlight_color.setter
43+
def highlight_color(self, value: str) -> None:
44+
self._set('highlight_color', value, str)
45+
46+
@property
47+
def include_current_line(self) -> bool:
48+
return self._get('include_current_line', bool)
49+
50+
@property
51+
def include_empty_lines(self) -> bool:
52+
return self._get('include_empty_lines', bool)
53+
54+
@property
55+
def modified_lines_only(self) -> bool:
56+
return self._get('modified_lines_only', bool)
57+
58+
@modified_lines_only.setter
59+
def modified_lines_only(self, value: bool) -> None:
60+
self._set('modified_lines_only', value, bool)
61+
62+
@property
63+
def non_visible_highlighting(self) -> int:
64+
return self._get('non_visible_highlighting', int)
65+
66+
@property
67+
def regexp(self) -> str:
68+
return self._get('regexp', str)
69+
70+
@property
71+
def save_after_trim(self) -> bool:
72+
return self._get('save_after_trim', bool)
73+
74+
@property
75+
def scope_ignore(self) -> List[str]:
76+
return self._get('scope_ignore', list)
77+
78+
@property
79+
def syntax_ignore(self) -> List[str]:
80+
value = self._settings.get('syntax_ignore')
81+
return value if isinstance(value, list) else []
82+
83+
@property
84+
def trim_on_save(self) -> bool:
85+
return self._get('trim_on_save', bool)
86+
87+
@property
88+
def update_interval(self) -> int:
89+
return self._get('update_interval', int)

trailing_spaces.py

+39-56
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,37 @@
88
@since: 2011-02-25
99
'''
1010

11-
import sublime
12-
import sublime_plugin
13-
import difflib
11+
from .settings import TrailingSpacesSettings
12+
from os.path import isfile
1413
import codecs
14+
import difflib
1515
import re
16-
17-
from os.path import isfile
18-
from collections import ChainMap
19-
from sublime_lib import NamedSettingsDict
20-
21-
SETTINGS_FILENAME = 'trailing_spaces.sublime-settings'
22-
# Only need defaults for settings that are not exposed in default settings.
23-
DEFAULT_SETTINGS = {
24-
'syntax_ignore': [],
25-
}
16+
import sublime
17+
import sublime_plugin
2618

2719
# dictionary of currently active view ids and last visible regions
2820
active_views = {}
29-
current_highlight_color = None
21+
current_highlight_color = ''
3022
on_disk = None
3123
# Highlight color as defined in settings. Plugin mutates that setting when disabled so
3224
# that has to be stored.
33-
INITIAL_HIGHLIGHT_COLOR = None
25+
INITIAL_HIGHLIGHT_COLOR = ''
3426
HIGHLIGHT_REGION_KEY = 'TrailingSpacesHighlightedRegions'
35-
settings = None
36-
named_settings = None
27+
settings = TrailingSpacesSettings()
3728

3829

3930
def plugin_loaded():
40-
global settings, named_settings, current_highlight_color, INITIAL_HIGHLIGHT_COLOR
41-
42-
# A settings layer that handles settings with the `trailing_spaces_` prefix (now deprecated).
43-
class DeprecatedSettingsDict(NamedSettingsDict):
44-
def __getitem__(self, key):
45-
return super().__getitem__('trailing_spaces_%s' % key)
31+
global current_highlight_color, INITIAL_HIGHLIGHT_COLOR
4632

47-
deprecated_settings = DeprecatedSettingsDict(SETTINGS_FILENAME)
48-
named_settings = NamedSettingsDict(SETTINGS_FILENAME)
49-
settings = ChainMap(deprecated_settings, named_settings, DEFAULT_SETTINGS)
33+
settings.load()
5034

51-
current_highlight_color = settings['highlight_color']
35+
current_highlight_color = settings.highlight_color
5236
INITIAL_HIGHLIGHT_COLOR = current_highlight_color
5337

54-
if not settings['enabled']:
38+
if not settings.enabled:
5539
current_highlight_color = ""
56-
if settings['highlight_color'] != current_highlight_color:
57-
named_settings.save()
40+
if settings.highlight_color != current_highlight_color:
41+
settings.save()
5842

5943

6044
# Private: Makes sure all timers are stopped.
@@ -98,16 +82,16 @@ def view_find_all_in_regions(view, regions, regex):
9882
# Returns both the list of regions which map to trailing spaces and the list of
9983
# regions which are to be highlighted, as a list [matched, highlightable].
10084
def find_trailing_spaces(view, scan_only_visible=True):
101-
include_empty_lines = settings['include_empty_lines']
102-
include_current_line = settings['include_current_line']
103-
regexp = settings['regexp'] + "$"
85+
include_empty_lines = settings.include_empty_lines
86+
include_current_line = settings.include_current_line
87+
regexp = settings.regexp + "$"
10488

10589
if not include_empty_lines:
10690
regexp = "(?<=\\S)%s$" % regexp
10791

10892
trailing_regions = []
10993

110-
non_visible_highlighting = settings['non_visible_highlighting']
94+
non_visible_highlighting = settings.non_visible_highlighting
11195

11296
if scan_only_visible:
11397
# 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):
120104
else:
121105
trailing_regions = view.find_all(regexp)
122106

123-
ignored_scopes = ",".join(settings['scope_ignore'])
107+
ignored_scopes = ",".join(settings.scope_ignore)
124108
# filter out ignored scopes
125109
trailing_regions = [
126110
region for region in trailing_regions
@@ -176,7 +160,7 @@ def ignore_view(view):
176160
if not view_syntax or view_settings.get('is_widget'):
177161
return False
178162

179-
for syntax_ignore in settings['syntax_ignore']:
163+
for syntax_ignore in settings.syntax_ignore:
180164
if syntax_ignore in view_syntax:
181165
return True
182166

@@ -189,7 +173,7 @@ def ignore_view(view):
189173
#
190174
# Returns True or False.
191175
def max_size_exceeded(view):
192-
return view.size() > settings['file_max_size']
176+
return view.size() > settings.file_max_size
193177

194178

195179
# Private: Highlights specified regions as trailing spaces.
@@ -222,7 +206,7 @@ def toggle_highlighting(view):
222206

223207
# If performing live, highlighted trailing regions must be updated
224208
# internally.
225-
if not settings['enabled']:
209+
if not settings.enabled:
226210
(matched, highlightable) = find_trailing_spaces(view)
227211
highlight_trailing_spaces_regions(view, highlightable)
228212

@@ -306,7 +290,7 @@ def find_regions_to_delete(view):
306290
(regions, highlightable) = find_trailing_spaces(view, scan_only_visible=False)
307291

308292
# Filtering is required in case triming is restricted to dirty regions only.
309-
if settings['modified_lines_only']:
293+
if settings.modified_lines_only:
310294
modified_lines = get_modified_lines(view)
311295

312296
# If there are no dirty lines, don't do nothing.
@@ -378,8 +362,8 @@ def run(self):
378362
return
379363

380364
state = toggle_highlighting(view)
381-
named_settings['highlight_color'] = current_highlight_color
382-
named_settings.save()
365+
settings.highlight_color = current_highlight_color
366+
settings.save()
383367
sublime.status_message('Highlighting of trailing spaces is %s' % state)
384368

385369
def is_checked(self):
@@ -389,47 +373,47 @@ def is_checked(self):
389373
# Public: Toggles "Modified Lines Only" mode on or off.
390374
class ToggleTrailingSpacesModifiedLinesOnlyCommand(sublime_plugin.WindowCommand):
391375
def run(self):
392-
was_on = settings['modified_lines_only']
393-
named_settings['modified_lines_only'] = not was_on
394-
named_settings.save()
376+
was_on = settings.modified_lines_only
377+
settings.modified_lines_only = not was_on
378+
settings.save()
395379

396380
message = "Let's trim trailing spaces everywhere" if was_on \
397381
else "Let's trim trailing spaces only on modified lines"
398382
sublime.status_message(message)
399383

400384
def is_checked(self):
401-
return settings['modified_lines_only']
385+
return settings.modified_lines_only
402386

403387

404388
# Public: Matches and highlights trailing spaces on key events, according to the
405389
# current settings.
406390
class TrailingSpacesListener(sublime_plugin.EventListener):
407391
def on_modified_async(self, view):
408-
if settings['enabled']:
392+
if settings.enabled:
409393
match_trailing_spaces(view)
410394

411395
def on_selection_modified_async(self, view):
412-
if settings['enabled']:
396+
if settings.enabled:
413397
match_trailing_spaces(view)
414398

415399
def on_activated_async(self, view):
416-
if settings['modified_lines_only']:
400+
if settings.modified_lines_only:
417401
self.freeze_last_version(view)
418402

419-
if settings['enabled']:
403+
if settings.enabled:
420404
match_trailing_spaces(view)
421405

422406
# continuously watch view for changes to the visible region
423-
if not view.id() in active_views:
407+
if view.id() not in active_views:
424408
# track
425409
active_views[view.id()] = view.visible_region()
426410
self.update_on_region_change(view)
427411

428412
def on_pre_save(self, view):
429-
if settings['modified_lines_only']:
413+
if settings.modified_lines_only:
430414
self.freeze_last_version(view)
431415

432-
if settings['trim_on_save']:
416+
if settings.trim_on_save:
433417
view.run_command("delete_trailing_spaces")
434418

435419
def on_close(self, view):
@@ -449,9 +433,8 @@ def update_on_region_change(self, view):
449433
active_views[view.id()] = view.visible_region()
450434

451435
# continue only if the view is still active
452-
if settings['enabled'] and view.id() in active_views:
453-
sublime.set_timeout_async(lambda: self.update_on_region_change(view),
454-
settings['update_interval'])
436+
if settings.enabled and view.id() in active_views:
437+
sublime.set_timeout_async(lambda: self.update_on_region_change(view), settings.update_interval)
455438

456439
# Toggling messes with what is red from the disk, and it breaks the diff
457440
# used when modified_lines_only is true. Honestly, I don't know why (yet).
@@ -522,7 +505,7 @@ def run(self, edit):
522505
deleted = delete_trailing_regions(self.view, edit)
523506

524507
if deleted:
525-
if settings['save_after_trim'] and not settings['trim_on_save']:
508+
if settings.save_after_trim and not settings.trim_on_save:
526509
sublime.set_timeout(lambda: self.save(self.view), 10)
527510

528511
msg_parts = {"nbRegions": deleted,

0 commit comments

Comments
 (0)