Skip to content

Commit

Permalink
Feature/hashed color syntax (#54)
Browse files Browse the repository at this point in the history
* hashed syntax highlight support

* Fix docs

* Rework scheme merging

* Only assign gradient if available

* Update docs and bump rev
  • Loading branch information
facelessuser authored Nov 4, 2017
1 parent 91b04ae commit 10e5806
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 49 deletions.
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# ExportHtml 2.10.0

Nov 4, 2017

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

# ExportHtml 2.9.1

Oct 30, 2017
Expand Down
140 changes: 109 additions & 31 deletions lib/color_scheme_matcher.py
Original file line number Diff line number Diff line change
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
)
21 changes: 16 additions & 5 deletions lib/color_scheme_tweaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def get_tmtheme(scheme):
background = rule.get('background')
fontstyle = rule.get(FONT_STYLE)

if foreground:
if foreground and isinstance(foreground, str):
entry['settings']['foreground'] = foreground
if background:
entry['settings']['background'] = background
Expand Down Expand Up @@ -182,10 +182,21 @@ def tweak(self, scheme, filters, tmtheme=False):
).get_rgba()

for rule in scheme['rules']:
foreground, background = self._filter_colors(
self.process_color(rule.get("foreground", None)),
self.process_color(rule.get("background", None))
)
fg = rule.get("foreground", None)
bg = self.process_color(rule.get("background", None))
if isinstance(fg, list):
foreground = []
f, background = self._filter_colors((self.process_color(fg[0]) if fg else None), bg)
if f:
foreground.append(f)
for gradient in fg[1:]:
f = self._filter_colors(self.process_color(gradient), bg)[0]
if f:
foreground.append(f)
if not foreground:
foreground = None
else:
foreground, background = self._filter_colors(self.process_color(fg), bg)
if foreground is not None:
rule["foreground"] = foreground
if background is not None:
Expand Down
2 changes: 1 addition & 1 deletion messages.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"install": "messages/install.md",
"2.9.0": "messages/recent.md"
"2.10.0": "messages/recent.md"
}
2 changes: 1 addition & 1 deletion messages/recent.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ExportHtml 2.9.0
# ExportHtml 2.10.0

New release!

Expand Down
23 changes: 13 additions & 10 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,26 @@ copyright: |
Copyright &copy; 2012 - 2016 <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 ExportHtml: index.md
- Installation: installation.md
- User Guide: usage.md
- Contributing &amp; Support: contributing.md
- License: license.md

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 @@ -63,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
2 changes: 1 addition & 1 deletion support.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import webbrowser
import re

__version__ = "2.9.1"
__version__ = "2.10.0"
__pc_name__ = 'ExportHtml'

CSS = '''
Expand Down

0 comments on commit 10e5806

Please sign in to comment.