""" Sublime highlight. Licensed under MIT. Copyright (C) 2012 Andrew Gibson <agibsonsw@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------- Original code has been heavily modified by Isaac Muse <isaacmuse@gmail.com> for the `ExportHtml` project. """ import sublime import re from .st_color_scheme_matcher import ColorSchemeMatcher from .st_mapping import lang_map NEW_SCHEMES = int(sublime.version()) >= 3150 INLINE_BODY_START = '<code class="inline-highlight">' BODY_START = '<div class="highlight"><pre>' LINE = '%(code)s<br>' INLINE_LINE = '%(code)s' CODE = '<span style="color: %(color)s;%(style)s">%(content)s</span>' CODEBG = '<span style="background-color: %(highlight)s; color: %(color)s;%(style)s">%(content)s</span>' BODY_END = '</pre></div>\n' INLINE_BODY_END = '</code>' ST_LANGUAGES = ('.sublime-syntax', '.tmLanguage') class SublimeHighlight(object): """Sublime highlight.""" def __init__(self, scheme): """Initialization.""" self.view = None self.legacy_color_matcher = not NEW_SCHEMES or sublime.load_settings( 'Preferences.sublime-settings' ).get('mdpopups.legacy_color_matcher', False) if self.legacy_color_matcher: self.csm = ColorSchemeMatcher(scheme) self.fground = self.csm.get_special_color('foreground', simulate_transparency=True) self.bground = self.csm.get_special_color('background', simulate_transparency=True) def setup(self, **kwargs): """Get get general document preferences from sublime preferences.""" self.tab_size = 4 self.size = self.view.size() self.pt = 0 self.end = 0 self.curr_row = 0 # ~~~ # self.ebground = self.bground # ~~~ def setup_print_block(self, curr_sel, multi=False): """Determine start and end points and whether to parse whole file or selection.""" self.size = self.view.size() self.pt = 0 self.end = 1 self.curr_row = 1 self.start_line = self.curr_row def print_line(self, line, num): """Print the line.""" html_line = (INLINE_LINE if self.inline else LINE) % { "code": line, } return html_line def convert_view_to_html(self): """Begin conversion of the view to HTML.""" for line in self.view.split_by_newlines(sublime.Region(self.pt, self.size)): self.char_count = 0 self.size = line.end() empty = not bool(line.size()) line = self.convert_line_to_html(empty) self.html.append(self.print_line(line, self.curr_row)) self.curr_row += 1 def html_encode(self, text): """Format text to HTML.""" new_text = [] for c in text: if c == '\t': tab_size = self.tab_size - self.char_count % self.tab_size new_text.append(' ' * tab_size) self.char_count += tab_size elif c == '&': new_text.append('&') self.char_count += 1 elif c == '>': new_text.append('>') self.char_count += 1 elif c == '<': new_text.append('<') self.char_count += 1 elif c != '\n': new_text.append(c) self.char_count += 1 return re.sub( (r'(?!\s($|\S))\s' if self.inline or self.code_wrap else r'\s'), ' ', ''.join(new_text) ) def format_text(self, line, text, color, bgcolor, style, empty, annotate=False): """Format the text.""" if empty: text = ' ' css_style = '' if style: if 'bold' in style: css_style += ' font-weight: bold;' if 'italic' in style: css_style += ' font-style: italic;' if 'underline' in style: css_style += ' text-decoration: underline;' if 'glow' in style: # This won't work in `minihtml`, but here it is. css_style += ' text-shadow: 0 0 3px currentColor;' if bgcolor is None: code = CODE % { "color": color, "content": text, "style": css_style } else: code = CODEBG % { "highlight": bgcolor, "color": color, "content": text, "style": css_style } line.append(code) def convert_line_to_html(self, empty): """Convert the line to its HTML representation.""" line = [] do_highlight = self.curr_row in self.hl_lines while self.end <= self.size: # Get text of like scope scope_name = self.view.scope_name(self.pt) while self.view.scope_name(self.end) == scope_name and self.end < self.size: self.end += 1 if not self.legacy_color_matcher: color_match = self.view.style_for_scope(scope_name) color = color_match.get('foreground', self.fground) bgcolor = color_match.get('background') style = [] if color_match.get('bold', False): style.append('bold') if color_match.get('italic', False): style.append('italic') if color_match.get('underline', False): style.append('underline') if color_match.get('glow', False): style.append('glow') if do_highlight: sfg = color_match.get('selection_forground', self.defaults.get('selection_forground')) if sfg: color = sfg bgcolor = color_match.get('selection', '#0000FF') else: color_match = self.csm.guess_color(scope_name, selected=do_highlight, explicit_background=True) color = color_match.fg_simulated bgcolor = color_match.bg_simulated style = color_match.style.split(' ') region = sublime.Region(self.pt, self.end) # Normal text formatting tidied_text = self.html_encode(self.view.substr(region)) self.format_text(line, tidied_text, color, bgcolor, style, empty) # Continue walking through line self.pt = self.end self.end = self.pt + 1 # ~~~ # # Get the color for the space at the end of a line # if self.end < self.view.size(): # end_key = self.view.scope_name(self.pt) # if not self.legacy_color_matcher: # color_match = self.view.style_for_scope(end_key) # self.ebground = color_match.get('background') # else: # color_match = self.csm.guess_color(end_key, explicit_background=True) # self.ebground = color_match.bg_simulated # ~~~ # Join line segments return ''.join(line) def write_body(self): """Write the body of the HTML.""" processed_rows = "" if not self.no_wrap: self.html.append(INLINE_BODY_START if self.inline else BODY_START) # Convert view to HTML self.setup_print_block(self.view.sel()[0]) processed_rows += "[" + str(self.curr_row) + "," self.convert_view_to_html() processed_rows += str(self.curr_row) + "]," # Write empty line to allow copying of last line and line number without issue if not self.no_wrap: self.html.append(INLINE_BODY_END if self.inline else BODY_END) def set_view(self, src, lang): """Setup view for conversion.""" # Get the output panel self.view = sublime.active_window().create_output_panel('mdpopups', unlisted=True) # Let all plugins no to leave this view alone self.view.settings().set('is_widget', True) # Don't translate anything. self.view.settings().set("translate_tabs_to_spaces", False) # Don't mess with my indenting Sublime! self.view.settings().set("auto_indent", False) # Insert into the view self.view.run_command('insert', {'characters': src}) # Setup the proper syntax lang = lang.lower() user_map = sublime.load_settings('Preferences.sublime-settings').get('mdpopups.sublime_user_lang_map', {}) keys = set(list(user_map.keys()) + list(lang_map.keys())) loaded = False for key in keys: v = lang_map.get(key, (tuple(), tuple())) user_v = user_map.get(key, (tuple(), tuple())) if lang in (tuple(user_v[0]) + v[0]): for l in (tuple(user_v[1]) + v[1]): for ext in ST_LANGUAGES: sytnax_file = 'Packages/%s%s' % (l, ext) try: sublime.load_binary_resource(sytnax_file) except Exception: continue self.view.set_syntax_file(sytnax_file) loaded = True break if loaded: break if loaded: break if not loaded: # Default to plain text for ext in ST_LANGUAGES: # Just in case text one day switches to 'sublime-syntax' sytnax_file = 'Packages/Plain text%s' % ext try: sublime.load_binary_resource(sytnax_file) except Exception: continue self.view.set_syntax_file(sytnax_file) def syntax_highlight(self, src, lang, hl_lines=[], inline=False, no_wrap=False, code_wrap=False): """Syntax Highlight.""" self.set_view(src, 'text' if not lang else lang) if not self.legacy_color_matcher: self.defaults = self.view.style() self.fground = self.defaults.get('foreground', '#000000') self.bground = self.defaults.get('background', '#FFFFFF') self.inline = inline self.hl_lines = hl_lines self.no_wrap = no_wrap self.code_wrap = code_wrap self.setup() self.html = [] self.write_body() return ''.join(self.html)