Skip to content

Commit ad9096d

Browse files
mheinzlerrchl
authored andcommitted
Speedup matching
This greatly improves the speed of live matching by only searching the currently visible region of text instead of the whole file. Also watches inactive views. Scrolling may happen even in views that are currently inactive, so to update the highlighting we need to watch them too.
1 parent e247190 commit ad9096d

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed

trailing_spaces.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import sublime_plugin
1313
import difflib
1414
import codecs
15+
import re
1516

1617
from os.path import isfile
1718

1819
DEFAULT_MAX_FILE_SIZE = 1048576
1920
DEFAULT_IS_ENABLED = True
21+
DEFAULT_UPDATE_INTERVAL = 250
2022
DEFAULT_MODIFIED_LINES_ONLY = False
2123

2224
# Global settings object and flags.
@@ -25,23 +27,30 @@
2527
ts_settings_filename = "trailing_spaces.sublime-settings"
2628
ts_settings = None
2729
trailing_spaces_live_matching = DEFAULT_IS_ENABLED
30+
trailing_spaces_update_interval = DEFAULT_UPDATE_INTERVAL
2831
trim_modified_lines_only = DEFAULT_MODIFIED_LINES_ONLY
2932
trailing_spaces_syntax_ignore = []
3033
startup_queue = []
3134
on_disk = None
3235

36+
# dictionary of currently active view ids and last visible regions
37+
active_views = {}
38+
3339

3440
# Private: Loads settings and sets whether the plugin (live matching) is enabled.
3541
#
3642
# Returns nothing.
3743
def plugin_loaded():
3844
global ts_settings_filename, ts_settings, trailing_spaces_live_matching
45+
global trailing_spaces_update_interval
3946
global current_highlighting_scope, trim_modified_lines_only, startup_queue
4047
global DEFAULT_COLOR_SCOPE_NAME, trailing_spaces_syntax_ignore
4148

4249
ts_settings = sublime.load_settings(ts_settings_filename)
4350
trailing_spaces_live_matching = bool(ts_settings.get("trailing_spaces_enabled",
4451
DEFAULT_IS_ENABLED))
52+
trailing_spaces_update_interval = int(ts_settings.get("trailing_spaces_update_interval",
53+
DEFAULT_UPDATE_INTERVAL))
4554
current_highlighting_scope = ts_settings.get("trailing_spaces_highlight_color",
4655
"region.redish")
4756
DEFAULT_COLOR_SCOPE_NAME = current_highlighting_scope
@@ -58,6 +67,14 @@ def plugin_loaded():
5867
persist_settings()
5968

6069

70+
# Private: Makes sure all timers are stopped.
71+
#
72+
# Returns nothing.
73+
def plugin_unloaded():
74+
# clear all active views to kill all timeouts
75+
active_views.clear()
76+
77+
6178
# Private: Updates user's settings with in-memory values.
6279
#
6380
# Allows for persistent settings from the menu.
@@ -67,6 +84,22 @@ def persist_settings():
6784
sublime.save_settings(ts_settings_filename)
6885

6986

87+
# Private: Returns all regions within region that match regex.
88+
#
89+
# view - the view, you know
90+
# region - the region to search
91+
# regex - the regex pattern to search for
92+
#
93+
# Returns all matching regions within region.
94+
def view_find_all_in_region(view, region, regex):
95+
# find all matches in the region's text
96+
text = view.substr(region)
97+
matches = re.finditer(regex, text, re.MULTILINE)
98+
99+
# return the found positions translated to the region's starting position
100+
return [sublime.Region(m.start() + region.begin(), m.end() + region.begin()) for m in matches]
101+
102+
70103
# Private: Get the regions matching trailing spaces.
71104
#
72105
# As the core regexp matches lines, the regions are, well, "per lines".
@@ -81,9 +114,12 @@ def find_trailing_spaces(view):
81114
include_current_line = bool(ts_settings.get("trailing_spaces_include_current_line",
82115
DEFAULT_IS_ENABLED))
83116
regexp = ts_settings.get("trailing_spaces_regexp") + "$"
84-
no_empty_lines_regexp = "(?<=\\S)%s$" % regexp
85117

86-
offending_lines = view.find_all(regexp if include_empty_lines else no_empty_lines_regexp)
118+
if not include_empty_lines:
119+
regexp = "(?<=\\S)%s$" % regexp
120+
121+
# find all matches in the currently visible region
122+
offending_lines = view_find_all_in_region(view, view.visible_region(), regexp)
87123
ignored_scopes = ",".join(ts_settings.get("trailing_spaces_scope_ignore", []))
88124
filtered_lines = []
89125
for region in offending_lines:
@@ -97,9 +133,10 @@ def find_trailing_spaces(view):
97133
if include_current_line or not line:
98134
return [filtered_lines, filtered_lines]
99135
else:
100-
current_offender = view.find(regexp if include_empty_lines else no_empty_lines_regexp, line.a)
101-
removal = False if current_offender is None else line.intersects(current_offender)
102-
highlightable = [i for i in filtered_lines if i != current_offender] if removal else filtered_lines
136+
# find all matches in the current line and exclude them from highlighting
137+
current_offenders = view_find_all_in_region(view, line, regexp)
138+
highlightable = [r for r in filtered_lines if r not in current_offenders]
139+
103140
return [filtered_lines, highlightable]
104141

105142

@@ -422,6 +459,12 @@ def on_activated(self, view):
422459
if trailing_spaces_live_matching:
423460
match_trailing_spaces(view)
424461

462+
# continuously watch view for changes to the visible region
463+
if not view.id() in active_views:
464+
# track
465+
active_views[view.id()] = view.visible_region()
466+
self.update_on_region_change(view)
467+
425468
def on_pre_save(self, view):
426469
global trim_modified_lines_only
427470
if trim_modified_lines_only:
@@ -430,6 +473,18 @@ def on_pre_save(self, view):
430473
if ts_settings.get("trailing_spaces_trim_on_save"):
431474
view.run_command("delete_trailing_spaces")
432475

476+
def update_on_region_change(self, view):
477+
# compare the currently visible region to the previous (if any) and
478+
# update if there were changes
479+
if view.visible_region() != active_views.get(view.id(), view.visible_region()):
480+
match_trailing_spaces(view)
481+
active_views[view.id()] = view.visible_region()
482+
483+
# continue only if the view is still active
484+
if trailing_spaces_live_matching and view.id() in active_views:
485+
sublime.set_timeout_async(lambda: self.update_on_region_change(view),
486+
trailing_spaces_update_interval)
487+
433488
# Toggling messes with what is red from the disk, and it breaks the diff
434489
# used when modified_lines_only is true. Honestly, I don't know why (yet).
435490
# Anyway, let's cache the persisted version of the document's buffer for

trailing_spaces.sublime-settings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@
4747

4848
// ---- NEXT SETTINGS ARE FOR POWER USERS ONLY! ----
4949

50+
// This is the interval at which the active view is tested for changes
51+
// (due to scrolling) to update the highlighting of the currently visible
52+
// region of text.
53+
// Adjust the value (in milliseconds) to whatever fits your needs and
54+
// performance.
55+
"trailing_spaces_update_interval" : 250,
56+
5057
// Highlighting will be disabled if the edited file's size is larger than
5158
// this.
5259
// Adjust the value (in number of chars) to whatever fits your performance.

0 commit comments

Comments
 (0)