Skip to content

Commit

Permalink
Feature/hashed color syntax (#45)
Browse files Browse the repository at this point in the history
* Handle hashed syntax highlighting when parsing

* Rework scheme merging

* Show hashed colors and rework scheme results

* Show hashed foreground selectors

* Fix doc errors

* Update version and changelog

* Only assign gradient if available
  • Loading branch information
facelessuser committed Nov 4, 2017
1 parent 6e2742c commit 994eb2f
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 63 deletions.
8 changes: 8 additions & 0 deletions CHANGES.md
@@ -1,3 +1,11 @@
# ScopeHunter 2.10.0

Nov 4, 2017

- **NEW**: Add support `.sublime-color-scheme` hashed syntax highlighting.
- **FIX**: Copy of color entries.
- **FIX**: `.sublime-color-scheme` merge logic.

# ScopeHunter 2.9.3

Oct 30, 2017
Expand Down
140 changes: 109 additions & 31 deletions lib/color_scheme_matcher.py
Expand Up @@ -38,7 +38,6 @@
GLOBAL_OPTIONS = "globals" if int(sublime.version()) >= 3152 else "defaults"

# XML
IS_XML_RE = re.compile(br'^[\r\n\s]*<')
XML_COMMENT_RE = re.compile(br"^[\r\n\s]*<!--[\s\S]*?-->[\s\r\n]*|<!--[\s\S]*?-->")

# For new Sublime format
Expand Down Expand Up @@ -309,7 +308,10 @@ def sublime_format_path(pth):
class SchemeColors(
namedtuple(
'SchemeColors',
['fg', 'fg_simulated', 'bg', "bg_simulated", "style", "fg_selector", "bg_selector", "style_selectors"],
[
'fg', 'fg_simulated', 'bg', "bg_simulated", "style", "color_gradient",
"fg_selector", "bg_selector", "style_selectors", "color_gradient_selector"
],
verbose=False
)
):
Expand All @@ -330,19 +332,17 @@ def __init__(self, scheme_file, color_filter=None):
self.color_scheme = path.normpath(scheme_file)
self.scheme_file = path.basename(self.color_scheme)

content = sublime.load_binary_resource(sublime_format_path(self.color_scheme))
if scheme_file.lower().endswith(('.tmtheme', '.hidden-tmtheme')) or IS_XML_RE.match(content) is not None:
if NEW_SCHEMES and scheme_file.endswith('.sublime-color-scheme'):
self.legacy = False
self.scheme_obj = {
'variables': {},
GLOBAL_OPTIONS: {},
'rules': []
}
else:
content = sublime.load_binary_resource(sublime_format_path(self.color_scheme))
self.legacy = True
self.convert_format(readPlistFromBytes(XML_COMMENT_RE.sub(b'', content)))
else:
self.legacy = False
self.scheme_obj = sublime.decode_value(content.decode('utf-8'))
if 'variables' not in self.scheme_obj:
self.scheme_obj['variables'] = {}
if GLOBAL_OPTIONS not in self.scheme_obj:
self.scheme_obj[GLOBAL_OPTIONS] = {}
if 'rules' not in self.scheme_obj:
self.scheme_obj['rules'] = []
self.overrides = []
if NEW_SCHEMES:
self.merge_overrides()
Expand Down Expand Up @@ -386,21 +386,26 @@ def convert_format(self, obj):
def merge_overrides(self):
"""Merge override schemes."""

current_file = sublime_format_path(self.color_scheme)
package_overrides = []
user_overrides = []
for override in sublime.find_resources('%s.sublime-color-scheme' % path.splitext(self.scheme_file)[0]):
if override != current_file:
ojson = sublime.decode_value(sublime.load_resource(override))
if override.startswith('Packages/User/'):
user_overrides.append(override)
else:
package_overrides.append(override)
for override in (package_overrides + user_overrides):
ojson = sublime.decode_value(sublime.load_resource(override))

for k, v in ojson.get('variables', {}).items():
self.scheme_obj['variables'][k] = v
for k, v in ojson.get('variables', {}).items():
self.scheme_obj['variables'][k] = v

for k, v in ojson.get(GLOBAL_OPTIONS, {}).items():
self.scheme_obj[GLOBAL_OPTIONS][k] = v
for k, v in ojson.get(GLOBAL_OPTIONS, {}).items():
self.scheme_obj[GLOBAL_OPTIONS][k] = v

for item in ojson.get('rules', []):
self.scheme_obj['rules'].append(item)
for item in ojson.get('rules', []):
self.scheme_obj['rules'].append(item)

self.overrides.append(override)
self.overrides.append(override)

def filter(self, scheme):
"""Dummy filter call that does nothing."""
Expand Down Expand Up @@ -458,15 +463,29 @@ def parse_scheme(self):
scolor = None
style = []
if scope is not None:
# Foreground color
color = item.get('foreground', None)
if color is not None:
if isinstance(color, list):
# Hashed Syntax Highlighting
for index, c in enumerate(color):
color[index] = translate_color(COLOR_RE.match(c.strip()), self.variables, {})
elif isinstance(color, str):
color = translate_color(COLOR_RE.match(color.strip()), self.variables, {})
else:
color = None
# Background color
bgcolor = item.get('background', None)
if bgcolor is not None:
if isinstance(bgcolor, str):
bgcolor = translate_color(COLOR_RE.match(bgcolor.strip()), self.variables, {})
else:
bgcolor = None
# Selection foreground color
scolor = item.get('selection_foreground', None)
if scolor is not None:
if isinstance(scolor, str):
scolor = translate_color(COLOR_RE.match(scolor.strip()), self.variables, {})
else:
scolor = None
# Font style
if FONT_STYLE in item:
for s in item.get(FONT_STYLE, '').split(' '):
if s == "bold" or s == "italic": # or s == "underline":
Expand All @@ -478,7 +497,10 @@ def parse_scheme(self):
def add_entry(self, name, scope, color, bgcolor, scolor, style):
"""Add color entry."""

if color is not None:
color_gradient = None
if isinstance(color, list):
fg, fg_sim, color_gradient = self.process_color_gradient(color)
elif color is not None:
fg, fg_sim = self.process_color(color)
else:
fg, fg_sim = None, None
Expand All @@ -497,13 +519,45 @@ def add_entry(self, name, scope, color, bgcolor, scolor, style):
"scope": scope,
"color": fg,
"color_simulated": fg_sim,
"color_gradient": color_gradient,
"bgcolor": bg,
"bgcolor_simulated": bg_sim,
"selection_color": sfg,
"selection_color_simulated": sfg_sim,
"style": style
}

def process_color_gradient(self, colors, simple_strip=False, bground=None):
"""
Strip transparency from the color gradient list.
Transparency can be stripped in one of two ways:
- Simply mask off the alpha channel.
- Apply the alpha channel to the color essential getting the color seen by the eye.
"""

gradient = []

for color in colors:
if color is None or color.strip() == "":
continue

if not color.startswith('#'):
continue

rgba = RGBA(color.replace(" ", ""))
if not simple_strip:
if bground is None:
bground = self.special_colors['background']['color_simulated']
rgba.apply_alpha(bground if bground != "" else "#FFFFFF")

gradient.append((color, rgba.get_rgb()))
if gradient:
color, color_sim = gradient[0]
return color, color_sim, gradient
else:
return None, None, None

def process_color(self, color, simple_strip=False, bground=None):
"""
Strip transparency from the color value.
Expand Down Expand Up @@ -565,6 +619,8 @@ def guess_color(self, scope_key, selected=False, explicit_background=False):

color = self.special_colors['foreground']['color']
color_sim = self.special_colors['foreground']['color_simulated']
color_gradient = None
color_gradient_selector = None
bgcolor = self.special_colors['background']['color'] if not explicit_background else None
bgcolor_sim = self.special_colors['background']['color_simulated'] if not explicit_background else None
scolor = self.special_colors['selection_foreground']['color']
Expand All @@ -577,6 +633,7 @@ def guess_color(self, scope_key, selected=False, explicit_background=False):
if scope_key in self.matched:
color = self.matched[scope_key]["color"]
color_sim = self.matched[scope_key]["color_simulated"]
color_gradient = self.matched[scope_key]["color_gradient"]
style = self.matched[scope_key]["style"]
bgcolor = self.matched[scope_key]["bgcolor"]
bgcolor_sim = self.matched[scope_key]["bgcolor_simulated"]
Expand All @@ -587,18 +644,31 @@ def guess_color(self, scope_key, selected=False, explicit_background=False):
bg_selector = selectors["background"]
scolor_selector = selectors["scolor"]
style_selectors = selectors["style"]
color_gradient_selector = selectors['color_gradient']
else:
best_match_bg = 0
best_match_fg = 0
best_match_style = 0
best_match_sfg = 0
best_match_fg_gradient = 0
for key in self.colors:
match = sublime.score_selector(scope_key, key)
if self.colors[key]["color"] is not None and match > best_match_fg:
if (
not self.colors[key]['color_gradient'] and
self.colors[key]["color"] is not None and
match > best_match_fg
):
best_match_fg = match
color = self.colors[key]["color"]
color_sim = self.colors[key]["color_simulated"]
color_selector = SchemeSelectors(self.colors[key]["name"], self.colors[key]["scope"])
if (
self.colors[key]["color"] is not None and
match > best_match_fg_gradient
):
best_match_fg_gradient = match
color_gradient = self.colors[key]["color_gradient"]
color_gradient_selector = SchemeSelectors(self.colors[key]["name"], self.colors[key]["scope"])
if self.colors[key]["selection_color"] is not None and match > best_match_sfg:
best_match_sfg = match
scolor = self.colors[key]["selection_color"]
Expand Down Expand Up @@ -627,19 +697,25 @@ def guess_color(self, scope_key, selected=False, explicit_background=False):
else:
style = ' '.join(style)

if not isinstance(color_gradient, list):
color_gradient = None
color_gradient_selector = None

self.matched[scope_key] = {
"color": color,
"bgcolor": bgcolor,
"scolor": scolor,
"color_simulated": color_sim,
"bgcolor_simulated": bgcolor_sim,
"scolor_simulated": scolor_sim,
"color_gradient": color_gradient,
"style": style,
"selectors": {
"color": color_selector,
"background": bg_selector,
"scolor": scolor_selector,
"style": style_selectors
"style": style_selectors,
"color_gradient": color_gradient_selector
}
}

Expand All @@ -648,12 +724,14 @@ def guess_color(self, scope_key, selected=False, explicit_background=False):
color = scolor
color_sim = scolor_sim
color_selector = scolor_selector
color_gradient = None
color_gradient_selector = None
if self.special_colors['selection']['color']:
bgcolor = self.special_colors['selection']['color']
bgcolor_sim = self.special_colors['selection']['color_simulated']
bg_selector = SchemeSelectors("selection", "selection")

return SchemeColors(
color, color_sim, bgcolor, bgcolor_sim, style,
color_selector, bg_selector, style_selectors
color, color_sim, bgcolor, bgcolor_sim, style, color_gradient,
color_selector, bg_selector, style_selectors, color_gradient_selector
)
2 changes: 1 addition & 1 deletion messages.json
@@ -1,3 +1,3 @@
{
"2.9.0": "messages/recent.md"
"2.10.0": "messages/recent.md"
}
2 changes: 1 addition & 1 deletion messages/recent.md
@@ -1,4 +1,4 @@
# RegReplace 2.9.0
# RegReplace 2.10.0

New release!

Expand Down
25 changes: 13 additions & 12 deletions mkdocs.yml
Expand Up @@ -7,19 +7,26 @@ copyright: |
Copyright &copy; 2012 - 2017 <a href="https://github.com/facelessuser">Isaac Muse</a>
<br><span class="md-footer-custom-text">emoji provided free by </span><a href="http://www.emojione.com">EmojiOne</a>
docs_dir: docs/src/markdown
theme:
name: material
custom_dir: docs/theme
palette:
primary: blue
accent: blue
logo:
icon: description
font:
text: Roboto
code: Roboto Mono

pages:
- About ScopeHunter: index.md
- Installation: installation.md
- User Guide: usage.md
- Contributing &amp; Support: contributing.md
- License: license.md

docs_dir: docs/src/markdown

theme: material
docs_dir: docs/src/markdown
theme_dir: docs/theme

markdown_extensions:
- markdown.extensions.toc:
slugify: !!python/name:pymdownx.slugs.uslugify
Expand Down Expand Up @@ -65,12 +72,6 @@ markdown_extensions:
- pymdownx.details:

extra:
palette:
primary: blue
accent: blue
font:
text: Roboto
code: Roboto Mono
social:
- type: github
link: https://github.com/facelessuser
Expand Down
30 changes: 26 additions & 4 deletions popup.j2
Expand Up @@ -24,6 +24,16 @@
**fg (simulated alpha):**{: .keyword} {{plugin.fg_sim_preview}} {{plugin.fg_sim_color}}
[(copy)](copy-fg-sim:{{plugin.fg_sim_index}}){: .small}

{% endif %}
{% if plugin.fg_hash %}
**hashed fg:**{: .keyword} {{plugin.fg_hash_preview}} {{plugin.fg_hash_color}}
[(copy)](copy-fg-hash:{{plugin.fg_hash_index}}){: .small}

{% endif %}
{% if plugin.fg_hash_sim %}
**hashed fg (simulated alpha):**{: .keyword} {{plugin.fg_hash_sim_preview}} {{plugin.fg_hash_sim_color}}
[(copy)](copy-fg-hash-sim:{{plugin.fg_hash_sim_index}}){: .small}

{% endif %}
**bg:**{: .keyword} {{plugin.bg_preview}} {{plugin.bg_color}}
[(copy)](copy-bg:{{plugin.bg_index}}){: .small}
Expand All @@ -48,6 +58,16 @@
**fg scope:**{: .keyword} {{plugin.fg_scope}}
[(copy)](copy-fg-sel-scope:{{plugin.fg_scope_index}}){: .small}

{% if plugin.fg_hash_name %}
**hashed fg name:**{: .keyword} {{plugin.fg_hash_name}}
[(copy)](copy-fg-hash-sel-name:{{plugin.fg_hash_name_index}}){: .small}

{% endif %}
{% if plugin.fg_hash_scope %}
**hashed fg scope:**{: .keyword} {{plugin.fg_hash_scope}}
[(copy)](copy-fg-hash-sel-scope:{{plugin.fg_hash_scope_index}}){: .small}

{% endif %}
{% if plugin.bg_name %}
**bg name:**{: .keyword} {{plugin.bg_name}}
[(copy)](copy-bg-sel-name:{{plugin.bg_name_index}}){: .small}
Expand Down Expand Up @@ -83,12 +103,14 @@
**syntax:**{: .keyword} [{{plugin.syntax}}](syntax)
[(copy)](copy-syntax:{{plugin.syntax_index}}){: .small}

**scheme:**{: .keyword} [{{plugin.scheme}}](scheme)
{% if plugin.scheme %}
**tmTheme:**{: .keyword} [{{plugin.scheme}}](scheme)
[(copy)](copy-scheme:{{plugin.scheme_index}}){: .small}
{% endif %}

{% for item in plugin.overrides %}
**override scheme {{loop.index}}:**{: .keyword} [{{item}}](override:{{plugin.overrides_index}}:{{loop.index}})
{% for item in plugin.overrides %}
**Scheme {{loop.index}}:**{: .keyword} [{{item}}](override:{{plugin.overrides_index}}:{{loop.index}})
[(copy)](copy-overrides:{{plugin.overrides_index}}:{{loop.index}}){: .small}

{% endfor %}
{% endfor %}
{% endif %}

0 comments on commit 994eb2f

Please sign in to comment.