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)
|