-
Notifications
You must be signed in to change notification settings - Fork 44
/
polyfill.py
201 lines (151 loc) · 6.88 KB
/
polyfill.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Copyright (C) 2018 The NeoVintageous Team (NeoVintageous).
#
# This file is part of NeoVintageous.
#
# NeoVintageous is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# NeoVintageous is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NeoVintageous. If not, see <https://www.gnu.org/licenses/>.
from contextlib import contextmanager
import re
from sublime import Region
from sublime import load_settings
from sublime import save_settings
# There's no Sublime API to set a window status.
# https://github.com/SublimeTextIssues/Core/issues/627
def set_window_status(window, key: str, value) -> None:
for view in window.views():
view.set_status(key, value)
# There's no Sublime API to erase a window status.
# https://github.com/SublimeTextIssues/Core/issues/627
def erase_window_status(window, key: str) -> None:
for view in window.views():
view.erase_status(key)
# A future compatable regular expression special character escaper. In Python
# 3.7 only characters that have special meaning in regex patterns are escaped.
def re_escape(pattern: str):
return re.escape(pattern).replace(
'\\<', '<').replace(
'\\>', '>')
# There's no Sublime API to show a corrections select list. The workaround is to
# mimic the mouse right button click which opens a corrections context menu.
# See: https://github.com/SublimeTextIssues/Core/issues/2539.
def spell_select(view) -> None:
x, y = view.text_to_window(view.sel()[0].b)
view.run_command('context_menu', {'event': {'button': 2, 'x': x, 'y': y}})
def spell_add(view, word: str) -> None:
view.run_command('add_word', {'word': word})
# There's no Sublime API to remove words from the added_words list.
# See: https://github.com/SublimeTextIssues/Core/issues/2539.
def spell_undo(word: str) -> None:
preferences = load_settings('Preferences.sublime-settings')
added_words = preferences.get('added_words', [])
try:
added_words.remove(word)
except ValueError:
return
added_words.sort()
preferences.set('added_words', added_words)
save_settings('Preferences.sublime-settings')
# Polyfill to workaround Sublime's view.find() return value issues.
# Returns None if nothing is found instead of returning Region(-1).
# See: https://forum.sublimetext.com/t/find-pattern-returns-1-1-instead-of-none/43866.
# See: https://github.com/SublimeTextIssues/Core/issues/534.
def view_find(view, pattern: str, start_pt: int, flags: int = 0):
match = view.find(pattern, start_pt, flags)
if match is None or match.b == -1:
return None
return match
# There's no Sublime API to find patterns in reverse direction.
# See: https://github.com/SublimeTextIssues/Core/issues/245.
def view_rfind_all(view, pattern: str, start_pt: int, flags: int = 0):
matches = view.find_all(pattern, flags)
for region in matches:
if region.b > start_pt:
return reversed(matches[:matches.index(region)])
return reversed(matches)
# There's no Sublime API to find a pattern in reverse direction.
# See: https://github.com/SublimeTextIssues/Core/issues/245.
def view_rfind(view, pattern: str, start_pt: int, flags: int = 0):
matches = view_rfind_all(view, pattern, start_pt, flags)
if matches:
try:
return next(matches)
except StopIteration:
pass
# There's no Sublime API to find a pattern within a start-end range.
# Note that this returns zero-length matches. Also see view_find().
# Returns None if nothing is found instead of returning Region(-1).
# See: https://forum.sublimetext.com/t/find-pattern-returns-1-1-instead-of-none/43866.
# See: https://github.com/SublimeTextIssues/Core/issues/534.
def view_find_in_range(view, pattern: str, pos: int, endpos: int, flags: int = 0):
match = view_find(view, pattern, pos, flags)
if match is not None and match.b <= endpos:
return match
# There's no Sublime API to find all matching pattern within a range.
# Note that this returns zero-length matches. Also see view_find().
# Returns None if nothing is found instead of returning Region(-1).
# See: https://forum.sublimetext.com/t/find-pattern-returns-1-1-instead-of-none/43866.
# See: https://github.com/SublimeTextIssues/Core/issues/534.
# TODO Refactor to generator
def view_find_all_in_range(view, pattern: str, pos: int, endpos: int, flags: int = 0) -> list:
matches = []
while pos <= endpos:
match = view.find(pattern, pos, flags)
if match is None or match.b == -1:
break
pos = match.b
if match.size() == 0:
pos += 1
if match.b <= endpos:
matches.append(match)
return matches
# Polyfill to work around bug in internal APIs.
# See: https://github.com/SublimeTextIssues/Core/issues/2879.
def view_indentation_level(view, pt: int):
return view.indentation_level(pt)
# Polyfill to allow specifying an inclusive flag to include or exclude leading
# and trailing whitespace. By default excludes leading and trailing whitespace.
def view_indented_region(view, pt: int, inclusive: bool = False):
indented_region = view.indented_region(pt)
if not inclusive:
ws = view_find(view, '\\s*', indented_region.begin())
if ws is not None:
indented_region.a = view.line(ws.b).a
else:
ws = view_find(view, '\\s*', indented_region.end())
if ws is not None:
indented_region.b = view.line(ws.b).a
return indented_region
def view_to_region(view) -> Region:
return Region(0, view.size())
def view_to_str(view) -> str:
return view.substr(view_to_region(view))
# Polyfill fix for Sublime Text 4. In Sublime Text 4 split_by_newlines includes
# full lines, previously the lines were constrianed to given region start and
# end points. See https://github.com/NeoVintageous/NeoVintageous/issues/647.
def split_by_newlines(view, region: Region) -> list:
regions = view.split_by_newlines(region)
if len(regions) > 0:
regions[0].a = min(region.a, region.b)
return regions
def toggle_side_bar(window) -> None:
window.run_command('toggle_side_bar')
# Ensure that the focus is put on the side bar if it's now visible,
# otherwise ensure that the focus returns to active group view.
if window.is_sidebar_visible():
window.run_command('focus_side_bar')
else:
window.focus_group(window.active_group())
@contextmanager
def save_preferences():
yield load_settings('Preferences.sublime-settings')
save_settings('Preferences.sublime-settings')