/
gutter_icons_color_highlighter.py
199 lines (165 loc) · 7.41 KB
/
gutter_icons_color_highlighter.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
"""A color highlighter that uses phantom sets to highlight colors."""
import os
import subprocess
import threading
try:
from . import path
from .st_helper import running_in_st
from .color_highlighter import ColorHighlighter
except ValueError:
import path
from st_helper import running_in_st
from color_highlighter import ColorHighlighter
if running_in_st():
import sublime # pylint: disable=import-error
else:
from . import sublime
class IconFactory(object):
"""A class for generating gutter icons with different styles and colors."""
_icon_style_circle = "circle"
_icon_style_square = "square"
_convert_styles = {
_icon_style_circle: "circle 15,16 8,10",
_icon_style_square: "rectangle 4,4 24,24"
}
_convert_command_template = (
'%s -type TrueColorMatte -channel RGBA -size 32x32 -alpha transparent xc:none -fill "%s" -draw "%s" png32:"%s"')
_icon_name_template = "%s_icon_%s.png"
_bad_icon_name = "bad-icon.png"
def __init__(self, icons_path, sublime_icons_path, convert_command, # pylint: disable=too-many-arguments
execute_timeout_seconds, debug):
"""
Init the icon factory.
Arguments:
- icons_path - an absolute path to the icons directory.
- sublime_icons_path - a relative to ST Packages path to the icons directory.
- convert_command - a convert tool path.
- execute_timeout_seconds - the timeout in seconds to wait for convert to finish.
- debug - whether to enable debug mode.
"""
self._icons_path = icons_path
self._sublime_icons_path = sublime_icons_path
self._convert_command = convert_command
self._execute_timeout_seconds = execute_timeout_seconds
self._debug = debug
self._icons_cache = {}
self._lock = threading.Lock()
def get_icon_path(self, style, color):
"""
Get the icon path given the icon style and color.
If the icon does not exist, create it.
Arguments:
- style - the style of the icon.
- color -- the color of the icon.
Returns the icon path of None if creating the icon has failed.
"""
assert style in self._convert_styles
icon_name = self._icon_name_template % (style, color[1:])
sublime_icon_path = path.normalize_path_for_st(os.path.join(self._sublime_icons_path, icon_name))
# TODO(#5): return sublime_icon_path immediately and create icon in background. # pylint: disable=fixme
icon_path = os.path.join(self._icons_path, icon_name)
cache_key = (style, color)
with self._lock:
if cache_key in self._icons_cache:
return self._icons_cache[cache_key]
if os.path.exists(icon_path):
self._icons_cache[cache_key] = sublime_icon_path
return sublime_icon_path
if self._create_icon(style, color, icon_path)[1]:
self._icons_cache[cache_key] = sublime_icon_path
return sublime_icon_path
if self._debug:
print("ColorHighlighter: action=could_not_create_icon style=%s color=%s" % (style, color))
return path.normalize_path_for_st(os.path.join(self._sublime_icons_path, IconFactory._bad_icon_name))
def check(self):
"""
Get the icon path given the icon style and color.
If the icon does not exist, create it.
Arguments:
- style - the style of the icon.
- color -- the color of the icon.
Returns the icon path of None if creating the icon has failed.
"""
style = "circle"
color = "#ffffffff"
icon_name = self._icon_name_template % (style, color[1:])
icon_path = os.path.join(self._icons_path, icon_name)
result = self._create_icon(style, color, icon_path)
return result[0] and result[1]
def _create_icon(self, style, color, icon_path):
convert_style = IconFactory._convert_styles[style]
convert_command = IconFactory._convert_command_template % (
self._convert_command, color, convert_style, icon_path)
if self._debug:
print("ColorHighlighter: action=create_icon style=%s color=%s" % (style, color))
success = self._run_command(convert_command)
return (success, os.path.exists(icon_path))
def _run_command(self, command):
_create_if_not_exists(path.data_path(path.ABSOLUTE))
_create_if_not_exists(path.icons_path(path.ABSOLUTE))
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
try:
output, error = process.communicate(timeout=self._execute_timeout_seconds)
except subprocess.TimeoutExpired:
process.kill()
output, error = process.communicate()
output = _decode_data(output)
error = _decode_data(error)
if error is not None and error != "":
print("Traceback: error.\n\nOutput:\n%s\n\nError:\n%s" % (output, error))
return False
return True
class GutterIconsColorHighlighter(ColorHighlighter):
"""A color highlighter that uses gutter icons to highlight colors."""
region_name_template = "CH_icon_%s_%d_%d"
region_scope = "ch_gutter_icon"
def __init__(self, view, icon_style, icon_factory, name, debug): # pylint: disable=too-many-arguments
"""
Init a GutterIconsColorHighlighter.
Arguments:
- view - a view to highlight colors in.
- icon_style - the icon style.
- icon_factory - the icon factory to create icons with.
- name - the name of the color highlighter.
- debug - whether to enable debug mode.
"""
assert icon_style in IconFactory._convert_styles # pylint: disable=protected-access
self._view = view
self._icon_style = icon_style
self._icon_factory = icon_factory
self._name = name
self._debug = debug
def highlight_region(self, context, value):
"""
Highlight a region.
Arguments:
- context - a dict with color highlighter run data.
- value - tuple (region to highlight, it's color).
Returns True, if highlighted, False otherwise.
"""
(region, color) = value
icon_path = self._icon_factory.get_icon_path(self._icon_style, color)
region_key = GutterIconsColorHighlighter.region_name_template % (self._name, region.a, region.b)
if self._debug:
print("ColorHighlighter: action=highlight highlighter=GutterIconsColorHighlighter region=%s color=%s"
% (region, color))
self._view.add_regions(
region_key, [region.region()], GutterIconsColorHighlighter.region_scope, icon_path, sublime.HIDDEN)
def unhighlight_region(self, context, value):
"""
Unhighlight a region.
Arguments:
- context - a dict with color highlighter run data.
- value - tuple (region to unhighlight, it's color).
"""
(region, _) = value
region_key = GutterIconsColorHighlighter.region_name_template % (self._name, region.a, region.b)
self._view.erase_regions(region_key)
def _decode_data(data):
try:
return data.decode("utf-8")
except UnicodeDecodeError as exception:
return str(exception)
def _create_if_not_exists(path_to_create):
if not os.path.exists(path_to_create):
os.mkdir(path_to_create)