diff --git a/Commands.sublime-commands b/Commands.sublime-commands index b7b8940..7bc41b8 100644 --- a/Commands.sublime-commands +++ b/Commands.sublime-commands @@ -1,18 +1,18 @@ [ -{ - "caption": "Colored Comments: Generate Color Scheme File", - "command": "colored_comments_theme_generator" -}, -{ - "caption": "Colored Comments: Revert to Original Color Scheme File", - "command": "colored_comments_theme_revert" -}, -{ - "caption": "Colored Comments: Edit Settings", - "command": "edit_settings", - "args": { - "base_file": "${packages}/Colored Comments/colored_comments.sublime-settings", - "default": "{\n\t$0\n}\n", + "caption": "Colored Comments: Generate Color Scheme", + "command": "colored_comments_theme_generator" + }, + { + "caption": "Colored Comments: Remove Generated Color Scheme", + "command": "colored_comments_theme_revert" + }, + { + "caption": "Colored Comments: Settings", + "command": "edit_settings", + "args": { + "base_file": "${packages}/Colored Comments/colored_comments.sublime-settings", + "default": "{\n\t$0\n}\n" + } } -}] \ No newline at end of file +] \ No newline at end of file diff --git a/color_manager.py b/color_manager.py deleted file mode 100644 index dd3fa97..0000000 --- a/color_manager.py +++ /dev/null @@ -1,188 +0,0 @@ -import json -import os -import sys -from string import Template - -import sublime - -from .lib import plistlib - -sublime_settings = "Preferences.sublime-settings" -scope_name = "colored.comments.color." -MSG = Template( - """ -Would you like to change your color scheme to '$scheme'? -To permanently disable this prompt, set 'prompt_new_color_scheme' -to false in the Colored Comments settings.""" -) - -sublime_default_cs = [ - "Mariana.sublime-color-scheme", - "Celeste.sublime-color-scheme", - "Monokai.sublime-color-scheme", - "Breakers.sublime-color-schem", - "Sixteen.sublime-color-scheme", -] - - -class ColorManager: - def __init__(self, new_color_scheme_path, tags, view, settings, regenerate, log): - self.new_color_scheme_path = new_color_scheme_path - self.view = view - self.sublime_pref = None - self.tags = tags - self.settings = settings - self.regenerate = regenerate - self.log = log - self.color_scheme = str() - self.update_preferences = True - self.awaiting_feedback = False - - def get_update_pref(self): - return self.update_preferences - - def get_awaiting_feedback(self): - return self.awaiting_feedback - - def set_awaiting_feedback(self, status): - self.awaiting_feedback = status - - def _add_colors_to_scheme(self, color_scheme, is_json): - scheme_rule_key = "rules" if is_json else "settings" - settings = color_scheme[scheme_rule_key] - scope_exist = bool() - updates = bool() - - for tag in self.tags: - curr_tag = self.tags[tag] - if not curr_tag.get("color", False): - continue - - name = _get_color_property("name", curr_tag) - background = _get_color_property("background", curr_tag) - foreground = _get_color_property("foreground", curr_tag) - if False in [name, background, foreground]: - continue - - scope = "{}{}".format(scope_name, name.lower().replace(" ", ".")) - - for setting in settings: - if "scope" in setting and setting["scope"] == scope: - scope_exist = True - - if not scope_exist: - updates = True - entry = dict() - entry["name"] = "[Colored Comments] {}".format(name.title()) - entry["scope"] = scope - if is_json: - entry["foreground"] = foreground - entry["background"] = background - else: - entry["settings"] = dict() - entry["settings"]["foreground"] = foreground - entry["settings"]["background"] = background - - settings.append(entry) - color_scheme[scheme_rule_key] = settings - return updates, color_scheme - - def _create_custom_color_scheme_directory(self): - path = os.path.join(sublime.packages_path(), self.new_color_scheme_path) - if not os.path.exists(path): - os.makedirs(path) - return path - - def create_user_custom_theme(self): - if self.awaiting_feedback: - return - self.awaiting_feedback = True - if not self.tags: - self.awaiting_feedback = False - return - - self.sublime_pref = sublime.load_settings(sublime_settings) - color_scheme = self.sublime_pref.get("color_scheme") - if self.regenerate and self.settings.get("old_color_scheme", "") != "": - color_scheme = self.settings.get("old_color_scheme", "") - - self.settings.set("old_color_scheme", color_scheme) - sublime.save_settings("colored_comments.sublime-settings") - cs_base = os.path.basename(color_scheme) - - if cs_base[0:16] != "Colored Comments": - cs_base = "{}{}".format("Colored Comments-", cs_base) - - custom_color_base = self._create_custom_color_scheme_directory() - new_cs_absolute = os.path.join(custom_color_base, cs_base) - self.color_scheme = "{}{}{}{}".format( - "Packages/", self.new_color_scheme_path, "/", cs_base - ) - print(self.color_scheme) - - updates, loaded_scheme, is_json = self.load_color_scheme(color_scheme) - - if self.regenerate or updates or color_scheme != self.color_scheme: - try: - os.remove(new_cs_absolute) - except OSError as ex: - self.log.debug(str(ex)) - pass - if is_json: - with open(new_cs_absolute, "w") as outfile: - json.dump(loaded_scheme, outfile, indent=4) - else: - with open(new_cs_absolute, "wb") as outfile: - outfile.write(plistlib.dumps(loaded_scheme)) - - if color_scheme != self.color_scheme: - if sublime.ok_cancel_dialog( - MSG.substitute(scheme=self.color_scheme), "Confirm" - ): - self.sublime_pref.set("color_scheme", self.color_scheme) - sublime.save_settings("Preferences.sublime-settings") - self.settings.set("prompt_new_color_scheme", False) - sublime.save_settings("colored_comments.sublime-settings") - self.update_preferences = False - self.awaiting_feedback = False - - def load_color_scheme(self, scheme): - is_json = bool() - try: - if scheme in sublime_default_cs: - scheme = "{}{}".format("Packages/Color Scheme - Default/", scheme) - scheme_content = sublime.load_binary_resource(scheme) - except Exception as ex: - sublime.error_message( - " ".join( - [ - "An error occured while reading color", - "scheme file. Please check the console", - "for details.", - ] - ) - ) - self.log.debug( - "[Colored Comments]: {} - {}".format( - self.load_color_scheme.__name__, ex - ) - ) - raise - if scheme.endswith(".sublime-color-scheme"): - is_json = True - updates, color_scheme = self._add_colors_to_scheme( - sublime.decode_value(scheme_content.decode("utf-8")), is_json - ) - elif scheme.endswith(".tmTheme"): - updates, color_scheme = self._add_colors_to_scheme( - plistlib.loads(bytes(scheme_content)), is_json - ) - else: - sys.exit(1) - return updates, color_scheme, is_json - - -def _get_color_property(property, tags): - if not tags["color"].get(property, False): - return False - return tags["color"][property] diff --git a/colored_comments.py b/colored_comments.py index 758664b..998147b 100644 --- a/colored_comments.py +++ b/colored_comments.py @@ -1,29 +1,23 @@ import logging -import os +import re import sys from collections import OrderedDict -import regex - import sublime import sublime_plugin -from .color_manager import ColorManager +from .plugin.color_manager import ColorManager NAME = "Colored Comments" -VERSION = "2.3.3" +VERSION = "3.0.0" log = logging.Logger region_keys = list() settings = dict() -tag_map = dict() tag_regex = OrderedDict() -continued_matching = bool() -continued_matching_pattern = str() icon = str() color_scheme_manager = ColorManager -scheme_path = "User/Colored Comments" icon_path = "Packages/Colored Comments/icons" settings_path = "colored_comments.sublime-settings" comment_selector = "comment - punctuation.definition.comment" @@ -43,26 +37,19 @@ def on_modified_async(self, view): class ColoredCommentsCommand(sublime_plugin.TextCommand): def run(self, edit): - global settings, tag_map, tag_regex, region_keys + global settings, tag_regex self.settings = settings - self.tag_map = tag_map - self.region_keys = region_keys self.tag_regex = tag_regex self.regions = self.view.find_by_selector(comment_selector) if self.view.match_selector(0, "text.plain"): return - if self.settings.get("prompt_new_color_scheme", False): - if color_scheme_manager.get_update_pref(): - color_scheme_manager.view = self.view - color_scheme_manager.create_user_custom_theme() - self.ClearDecorations() self.ApplyDecorations() def ClearDecorations(self): - for region_key in self.region_keys: + for region_key in region_keys: self.view.erase_regions(region_key) def ApplyDecorations(self): @@ -71,15 +58,17 @@ def ApplyDecorations(self): for region in self.regions: for reg in self.view.split_by_newlines(region): line = self.view.substr(reg) + continued_matching_pattern = settings.get( + "continued_matching_pattern", "-") if not continued_matching_pattern.startswith(" "): line = line.strip() for tag_identifier in self.tag_regex: - matches = self.tag_regex[tag_identifier].search( - self.view.substr(reg).strip() + matches = self.tag_regex.get(tag_identifier).search( + line.strip() ) if not matches: if ( - continued_matching + settings.get("continued_matching", False) and prev_match and line and line.startswith(continued_matching_pattern) @@ -93,19 +82,19 @@ def ApplyDecorations(self): break for key in to_decorate: - sel_tag = self.tag_map[key] + sel_tag = self.settings.get("tags", []).get(key) flags = self._get_tag_flags(sel_tag) scope_to_use = "" - if "scope" in sel_tag.keys(): - scope_to_use = sel_tag["scope"] + if sel_tag.get("scope"): + scope_to_use = sel_tag.get("scope") else: scope_to_use = ( - "colored.comments.color." - + sel_tag["color"]["name"].replace(" ", ".").lower() + "colored.comments.color.{}".format( + sel_tag["color"]["name"].replace(" ", ".").lower()) ) self.view.add_regions( key=key.lower(), - regions=to_decorate[key], + regions=to_decorate.get(key), scope=scope_to_use, icon=icon, flags=flags, @@ -127,32 +116,19 @@ def _get_tag_flags(self, tag): class ColoredCommentsThemeGeneratorCommand(sublime_plugin.TextCommand): def run(self, edit): - global color_scheme_manager - color_scheme_manager.update_preferences = True - color_scheme_manager.regenerate = True - color_scheme_manager.awaiting_feedback = False - color_scheme_manager.view = self.view color_scheme_manager.create_user_custom_theme() class ColoredCommentsThemeRevertCommand(sublime_plugin.TextCommand): def run(self, edit): - global settings - preferences = sublime.load_settings("Preferences.sublime-settings") - old_color_scheme = settings.get("old_color_scheme", "") - print(old_color_scheme) - if not old_color_scheme: - preferences.erase("color_scheme") - else: - preferences.set("color_scheme", old_color_scheme) - sublime.save_settings("Preferences.sublime-settings") - settings.erase("old_color_scheme") - sublime.save_settings(settings_path) + if preferences.get("color_scheme"): + color_scheme_manager.remove_override( + preferences.get("color_scheme")) def escape_regex(pattern): - pattern = regex.escape(pattern) + pattern = re.escape(pattern) for character in "'<>`": pattern = pattern.replace("\\" + character, character) return pattern @@ -186,8 +162,8 @@ def _generate_identifier_expression(tags): else escape_regex(tag["settings"]["identifier"]) ) tag_identifier.append(")[ \t]+(?:.*)") - flag = regex.I if tag["settings"].get("ignorecase", False) else 0 - identifiers[tag["name"]] = regex.compile( + flag = re.I if tag["settings"].get("ignorecase", False) else 0 + identifiers[tag["name"]] = re.compile( "".join(tag_identifier), flags=flag ) return identifiers @@ -215,9 +191,8 @@ def _get_icon(): def load_settings(): - global tag_map, settings, continued_matching, continued_matching_pattern + global settings, continued_matching, continued_matching_pattern settings = sublime.load_settings(settings_path) - tag_map = settings.get("tags", []) continued_matching = settings.get("continued_matching", False) continued_matching_pattern = settings.get( "continued_matching_pattern", "-") @@ -225,7 +200,7 @@ def load_settings(): def setup_logging(): global log - log = logging.getLogger(__name__) + log = logging.getLogger("colored_comments") out_hdlr = logging.StreamHandler(sys.stdout) out_hdlr.setFormatter(logging.Formatter("%(asctime)s %(message)s")) out_hdlr.setLevel(logging.DEBUG) @@ -233,40 +208,18 @@ def setup_logging(): def plugin_loaded(): - global tag_regex, tag_map, region_keys - global log, icon, color_scheme_manager, settings + global tag_regex, region_keys + global log, icon, color_scheme_manager load_settings() setup_logging() - tag_regex = _generate_identifier_expression(tag_map) - _generate_region_keys(region_keys, tag_map) + tag_regex = _generate_identifier_expression(settings.get("tags", [])) + _generate_region_keys(region_keys, settings.get("tags", [])) icon = _get_icon() if settings.get("debug", False): log.setLevel(logging.DEBUG) - - sublime_preferences = sublime.load_settings("Preferences.sublime-settings") - sublime_cs = sublime_preferences.get("color_scheme") - if os.path.basename(sublime_cs)[0:16] != "Colored Comments": - settings.set("old_color_scheme", sublime_cs) - sublime.save_settings("colored_comments.sublime-settings") - color_scheme_manager = ColorManager( - new_color_scheme_path=scheme_path, - tags=tag_map, - view=None, - settings=settings, - regenerate=False, + tags=settings.get("tags", []), log=log, ) - - -def plugin_unloaded(): - preferences = sublime.load_settings("Preferences.sublime-settings") - cc_preferences = sublime.load_settings(settings_path) - old_color_scheme = cc_preferences.get("old_color_scheme", "") - if old_color_scheme != "": - preferences.set("color_scheme", old_color_scheme) - else: - preferences.erase("color_scheme") - sublime.save_settings("Preferences.sublime-settings") diff --git a/colored_comments.sublime-settings b/colored_comments.sublime-settings index c2278df..6ac1270 100644 --- a/colored_comments.sublime-settings +++ b/colored_comments.sublime-settings @@ -1,12 +1,7 @@ { // Enable debug logging "debug": false, - // Color Scheme used prior to Colored Comments modification - "old_color_scheme": "", - // Determines if you should be prompted for setting - // the color scheme on load of the plugin - "prompt_new_color_scheme": true, - + // Enables continued matching of the previous tag "continued_matching": true, diff --git a/dependencies.json b/dependencies.json deleted file mode 100644 index 159641a..0000000 --- a/dependencies.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "*": { - "*": [ - "regex" - ] - } -} \ No newline at end of file diff --git a/lib/plistlib.py b/lib/plistlib.py deleted file mode 100644 index 2677683..0000000 --- a/lib/plistlib.py +++ /dev/null @@ -1,1095 +0,0 @@ -r"""plistlib.py -- a tool to generate and parse MacOSX .plist files. - -The property list (.plist) file format is a simple XML pickle supporting -basic object types, like dictionaries, lists, numbers and strings. -Usually the top level object is a dictionary. - -To write out a plist file, use the dump(value, file) -function. 'value' is the top level object, 'file' is -a (writable) file object. - -To parse a plist from a file, use the load(file) function, -with a (readable) file object as the only argument. It -returns the top level object (again, usually a dictionary). - -To work with plist data in bytes objects, you can use loads() -and dumps(). - -Values can be strings, integers, floats, booleans, tuples, lists, -dictionaries (but only with string keys), Data, bytes, bytearray, or -datetime.datetime objects. - -Generate Plist example: - - pl = dict( - aString = "Doodah", - aList = ["A", "B", 12, 32.1, [1, 2, 3]], - aFloat = 0.1, - anInt = 728, - aDict = dict( - anotherString = "", - aUnicodeValue = "M\xe4ssig, Ma\xdf", - aTrueValue = True, - aFalseValue = False, - ), - someData = b"", - someMoreData = b"" * 10, - aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())), - ) - with open(fileName, 'wb') as fp: - dump(pl, fp) - -Parse Plist example: - - with open(fileName, 'rb') as fp: - pl = load(fp) - print(pl["aKey"]) -""" -__all__ = [ - "readPlist", - "writePlist", - "readPlistFromBytes", - "writePlistToBytes", - "Plist", - "Data", - "Dict", - "FMT_XML", - "FMT_BINARY", - "load", - "dump", - "loads", - "dumps", -] - -import binascii -import codecs -import contextlib -import datetime -from io import BytesIO -import itertools -import os -import re -import struct -from warnings import warn -from xml.parsers.expat import ParserCreate - -FMT_XML = "FMT_XML" -FMT_BINARY = "FMT_BINARY" - -# -# -# Deprecated functionality -# -# - - -class _InternalDict(dict): - - # This class is needed while Dict is scheduled for deprecation: - # we only need to warn when a *user* instantiates Dict or when - # the "attribute notation for dict keys" is used. - __slots__ = () - - def __getattr__(self, attr): - try: - value = self[attr] - except KeyError: - raise AttributeError(attr) - warn( - "Attribute access from plist dicts is deprecated, use d[key] " - "notation instead", - DeprecationWarning, - 2, - ) - return value - - def __setattr__(self, attr, value): - warn( - "Attribute access from plist dicts is deprecated, use d[key] " - "notation instead", - DeprecationWarning, - 2, - ) - self[attr] = value - - def __delattr__(self, attr): - try: - del self[attr] - except KeyError: - raise AttributeError(attr) - warn( - "Attribute access from plist dicts is deprecated, use d[key] " - "notation instead", - DeprecationWarning, - 2, - ) - - -class Dict(_InternalDict): - def __init__(self, **kwargs): - warn( - "The plistlib.Dict class is deprecated, use builtin dict instead", - DeprecationWarning, - 2, - ) - super().__init__(**kwargs) - - -@contextlib.contextmanager -def _maybe_open(pathOrFile, mode): - if isinstance(pathOrFile, str): - with open(pathOrFile, mode) as fp: - yield fp - - else: - yield pathOrFile - - -class Plist(_InternalDict): - """This class has been deprecated. Use dump() and load() - functions instead, together with regular dict objects. - """ - - def __init__(self, **kwargs): - warn( - "The Plist class is deprecated, use the load() and " - "dump() functions instead", - DeprecationWarning, - 2, - ) - super().__init__(**kwargs) - - @classmethod - def fromFile(cls, pathOrFile): - """Deprecated. Use the load() function instead.""" - with _maybe_open(pathOrFile, "rb") as fp: - value = load(fp) - plist = cls() - plist.update(value) - return plist - - def write(self, pathOrFile): - """Deprecated. Use the dump() function instead.""" - with _maybe_open(pathOrFile, "wb") as fp: - dump(self, fp) - - -def readPlist(pathOrFile): - """ - Read a .plist from a path or file. pathOrFile should either - be a file name, or a readable binary file object. - - This function is deprecated, use load instead. - """ - warn( - "The readPlist function is deprecated, use load() instead", - DeprecationWarning, - 2, - ) - - with _maybe_open(pathOrFile, "rb") as fp: - return load(fp, fmt=None, use_builtin_types=False, dict_type=_InternalDict) - - -def writePlist(value, pathOrFile): - """ - Write 'value' to a .plist file. 'pathOrFile' may either be a - file name or a (writable) file object. - - This function is deprecated, use dump instead. - """ - warn( - "The writePlist function is deprecated, use dump() instead", - DeprecationWarning, - 2, - ) - with _maybe_open(pathOrFile, "wb") as fp: - dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False) - - -def readPlistFromBytes(data): - """ - Read a plist data from a bytes object. Return the root object. - - This function is deprecated, use loads instead. - """ - warn( - "The readPlistFromBytes function is deprecated, use loads() instead", - DeprecationWarning, - 2, - ) - return load( - BytesIO(data), fmt=None, use_builtin_types=False, dict_type=_InternalDict - ) - - -def writePlistToBytes(value): - """ - Return 'value' as a plist-formatted bytes object. - - This function is deprecated, use dumps instead. - """ - warn( - "The writePlistToBytes function is deprecated, use dumps() instead", - DeprecationWarning, - 2, - ) - f = BytesIO() - dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False) - return f.getvalue() - - -class Data: - """ - Wrapper for binary data. - - This class is deprecated, use a bytes object instead. - """ - - def __init__(self, data): - if not isinstance(data, bytes): - raise TypeError("data must be as bytes") - self.data = data - - @classmethod - def fromBase64(cls, data): - # base64.decodebytes just calls binascii.a2b_base64; - # it seems overkill to use both base64 and binascii. - return cls(_decode_base64(data)) - - def asBase64(self, maxlinelength=76): - return _encode_base64(self.data, maxlinelength) - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.data == other.data - elif isinstance(other, str): - return self.data == other - else: - return id(self) == id(other) - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, repr(self.data)) - - -# -# -# End of deprecated functionality -# -# - - -# -# XML support -# - - -# XML 'header' -PLISTHEADER = b"""\ - - -""" - - -# Regex to find any control chars, except for \t \n and \r -_controlCharPat = re.compile( - r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" - r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]" -) - - -def _encode_base64(s, maxlinelength=76): - # copied from base64.encodebytes(), with added maxlinelength argument - maxbinsize = (maxlinelength // 4) * 3 - pieces = [] - for i in range(0, len(s), maxbinsize): - chunk = s[i : i + maxbinsize] - pieces.append(binascii.b2a_base64(chunk)) - return b"".join(pieces) - - -def _decode_base64(s): - if isinstance(s, str): - return binascii.a2b_base64(s.encode("utf-8")) - - else: - return binascii.a2b_base64(s) - - -# Contents should conform to a subset of ISO 8601 -# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units -# may be omitted with # a loss of precision) -_dateParser = re.compile( - r"(?P\d\d\d\d)(?:-(?P\d\d)(?:-(?P\d\d)(?:T(?P\d\d)(?::(?P\d\d)(?::(?P\d\d))?)?)?)?)?Z", - re.ASCII, -) - - -def _date_from_string(s): - order = ("year", "month", "day", "hour", "minute", "second") - gd = _dateParser.match(s).groupdict() - lst = [] - for key in order: - val = gd[key] - if val is None: - break - lst.append(int(val)) - return datetime.datetime(*lst) - - -def _date_to_string(d): - return "%04d-%02d-%02dT%02d:%02d:%02dZ" % ( - d.year, - d.month, - d.day, - d.hour, - d.minute, - d.second, - ) - - -def _escape(text): - m = _controlCharPat.search(text) - if m is not None: - raise ValueError( - "strings can't contains control characters; " "use bytes instead" - ) - text = text.replace("\r\n", "\n") # convert DOS line endings - text = text.replace("\r", "\n") # convert Mac line endings - text = text.replace("&", "&") # escape '&' - text = text.replace("<", "<") # escape '<' - text = text.replace(">", ">") # escape '>' - return text - - -class _PlistParser: - def __init__(self, use_builtin_types, dict_type): - self.stack = [] - self.current_key = None - self.root = None - self._use_builtin_types = use_builtin_types - self._dict_type = dict_type - - def parse(self, fileobj): - self.parser = ParserCreate() - self.parser.StartElementHandler = self.handle_begin_element - self.parser.EndElementHandler = self.handle_end_element - self.parser.CharacterDataHandler = self.handle_data - self.parser.ParseFile(fileobj) - return self.root - - def handle_begin_element(self, element, attrs): - self.data = [] - handler = getattr(self, "begin_" + element, None) - if handler is not None: - handler(attrs) - - def handle_end_element(self, element): - handler = getattr(self, "end_" + element, None) - if handler is not None: - handler() - - def handle_data(self, data): - self.data.append(data) - - def add_object(self, value): - if self.current_key is not None: - if not isinstance(self.stack[-1], type({})): - raise ValueError( - "unexpected element at line %d" % self.parser.CurrentLineNumber - ) - self.stack[-1][self.current_key] = value - self.current_key = None - elif not self.stack: - # this is the root object - self.root = value - else: - if not isinstance(self.stack[-1], type([])): - raise ValueError( - "unexpected element at line %d" % self.parser.CurrentLineNumber - ) - self.stack[-1].append(value) - - def get_data(self): - data = "".join(self.data) - self.data = [] - return data - - # element handlers - - def begin_dict(self, attrs): - d = self._dict_type() - self.add_object(d) - self.stack.append(d) - - def end_dict(self): - if self.current_key: - raise ValueError( - "missing value for key '%s' at line %d" - % (self.current_key, self.parser.CurrentLineNumber) - ) - self.stack.pop() - - def end_key(self): - if self.current_key or not isinstance(self.stack[-1], type({})): - raise ValueError( - "unexpected key at line %d" % self.parser.CurrentLineNumber - ) - self.current_key = self.get_data() - - def begin_array(self, attrs): - a = [] - self.add_object(a) - self.stack.append(a) - - def end_array(self): - self.stack.pop() - - def end_true(self): - self.add_object(True) - - def end_false(self): - self.add_object(False) - - def end_integer(self): - self.add_object(int(self.get_data())) - - def end_real(self): - self.add_object(float(self.get_data())) - - def end_string(self): - self.add_object(self.get_data()) - - def end_data(self): - if self._use_builtin_types: - self.add_object(_decode_base64(self.get_data())) - - else: - self.add_object(Data.fromBase64(self.get_data())) - - def end_date(self): - self.add_object(_date_from_string(self.get_data())) - - -class _DumbXMLWriter: - def __init__(self, file, indent_level=0, indent="\t"): - self.file = file - self.stack = [] - self._indent_level = indent_level - self.indent = indent - - def begin_element(self, element): - self.stack.append(element) - self.writeln("<%s>" % element) - self._indent_level += 1 - - def end_element(self, element): - assert self._indent_level > 0 - assert self.stack.pop() == element - self._indent_level -= 1 - self.writeln("" % element) - - def simple_element(self, element, value=None): - if value is not None: - value = _escape(value) - self.writeln("<%s>%s" % (element, value, element)) - - else: - self.writeln("<%s/>" % element) - - def writeln(self, line): - if line: - # plist has fixed encoding of utf-8 - - # XXX: is this test needed? - if isinstance(line, str): - line = line.encode("utf-8") - self.file.write(self._indent_level * self.indent) - self.file.write(line) - self.file.write(b"\n") - - -class _PlistWriter(_DumbXMLWriter): - def __init__( - self, - file, - indent_level=0, - indent=b"\t", - writeHeader=1, - sort_keys=True, - skipkeys=False, - ): - - if writeHeader: - file.write(PLISTHEADER) - _DumbXMLWriter.__init__(self, file, indent_level, indent) - self._sort_keys = sort_keys - self._skipkeys = skipkeys - - def write(self, value): - self.writeln('') - self.write_value(value) - self.writeln("") - - def write_value(self, value): - if isinstance(value, str): - self.simple_element("string", value) - - elif value is True: - self.simple_element("true") - - elif value is False: - self.simple_element("false") - - elif isinstance(value, int): - if -1 << 63 <= value < 1 << 64: - self.simple_element("integer", "%d" % value) - else: - raise OverflowError(value) - - elif isinstance(value, float): - self.simple_element("real", repr(value)) - - elif isinstance(value, dict): - self.write_dict(value) - - elif isinstance(value, Data): - self.write_data(value) - - elif isinstance(value, (bytes, bytearray)): - self.write_bytes(value) - - elif isinstance(value, datetime.datetime): - self.simple_element("date", _date_to_string(value)) - - elif isinstance(value, (tuple, list)): - self.write_array(value) - - else: - raise TypeError("unsupported type: %s" % type(value)) - - def write_data(self, data): - self.write_bytes(data.data) - - def write_bytes(self, data): - self.begin_element("data") - self._indent_level -= 1 - maxlinelength = max( - 16, 76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level) - ) - - for line in _encode_base64(data, maxlinelength).split(b"\n"): - if line: - self.writeln(line) - self._indent_level += 1 - self.end_element("data") - - def write_dict(self, d): - if d: - self.begin_element("dict") - if self._sort_keys: - items = sorted(d.items()) - else: - items = d.items() - - for key, value in items: - if not isinstance(key, str): - if self._skipkeys: - continue - raise TypeError("keys must be strings") - self.simple_element("key", key) - self.write_value(value) - self.end_element("dict") - - else: - self.simple_element("dict") - - def write_array(self, array): - if array: - self.begin_element("array") - for value in array: - self.write_value(value) - self.end_element("array") - - else: - self.simple_element("array") - - -def _is_fmt_xml(header): - prefixes = (b"offset... - # TRAILER - self._fp = fp - self._fp.seek(-32, os.SEEK_END) - trailer = self._fp.read(32) - if len(trailer) != 32: - raise InvalidFileException() - ( - offset_size, - self._ref_size, - num_objects, - top_object, - offset_table_offset, - ) = struct.unpack(">6xBBQQQ", trailer) - self._fp.seek(offset_table_offset) - self._object_offsets = self._read_ints(num_objects, offset_size) - return self._read_object(self._object_offsets[top_object]) - - except (OSError, IndexError, struct.error): - raise InvalidFileException() - - def _get_size(self, tokenL): - """ return the size of the next object.""" - if tokenL == 0xF: - m = self._fp.read(1)[0] & 0x3 - s = 1 << m - f = ">" + _BINARY_FORMAT[s] - return struct.unpack(f, self._fp.read(s))[0] - - return tokenL - - def _read_ints(self, n, size): - data = self._fp.read(size * n) - if size in _BINARY_FORMAT: - return struct.unpack(">" + _BINARY_FORMAT[size] * n, data) - else: - return tuple( - int.from_bytes(data[i : i + size], "big") - for i in range(0, size * n, size) - ) - - def _read_refs(self, n): - return self._read_ints(n, self._ref_size) - - def _read_object(self, offset): - """ - read the object at offset. - - May recursively read sub-objects (content of an array/dict/set) - """ - self._fp.seek(offset) - token = self._fp.read(1)[0] - tokenH, tokenL = token & 0xF0, token & 0x0F - - if token == 0x00: - return None - - elif token == 0x08: - return False - - elif token == 0x09: - return True - - # The referenced source code also mentions URL (0x0c, 0x0d) and - # UUID (0x0e), but neither can be generated using the Cocoa libraries. - - elif token == 0x0F: - return b"" - - elif tokenH == 0x10: # int - return int.from_bytes(self._fp.read(1 << tokenL), "big", signed=tokenL >= 3) - - elif token == 0x22: # real - return struct.unpack(">f", self._fp.read(4))[0] - - elif token == 0x23: # real - return struct.unpack(">d", self._fp.read(8))[0] - - elif token == 0x33: # date - f = struct.unpack(">d", self._fp.read(8))[0] - # timestamp 0 of binary plists corresponds to 1/1/2001 - # (year of Mac OS X 10.0), instead of 1/1/1970. - return datetime.datetime.utcfromtimestamp(f + (31 * 365 + 8) * 86400) - - elif tokenH == 0x40: # data - s = self._get_size(tokenL) - if self._use_builtin_types: - return self._fp.read(s) - else: - return Data(self._fp.read(s)) - - elif tokenH == 0x50: # ascii string - s = self._get_size(tokenL) - result = self._fp.read(s).decode("ascii") - return result - - elif tokenH == 0x60: # unicode string - s = self._get_size(tokenL) - return self._fp.read(s * 2).decode("utf-16be") - - # tokenH == 0x80 is documented as 'UID' and appears to be used for - # keyed-archiving, not in plists. - - elif tokenH == 0xA0: # array - s = self._get_size(tokenL) - obj_refs = self._read_refs(s) - return [self._read_object(self._object_offsets[x]) for x in obj_refs] - - # tokenH == 0xB0 is documented as 'ordset', but is not actually - # implemented in the Apple reference code. - - # tokenH == 0xC0 is documented as 'set', but sets cannot be used in - # plists. - - elif tokenH == 0xD0: # dict - s = self._get_size(tokenL) - key_refs = self._read_refs(s) - obj_refs = self._read_refs(s) - result = self._dict_type() - for k, o in zip(key_refs, obj_refs): - result[self._read_object(self._object_offsets[k])] = self._read_object( - self._object_offsets[o] - ) - return result - - raise InvalidFileException() - - -def _count_to_size(count): - if count < 1 << 8: - return 1 - - elif count < 1 << 16: - return 2 - - elif count << 1 << 32: - return 4 - - else: - return 8 - - -class _BinaryPlistWriter(object): - def __init__(self, fp, sort_keys, skipkeys): - self._fp = fp - self._sort_keys = sort_keys - self._skipkeys = skipkeys - - def write(self, value): - - # Flattened object list: - self._objlist = [] - - # Mappings from object->objectid - # First dict has (type(object), object) as the key, - # second dict is used when object is not hashable and - # has id(object) as the key. - self._objtable = {} - self._objidtable = {} - - # Create list of all objects in the plist - self._flatten(value) - - # Size of object references in serialized containers - # depends on the number of objects in the plist. - num_objects = len(self._objlist) - self._object_offsets = [0] * num_objects - self._ref_size = _count_to_size(num_objects) - - self._ref_format = _BINARY_FORMAT[self._ref_size] - - # Write file header - self._fp.write(b"bplist00") - - # Write object list - for obj in self._objlist: - self._write_object(obj) - - # Write refnum->object offset table - top_object = self._getrefnum(value) - offset_table_offset = self._fp.tell() - offset_size = _count_to_size(offset_table_offset) - offset_format = ">" + _BINARY_FORMAT[offset_size] * num_objects - self._fp.write(struct.pack(offset_format, *self._object_offsets)) - - # Write trailer - sort_version = 0 - trailer = ( - sort_version, - offset_size, - self._ref_size, - num_objects, - top_object, - offset_table_offset, - ) - self._fp.write(struct.pack(">5xBBBQQQ", *trailer)) - - def _flatten(self, value): - # First check if the object is in the object table, not used for - # containers to ensure that two subcontainers with the same contents - # will be serialized as distinct values. - if isinstance(value, (str, int, float, datetime.datetime, bytes, bytearray)): - if (type(value), value) in self._objtable: - return - - elif isinstance(value, Data): - if (type(value.data), value.data) in self._objtable: - return - - # Add to objectreference map - refnum = len(self._objlist) - self._objlist.append(value) - try: - if isinstance(value, Data): - self._objtable[(type(value.data), value.data)] = refnum - else: - self._objtable[(type(value), value)] = refnum - except TypeError: - self._objidtable[id(value)] = refnum - - # And finally recurse into containers - if isinstance(value, dict): - keys = [] - values = [] - items = value.items() - if self._sort_keys: - items = sorted(items) - - for k, v in items: - if not isinstance(k, str): - if self._skipkeys: - continue - raise TypeError("keys must be strings") - keys.append(k) - values.append(v) - - for o in itertools.chain(keys, values): - self._flatten(o) - - elif isinstance(value, (list, tuple)): - for o in value: - self._flatten(o) - - def _getrefnum(self, value): - try: - if isinstance(value, Data): - return self._objtable[(type(value.data), value.data)] - else: - return self._objtable[(type(value), value)] - except TypeError: - return self._objidtable[id(value)] - - def _write_size(self, token, size): - if size < 15: - self._fp.write(struct.pack(">B", token | size)) - - elif size < 1 << 8: - self._fp.write(struct.pack(">BBB", token | 0xF, 0x10, size)) - - elif size < 1 << 16: - self._fp.write(struct.pack(">BBH", token | 0xF, 0x11, size)) - - elif size < 1 << 32: - self._fp.write(struct.pack(">BBL", token | 0xF, 0x12, size)) - - else: - self._fp.write(struct.pack(">BBQ", token | 0xF, 0x13, size)) - - def _write_object(self, value): - ref = self._getrefnum(value) - self._object_offsets[ref] = self._fp.tell() - if value is None: - self._fp.write(b"\x00") - - elif value is False: - self._fp.write(b"\x08") - - elif value is True: - self._fp.write(b"\x09") - - elif isinstance(value, int): - if value < 0: - try: - self._fp.write(struct.pack(">Bq", 0x13, value)) - except struct.error: - raise OverflowError(value) from None - elif value < 1 << 8: - self._fp.write(struct.pack(">BB", 0x10, value)) - elif value < 1 << 16: - self._fp.write(struct.pack(">BH", 0x11, value)) - elif value < 1 << 32: - self._fp.write(struct.pack(">BL", 0x12, value)) - elif value < 1 << 63: - self._fp.write(struct.pack(">BQ", 0x13, value)) - elif value < 1 << 64: - self._fp.write(b"\x14" + value.to_bytes(16, "big", signed=True)) - else: - raise OverflowError(value) - - elif isinstance(value, float): - self._fp.write(struct.pack(">Bd", 0x23, value)) - - elif isinstance(value, datetime.datetime): - f = (value - datetime.datetime(2001, 1, 1)).total_seconds() - self._fp.write(struct.pack(">Bd", 0x33, f)) - - elif isinstance(value, Data): - self._write_size(0x40, len(value.data)) - self._fp.write(value.data) - - elif isinstance(value, (bytes, bytearray)): - self._write_size(0x40, len(value)) - self._fp.write(value) - - elif isinstance(value, str): - try: - t = value.encode("ascii") - self._write_size(0x50, len(value)) - except UnicodeEncodeError: - t = value.encode("utf-16be") - self._write_size(0x60, len(value)) - - self._fp.write(t) - - elif isinstance(value, (list, tuple)): - refs = [self._getrefnum(o) for o in value] - s = len(refs) - self._write_size(0xA0, s) - self._fp.write(struct.pack(">" + self._ref_format * s, *refs)) - - elif isinstance(value, dict): - keyRefs, valRefs = [], [] - - if self._sort_keys: - rootItems = sorted(value.items()) - else: - rootItems = value.items() - - for k, v in rootItems: - if not isinstance(k, str): - if self._skipkeys: - continue - raise TypeError("keys must be strings") - keyRefs.append(self._getrefnum(k)) - valRefs.append(self._getrefnum(v)) - - s = len(keyRefs) - self._write_size(0xD0, s) - self._fp.write(struct.pack(">" + self._ref_format * s, *keyRefs)) - self._fp.write(struct.pack(">" + self._ref_format * s, *valRefs)) - - else: - raise TypeError(value) - - -def _is_fmt_binary(header): - return header[:8] == b"bplist00" - - -# -# Generic bits -# - -_FORMATS = { - FMT_XML: dict(detect=_is_fmt_xml, parser=_PlistParser, writer=_PlistWriter,), - FMT_BINARY: dict( - detect=_is_fmt_binary, parser=_BinaryPlistParser, writer=_BinaryPlistWriter, - ), -} - - -def load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict): - """Read a .plist file. 'fp' should be (readable) file object. - Return the unpacked root object (which usually is a dictionary). - """ - if fmt is None: - header = fp.read(32) - fp.seek(0) - for info in _FORMATS.values(): - if info["detect"](header): - P = info["parser"] - break - - else: - raise InvalidFileException() - - else: - P = _FORMATS[fmt]["parser"] - - p = P(use_builtin_types=use_builtin_types, dict_type=dict_type) - return p.parse(fp) - - -def loads(value, *, fmt=None, use_builtin_types=True, dict_type=dict): - """Read a .plist file from a bytes object. - Return the unpacked root object (which usually is a dictionary). - """ - fp = BytesIO(value) - return load(fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type) - - -def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False): - """Write 'value' to a .plist file. 'fp' should be a (writable) - file object. - """ - if fmt not in _FORMATS: - raise ValueError("Unsupported format: %r" % (fmt,)) - - writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys) - writer.write(value) - - -def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True): - """Return a bytes object with the contents for a .plist file. - """ - fp = BytesIO() - dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys) - return fp.getvalue() diff --git a/messages.json b/messages.json new file mode 100644 index 0000000..21a4b74 --- /dev/null +++ b/messages.json @@ -0,0 +1,4 @@ +{ + "install": "messages/install.txt", + "1.0.1": "messages/3.0.0.txt", +} \ No newline at end of file diff --git a/messages/3.0.0.txt b/messages/3.0.0.txt new file mode 100644 index 0000000..8b305ca --- /dev/null +++ b/messages/3.0.0.txt @@ -0,0 +1,5 @@ +Version 3.0.0 (May 13, 2020) +-------------------------- + + * Fix the Color Manager from overriding user files + * Modify command names \ No newline at end of file diff --git a/messages/install.txt b/messages/install.txt new file mode 100644 index 0000000..8713912 --- /dev/null +++ b/messages/install.txt @@ -0,0 +1,16 @@ +Colored Comments is the missing comment helpers that allows you to +create patterns for comment identification. Which then colors the comments +the color of your choice. + +Quick Start +----------- + +All commands are available in the command palette. + +To get started, first run the `Colored Comments: Generate Color Scheme` +command from the command palette. Once done, comment of your choice will +begin to be colored. + +Removing the comment color scheme, run the following: +`Colored Comments: Remove Generated Color Scheme` + diff --git a/plugin/color_manager.py b/plugin/color_manager.py new file mode 100644 index 0000000..516c111 --- /dev/null +++ b/plugin/color_manager.py @@ -0,0 +1,69 @@ +import os + +import sublime + +sublime_settings = "Preferences.sublime-settings" +override_path = "Colored Comments Override" +scope_name = "colored.comments.color." + + +class ColorManager: + def __init__(self, tags, log): + self.tags = tags + self.log = log + + def remove_override(self, scheme): + self.save_scheme(os.path.basename(scheme), {"rules": [], "variables": {}}) + + def create_user_custom_theme(self): + if not self.tags: + return + + self.sublime_pref = sublime.load_settings(sublime_settings) + color_scheme = self.sublime_pref.get("color_scheme") + scheme_content = self._add_colors_to_scheme({"rules": [], "variables": {}}) + self.save_scheme(os.path.basename(color_scheme), scheme_content) + + def save_scheme(self, scheme_name: str, scheme_content: dict) -> None: + user_override_path = _build_scheme_path(os.path.basename(scheme_name)) + with open(user_override_path, "w") as outfile: + outfile.write(sublime.encode_value(scheme_content, True)) + + def _add_colors_to_scheme(self, scheme_content: dict) -> dict: + rules = scheme_content.get("rules") + for tag in self.tags: + if not self.tags.get(tag) and not self.tags.get(tag).get("color"): + continue + + name = _get_color_property("name", self.tags.get(tag)) + background = _get_color_property("background", self.tags.get(tag)) + foreground = _get_color_property("foreground", self.tags.get(tag)) + if False in [name, background, foreground]: + continue + + scope = "{}{}".format(scope_name, name.lower().replace(" ", ".")) + if not any(rule.get("scope") == scope for rule in rules): + entry = { + "name": "[Colored Comments] {}".format(name.title()), + "scope": scope, + "foreground": foreground, + "background": background + } + rules.append(entry) + scheme_content["rules"] = rules + return scheme_content + + +def _build_scheme_path(scheme: str) -> str: + _create_override_path() + return os.path.join(sublime.packages_path(), override_path, scheme) + + +def _create_override_path() -> None: + return os.makedirs(os.path.join(sublime.packages_path(), override_path), exist_ok=True) + + +def _get_color_property(property: str, tags: dict) -> str: + if not tags.get("color") and tags.get("color").get(property, False): + return False + return tags.get("color").get(property)