├── .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 | [![Package Control](https://img.shields.io/packagecontrol/dt/Color%20Highlight.svg)](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 | ![Description](screenshots/screenshot.gif?raw=true) 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 | ![Gutter Icon](screenshots/gutter_icon.png?raw=true) 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 | --------------------------------------------------------------------------------