353 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import sublime
 | |
| import sublime_plugin
 | |
| from os import path
 | |
| import tempfile
 | |
| import sys
 | |
| import re
 | |
| 
 | |
| PACKAGE_SETTINGS = "ExportHtml.sublime-settings"
 | |
| 
 | |
| if sublime.platform() == "linux":
 | |
|     # Try and load Linux Python2.6 lib.  Default path is for Ubuntu.
 | |
|     linux_lib = sublime.load_settings(PACKAGE_SETTINGS).get("linux_python2.6_lib", "/usr/lib/python2.6/lib-dynload")
 | |
|     if not linux_lib in sys.path and path.exists(linux_lib):
 | |
|         sys.path.append(linux_lib)
 | |
| from plistlib import readPlist
 | |
| from ExportHtmlLib.rgba.rgba import RGBA
 | |
| 
 | |
| NUMBERED_BBCODE_LINE = '[color=%(color)s]%(line)s [/color]%(code)s\n'
 | |
| 
 | |
| BBCODE_LINE = '%(code)s\n'
 | |
| 
 | |
| BBCODE_CODE = '[color=%(color)s]%(content)s[/color]'
 | |
| 
 | |
| BBCODE_ESCAPE = '[/color][color=%(color_open)s]%(content)s[/color][color=%(color_close)s]'
 | |
| 
 | |
| BBCODE_BOLD = '[b]%(content)s[/b]'
 | |
| 
 | |
| BBCODE_ITALIC = '[i]%(content)s[/i]'
 | |
| 
 | |
| POST_START = '[pre=%(bg_color)s]'
 | |
| 
 | |
| POST_END = '[/pre]\n'
 | |
| 
 | |
| BBCODE_MATCH = re.compile(r"""(\[/?)((?:code|pre|table|tr|td|th|b|i|u|sup|color|url|img|list|trac|center|quote|size|li|ul|ol|youtube|gvideo)(?:=[^\]]+)?)(\])""")
 | |
| 
 | |
| FILTER_MATCH = re.compile(r'^(?:(brightness|saturation|hue|colorize)\((-?[\d]+|[\d]*\.[\d]+)\)|(sepia|grayscale|invert))$')
 | |
| 
 | |
| 
 | |
| class ExportBbcodePanelCommand(sublime_plugin.WindowCommand):
 | |
|     def execute(self, value):
 | |
|         if value >= 0:
 | |
|             view = self.window.active_view()
 | |
|             if view != None:
 | |
|                 ExportBbcode(view).run(**self.args[value])
 | |
| 
 | |
|     def run(self):
 | |
|         options = sublime.load_settings(PACKAGE_SETTINGS).get("bbcode_panel", {})
 | |
|         menu = []
 | |
|         self.args = []
 | |
|         for opt in options:
 | |
|             k, v = opt.items()[0]
 | |
|             menu.append(k)
 | |
|             self.args.append(v)
 | |
| 
 | |
|         if len(menu):
 | |
|             self.window.show_quick_panel(
 | |
|                 menu,
 | |
|                 self.execute
 | |
|             )
 | |
| 
 | |
| 
 | |
| class ExportBbcodeCommand(sublime_plugin.WindowCommand):
 | |
|     def run(self, **kwargs):
 | |
|         view = self.window.active_view()
 | |
|         if view != None:
 | |
|             ExportBbcode(view).run(**kwargs)
 | |
| 
 | |
| 
 | |
| class ExportBbcode(object):
 | |
|     def __init__(self, view):
 | |
|         self.view = view
 | |
| 
 | |
|     def process_inputs(self, **kwargs):
 | |
|         return {
 | |
|             "numbers": bool(kwargs.get("numbers", False)),
 | |
|             "color_scheme": kwargs.get("color_scheme", None),
 | |
|             "multi_select": bool(kwargs.get("multi_select", False)),
 | |
|             "clipboard_copy": bool(kwargs.get("clipboard_copy", True)),
 | |
|             "view_open": bool(kwargs.get("view_open", False)),
 | |
|             "filter": kwargs.get("filter", "")
 | |
|         }
 | |
| 
 | |
|     def setup(self, **kwargs):
 | |
|         path_packages = sublime.packages_path()
 | |
| 
 | |
|         # Get get general document preferences from sublime preferences
 | |
|         settings = sublime.load_settings('Preferences.sublime-settings')
 | |
|         eh_settings = sublime.load_settings(PACKAGE_SETTINGS)
 | |
|         self.tab_size = settings.get('tab_size', 4)
 | |
|         self.char_limit = int(eh_settings.get("valid_selection_size", 4))
 | |
|         self.bground = ''
 | |
|         self.fground = ''
 | |
|         self.gbground = ''
 | |
|         self.gfground = ''
 | |
|         self.sbground = ''
 | |
|         self.sfground = ''
 | |
|         self.numbers = kwargs["numbers"]
 | |
|         self.hl_continue = None
 | |
|         self.curr_hl = None
 | |
|         self.sels = []
 | |
|         self.multi_select = self.check_sel() if kwargs["multi_select"] else False
 | |
|         self.size = self.view.size()
 | |
|         self.pt = 0
 | |
|         self.end = 0
 | |
|         self.curr_row = 0
 | |
|         self.empty_space = None
 | |
|         self.filter = []
 | |
|         for f in kwargs["filter"].split(";"):
 | |
|             m = FILTER_MATCH.match(f)
 | |
|             if m:
 | |
|                 if m.group(1):
 | |
|                     self.filter.append((m.group(1), float(m.group(2))))
 | |
|                 else:
 | |
|                     self.filter.append((m.group(3), 0.0))
 | |
| 
 | |
|         # Get color scheme
 | |
|         if kwargs["color_scheme"] != None:
 | |
|             alt_scheme = kwargs["color_scheme"]
 | |
|         else:
 | |
|             alt_scheme = eh_settings.get("alternate_scheme", False)
 | |
|         scheme_file = settings.get('color_scheme') if alt_scheme == False else alt_scheme
 | |
|         colour_scheme = path.normpath(scheme_file)
 | |
|         self.plist_file = self.apply_filters(readPlist(path_packages + colour_scheme.replace('Packages', '')))
 | |
|         colour_settings = self.plist_file["settings"][0]["settings"]
 | |
| 
 | |
|         # Get general theme colors from color scheme file
 | |
|         self.bground = self.strip_transparency(colour_settings.get("background", '#FFFFFF'), simple_strip=True)
 | |
|         self.fground = self.strip_transparency(colour_settings.get("foreground", '#000000'))
 | |
|         self.gbground = self.bground
 | |
|         self.gfground = self.fground
 | |
| 
 | |
|         # Create scope colors mapping from color scheme file
 | |
|         self.colours = {self.view.scope_name(self.end).split(' ')[0]: {"color": self.fground, "style": []}}
 | |
|         for item in self.plist_file["settings"]:
 | |
|             scope = item.get('scope', None)
 | |
|             colour = None
 | |
|             style = []
 | |
|             if 'scope' in item:
 | |
|                 scope = item['scope']
 | |
|             if 'settings' in item:
 | |
|                 colour = item['settings'].get('foreground', None)
 | |
|                 if 'fontStyle' in item['settings']:
 | |
|                     for s in item['settings']['fontStyle'].split(' '):
 | |
|                         if s == "bold" or s == "italic":  # or s == "underline":
 | |
|                             style.append(s)
 | |
| 
 | |
|             if scope != None and colour != None:
 | |
|                 self.colours[scope] = {"color": self.strip_transparency(colour), "style": style}
 | |
| 
 | |
|     def apply_filters(self, tmtheme):
 | |
|         def filter_color(color):
 | |
|             rgba = RGBA(color)
 | |
|             for f in self.filter:
 | |
|                 name = f[0]
 | |
|                 value = f[1]
 | |
|                 if name == "grayscale":
 | |
|                     rgba.grayscale()
 | |
|                 elif name == "sepia":
 | |
|                     rgba.sepia()
 | |
|                 elif name == "saturation":
 | |
|                     rgba.saturation(value)
 | |
|                 elif name == "invert":
 | |
|                     rgba.invert()
 | |
|                 elif name == "brightness":
 | |
|                     rgba.brightness(value)
 | |
|                 elif name == "hue":
 | |
|                     rgba.hue(value)
 | |
|                 elif name == "colorize":
 | |
|                     rgba.colorize(value)
 | |
|             return rgba.get_rgba()
 | |
| 
 | |
|         if len(self.filter):
 | |
|             general_settings_read = False
 | |
|             for settings in tmtheme["settings"]:
 | |
|                 if not general_settings_read:
 | |
|                     for k, v in settings["settings"].items():
 | |
|                         try:
 | |
|                             settings["settings"][k] = filter_color(v)
 | |
|                         except:
 | |
|                             pass
 | |
|                     general_settings_read = True
 | |
|                     continue
 | |
| 
 | |
|                 try:
 | |
|                     settings["settings"]["foreground"] = filter_color(settings["settings"]["foreground"])
 | |
|                 except:
 | |
|                     pass
 | |
|                 try:
 | |
|                     settings["settings"]["background"] = filter_color(settings["settings"]["background"])
 | |
|                 except:
 | |
|                     pass
 | |
|         return tmtheme
 | |
| 
 | |
|     def strip_transparency(self, color, track_darkness=False, simple_strip=False):
 | |
|         if color is None:
 | |
|             return color
 | |
|         rgba = RGBA(color.replace(" ", ""))
 | |
|         if not simple_strip:
 | |
|             rgba.apply_alpha(self.bground if self.bground != "" else "#FFFFFF")
 | |
|         return rgba.get_rgb()
 | |
| 
 | |
|     def setup_print_block(self, curr_sel, multi=False):
 | |
|         # Determine start and end points and whether to parse whole file or selection
 | |
|         if not multi and (curr_sel.empty() or curr_sel.size() <= self.char_limit):
 | |
|             self.size = self.view.size()
 | |
|             self.pt = 0
 | |
|             self.end = 1
 | |
|             self.curr_row = 1
 | |
|         else:
 | |
|             self.size = curr_sel.end()
 | |
|             self.pt = curr_sel.begin()
 | |
|             self.end = self.pt + 1
 | |
|             self.curr_row = self.view.rowcol(self.pt)[0] + 1
 | |
|         self.start_line = self.curr_row
 | |
| 
 | |
|         self.gutter_pad = len(str(self.view.rowcol(self.size)[0])) + 1
 | |
| 
 | |
|     def check_sel(self):
 | |
|         multi = False
 | |
|         for sel in self.view.sel():
 | |
|             if not sel.empty() and sel.size() >= self.char_limit:
 | |
|                 multi = True
 | |
|                 self.sels.append(sel)
 | |
|         return multi
 | |
| 
 | |
|     def guess_colour(self, the_key):
 | |
|         the_colour = None
 | |
|         the_style = None
 | |
|         if the_key in self.colours:
 | |
|             the_colour = self.colours[the_key]["color"]
 | |
|             the_style = self.colours[the_key]["style"]
 | |
|         else:
 | |
|             best_match = 0
 | |
|             for key in self.colours:
 | |
|                 if self.view.score_selector(self.pt, key) > best_match:
 | |
|                     best_match = self.view.score_selector(self.pt, key)
 | |
|                     the_colour = self.colours[key]["color"]
 | |
|                     the_style = self.colours[key]["style"]
 | |
|             self.colours[the_key] = {"color": the_colour, "style": the_style}
 | |
|         return the_colour, the_style
 | |
| 
 | |
|     def print_line(self, line, num):
 | |
|         if self.numbers:
 | |
|             bbcode_line = NUMBERED_BBCODE_LINE % {
 | |
|                 "color": self.gfground,
 | |
|                 "line": str(num).rjust(self.gutter_pad),
 | |
|                 "code": line
 | |
|             }
 | |
|         else:
 | |
|             bbcode_line = BBCODE_LINE % {"code": line}
 | |
| 
 | |
|         return bbcode_line
 | |
| 
 | |
|     def convert_view_to_bbcode(self, the_bbcode):
 | |
|         for line in self.view.split_by_newlines(sublime.Region(self.end, self.size)):
 | |
|             self.empty_space = None
 | |
|             self.size = line.end()
 | |
|             line = self.convert_line_to_bbcode()
 | |
|             the_bbcode.write(self.print_line(line, self.curr_row))
 | |
|             self.curr_row += 1
 | |
| 
 | |
|     def repl(self, m, the_colour):
 | |
|         return m.group(1) + (
 | |
|             BBCODE_ESCAPE % {
 | |
|                 "color_open": the_colour,
 | |
|                 "color_close": the_colour,
 | |
|                 "content": m.group(2)
 | |
|             }
 | |
|         ) + m.group(3)
 | |
| 
 | |
|     def format_text(self, line, text, the_colour, the_style):
 | |
|         text = text.replace('\t', ' ' * self.tab_size).replace('\n', '')
 | |
|         if self.empty_space != None:
 | |
|             text = self.empty_space + text
 | |
|             self.empty_space = None
 | |
|         if text.strip(' ') == '':
 | |
|             self.empty_space = text
 | |
|         else:
 | |
|             code = ""
 | |
|             text = BBCODE_MATCH.sub(lambda m: self.repl(m, the_colour), text)
 | |
|             bold = False
 | |
|             italic = False
 | |
|             for s in the_style:
 | |
|                 if s == "bold":
 | |
|                     bold = True
 | |
|                 if s == "italic":
 | |
|                     italic = True
 | |
|             code += (BBCODE_CODE % {"color": the_colour, "content": text})
 | |
|             if italic:
 | |
|                 code = (BBCODE_ITALIC % {"color": the_colour, "content": code})
 | |
|             if bold:
 | |
|                 code = (BBCODE_BOLD % {"color": the_colour, "content": code})
 | |
|             line.append(code)
 | |
| 
 | |
|     def convert_line_to_bbcode(self):
 | |
|         line = []
 | |
| 
 | |
|         while self.end <= self.size:
 | |
|             # Get text of like scope up to a highlight
 | |
|             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
 | |
|             the_colour, the_style = self.guess_colour(scope_name)
 | |
| 
 | |
|             region = sublime.Region(self.pt, self.end)
 | |
|             # Normal text formatting
 | |
|             text = self.view.substr(region)
 | |
|             self.format_text(line, text, the_colour, the_style)
 | |
| 
 | |
|             # Continue walking through line
 | |
|             self.pt = self.end
 | |
|             self.end = self.pt + 1
 | |
| 
 | |
|         # Join line segments
 | |
|         return ''.join(line)
 | |
| 
 | |
|     def write_body(self, the_bbcode):
 | |
|         the_bbcode.write(POST_START % {"bg_color": self.bground})
 | |
| 
 | |
|         # Convert view to HTML
 | |
|         if self.multi_select:
 | |
|             count = 0
 | |
|             total = len(self.sels)
 | |
|             for sel in self.sels:
 | |
|                 self.setup_print_block(sel, multi=True)
 | |
|                 self.convert_view_to_bbcode(the_bbcode)
 | |
|                 count += 1
 | |
| 
 | |
|                 if count < total:
 | |
|                     the_bbcode.write("\n" + (BBCODE_CODE % {"color": self.fground, "content": "..."}) + "\n\n")
 | |
| 
 | |
|         else:
 | |
|             self.setup_print_block(self.view.sel()[0])
 | |
|             self.convert_view_to_bbcode(the_bbcode)
 | |
| 
 | |
|         the_bbcode.write(POST_END)
 | |
| 
 | |
|     def run(self, **kwargs):
 | |
|         inputs = self.process_inputs(**kwargs)
 | |
|         self.setup(**inputs)
 | |
| 
 | |
|         delete = False if inputs["view_open"] else True
 | |
| 
 | |
|         with tempfile.NamedTemporaryFile(delete=delete, suffix='.txt') as the_bbcode:
 | |
|             self.write_body(the_bbcode)
 | |
|             if inputs["clipboard_copy"]:
 | |
|                 the_bbcode.seek(0)
 | |
|                 sublime.set_clipboard(the_bbcode.read())
 | |
|                 sublime.status_message("Export to BBCode: copied to clipboard")
 | |
| 
 | |
|         if inputs["view_open"]:
 | |
|             self.view.window().open_file(the_bbcode.name)
 |