├── .gitignore
├── screenshots
├── gutter_icon.png
└── screenshot.gif
├── LICENSE
├── Default.sublime-commands
├── Main.sublime-menu
├── Color Highlight.sublime-settings
├── README.md
├── settings.py
├── colorizer.py
├── colors.py
└── ColorHighlight.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | *.tmLanguage.cache
4 | package-metadata.json
--------------------------------------------------------------------------------
/screenshots/gutter_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SublimeText/ColorHighlight/HEAD/screenshots/gutter_icon.png
--------------------------------------------------------------------------------
/screenshots/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SublimeText/ColorHighlight/HEAD/screenshots/screenshot.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Germán Méndez Bravo (Kronuz)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences: Color Highlight Settings",
4 | "command": "edit_settings",
5 | "args": {
6 | "base_file": "${packages}/Color Highlight/Color Highlight.sublime-settings",
7 | "default": "/*\n\tColor Highlight user settings in here override the default\n*/\n{\n\t\"user\": {\n\t\t$0\n\t}\n}\n"}
8 | },
9 | {
10 | "caption": "Color Highlight: Color Highlight Current File",
11 | "command": "color_highlight",
12 | "args": {"action": "highlight"}
13 | },
14 | {
15 | "caption": "Color Highlight: Load-Save Color Highlighting",
16 | "command": "color_highlight_enable_load_save",
17 | "args": {"action": "load-save"}
18 | },
19 | {
20 | "caption": "Color Highlight: Save-Only Color Highlighting",
21 | "command": "color_highlight_enable_save_only",
22 | "args": {"action": "save-only"}
23 | },
24 | {
25 | "caption": "Color Highlight: Disable Color Highlighting",
26 | "command": "color_highlight_disable",
27 | "args": {"action": "off"}
28 | },
29 | {
30 | "caption": "Color Highlight: Enable Color Highlighting",
31 | "command": "color_highlight_enable",
32 | "args": {"action": "on"}
33 | },
34 | {
35 | "caption": "Color Highlight: Reset",
36 | "command": "color_highlight",
37 | "args": {"action": "reset"}
38 | },
39 | {
40 | "caption": "Color Highlight: Restore Color Scheme",
41 | "command": "color_highlight_restore",
42 | }
43 | ]
44 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "tools",
4 | "children": [
5 | {
6 | "id": "color-highlight",
7 | "caption": "Color Highlight",
8 | "children": [
9 | {
10 | "caption": "Color Highlight",
11 | "command": "color_highlight",
12 | "mnemonic": "C",
13 | "checkbox": true
14 | },
15 | {
16 | "caption": "Restore color scheme",
17 | "command": "color_highlight_restore",
18 | "mnemonic": "s"
19 | },
20 | {
21 | "caption": "Reset Text Marker",
22 | "command": "text_marker_reset",
23 | "mnemonic": "R"
24 | }
25 | ]
26 | }
27 | ]
28 | },
29 | {
30 | "id": "preferences",
31 | "caption": "Preferences",
32 | "mnemonic": "n",
33 | "children": [
34 | {
35 | "id": "package-settings",
36 | "caption": "Package Settings",
37 | "mnemonic": "P",
38 | "children": [
39 | {
40 | "caption": "Color Highlight Settings",
41 | "command": "edit_settings",
42 | "args": {
43 | "base_file": "${packages}/Color Highlight/Color Highlight.sublime-settings",
44 | "default": "/*\n\tColor Highlight user settings in here override the default\n*/\n{\n\t\"user\": {\n\t\t$0\n\t}\n}\n"
45 | }
46 | }
47 | ]
48 | }
49 | ]
50 | }
51 | ]
52 |
--------------------------------------------------------------------------------
/Color Highlight.sublime-settings:
--------------------------------------------------------------------------------
1 | /*
2 | Color Highlight default settings
3 | */
4 | {
5 | "default": {
6 | /*
7 | highlight - Sets the mode in which Color Highlight runs:
8 |
9 | true - Color highlighting occurs in the background as you type (the default).
10 | false - Color highlighting only occurs when you initiate it.
11 | "load-save" - Color highlighting occurs only when a file is loaded and saved.
12 | "save-only" - Color highlighting occurs only when a file is saved.
13 | */
14 | "highlight": true,
15 |
16 | /*
17 | gutter_icon - Show color as gutter icon:
18 |
19 | "circle" - Circle gutter icon (the default)
20 | "square" - Square gutter icon
21 | "fill" - Fill gutter with color
22 | false - Disable gutter icon
23 | */
24 | "gutter_icon": "circle",
25 |
26 | /*
27 | highlight_values - Show color by highlighting the value region
28 | */
29 | "highlight_values": true,
30 |
31 | /*
32 | named_values - Highlights HTML named colors
33 | */
34 | "named_values": true,
35 |
36 | /*
37 | hex_values - Highlights #RRGGBBAA Hash Hex colors
38 | */
39 | "hex_values": true,
40 |
41 | /*
42 | 0x_hex_values - Highlights 0xRRGGBBAA Hexadecimal colors
43 | */
44 | "0x_hex_values": true,
45 |
46 | /*
47 | xterm_color_values - Highlights xterm colors
48 | */
49 | "xterm_color_values": true,
50 |
51 | /*
52 | XXX_values - Highlights rgb/rgba, hsv/hsva, hsl/hsla, hwb, lab, lch colors
53 | */
54 | "rgb_values": true,
55 | "hsv_values": true,
56 | "hsl_values": true,
57 | "hwb_values": true,
58 | "lab_values": true,
59 | "lch_values": true
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🎨 Color Highlight
2 |
3 | > [!WARNING]
4 | > ### This package is unmaintained!
5 | >
6 | > This package has been unmaintained since 2018
7 | > and has been transferred to the SublimeText organization
8 | > for minimal maintenance.
9 | > However, besides small bug fixes,
10 | > there are no further plans for maintenance.
11 | >
12 | > As an alternative,
13 | > you may want to check out
14 | > the much more modern [Color Helper](https://github.com/facelessuser/ColorHelper) package.
15 |
16 | [](https://packagecontrol.io/packages/Color%20Highlight)
17 |
18 | Show color codes (like "#ffffff", 0xffffff "rgb(255, 255, 255)", "white",
19 | hsl(0, 0%, 100%), etc.) with their real color as the background and/or gutter icons.
20 |
21 | 
22 |
23 | ## Installation
24 |
25 | - **_Recommended_** - Using [Sublime Package Control](https://packagecontrol.io "Sublime Package Control")
26 | - Ctrl+Shift+P then select `Package Control: Install Package`
27 | - install `Color Highlight`
28 | - Alternatively, download the package from [GitHub](https://github.com/Kronuz/ColorHighlight "Color Highlight") into your `Packages` folder and make sure to rename the directory to "Color Highlight".
29 |
30 |
31 | ## Usage
32 |
33 | Supported color representations are:
34 |
35 | - Named colors in the form of CSS3 color names
36 | e.g. `green`, `black` and many others are also supported.
37 |
38 | - Hexademical in the form of `#RGB`, `#RGBA`, `#RRGGBB` or `#RRGGBBAA`
39 | (you can use both upper and lower case letters)
40 |
41 | - Hexadecimal numbers with prefix 0x in the form of `0xRRGGBB` or `0xRRGGBBAA`
42 |
43 | - RBG or RGBA value in the form of `rgb(red, green, blue)` or `rgba(red, green, blue, alpha)`
44 | with decimal channel values
45 |
46 | - HSL or HSLA value in the form of `hsl(hue, sat%, lum%)` or `hsla(hue, sat%, lum%, alpha)`
47 |
48 | - HSV or HSVA value in the form of `hsv(hue, sat%, val%)` or `hsva(hue, sat%, val%, alpha)`
49 |
50 | - HWB value in the form of `hwb(hue, white%, black%)` or `hwb(hue, white%, black%, alpha)`
51 |
52 | - CIELAB (Lab) value in the form of `lab(lum, a, b)` or `lab(lum, a, b, alpha)`
53 |
54 | - Cylindrical CIELAB (LCH) in the form of `lch(hue, chroma, lum)` or `lch(hue, chroma, lum, alpha)`
55 |
56 | - ANSI escape sequences: 3/4 bit (8-color), 8-bit (256-color), 24-bit (true color)
57 | in the form of `\033[3Xm`, `\033[4Xm`, `\033[38;5;IIIm` or `\033[38;2;RRR,GGG,BBBm`.
58 | Escape part accepting "`^[`[", "\033", "\x1b[", "\u001b[" and "\e["
59 |
60 |
61 | Those will be shown with colored background and gutter icons when they're found in
62 | your documents.
63 |
64 |
65 | ## Configuration
66 |
67 | - You can disable live highlight directly from the command palette:
68 | `Color Highlight: Disable Color Highlight`
69 |
70 | - Open settings using the command palette:
71 | `Preferences: Color Highlight Settings - User`
72 |
73 | - Gutter icons can be switched among three flavors (or disabled) by using
74 | the `gutter_icon` setting:
75 |
76 | + "circle" - Gutter icon with the color in a circle
77 | + "square" - Gutter icon with the color in a square
78 | + "fill" - Fill whole gutter with color
79 |
80 | ```
81 | "user": {
82 | "gutter_icon": "fill"
83 | }
84 | ```
85 |
86 | 
87 |
88 | - Highlighting the value region in the color can be enabled or disabled by
89 | using the `highlight_values` setting.
90 |
91 | - Enabling/disabling coloring of different types of values can be configured.
92 |
93 |
94 | ## License
95 |
96 | Copyright (C) 2018 German Mendez Bravo (Kronuz). All rights reserved.
97 |
98 | MIT license
99 |
100 | This plugin was initially a fork of
101 | https://github.com/Monnoroch/ColorHighlighter
102 |
--------------------------------------------------------------------------------
/settings.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, unicode_literals, print_function
2 |
3 | import os
4 | import json
5 | from copy import deepcopy
6 | from collections import defaultdict
7 |
8 | import sublime
9 | import sublime_plugin
10 |
11 |
12 | class Settings(object):
13 | """This class provides global access to and management of plugin settings."""
14 | nested_settings = ()
15 |
16 | def __init__(self, name):
17 | """Initialize a new instance."""
18 | self.name = name
19 | self.settings = {}
20 | self.previous_settings = {}
21 | self.changeset = set()
22 | self.plugin_settings = None
23 | self.edits = defaultdict(list)
24 |
25 | def load(self, force=False):
26 | """Load the plugin settings."""
27 | if force or not self.settings:
28 | self.observe()
29 | self.on_change()
30 |
31 | def has_setting(self, setting):
32 | """Return whether the given setting exists."""
33 | return setting in self.settings
34 |
35 | def get(self, setting, default=None):
36 | """Return a plugin setting, defaulting to default if not found."""
37 | return self.settings.get(setting, default)
38 |
39 | def set(self, setting, value, changed=False):
40 | """
41 | Set a plugin setting to the given value.
42 |
43 | Clients of this module should always call this method to set a value
44 | instead of doing settings['foo'] = 'bar'.
45 |
46 | If the caller knows for certain that the value has changed,
47 | they should pass changed=True.
48 |
49 | """
50 | self.copy()
51 | self.settings[setting] = value
52 |
53 | if changed:
54 | self.changeset.add(setting)
55 |
56 | def pop(self, setting, default=None):
57 | """
58 | Remove a given setting and return default if it is not in self.settings.
59 |
60 | Clients of this module should always call this method to pop a value
61 | instead of doing settings.pop('foo').
62 |
63 | """
64 | self.copy()
65 | return self.settings.pop(setting, default)
66 |
67 | def copy(self):
68 | """Save a copy of the plugin settings."""
69 | self.previous_settings = deepcopy(self.settings)
70 |
71 | def observe(self, observer=None):
72 | """Observer changes to the plugin settings."""
73 | self.plugin_settings = sublime.load_settings('{}.sublime-settings'.format(self.name))
74 | self.plugin_settings.clear_on_change(self.name)
75 | self.plugin_settings.add_on_change(self.name, observer or self.on_change)
76 |
77 | def merge_user_settings(self, settings):
78 | """
79 | Return the default settings merged with the user's settings.
80 | If there are any nested settings, those get merged as well.
81 |
82 | """
83 |
84 | default = settings.get('default', {})
85 | user = settings.get('user', {})
86 |
87 | if user:
88 | for setting_name in self.nested_settings:
89 | default_setting = default.pop(setting_name, {})
90 | user_setting = user.get(setting_name, {})
91 |
92 | for name, data in user_setting.items():
93 | if name in default_setting and isinstance(default_setting[name], dict):
94 | default_setting[name].update(data)
95 | else:
96 | default_setting[name] = data
97 | default[setting_name] = default_setting
98 | user.pop(setting_name, None)
99 | default.update(user)
100 |
101 | return default
102 |
103 | def on_change(self):
104 | """Update state when the user settings change."""
105 |
106 | settings = self.merge_user_settings(self.plugin_settings)
107 | self.settings.clear()
108 | self.settings.update(settings)
109 |
110 | self.on_update()
111 |
112 | self.changeset.clear()
113 | self.copy()
114 |
115 | def on_update(self):
116 | """To be implemented by the user, when needed."""
117 | pass
118 |
119 | def save(self, view=None):
120 | """
121 | Regenerate and save the user settings.
122 |
123 | User settings are updated and merged with the default settings and if
124 | the user settings are currently being edited, the view is also updated.
125 |
126 | """
127 | self.load()
128 |
129 | # Fill in default settings
130 | settings = self.settings
131 |
132 | settings_filename = '{}.sublime-settings'.format(self.name)
133 | user_settings_path = os.path.join(sublime.packages_path(), 'User', settings_filename)
134 | settings_views = []
135 |
136 | if view is None:
137 | # See if any open views are the user prefs
138 | for window in sublime.windows():
139 | for view in window.views():
140 | if view.file_name() == user_settings_path:
141 | settings_views.append(view)
142 | else:
143 | settings_views = [view]
144 |
145 | if settings_views:
146 | def replace(edit):
147 | if not view.is_dirty():
148 | j = json.dumps({'user': settings}, indent=4, sort_keys=True)
149 | j = j.replace(' \n', '\n')
150 | view.replace(edit, sublime.Region(0, view.size()), j)
151 |
152 | for view in settings_views:
153 | self.edits[view.id()].append(replace)
154 | view.run_command('settings_view_editor', self)
155 | view.run_command('save')
156 | else:
157 | user_settings = sublime.load_settings(settings_filename)
158 | user_settings.set('user', settings)
159 | sublime.save_settings(settings_filename)
160 |
161 | def edit(self, vid, edit):
162 | """Perform an operation on a view with the given edit object."""
163 | callbacks = self.edits.pop(vid, [])
164 |
165 | for c in callbacks:
166 | c(edit)
167 |
168 |
169 | class SettingsViewEditorCommand(sublime_plugin.TextCommand):
170 | """A plugin command used to generate an edit object for a view."""
171 |
172 | def run(self, edit, settings):
173 | """Run the command."""
174 | settings.edit(self.view.id(), edit)
175 |
176 |
177 | class SettingTogglerCommandMixin(object):
178 | """Command that toggles a setting."""
179 |
180 | settings = None
181 |
182 | def is_visible(self, **args):
183 | """Return True if the opposite of the setting is True."""
184 | if args.get('checked', False):
185 | return True
186 |
187 | if self.settings.has_setting(args['setting']):
188 | setting = self.settings.get(args['setting'], None)
189 | return setting is not None and setting is not args['value']
190 | else:
191 | return args['value'] is not None
192 |
193 | def is_checked(self, **args):
194 | """Return True if the setting should be checked."""
195 | if args.get('checked', False):
196 | setting = self.settings.get(args['setting'], False)
197 | return setting is True
198 | else:
199 | return False
200 |
201 | def run(self, **args):
202 | """Toggle the setting if value is boolean, or remove it if None."""
203 |
204 | if 'value' in args:
205 | if args['value'] is None:
206 | self.settings.pop(args['setting'])
207 | else:
208 | self.settings.set(args['setting'], args['value'], changed=True)
209 | else:
210 | setting = self.settings.get(args['setting'], False)
211 | self.settings.set(args['setting'], not setting, changed=True)
212 |
213 | self.settings.save()
214 |
--------------------------------------------------------------------------------
/colorizer.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import os
4 | import re
5 | import sys
6 | import json
7 | import errno
8 | import plistlib
9 | import datetime
10 |
11 | import sublime
12 |
13 | from .colors import names_to_hex, xterm_to_hex, xterm8_to_hex, xterm8b_to_hex, xterm8f_to_hex
14 |
15 | DEFAULT_COLOR_SCHEME = 'Monokai.sublime-color-scheme'
16 |
17 | all_names_to_hex = dict(names_to_hex, **xterm_to_hex)
18 |
19 |
20 | class Log(object):
21 | def info(self, *args):
22 | print("[Colorizer]", end=" ")
23 | for arg in args:
24 | print(arg, end=" ")
25 | print()
26 |
27 | debug = lambda s, *a: None
28 | error = info
29 | warn = info
30 | # debug = info
31 |
32 | log = Log()
33 |
34 |
35 | if sys.version_info[0] == 3:
36 | if not hasattr(plistlib, 'loads'):
37 | plistlib.loads = lambda data: plistlib.readPlistFromBytes(data)
38 | plistlib.dumps = lambda value: plistlib.writePlistToBytes(value)
39 | else:
40 | plistlib.loads = lambda data: plistlib.readPlistFromString(data)
41 | plistlib.dumps = lambda value: plistlib.writePlistToString(value)
42 |
43 |
44 | def write_package(path, content):
45 | rf = sublime.packages_path() + path
46 | try:
47 | os.makedirs(os.path.dirname(rf))
48 | except OSError as e:
49 | if e.errno != errno.EEXIST:
50 | raise
51 | with open(rf, 'w') as f:
52 | f.write(content)
53 |
54 |
55 | def read_package(path):
56 | rf = sublime.packages_path() + path
57 | if os.path.exists(rf):
58 | with open(rf, 'r') as f:
59 | res = f.read()
60 | else:
61 | rf = 'Packages' + path
62 | res = sublime.load_resource(rf)
63 | return res
64 |
65 |
66 | class ColorScheme(object):
67 | backup_ext = ".chback"
68 |
69 | def __init__(self, settings):
70 | path = settings.get('color_scheme') or DEFAULT_COLOR_SCHEME
71 | if not path.startswith('Packages/'):
72 | path = 'Packages/Color Scheme - Default/' + path
73 | self.path = path[8:]
74 | self.time = datetime.datetime.now()
75 |
76 | def hash(self):
77 | if not hasattr(self, '_hash'):
78 | self._hash = hash(self.content())
79 | return self._hash
80 |
81 | def restore(self):
82 | # Remove "Packages" part from name
83 | if not os.path.exists(sublime.packages_path() + self.path + self.backup_ext):
84 | log.debug("No backup :(")
85 | return False
86 | log.debug("Starting restore scheme: " + self.path)
87 | write_package(self.path, read_package(self.path + self.backup_ext))
88 | log.debug("Restore done.")
89 | return True
90 |
91 | def backup(self, content):
92 | if os.path.exists(sublime.packages_path() + self.path + self.backup_ext):
93 | log.debug("Already backed up")
94 | return False
95 | write_package(self.path + self.backup_ext, content) # backup
96 | log.debug("Backup done")
97 | return True
98 |
99 | def content(self):
100 | if not hasattr(self, '_content'):
101 | # Remove "Packages" part from name
102 | content = read_package(self.path)
103 | self.backup(content)
104 | self._content = content
105 | return self._content
106 |
107 |
108 | class SchemaColorizer(object):
109 | prefix = "col_"
110 |
111 | colors = {}
112 | color_scheme = None
113 | need_update = False
114 |
115 | def normalize(self, col):
116 | if col:
117 | col = all_names_to_hex.get(col.lower(), col.upper())
118 | if col.startswith('0X'):
119 | col = '#' + col[2:]
120 | try:
121 | if col[0] != '#':
122 | raise ValueError
123 | if len(col) == 4:
124 | col = '#' + col[1] * 2 + col[2] * 2 + col[3] * 2 + 'FF'
125 | elif len(col) == 5:
126 | col = '#' + col[1] * 2 + col[2] * 2 + col[3] * 2 + col[4] * 2
127 | elif len(col) == 7:
128 | col += 'FF'
129 | r = int(col[1:3], 16)
130 | g = int(col[3:5], 16)
131 | b = int(col[5:7], 16)
132 | a = int(col[7:9], 16) or 1 # alpha == 0 doesn't apply alpha in Sublime
133 | return '#%02X%02X%02X%02X' % (r, g, b, a)
134 | except Exception:
135 | log.debug("Invalid color: %r" % col)
136 |
137 | def get_inv_col(self, bg_col, col):
138 | br = int(bg_col[1:3], 16)
139 | bg = int(bg_col[3:5], 16)
140 | bb = int(bg_col[5:7], 16)
141 |
142 | r = int(col[1:3], 16)
143 | g = int(col[3:5], 16)
144 | b = int(col[5:7], 16)
145 | a = int(col[7:9], 16) / 255.0
146 |
147 | r = br * (1 - a) + r * a
148 | g = bg * (1 - a) + g * a
149 | b = bb * (1 - a) + b * a
150 |
151 | # L = (max(r, g, b) + min(r, g, b)) / 2
152 | # Y709 = 0.2126 * r + 0.7152 * g + 0.0722 * b
153 | Y601 = ((r * 299) + (g * 587) + (b * 114)) / 1000
154 |
155 | v = Y601
156 |
157 | if v >= 128:
158 | v -= 128
159 | else:
160 | v += 128
161 |
162 | return '#%sFF' % (('%02X' % round(v)) * 3)
163 |
164 | def region_name(self, s):
165 | return self.prefix + s[1:]
166 |
167 | def add_color(self, col):
168 | col = self.normalize(col)
169 | if not col:
170 | return
171 | if col not in self.colors:
172 | self.colors[col] = self.region_name(col)
173 | self.need_update = True
174 | return self.colors[col]
175 |
176 | def current_views(self):
177 | for window in sublime.windows():
178 | for view in window.views():
179 | yield view
180 |
181 | def get_background_col(self, view=None):
182 | style = view.style()
183 | bg_col = style.get('background')
184 | if bg_col:
185 | return (bg_col + 'FF')[:9].upper()
186 | return '#333333FF'
187 |
188 | def update(self, view):
189 | if not self.need_update:
190 | return
191 | self.need_update = False
192 |
193 | content = self.color_scheme.content()
194 | current_colors = set("#%s" % c.upper() for c in re.findall(r'\b%s([a-fA-F0-9]{8})\b' % self.prefix, content))
195 |
196 | bg_col = self.get_background_col(view)
197 |
198 | rules = []
199 | if not re.search(r'\b%sgutter\b' % self.prefix, content):
200 | rules.append({
201 | "scope": "%sgutter" % self.prefix,
202 | "background": "#000000",
203 | "foreground": "#ffffff",
204 | })
205 | for col, name in self.colors.items():
206 | if col not in current_colors:
207 | fg_col = self.get_inv_col(bg_col, col)
208 | rules.append({
209 | "scope": name,
210 | "background": col,
211 | "foreground": fg_col,
212 | })
213 |
214 | if rules:
215 | try:
216 | # For sublime-color-scheme
217 | m = re.search(r'([\t ]*)"rules":\s*\[[\r\n]*', content)
218 | if m:
219 | json_rules = json.dumps({"rules": rules}, indent=m.group(1))
220 | json_rules = '\n'.join(map(str.rstrip, json_rules.split('\n')[2:-2])) + ',\n'
221 | content = content[:m.end()] + json_rules + content[m.end():]
222 | write_package(self.color_scheme.path, content)
223 | log.debug("Updated sublime-color-scheme")
224 | return
225 |
226 | # for tmTheme
227 | if re.match(r'\s*<(?:\?xml|!DOCTYPE|plist)\b', content):
228 | plist_content = plistlib.loads(content.encode('utf-8'))
229 | plist_content['settings'].extend({
230 | "scope": r['scope'],
231 | "settings": {
232 | "foreground": r['foreground'],
233 | "background": r['background'],
234 | }
235 | } for r in rules)
236 | content = plistlib.dumps(plist_content).decode('utf-8')
237 | write_package(self.color_scheme.path, content)
238 | log.debug("Updated tmTheme")
239 | return
240 |
241 | log.error("Not Updated: Schema format not recognized")
242 | except Exception as e:
243 | import traceback; traceback.print_exc();
244 | log.error("Not Updated: %r" % e)
245 |
246 | def clear(self):
247 | self.colors = {}
248 |
249 | def setup_color_scheme(self, settings):
250 | color_scheme = ColorScheme(settings)
251 | if self.color_scheme and self.color_scheme.path == color_scheme.path:
252 | if self.color_scheme.time + datetime.timedelta(seconds=1) > color_scheme.time:
253 | return
254 | if self.color_scheme.hash() == color_scheme.hash():
255 | self.color_scheme.time = color_scheme.time
256 | return
257 | log.debug("Color scheme %s setup" % color_scheme.path)
258 | self.color_scheme = color_scheme
259 | content = self.color_scheme.content()
260 | self.colors = dict(("#%s" % c, "%s%s" % (self.prefix, c)) for c in re.findall(r'\b%s([a-fA-F0-9]{8})\b' % self.prefix, content))
261 |
262 | def restore_color_scheme(self):
263 | # do not support empty color scheme
264 | if not self.color_scheme:
265 | log.error("Empty scheme, can't restore")
266 | return
267 | if self.color_scheme.restore():
268 | self.colors = {}
269 |
--------------------------------------------------------------------------------
/colors.py:
--------------------------------------------------------------------------------
1 | names_to_hex = {
2 | 'aliceblue': '#F0F8FFFF',
3 | 'antiquewhite': '#FAEBD7FF',
4 | 'aqua': '#00FFFFFF',
5 | 'aquamarine': '#7FFFD4FF',
6 | 'azure': '#F0FFFFFF',
7 | 'beige': '#F5F5DCFF',
8 | 'bisque': '#FFE4C4FF',
9 | 'black': '#000000FF',
10 | 'blanchedalmond': '#FFEBCDFF',
11 | 'blue': '#0000FFFF',
12 | 'blueviolet': '#8A2BE2FF',
13 | 'brown': '#A52A2AFF',
14 | 'burlywood': '#DEB887FF',
15 | 'cadetblue': '#5F9EA0FF',
16 | 'chartreuse': '#7FFF00FF',
17 | 'chocolate': '#D2691EFF',
18 | 'coral': '#FF7F50FF',
19 | 'cornflowerblue': '#6495EDFF',
20 | 'cornsilk': '#FFF8DCFF',
21 | 'crimson': '#DC143CFF',
22 | 'cyan': '#00FFFFFF',
23 | 'darkblue': '#00008BFF',
24 | 'darkcyan': '#008B8BFF',
25 | 'darkgoldenrod': '#B8860BFF',
26 | 'darkgray': '#A9A9A9FF',
27 | 'darkgrey': '#A9A9A9FF',
28 | 'darkgreen': '#006400FF',
29 | 'darkkhaki': '#BDB76BFF',
30 | 'darkmagenta': '#8B008BFF',
31 | 'darkolivegreen': '#556B2FFF',
32 | 'darkorange': '#FF8C00FF',
33 | 'darkorchid': '#9932CCFF',
34 | 'darkred': '#8B0000FF',
35 | 'darksalmon': '#E9967AFF',
36 | 'darkseagreen': '#8FBC8FFF',
37 | 'darkslateblue': '#483D8BFF',
38 | 'darkslategray': '#2F4F4FFF',
39 | 'darkslategrey': '#2F4F4FFF',
40 | 'darkturquoise': '#00CED1FF',
41 | 'darkviolet': '#9400D3FF',
42 | 'deeppink': '#FF1493FF',
43 | 'deepskyblue': '#00BFFFFF',
44 | 'dimgray': '#696969FF',
45 | 'dimgrey': '#696969FF',
46 | 'dodgerblue': '#1E90FFFF',
47 | 'firebrick': '#B22222FF',
48 | 'floralwhite': '#FFFAF0FF',
49 | 'forestgreen': '#228B22FF',
50 | 'fuchsia': '#FF00FFFF',
51 | 'gainsboro': '#DCDCDCFF',
52 | 'ghostwhite': '#F8F8FFFF',
53 | 'gold': '#FFD700FF',
54 | 'goldenrod': '#DAA520FF',
55 | 'gray': '#808080FF',
56 | 'grey': '#808080FF',
57 | 'green': '#008000FF',
58 | 'greenyellow': '#ADFF2FFF',
59 | 'honeydew': '#F0FFF0FF',
60 | 'hotpink': '#FF69B4FF',
61 | 'indianred': '#CD5C5CFF',
62 | 'indigo': '#4B0082FF',
63 | 'ivory': '#FFFFF0FF',
64 | 'khaki': '#F0E68CFF',
65 | 'lavender': '#E6E6FAFF',
66 | 'lavenderblush': '#FFF0F5FF',
67 | 'lawngreen': '#7CFC00FF',
68 | 'lemonchiffon': '#FFFACDFF',
69 | 'lightblue': '#ADD8E6FF',
70 | 'lightcoral': '#F08080FF',
71 | 'lightcyan': '#E0FFFFFF',
72 | 'lightgoldenrodyellow': '#FAFAD2FF',
73 | 'lightgray': '#D3D3D3FF',
74 | 'lightgrey': '#D3D3D3FF',
75 | 'lightgreen': '#90EE90FF',
76 | 'lightpink': '#FFB6C1FF',
77 | 'lightsalmon': '#FFA07AFF',
78 | 'lightseagreen': '#20B2AAFF',
79 | 'lightskyblue': '#87CEFAFF',
80 | 'lightslategray': '#778899FF',
81 | 'lightslategrey': '#778899FF',
82 | 'lightsteelblue': '#B0C4DEFF',
83 | 'lightyellow': '#FFFFE0FF',
84 | 'lime': '#00FF00FF',
85 | 'limegreen': '#32CD32FF',
86 | 'linen': '#FAF0E6FF',
87 | 'magenta': '#FF00FFFF',
88 | 'maroon': '#800000FF',
89 | 'mediumaquamarine': '#66CDAAFF',
90 | 'mediumblue': '#0000CDFF',
91 | 'mediumorchid': '#BA55D3FF',
92 | 'mediumpurple': '#9370D8FF',
93 | 'mediumseagreen': '#3CB371FF',
94 | 'mediumslateblue': '#7B68EEFF',
95 | 'mediumspringgreen': '#00FA9AFF',
96 | 'mediumturquoise': '#48D1CCFF',
97 | 'mediumvioletred': '#C71585FF',
98 | 'midnightblue': '#191970FF',
99 | 'mintcream': '#F5FFFAFF',
100 | 'mistyrose': '#FFE4E1FF',
101 | 'moccasin': '#FFE4B5FF',
102 | 'monnoroch': '#000000FF',
103 | 'navajowhite': '#FFDEADFF',
104 | 'navy': '#000080FF',
105 | 'oldlace': '#FDF5E6FF',
106 | 'olive': '#808000FF',
107 | 'olivedrab': '#6B8E23FF',
108 | 'orange': '#FFA500FF',
109 | 'orangered': '#FF4500FF',
110 | 'orchid': '#DA70D6FF',
111 | 'palegoldenrod': '#EEE8AAFF',
112 | 'palegreen': '#98FB98FF',
113 | 'paleturquoise': '#AFEEEEFF',
114 | 'palevioletred': '#D87093FF',
115 | 'papayawhip': '#FFEFD5FF',
116 | 'peachpuff': '#FFDAB9FF',
117 | 'peru': '#CD853FFF',
118 | 'pink': '#FFC0CBFF',
119 | 'plum': '#DDA0DDFF',
120 | 'powderblue': '#B0E0E6FF',
121 | 'purple': '#800080FF',
122 | 'red': '#FF0000FF',
123 | 'rosybrown': '#BC8F8FFF',
124 | 'royalblue': '#4169E1FF',
125 | 'saddlebrown': '#8B4513FF',
126 | 'salmon': '#FA8072FF',
127 | 'sandybrown': '#F4A460FF',
128 | 'seagreen': '#2E8B57FF',
129 | 'seashell': '#FFF5EEFF',
130 | 'sienna': '#A0522DFF',
131 | 'silver': '#C0C0C0FF',
132 | 'skyblue': '#87CEEBFF',
133 | 'slateblue': '#6A5ACDFF',
134 | 'slategray': '#708090FF',
135 | 'slategrey': '#708090FF',
136 | 'snow': '#FFFAFAFF',
137 | 'springgreen': '#00FF7FFF',
138 | 'steelblue': '#4682B4FF',
139 | 'tan': '#D2B48CFF',
140 | 'teal': '#008080FF',
141 | 'thistle': '#D8BFD8FF',
142 | 'tomato': '#FF6347FF',
143 | 'turquoise': '#40E0D0FF',
144 | 'violet': '#EE82EEFF',
145 | 'wheat': '#F5DEB3FF',
146 | 'white': '#FFFFFFFF',
147 | 'whitesmoke': '#F5F5F5FF',
148 | 'yellow': '#FFFF00FF',
149 | 'yellowgreen': '#9ACD32FF',
150 | }
151 |
152 | xterm_to_hex = {
153 | '0': '#101010FF',
154 | '1': '#E84F4FFF',
155 | '2': '#B8D68CFF',
156 | '3': '#E1AA5DFF',
157 | '4': '#7DC1CFFF',
158 | '5': '#9B64FBFF',
159 | '6': '#6D878DFF',
160 | '7': '#DDDDDDFF',
161 | '8': '#404040FF',
162 | '9': '#D23D3DFF',
163 | '10': '#A0CF5DFF',
164 | '11': '#F39D21FF',
165 | '12': '#4E9FB1FF',
166 | '13': '#8542FFFF',
167 | '14': '#42717BFF',
168 | '15': '#DDDDDDFF',
169 | '16': '#000000FF',
170 | '17': '#00005FFF',
171 | '18': '#000087FF',
172 | '19': '#0000AFFF',
173 | '20': '#0000D7FF',
174 | '21': '#0000FFFF',
175 | '22': '#005F00FF',
176 | '23': '#005F5FFF',
177 | '24': '#005F87FF',
178 | '25': '#005FAFFF',
179 | '26': '#005FD7FF',
180 | '27': '#005FFFFF',
181 | '28': '#008700FF',
182 | '29': '#00875FFF',
183 | '30': '#008787FF',
184 | '31': '#0087AFFF',
185 | '32': '#0087D7FF',
186 | '33': '#0087FFFF',
187 | '34': '#00AF00FF',
188 | '35': '#00AF5FFF',
189 | '36': '#00AF87FF',
190 | '37': '#00AFAFFF',
191 | '38': '#00AFD7FF',
192 | '39': '#00AFFFFF',
193 | '40': '#00D700FF',
194 | '41': '#00D75FFF',
195 | '42': '#00D787FF',
196 | '43': '#00D7AFFF',
197 | '44': '#00D7D7FF',
198 | '45': '#00D7FFFF',
199 | '46': '#00FF00FF',
200 | '47': '#00FF5FFF',
201 | '48': '#00FF87FF',
202 | '49': '#00FFAFFF',
203 | '50': '#00FFD7FF',
204 | '51': '#00FFFFFF',
205 | '52': '#5F0000FF',
206 | '53': '#5F005FFF',
207 | '54': '#5F0087FF',
208 | '55': '#5F00AFFF',
209 | '56': '#5F00D7FF',
210 | '57': '#5F00FFFF',
211 | '58': '#5F5F00FF',
212 | '59': '#5F5F5FFF',
213 | '60': '#5F5F87FF',
214 | '61': '#5F5FAFFF',
215 | '62': '#5F5FD7FF',
216 | '63': '#5F5FFFFF',
217 | '64': '#5F8700FF',
218 | '65': '#5F875FFF',
219 | '66': '#5F8787FF',
220 | '67': '#5F87AFFF',
221 | '68': '#5F87D7FF',
222 | '69': '#5F87FFFF',
223 | '70': '#5FAF00FF',
224 | '71': '#5FAF5FFF',
225 | '72': '#5FAF87FF',
226 | '73': '#5FAFAFFF',
227 | '74': '#5FAFD7FF',
228 | '75': '#5FAFFFFF',
229 | '76': '#5FD700FF',
230 | '77': '#5FD75FFF',
231 | '78': '#5FD787FF',
232 | '79': '#5FD7AFFF',
233 | '80': '#5FD7D7FF',
234 | '81': '#5FD7FFFF',
235 | '82': '#5FFF00FF',
236 | '83': '#5FFF5FFF',
237 | '84': '#5FFF87FF',
238 | '85': '#5FFFAFFF',
239 | '86': '#5FFFD7FF',
240 | '87': '#5FFFFFFF',
241 | '88': '#870000FF',
242 | '89': '#87005FFF',
243 | '90': '#870087FF',
244 | '91': '#8700AFFF',
245 | '92': '#8700D7FF',
246 | '93': '#8700FFFF',
247 | '94': '#875F00FF',
248 | '95': '#875F5FFF',
249 | '96': '#875F87FF',
250 | '97': '#875FAFFF',
251 | '98': '#875FD7FF',
252 | '99': '#875FFFFF',
253 | '100': '#878700FF',
254 | '101': '#87875FFF',
255 | '102': '#878787FF',
256 | '103': '#8787AFFF',
257 | '104': '#8787D7FF',
258 | '105': '#8787FFFF',
259 | '106': '#87AF00FF',
260 | '107': '#87AF5FFF',
261 | '108': '#87AF87FF',
262 | '109': '#87AFAFFF',
263 | '110': '#87AFD7FF',
264 | '111': '#87AFFFFF',
265 | '112': '#87D700FF',
266 | '113': '#87D75FFF',
267 | '114': '#87D787FF',
268 | '115': '#87D7AFFF',
269 | '116': '#87D7D7FF',
270 | '117': '#87D7FFFF',
271 | '118': '#87FF00FF',
272 | '119': '#87FF5FFF',
273 | '120': '#87FF87FF',
274 | '121': '#87FFAFFF',
275 | '122': '#87FFD7FF',
276 | '123': '#87FFFFFF',
277 | '124': '#AF0000FF',
278 | '125': '#AF005FFF',
279 | '126': '#AF0087FF',
280 | '127': '#AF00AFFF',
281 | '128': '#AF00D7FF',
282 | '129': '#AF00FFFF',
283 | '130': '#AF5F00FF',
284 | '131': '#AF5F5FFF',
285 | '132': '#AF5F87FF',
286 | '133': '#AF5FAFFF',
287 | '134': '#AF5FD7FF',
288 | '135': '#AF5FFFFF',
289 | '136': '#AF8700FF',
290 | '137': '#AF875FFF',
291 | '138': '#AF8787FF',
292 | '139': '#AF87AFFF',
293 | '140': '#AF87D7FF',
294 | '141': '#AF87FFFF',
295 | '142': '#AFAF00FF',
296 | '143': '#AFAF5FFF',
297 | '144': '#AFAF87FF',
298 | '145': '#AFAFAFFF',
299 | '146': '#AFAFD7FF',
300 | '147': '#AFAFFFFF',
301 | '148': '#AFD700FF',
302 | '149': '#AFD75FFF',
303 | '150': '#AFD787FF',
304 | '151': '#AFD7AFFF',
305 | '152': '#AFD7D7FF',
306 | '153': '#AFD7FFFF',
307 | '154': '#AFFF00FF',
308 | '155': '#AFFF5FFF',
309 | '156': '#AFFF87FF',
310 | '157': '#AFFFAFFF',
311 | '158': '#AFFFD7FF',
312 | '159': '#AFFFFFFF',
313 | '160': '#D70000FF',
314 | '161': '#D7005FFF',
315 | '162': '#D70087FF',
316 | '163': '#D700AFFF',
317 | '164': '#D700D7FF',
318 | '165': '#D700FFFF',
319 | '166': '#D75F00FF',
320 | '167': '#D75F5FFF',
321 | '168': '#D75F87FF',
322 | '169': '#D75FAFFF',
323 | '170': '#D75FD7FF',
324 | '171': '#D75FFFFF',
325 | '172': '#D78700FF',
326 | '173': '#D7875FFF',
327 | '174': '#D78787FF',
328 | '175': '#D787AFFF',
329 | '176': '#D787D7FF',
330 | '177': '#D787FFFF',
331 | '178': '#D7AF00FF',
332 | '179': '#D7AF5FFF',
333 | '180': '#D7AF87FF',
334 | '181': '#D7AFAFFF',
335 | '182': '#D7AFD7FF',
336 | '183': '#D7AFFFFF',
337 | '184': '#D7D700FF',
338 | '185': '#D7D75FFF',
339 | '186': '#D7D787FF',
340 | '187': '#D7D7AFFF',
341 | '188': '#D7D7D7FF',
342 | '189': '#D7D7FFFF',
343 | '190': '#D7FF00FF',
344 | '191': '#D7FF5FFF',
345 | '192': '#D7FF87FF',
346 | '193': '#D7FFAFFF',
347 | '194': '#D7FFD7FF',
348 | '195': '#D7FFFFFF',
349 | '196': '#FF0000FF',
350 | '197': '#FF005FFF',
351 | '198': '#FF0087FF',
352 | '199': '#FF00AFFF',
353 | '200': '#FF00D7FF',
354 | '201': '#FF00FFFF',
355 | '202': '#FF5F00FF',
356 | '203': '#FF5F5FFF',
357 | '204': '#FF5F87FF',
358 | '205': '#FF5FAFFF',
359 | '206': '#FF5FD7FF',
360 | '207': '#FF5FFFFF',
361 | '208': '#FF8700FF',
362 | '209': '#FF875FFF',
363 | '210': '#FF8787FF',
364 | '211': '#FF87AFFF',
365 | '212': '#FF87D7FF',
366 | '213': '#FF87FFFF',
367 | '214': '#FFAF00FF',
368 | '215': '#FFAF5FFF',
369 | '216': '#FFAF87FF',
370 | '217': '#FFAFAFFF',
371 | '218': '#FFAFD7FF',
372 | '219': '#FFAFFFFF',
373 | '220': '#FFD700FF',
374 | '221': '#FFD75FFF',
375 | '222': '#FFD787FF',
376 | '223': '#FFD7AFFF',
377 | '224': '#FFD7D7FF',
378 | '225': '#FFD7FFFF',
379 | '226': '#FFFF00FF',
380 | '227': '#FFFF5FFF',
381 | '228': '#FFFF87FF',
382 | '229': '#FFFFAFFF',
383 | '230': '#FFFFD7FF',
384 | '231': '#FFFFFFFF',
385 | '232': '#080808FF',
386 | '233': '#121212FF',
387 | '234': '#1C1C1CFF',
388 | '235': '#262626FF',
389 | '238': '#444444FF',
390 | '239': '#4E4E4EFF',
391 | '240': '#585858FF',
392 | '241': '#626262FF',
393 | '242': '#6C6C6CFF',
394 | '243': '#767676FF',
395 | '244': '#808080FF',
396 | '245': '#8A8A8AFF',
397 | '246': '#949494FF',
398 | '247': '#9E9E9EFF',
399 | '248': '#A8A8A8FF',
400 | '249': '#B2B2B2FF',
401 | '250': '#BCBCBCFF',
402 | '251': '#C6C6C6FF',
403 | '252': '#D0D0D0FF',
404 | '253': '#DADADAFF',
405 | '254': '#E4E4E4FF',
406 | '255': '#EEEEEEFF',
407 | }
408 |
409 | xterm8_to_hex = {
410 | '30': '#000000FF',
411 | '31': '#800000FF',
412 | '32': '#008000FF',
413 | '33': '#808000FF',
414 | '34': '#000080FF',
415 | '35': '#800080FF',
416 | '36': '#008080FF',
417 | '37': '#C0C0C0FF',
418 | '90': '#808080FF',
419 | '91': '#FF0000FF',
420 | '92': '#00FF00FF',
421 | '93': '#FFFF00FF',
422 | '94': '#0000FFFF',
423 | '95': '#FF00FFFF',
424 | '96': '#00FFFFFF',
425 | '97': '#FFFFFFFF',
426 | '40': '#000000FF',
427 | '41': '#800000FF',
428 | '42': '#008000FF',
429 | '43': '#808000FF',
430 | '44': '#000080FF',
431 | '45': '#800080FF',
432 | '46': '#008080FF',
433 | '47': '#C0C0C0FF',
434 | '100': '#808080FF',
435 | '101': '#FF0000FF',
436 | '102': '#00FF00FF',
437 | '103': '#FFFF00FF',
438 | '104': '#0000FFFF',
439 | '105': '#FF00FFFF',
440 | '106': '#00FFFFFF',
441 | '107': '#FFFFFFFF',
442 | }
443 |
444 | xterm8b_to_hex = {
445 | '30': '#000000FF',
446 | '31': '#DD0000FF',
447 | '32': '#00DD00FF',
448 | '33': '#DD8800FF',
449 | '34': '#0000DDFF',
450 | '35': '#DD00DDFF',
451 | '36': '#00DDDDFF',
452 | '37': '#DDDDDDFF',
453 | '90': '#888888FF',
454 | '91': '#FF8888FF',
455 | '92': '#88FF88FF',
456 | '93': '#FFFF88FF',
457 | '94': '#8888FFFF',
458 | '95': '#FF88FFFF',
459 | '96': '#88FFFFFF',
460 | '97': '#FFFFFFFF',
461 | '40': '#000000FF',
462 | '41': '#DD0000FF',
463 | '42': '#00DD00FF',
464 | '43': '#DD8800FF',
465 | '44': '#0000DDFF',
466 | '45': '#DD00DDFF',
467 | '46': '#00DDDDFF',
468 | '47': '#DDDDDDFF',
469 | '100': '#888888FF',
470 | '101': '#FF8888FF',
471 | '102': '#88FF88FF',
472 | '103': '#FFFF88FF',
473 | '104': '#8888FFFF',
474 | '105': '#FF88FFFF',
475 | '106': '#88FFFFFF',
476 | '107': '#FFFFFFFF',
477 | }
478 |
479 | xterm8f_to_hex = {
480 | '30': '#000000FF',
481 | '31': '#550000FF',
482 | '32': '#005500FF',
483 | '33': '#550000FF',
484 | '34': '#000055FF',
485 | '35': '#550055FF',
486 | '36': '#005555FF',
487 | '37': '#555555FF',
488 | '90': '#000000FF',
489 | '91': '#AA0000FF',
490 | '92': '#00AA00FF',
491 | '93': '#AAAA00FF',
492 | '94': '#0000AAFF',
493 | '95': '#AA00AAFF',
494 | '96': '#00AAAAFF',
495 | '97': '#AAAAAAFF',
496 | '40': '#000000FF',
497 | '41': '#550000FF',
498 | '42': '#005500FF',
499 | '43': '#550000FF',
500 | '44': '#000055FF',
501 | '45': '#550055FF',
502 | '46': '#005555FF',
503 | '47': '#555555FF',
504 | '100': '#000000FF',
505 | '101': '#AA0000FF',
506 | '102': '#00AA00FF',
507 | '103': '#AAAA00FF',
508 | '104': '#0000AAFF',
509 | '105': '#AA00AAFF',
510 | '106': '#00AAAAFF',
511 | '107': '#AAAAAAFF',
512 | }
513 |
--------------------------------------------------------------------------------
/ColorHighlight.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import re
4 | import os
5 | import time
6 | import zlib
7 | import math
8 | import struct
9 | import threading
10 | import colorsys
11 | from functools import partial
12 |
13 | import sublime
14 | import sublime_plugin
15 |
16 | from .settings import Settings, SettingTogglerCommandMixin
17 | from .colorizer import SchemaColorizer, all_names_to_hex, names_to_hex, xterm_to_hex, xterm8_to_hex, xterm8b_to_hex, xterm8f_to_hex
18 |
19 | NAME = "Color Highlight"
20 | VERSION = "1.2.3"
21 |
22 |
23 | # Color formats:
24 | # #000000FF
25 | # #FFFFFF
26 | # #FFF7
27 | # #FFF
28 | # rgb(255,255,255)
29 | # rgba(255, 255, 255, 1)
30 | # rgba(255, 255, 255, .2)
31 | # rgba(255, 255, 255, 0.5)
32 | # black
33 | # rgba(white, 20%)
34 | # 0xFFFFFF
35 | # hsl(360, 0%, 50%)
36 | # hsla(360, 0%, 50%, 0.5)
37 | # hwb(360, 50%, 50%)
38 | # lab(100, 100, 100) <-> #ff9331
39 | # lch(100, 100, 100) <-> #ffff00
40 | # hsv(40, 70%, 100%) <-> #ffc34d
41 | # \033[31m
42 | # \033[38;5;22m
43 | # \033[38;2;0;0;255m
44 |
45 | regex_cache = {}
46 | re_cache = {}
47 |
48 |
49 | def regex_factory(
50 | named_values,
51 | x_hex_values,
52 | hex_values,
53 | xterm_color_values,
54 | rgb_values,
55 | hsv_values,
56 | hsl_values,
57 | hwb_values,
58 | lab_values,
59 | lch_values,
60 | ):
61 | key = (
62 | named_values,
63 | x_hex_values,
64 | hex_values,
65 | xterm_color_values,
66 | rgb_values,
67 | hsv_values,
68 | hsl_values,
69 | hwb_values,
70 | lab_values,
71 | lch_values,
72 | )
73 | try:
74 | colors_regex, colors_regex_capture = regex_cache[key]
75 | except KeyError:
76 | function_colors = []
77 | if rgb_values:
78 | function_colors.extend([r'rgb', r'rgba'])
79 | if hsv_values:
80 | function_colors.extend([r'hsv', r'hsva'])
81 | if hsl_values:
82 | function_colors.extend([r'hsl', r'hsla'])
83 | if hwb_values:
84 | function_colors.append(r'hwb')
85 | if lab_values:
86 | function_colors.append(r'lab')
87 | if lch_values:
88 | function_colors.append(r'lch')
89 |
90 | simple_colors = []
91 | if named_values:
92 | simple_colors.append(r'(? [0, 360)
182 | # s -> [0, 100]
183 | # l -> [0, 100]
184 |
185 | H = h / 360.0
186 | S = s / 100.0
187 | V = v / 100.0
188 |
189 | RR, GG, BB = colorsys.hsv_to_rgb(H, S, V)
190 | return int(RR * 255), int(GG * 255), int(BB * 255)
191 |
192 |
193 | def hsl_to_rgb(h, s, l):
194 | # h -> [0, 360)
195 | # s -> [0, 100]
196 | # l -> [0, 100]
197 |
198 | H = h / 360.0
199 | S = s / 100.0
200 | L = l / 100.0
201 |
202 | RR, GG, BB = colorsys.hls_to_rgb(H, L, S)
203 | return int(RR * 255), int(GG * 255), int(BB * 255)
204 |
205 |
206 | def hwb_to_rgb(h, w, b):
207 | # h -> [0, 360)
208 | # w -> [0, 100]
209 | # b -> [0, 100]
210 | H = h / 360.0
211 | W = w / 100.0
212 | B = b / 100.0
213 |
214 | RR, GG, BB = colorsys.hls_to_rgb(H, 0.5, 1)
215 | RR = RR * (1 - W - B) + W
216 | GG = GG * (1 - W - B) + W
217 | BB = BB * (1 - W - B) + W
218 |
219 | r, g, b = int(RR * 255), int(GG * 255), int(BB * 255)
220 | r = 0 if r < 0 else 255 if r > 255 else r
221 | g = 0 if g < 0 else 255 if g > 255 else g
222 | b = 0 if b < 0 else 255 if b > 255 else b
223 | return r, g, b
224 |
225 |
226 | def lab_to_rgb(L, a, b):
227 | # L -> [0, 100]
228 | # a -> [-160, 160]
229 | # b -> [-160, 160]
230 |
231 | Y = (L + 16.0) / 116.0
232 | X = a / 500.0 + Y
233 | Z = Y - b / 200.0
234 |
235 | Y3 = Y ** 3.0
236 | Y = Y3 if Y3 > 0.008856 else (Y - 16.0 / 116.0) / 7.787
237 |
238 | X3 = X ** 3.0
239 | X = X3 if X3 > 0.008856 else (X - 16.0 / 116.0) / 7.787
240 |
241 | Z3 = Z ** 3.0
242 | Z = Z3 if Z3 > 0.008856 else (Z - 16.0 / 116.0) / 7.787
243 |
244 | # Normalize white point for Observer=2°, Illuminant=D65
245 | X *= 0.95047
246 | Y *= 1.0
247 | Z *= 1.08883
248 |
249 | # XYZ to RGB
250 | RR = X * 3.240479 + Y * -1.537150 + Z * - 0.498535
251 | GG = X * -0.969256 + Y * 1.875992 + Z * 0.041556
252 | BB = X * 0.055648 + Y * -0.204043 + Z * 1.057311
253 |
254 | RR = 1.055 * RR ** (1 / 2.4) - 0.055 if RR > 0.0031308 else 12.92 * RR
255 | GG = 1.055 * GG ** (1 / 2.4) - 0.055 if GG > 0.0031308 else 12.92 * GG
256 | BB = 1.055 * BB ** (1 / 2.4) - 0.055 if BB > 0.0031308 else 12.92 * BB
257 |
258 | r, g, b = int(RR * 255), int(GG * 255), int(BB * 255)
259 | r = 0 if r < 0 else 255 if r > 255 else r
260 | g = 0 if g < 0 else 255 if g > 255 else g
261 | b = 0 if b < 0 else 255 if b > 255 else b
262 | return r, g, b
263 |
264 |
265 | def lch_to_lab(L, c, h):
266 | # L -> [0, 100]
267 | # c -> [0, 230]
268 | # h -> [0, 360)
269 | a = c * math.cos(math.radians(h))
270 | b = c * math.sin(math.radians(h))
271 | return L, a, b
272 |
273 |
274 | def lch_to_rgb(L, c, h):
275 | L, a, b = lch_to_lab(L, c, h)
276 | return lab_to_rgb(L, a, b)
277 |
278 |
279 | def tohex(r, g, b, a):
280 | if g is not None and b is not None:
281 | sr = '%X' % r
282 | if len(sr) == 1:
283 | sr = '0' + sr
284 | sg = '%X' % g
285 | if len(sg) == 1:
286 | sg = '0' + sg
287 | sb = '%X' % b
288 | if len(sb) == 1:
289 | sb = '0' + sb
290 | else:
291 | sr = r[1:3]
292 | sg = r[3:5]
293 | sb = r[5:7]
294 | sa = '%X' % int(a / 100.0 * 255)
295 | if len(sa) == 1:
296 | sa = '0' + sa
297 | return '#%s%s%s%s' % (sr, sg, sb, sa)
298 |
299 |
300 | # Full PNG is: PNG_HEAD + PNG_IHDR + PNG_IDAT[mode] + PNG_IEND
301 | PNG_HEAD = b'\x89PNG\r\n\x1a\n'
302 | PNG_IHDR = b'\x00\x00\x00\x0dIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4'
303 | PNG_IDAT = {
304 | 'circle': b'\x00\x00\x01\x13IDATx\x9c\xed\xd6\xc1\r\xc3 \x0c@QX!g\xa4\x8c\xd0\x11:BF\xe8\x01q\xee\x1c\xdd\x82e2\x00\xb30\x00\xb5U#U\x11\x85`\xac\xe6\xc2\xe1_\xc3K\x93\xd8U)%ue\x97\x1e>\x01\x13P\x05\xac\xb7{)\x03Y\xc8C\x01\x8a\xdb\xe3\x89\x05\xc8C\x162\x90:6\n\xd0\x90\x83v(}\x07\x17?\xb6C\x0e\xd2R\x80\x05z\x1d\x0f\xae\x00r/h\x19\x05\xe8\xda\xe1\r@F\xe8\x11\x80\xab\x1d~\x02\x90\xe8q\xb0\x00\xa6\xf4\xcc\x19\x00|\'\x0c\x07`[\x87\x9f\x04`\x96\x03\xf0\x82\x00\xcf\x01\x04A@\xe0\x00\xa2 v\x03h\xc25/~\x06\x897\xc3\x01\x04A@\xff#\xa0\xd9.\x05\xe8\x7f\ti\xb1H\x01\xfa?\xc3\xed\xb3\xd5v\x01\x00\x0e\xb3\xfeADK\xc4\t\x00p\x9c\xf7\x8fb\x02hZ(\\\x00.2=\x02\xc0\x96\x1a\xa2q8\xaer5\n\xc8\xbf\x84+\xbd\x13?\x9e\xb9\xcbw.\x05\xc8\x19\xfa:<\xcd\x89H\x133\xd0\xee\xc0\x05f\xd6\xc2\xdf\xb9n\xc0\xbf\x9a\x80\t\xb8\x1c\xf0\x06-\x9f\xcd\xf4\x17\xe9(\x03',
305 | 'square': b'\x00\x00\x00\x4aIDATx\x9c\xed\xceA\r\x00 \x0cC\xd19A\x02\x12\x90\x80\x04$\xe0\xff\xd49 =\xb1,\xf9\x87\x7fm_H\x8a\xcaJ\xcf\x01\x00x\x02\xc6\\r\xda\xe7Z\x01\x00\x00\x00@?\x80;\xecB\x01\x00\x00\x00\xa0\x1f\xe0W\x00\x00\x94\x03\x12\\\xf0$\x87\xd4i\x0c\x98',
306 | 'fill': b'\x00\x00\x00\x40IDATx\x9c\xed\xcf1\x11\x00 \x10\x03\xc1w\x82\x04$ \x01\tH\xc0\x7f\x05"R|\xb3\xc5\xb5\x99M\x8d\xb9^\xd2>7\xaa\x00\x00\x00\x00\x00\x00\x00\xda\x01\xe9@z\x00\x00\x00\x00\x00\x00\x00\xa0\x1d\xf0\x01\xb4]Pj]\x9av\xf7',
307 | }
308 | PNG_IEND = b'\x00\x00\x00\x00IEND\xaeB`\x82'
309 |
310 | PNG_RE = re.compile(b'\\x1f\\x2f\\x3f|\\x4f\\x5f\\x6f')
311 | PNG_DATA = {
312 | 'circle': zlib.decompress(PNG_IDAT['circle'][8:-4]),
313 | 'square': zlib.decompress(PNG_IDAT['square'][8:-4]),
314 | 'fill': zlib.decompress(PNG_IDAT['fill'][8:-4]),
315 | }
316 | DEFAULT_GUTTER_ICON = 'circle'
317 |
318 |
319 | def toicon(name, gutter_icon=True, light=True):
320 | base_path = os.path.join(sublime.packages_path(), 'User', '%s.cache' % NAME)
321 | if not os.path.exists(base_path):
322 | os.mkdir(base_path)
323 | if gutter_icon not in PNG_DATA:
324 | gutter_icon = DEFAULT_GUTTER_ICON
325 | icon_path = os.path.join(base_path, name + '_' + gutter_icon + '.png')
326 | if not os.path.exists(icon_path):
327 | r = int(name[4:6], 16)
328 | g = int(name[6:8], 16)
329 | b = int(name[8:10], 16)
330 | a = int(name[10:12] or 'ff', 16) / 255.0
331 | # print("r={} g={} b={} a={}".format(r, g, b, a))
332 | if light:
333 | x = 0xff * (1 - a)
334 | y = 0xcc * (1 - a)
335 | else:
336 | x = 0x99 * (1 - a)
337 | y = 0x66 * (1 - a)
338 | r *= a
339 | g *= a
340 | b *= a
341 | # print("x(r={} g={} b={}), y(r={} g={} b={})".format(int(r + x), int(g + x), int(b + x), int(r + y), int(g + y), int(b + y)))
342 | I1 = lambda v: struct.pack("!B", v & (2**8 - 1))
343 | I4 = lambda v: struct.pack("!I", v & (2**32 - 1))
344 | png = PNG_HEAD + PNG_IHDR
345 | col_map = {
346 | b'\x1f\x2f\x3f': I1(int(r + x)) + I1(int(g + x)) + I1(int(b + x)),
347 | b'\x4f\x5f\x6f': I1(int(r + y)) + I1(int(g + y)) + I1(int(b + y)),
348 | }
349 | data = PNG_RE.sub(lambda m: col_map[m.group(0)], PNG_DATA[gutter_icon])
350 | compressed = zlib.compress(data)
351 | idat = b'IDAT' + compressed
352 | png += I4(len(compressed)) + idat + I4(zlib.crc32(idat))
353 | png += PNG_IEND
354 | with open(icon_path, 'wb') as fp:
355 | fp.write(png)
356 | relative_icon_path = os.path.relpath(icon_path, os.path.dirname(sublime.packages_path()))
357 | relative_icon_path = relative_icon_path.replace('\\', '/')
358 | return relative_icon_path
359 |
360 |
361 | # Commands
362 |
363 | # treat hex vals as colors
364 | class ColorHighlightCommand(sublime_plugin.WindowCommand):
365 | def run_(self, edit_token, args={}):
366 | view = self.window.active_view()
367 | view.run_command('color_highlight', args)
368 |
369 | def is_enabled(self):
370 | return True
371 |
372 |
373 | class ColorHighlightEnableLoadSaveCommand(ColorHighlightCommand):
374 | def is_enabled(self):
375 | enabled = super(ColorHighlightEnableLoadSaveCommand, self).is_enabled()
376 |
377 | if enabled:
378 | if settings.get('highlight') == 'load-save':
379 | return False
380 |
381 | return enabled
382 |
383 |
384 | class ColorHighlightEnableSaveOnlyCommand(ColorHighlightCommand):
385 | def is_enabled(self):
386 | enabled = super(ColorHighlightEnableSaveOnlyCommand, self).is_enabled()
387 |
388 | if enabled:
389 | if settings.get('highlight') == 'save-only':
390 | return False
391 |
392 | return enabled
393 |
394 |
395 | class ColorHighlightDisableCommand(ColorHighlightCommand):
396 | def is_enabled(self):
397 | enabled = super(ColorHighlightDisableCommand, self).is_enabled()
398 |
399 | if enabled:
400 | if settings.get('highlight') is False:
401 | return False
402 |
403 | return enabled
404 |
405 |
406 | class ColorHighlightEnableCommand(ColorHighlightCommand):
407 | def is_enabled(self):
408 | view = self.window.active_view()
409 |
410 | if view:
411 | if settings.get('highlight') is not False:
412 | return False
413 |
414 | return True
415 |
416 |
417 | # treat hex vals as colors
418 | class ColorHighlightHexValsAsColorsCommand(ColorHighlightCommand):
419 | def is_enabled(self):
420 | enabled = super(ColorHighlightHexValsAsColorsCommand, self).is_enabled()
421 |
422 | if enabled:
423 | if settings.get('hex_values') is False:
424 | return False
425 |
426 | return enabled
427 | is_checked = is_enabled
428 |
429 |
430 | # treat hex vals as colors
431 | class ColorHighlightXHexValsAsColorsCommand(ColorHighlightCommand):
432 | def is_enabled(self):
433 | enabled = super(ColorHighlightXHexValsAsColorsCommand, self).is_enabled()
434 |
435 | if enabled:
436 | if settings.get('0x_hex_values') is False:
437 | return False
438 |
439 | return enabled
440 | is_checked = is_enabled
441 |
442 |
443 | # command to restore color scheme
444 | class ColorHighlightRestoreCommand(sublime_plugin.TextCommand):
445 | def run(self, edit):
446 | erase_highlight_colors()
447 | colorizer.restore_color_scheme()
448 |
449 |
450 | all_regs = []
451 |
452 |
453 | class ColorHighlightCommand(sublime_plugin.TextCommand):
454 | '''command to interact with linters'''
455 |
456 | def __init__(self, view):
457 | self.view = view
458 | self.help_called = False
459 |
460 | def run_(self, edit_token, args={}):
461 | '''method called by default via view.run_command;
462 | used to dispatch to appropriate method'''
463 |
464 | action = args.get('action', '')
465 | if not action:
466 | return
467 |
468 | lc_action = action.lower()
469 |
470 | if lc_action == 'reset':
471 | self.reset()
472 | elif lc_action == 'off':
473 | self.off()
474 | elif lc_action == 'on':
475 | self.on()
476 | elif lc_action == 'load-save':
477 | self.enable_load_save()
478 | elif lc_action == 'save-only':
479 | self.enable_save_only()
480 | elif lc_action == 'hex':
481 | self.toggle_hex_values()
482 | elif lc_action == 'xhex':
483 | self.toggle_xhex_values()
484 | else:
485 | highlight_colors(self.view)
486 |
487 | def toggle_hex_values(self):
488 | settings.set('hex_values', not settings.get('hex_values'), changed=True)
489 | settings.save()
490 | queue_highlight_colors(self.view, preemptive=True)
491 |
492 | def toggle_xhex_values(self):
493 | settings.set('0x_hex_values', not settings.get('0x_hex_values'), changed=True)
494 | settings.save()
495 | queue_highlight_colors(self.view, preemptive=True)
496 |
497 | def reset(self):
498 | '''Removes existing lint marks and restores user settings.'''
499 | erase_highlight_colors()
500 | TIMES.clear()
501 | colorizer.setup_color_scheme(self.view.settings())
502 | queue_highlight_colors(self.view, preemptive=True)
503 |
504 | def on(self):
505 | '''Turns background linting on.'''
506 | settings.set('highlight', True)
507 | settings.save()
508 | queue_highlight_colors(self.view, preemptive=True)
509 |
510 | def enable_load_save(self):
511 | '''Turns load-save linting on.'''
512 | settings.set('highlight', 'load-save')
513 | settings.save()
514 | erase_highlight_colors()
515 |
516 | def enable_save_only(self):
517 | '''Turns save-only linting on.'''
518 | settings.set('highlight', 'save-only')
519 | settings.save()
520 | erase_highlight_colors()
521 |
522 | def off(self):
523 | '''Turns background linting off.'''
524 | settings.set('highlight', False)
525 | settings.save()
526 | erase_highlight_colors()
527 |
528 |
529 | class ColorHighlightViewEventListener(sublime_plugin.ViewEventListener):
530 | def on_modified(self):
531 | if settings.get('highlight') is not True:
532 | return
533 |
534 | action = self.view.command_history(0, True)[0]
535 | if action == 'revert':
536 | erase_highlight_colors()
537 | queue_highlight_colors(self.view, preemptive=True)
538 | else:
539 | selection = action != 'paste'
540 | queue_highlight_colors(self.view, preemptive=selection, selection=selection)
541 |
542 | def on_close(self):
543 | vid = self.view.id()
544 | if vid in TIMES:
545 | del TIMES[vid]
546 | if vid in COLOR_HIGHLIGHTS:
547 | del COLOR_HIGHLIGHTS[vid]
548 |
549 | def on_activated(self):
550 | if self.view.file_name() is None:
551 | return
552 | vid = self.view.id()
553 | if vid in TIMES:
554 | return
555 | TIMES[vid] = 100
556 |
557 | if settings.get('highlight') in (False, 'save-only'):
558 | return
559 |
560 | queue_highlight_colors(self.view, preemptive=True)
561 |
562 | def on_post_save(self):
563 | if settings.get('highlight') is False:
564 | return
565 |
566 | queue_highlight_colors(self.view, preemptive=True)
567 |
568 | def on_selection_modified(self):
569 | delay_queue(1000) # on movement, delay queue (to make movement responsive)
570 |
571 |
572 | TIMES = {} # collects how long it took the color highlight to complete
573 | COLOR_HIGHLIGHTS = {} # Highlighted regions
574 |
575 |
576 | def erase_highlight_colors(view=None):
577 | if view:
578 | vid = view.id()
579 | if vid in COLOR_HIGHLIGHTS:
580 | for name in COLOR_HIGHLIGHTS[vid]:
581 | view.erase_regions(name)
582 | view.erase_regions(name + '_icon')
583 | COLOR_HIGHLIGHTS[vid] = set()
584 | else:
585 | for window in sublime.windows():
586 | for view in window.views():
587 | erase_highlight_colors(view)
588 |
589 |
590 | def highlight_colors(view, selection=False, **kwargs):
591 | view_settings = view.settings()
592 | colorizer.setup_color_scheme(view_settings)
593 |
594 | vid = view.id()
595 | start = time.time()
596 |
597 | named_values = bool(settings.get('named_values', True))
598 | hex_values = bool(settings.get('hex_values', True))
599 | x_hex_values = bool(settings.get('0x_hex_values', True))
600 | xterm_color_values = bool(settings.get('xterm_color_values', True))
601 | rgb_values = bool(settings.get('rgb_values', True))
602 | hsv_values = bool(settings.get('hsv_values', True))
603 | hsl_values = bool(settings.get('hsl_values', True))
604 | hwb_values = bool(settings.get('hwb_values', True))
605 | lab_values = bool(settings.get('lab_values', True))
606 | lch_values = bool(settings.get('lch_values', True))
607 |
608 | if len(view.sel()) > 100:
609 | selection = False
610 |
611 | if selection:
612 | selected_lines = [ln for r in view.sel() for ln in view.lines(r)]
613 | elif view.size() > 512000:
614 | selected_lines = view.lines(view.visible_region())
615 | else:
616 | selected_lines = None
617 |
618 | words = {}
619 | found = []
620 | if selected_lines:
621 | colors_re, colors_re_capture = re_factory(
622 | named_values=named_values,
623 | x_hex_values=x_hex_values,
624 | hex_values=hex_values,
625 | xterm_color_values=xterm_color_values,
626 | rgb_values=rgb_values,
627 | hsv_values=hsv_values,
628 | hsl_values=hsl_values,
629 | hwb_values=hwb_values,
630 | lab_values=lab_values,
631 | lch_values=lch_values,
632 | )
633 | matches = [colors_re.finditer(view.substr(l)) for l in selected_lines]
634 | matches = [
635 | (
636 | sublime.Region(
637 | selected_lines[i].begin() + m.start(),
638 | selected_lines[i].begin() + m.end()
639 | ),
640 | m.groups()
641 | ) if m else (None, None)
642 | for i, am in enumerate(matches) for m in am
643 | ]
644 | matches = [
645 | (
646 | rg,
647 | ''.join(
648 | gr[ord(g) - 1] or '' if ord(g) < 10 else g for g in colors_re_capture
649 | )
650 | )
651 | for rg, gr in matches if rg
652 | ]
653 | if matches:
654 | ranges, found = zip(*[q for q in matches if q])
655 | else:
656 | ranges = []
657 | else:
658 | colors_regex, colors_regex_capture = regex_factory(
659 | named_values=named_values,
660 | x_hex_values=x_hex_values,
661 | hex_values=hex_values,
662 | xterm_color_values=xterm_color_values,
663 | rgb_values=rgb_values,
664 | hsv_values=hsv_values,
665 | hsl_values=hsl_values,
666 | hwb_values=hwb_values,
667 | lab_values=lab_values,
668 | lch_values=lch_values,
669 | )
670 | ranges = view.find_all(colors_regex, 0, colors_regex_capture, found)
671 |
672 | for i, col in enumerate(found):
673 | mode, _, col = col.partition('|')
674 | col = col.rstrip(',')
675 | col = col.split(',')
676 | try:
677 | if mode in ('hsl', 'hsla', 'hsv', 'hsva', 'hwb'):
678 | if len(col) > 2 and col[0] and col[1] and col[2]:
679 | # In the form of hsl(360, 100%, 100%) or hsla(360, 100%, 100%, 1.0) or hwb(360, 50%, 50%):
680 | if col[0].endswith('deg'):
681 | col[0] = col[0][:-3]
682 | h = float(col[0]) % 360
683 | if col[1].endswith('%'):
684 | sb = float(col[1][:-1])
685 | else:
686 | sb = float(col[1]) * 100.0
687 | if sb < 0 or sb > 100:
688 | raise ValueError("sb out of range")
689 | if col[2].endswith('%'):
690 | lwv = float(col[2][:-1])
691 | else:
692 | lwv = float(col[2]) * 100.0
693 | if lwv < 0 or lwv > 100:
694 | raise ValueError("lwv out of range")
695 | if mode == 'hwb':
696 | if sb + lwv > 100:
697 | raise ValueError("sb + lwv > 100")
698 | if len(col) == 4:
699 | if mode in ('hsl', 'hsv'):
700 | raise ValueError("hsl/hsv should not have alpha")
701 | if col[3].endswith('%'):
702 | alpha = float(col[3][:-1])
703 | else:
704 | alpha = float(col[3]) * 100.0
705 | if alpha < 0 or alpha > 100:
706 | raise ValueError("alpha out of range")
707 | elif mode in ('hsla', 'hsva'):
708 | continue
709 | else:
710 | alpha = 100.0
711 | if mode in ('hsl', 'hsla'):
712 | r, g, b = hsl_to_rgb(h, sb, lwv)
713 | elif mode in ('hsv', 'hsva'):
714 | r, g, b = hsv_to_rgb(h, sb, lwv)
715 | else:
716 | r, g, b = hwb_to_rgb(h, sb, lwv)
717 | col = tohex(r, g, b, alpha)
718 | else:
719 | raise ValueError("invalid hsl/hsla/hwb")
720 | elif mode == 'lab':
721 | # The first argument specifies the CIE Lightness, the second
722 | # argument is a and the third is b. L is constrained to the
723 | # range [0, 100] while a and b are signed values and
724 | # theoretically unbounded (but in practice do not exceed ±160).
725 | # There is an optional fourth alpha value separated by a comma.
726 | if len(col) > 2 and col[0] and col[1] and col[2]:
727 | # In the form of lab(100, 0, 0) or lab(100, 0, 0, 1.0):
728 | # lab(100, 0, 127) <-> rgb(255, 250, 0)
729 | L = float(col[0])
730 | if L < 0 or L > 100:
731 | raise ValueError("L out of range")
732 | a = float(col[1])
733 | b = float(col[2])
734 | if len(col) == 4:
735 | if col[3].endswith('%'):
736 | alpha = float(col[3][:-1])
737 | else:
738 | alpha = float(col[3]) * 100.0
739 | if alpha < 0 or alpha > 100:
740 | raise ValueError("alpha out of range")
741 | else:
742 | alpha = 100.0
743 | r, g, b = lab_to_rgb(L, a, b)
744 | col = tohex(r, g, b, alpha)
745 | else:
746 | raise ValueError("invalid lab")
747 | elif mode == 'lch':
748 | # The first argument specifies the CIE Lightness, the second
749 | # argument is C and the third is H. L is constrained to the
750 | # range [0, 100]. C is an unsigned number, theoretically
751 | # unbounded (but in practice does not exceed 230). H is
752 | # constrained to the range [0, 360). There is an optional
753 | # fourth alpha value separated by a comma.
754 | if len(col) > 2 and col[0] and col[1] and col[2]:
755 | # In the form of lch(0, 250, 360) or lch(100, 100, 360, 1.0):
756 | L = float(col[0])
757 | if L < 0 or L > 100:
758 | raise ValueError("L out of range")
759 | c = float(col[1])
760 | if c < 0:
761 | raise ValueError("c out of range")
762 | if col[2].endswith('deg'):
763 | col[2] = col[2][:-3]
764 | h = float(col[2]) % 360
765 | if len(col) == 4:
766 | if col[3].endswith('%'):
767 | alpha = float(col[3][:-1])
768 | else:
769 | alpha = float(col[3]) * 100.0
770 | if alpha < 0 or alpha > 100:
771 | raise ValueError("alpha out of range")
772 | else:
773 | alpha = 100.0
774 | r, g, b = lch_to_rgb(L, c, h)
775 | col = tohex(r, g, b, alpha)
776 | else:
777 | raise ValueError("invalid lch")
778 | elif len(col) == 1:
779 | # In the form of: black, #FFFFFFFF, 0xFFFFFF, \033[1;37m, \033[38;5;255m, \033[38;2;255;255;255m:
780 | col0 = col[0]
781 | if col0.endswith('m') and '[' in col0:
782 | _, _, col0 = col0[:-1].partition('[')
783 | col0 = ';' + col0 + ';'
784 | col0 = re.sub(r';0*(?=\d)', r';', col0)
785 | xterm_true = col0.find(';38;2;')
786 | xterm = col0.find(';38;5;')
787 | if xterm_true != -1:
788 | col = col0[xterm_true + 6:-1].split(';')
789 | r = int(col[0])
790 | g = int(col[1])
791 | b = int(col[2])
792 | if (r < 0 or r > 255) or (g < 0 or g > 255) or (b < 0 or b > 255):
793 | raise ValueError("rgb out of range")
794 | col = tohex(r, g, b, 100.0)
795 | elif xterm != -1:
796 | col = col0[xterm + 6:-1].split(';')[0]
797 | col = xterm_to_hex.get(col)
798 | if not col:
799 | continue
800 | else:
801 | mode = xterm8_to_hex
802 | modes = (xterm8_to_hex, xterm8b_to_hex, xterm8f_to_hex)
803 | q = -1
804 | for m in (0, 1, 2):
805 | p = col0.find(';%s;' % m)
806 | if p != -1 and p > q:
807 | mode = modes[m]
808 | xterm8 = col0[1:-1].split(';')
809 | col = None
810 | for x in xterm8:
811 | if x in mode:
812 | col = mode[x]
813 | if not col:
814 | continue
815 | else:
816 | if col0.startswith('0x'):
817 | col0 = '#' + col0[2:]
818 | else:
819 | col0 = all_names_to_hex.get(col0.lower(), col0.upper())
820 | if len(col0) == 4:
821 | col0 = '#' + col0[1] * 2 + col0[2] * 2 + col0[3] * 2 + 'FF'
822 | elif len(col0) == 7:
823 | col0 += 'FF'
824 | col = col0
825 | elif col[1] and col[2]:
826 | # In the form of rgb(255, 255, 255) or rgba(255, 255, 255, 1.0):
827 | r = int(col[0])
828 | g = int(col[1])
829 | b = int(col[2])
830 | if (r < 0 or r > 255) or (g < 0 or g > 255) or (b < 0 or b > 255):
831 | raise ValueError("rgb out of range")
832 | if len(col) == 4:
833 | if col[3].endswith('%'):
834 | alpha = float(col[3][:-1])
835 | else:
836 | alpha = float(col[3]) * 100.0
837 | if alpha < 0 or alpha > 100:
838 | raise ValueError("alpha out of range")
839 | else:
840 | alpha = 100.0
841 | col = tohex(r, g, b, alpha)
842 | else:
843 | # In the form of rgba(white, 20%) or rgba(#FFFFFF, 0.4):
844 | col0 = col[0]
845 | col0 = all_names_to_hex.get(col0.lower(), col0.upper())
846 | if col0.startswith('0X'):
847 | col0 = '#' + col0[2:]
848 | if len(col0) == 4:
849 | col0 = '#' + col0[1] * 2 + col0[2] * 2 + col0[3] * 2 + 'FF'
850 | elif len(col0) == 7:
851 | col0 += 'FF'
852 | if len(col) == 4:
853 | col3 = col[3]
854 | if col3.endswith('%'):
855 | alpha = float(col3[:-1])
856 | else:
857 | alpha = float(col3) * 100.0
858 | if alpha < 0 or alpha > 100:
859 | raise ValueError("alpha out of range")
860 | else:
861 | alpha = 100.0
862 | col = tohex(col0, None, None, alpha)
863 | except (ValueError, IndexError, KeyError) as e:
864 | # print(e)
865 | continue
866 |
867 | # Fix case when color it's the same as background color:
868 | if hasattr(view, 'style'):
869 | bg_col = (view.style()['background'] + 'FF')[:9].upper()
870 | if col == bg_col:
871 | br = int(bg_col[1:3], 16)
872 | bg = int(bg_col[3:5], 16)
873 | bb = int(bg_col[5:7], 16)
874 | ba = int(bg_col[7:9], 16)
875 | br += -1 if br > 1 else 1
876 | bg += -1 if bg > 1 else 1
877 | bb += -1 if bb > 1 else 1
878 | col = '#%02X%02X%02X%02X' % (br, bg, bb, ba)
879 |
880 | name = colorizer.add_color(col)
881 | if name not in words:
882 | words[name] = [ranges[i]]
883 | else:
884 | words[name].append(ranges[i])
885 |
886 | colorizer.update(view)
887 |
888 | if selected_lines:
889 | if vid not in COLOR_HIGHLIGHTS:
890 | COLOR_HIGHLIGHTS[vid] = set()
891 | for name in COLOR_HIGHLIGHTS[vid]:
892 | ranges = []
893 | affected_line = False
894 | for _range in view.get_regions(name):
895 | _line_range = False
896 | for _line in selected_lines:
897 | if _line.contains(_range):
898 | _line_range = True
899 | break
900 | if _line_range:
901 | affected_line = True
902 | else:
903 | ranges.append(_range)
904 | if affected_line or name in words:
905 | if name not in words:
906 | words[name] = ranges
907 | else:
908 | words[name].extend(ranges)
909 | else:
910 | erase_highlight_colors(view)
911 | all_regs = COLOR_HIGHLIGHTS[vid]
912 |
913 | highlight_values = bool(settings.get('highlight_values', True))
914 | gutter_icon = settings.get('gutter_icon', True)
915 |
916 | for name, w in words.items():
917 | if highlight_values:
918 | view.add_regions(name, w, name, flags=sublime.PERSISTENT)
919 | if gutter_icon:
920 | wi = [sublime.Region(i, i) for i in set(view.line(r).a for r in w)]
921 | view.add_regions(name + '_icon', wi, '%sgutter' % colorizer.prefix, icon=toicon(name, gutter_icon=gutter_icon), flags=sublime.PERSISTENT)
922 | all_regs.add(name)
923 |
924 | if not selection:
925 | TIMES[vid] = (time.time() - start) * 1000 # Keep how long it took to do a full color highlight
926 | # print('highlight took %s' % TIMES[vid])
927 |
928 |
929 | ################################################################################
930 | # Queue connection
931 |
932 | QUEUE = {} # views waiting to be processed by Color Highlight
933 |
934 | # For snappier color highlighting, different delays are used for different color highlighting times:
935 | # (color_highlighting_time, (delay, delay_when_busy))
936 | DELAYS = (
937 | (50, (50, 100)),
938 | (100, (100, 300)),
939 | (200, (200, 500)),
940 | (400, (400, 1000)),
941 | (600, (600, 1500)),
942 | (800, (800, 2000)),
943 | (1200, (1200, 1000)),
944 | (1600, (1600, 3000)),
945 | )
946 |
947 |
948 | def get_delay(t, view):
949 | delays = 0
950 |
951 | for _t, d in DELAYS:
952 | if _t <= t:
953 | delays = d
954 | else:
955 | break
956 |
957 | delays = delays or DELAYS[0][1]
958 |
959 | # If the user specifies a delay greater than the built in delay,
960 | # figure they only want to see marks when idle.
961 | min_delay = int(settings.get('delay', 0) * 1000)
962 |
963 | return (min_delay, min_delay) if min_delay > delays[1] else delays
964 |
965 |
966 | def _update_view(view, filename, **kwargs):
967 | # It is possible that by the time the queue is run,
968 | # the original file is no longer being displayed in the view,
969 | # or the view may be gone. This happens especially when
970 | # viewing files temporarily by single-clicking on a filename
971 | # in the sidebar or when selecting a file through the choose file palette.
972 | valid_view = False
973 | view_id = view.id()
974 |
975 | for window in sublime.windows():
976 | for v in window.views():
977 | if v.id() == view_id:
978 | valid_view = True
979 | break
980 |
981 | if not valid_view or view.is_loading() or (view.file_name() or '').encode('utf-8') != filename:
982 | return
983 |
984 | highlight_colors(view, **kwargs)
985 |
986 |
987 | def queue_highlight_colors(view, delay=-1, preemptive=False, **kwargs):
988 | '''Put the current view in a queue to be examined by a Color Highlight'''
989 |
990 | if preemptive:
991 | delay = delay_when_busy = 0
992 | elif delay == -1:
993 | delay, delay_when_busy = get_delay(TIMES.get(view.id(), 100), view)
994 | else:
995 | delay_when_busy = delay
996 |
997 | kwargs.update({'delay': delay, 'delay_when_busy': delay_when_busy, 'preemptive': preemptive})
998 | queue(view, partial(_update_view, view, (view.file_name() or '').encode('utf-8'), **kwargs), kwargs)
999 |
1000 |
1001 | def _callback(view, filename, kwargs):
1002 | kwargs['callback'](view, filename, **kwargs)
1003 |
1004 |
1005 | def background_color_highlight():
1006 | __lock_.acquire()
1007 |
1008 | try:
1009 | callbacks = list(QUEUE.values())
1010 | QUEUE.clear()
1011 | finally:
1012 | __lock_.release()
1013 |
1014 | for callback in callbacks:
1015 | sublime.set_timeout(callback, 0)
1016 |
1017 |
1018 | ################################################################################
1019 | # Queue dispatcher system:
1020 |
1021 | queue_dispatcher = background_color_highlight
1022 | queue_thread_name = 'background color highlight'
1023 | MAX_DELAY = 10
1024 |
1025 |
1026 | def queue_loop():
1027 | '''An infinite loop running the color highlight in a background thread meant to
1028 | update the view after user modifies it and then does no further
1029 | modifications for some time as to not slow down the UI with color highlighting.'''
1030 | global __signaled_, __signaled_first_
1031 |
1032 | while __loop_:
1033 | # print('acquire...')
1034 | __semaphore_.acquire()
1035 | __signaled_first_ = 0
1036 | __signaled_ = 0
1037 | # print('DISPATCHING!', len(QUEUE))
1038 | queue_dispatcher()
1039 |
1040 |
1041 | def queue(view, callback, kwargs):
1042 | global __signaled_, __signaled_first_
1043 | now = time.time()
1044 | __lock_.acquire()
1045 |
1046 | try:
1047 | QUEUE[view.id()] = callback
1048 | delay = kwargs['delay']
1049 |
1050 | if now < __signaled_ + delay * 4:
1051 | delay = kwargs['delay_when_busy']
1052 |
1053 | __signaled_ = now
1054 | _delay_queue(delay, kwargs['preemptive'])
1055 |
1056 | # print('%s queued in %s' % ('' if __signaled_first_ else 'first ', __signaled_ - now))
1057 | if not __signaled_first_:
1058 | __signaled_first_ = __signaled_
1059 | finally:
1060 | __lock_.release()
1061 |
1062 |
1063 | def _delay_queue(delay, preemptive):
1064 | global __signaled_, __queued_
1065 | now = time.time()
1066 |
1067 | if not preemptive and now <= __queued_ + 0.01:
1068 | return # never delay queues too fast (except preemptively)
1069 |
1070 | __queued_ = now
1071 | _delay = float(delay) / 1000
1072 |
1073 | if __signaled_first_:
1074 | if MAX_DELAY > 0 and now - __signaled_first_ + _delay > MAX_DELAY:
1075 | _delay -= now - __signaled_first_
1076 | if _delay < 0:
1077 | _delay = 0
1078 | delay = int(round(_delay * 1000, 0))
1079 |
1080 | new__signaled_ = now + _delay - 0.01
1081 |
1082 | if __signaled_ >= now - 0.01 and (preemptive or new__signaled_ >= __signaled_ - 0.01):
1083 | __signaled_ = new__signaled_
1084 | # print('delayed to %s' % (preemptive, __signaled_ - now))
1085 |
1086 | def _signal():
1087 | if time.time() < __signaled_:
1088 | return
1089 | __semaphore_.release()
1090 |
1091 | sublime.set_timeout(_signal, delay)
1092 |
1093 |
1094 | def delay_queue(delay):
1095 | __lock_.acquire()
1096 | try:
1097 | _delay_queue(delay, False)
1098 | finally:
1099 | __lock_.release()
1100 |
1101 |
1102 | # only start the thread once - otherwise the plugin will get laggy
1103 | # when saving it often.
1104 | __semaphore_ = threading.Semaphore(0)
1105 | __lock_ = threading.Lock()
1106 | __queued_ = 0
1107 | __signaled_ = 0
1108 | __signaled_first_ = 0
1109 |
1110 | # First finalize old standing threads:
1111 | __loop_ = False
1112 | __pre_initialized_ = False
1113 |
1114 |
1115 | def queue_finalize(timeout=None):
1116 | global __pre_initialized_
1117 |
1118 | for thread in threading.enumerate():
1119 | if thread.isAlive() and thread.name == queue_thread_name:
1120 | __pre_initialized_ = True
1121 | thread.__semaphore_.release()
1122 | thread.join(timeout)
1123 |
1124 |
1125 | queue_finalize()
1126 |
1127 | # Initialize background thread:
1128 | __loop_ = True
1129 | __active_color_highlight_thread = threading.Thread(target=queue_loop, name=queue_thread_name)
1130 | __active_color_highlight_thread.__semaphore_ = __semaphore_
1131 | __active_color_highlight_thread.start()
1132 |
1133 |
1134 | ################################################################################
1135 | # Initialize settings and main objects only once
1136 | class ColorHighlightSettings(Settings):
1137 | def on_update(self):
1138 | window = sublime.active_window()
1139 | view = window.active_view()
1140 | view.run_command('color_highlight', dict(action='reset'))
1141 |
1142 |
1143 | settings = ColorHighlightSettings(NAME)
1144 |
1145 |
1146 | class ColorHighlightSettingCommand(SettingTogglerCommandMixin, sublime_plugin.WindowCommand):
1147 | settings = settings
1148 |
1149 |
1150 | if 'colorizer' not in globals():
1151 | colorizer = SchemaColorizer()
1152 |
1153 |
1154 | ################################################################################
1155 |
1156 | def plugin_loaded():
1157 | settings.load()
1158 |
1159 |
1160 | # ST3 features a plugin_loaded hook which is called when ST's API is ready.
1161 | #
1162 | # We must therefore call our init callback manually on ST2. It must be the last
1163 | # thing in this plugin (thanks, beloved contributors!).
1164 | if int(sublime.version()) < 3000:
1165 | plugin_loaded()
1166 |
--------------------------------------------------------------------------------