915 lines
39 KiB
Python
915 lines
39 KiB
Python
import sublime
|
|
import sublime_plugin
|
|
from os import path
|
|
import tempfile
|
|
import sys
|
|
import time
|
|
import webbrowser
|
|
import re
|
|
from HtmlAnnotations import get_annotations
|
|
import ExportHtmlLib.desktop as desktop
|
|
import json
|
|
|
|
PACKAGE_SETTINGS = "ExportHtml.sublime-settings"
|
|
JS_DIR = path.join(sublime.packages_path(), 'ExportHtml', "js")
|
|
CSS_DIR = path.join(sublime.packages_path(), 'ExportHtml', "css")
|
|
|
|
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
|
|
|
|
FILTER_MATCH = re.compile(r'^(?:(brightness|saturation|hue|colorize)\((-?[\d]+|[\d]*\.[\d]+)\)|(sepia|grayscale|invert))$')
|
|
|
|
# HTML Code
|
|
HTML_HEADER = \
|
|
'''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
|
<html>
|
|
<head>
|
|
<title>%(title)s</title>
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
<meta http-equiv="Expires" content="0" />
|
|
<style type="text/css">
|
|
%(css)s
|
|
</style>
|
|
%(js)s
|
|
</head>
|
|
'''
|
|
|
|
TOOL_GUTTER = '''<img onclick="toggle_gutter();" alt="" title="Toggle Gutter" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AofFg8FBLseHgAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAAM5JREFUOMvdjzFqAlEQhuephZWNYEq9wJ4kt7C08AiyhWCXQI6xh5CtUqVJEbAU1kA6i92VzWPnmxR5gUUsfAQbf5himPm+YURumSzLetFQWZZj4Bn45DcHYFMUxfAqAbA1MwO+gHeA0L9cJfDeJ6q6yvO8LyKiqosgOKVp6qJf8t4nQfD9J42Kqi6D4DUabppmBhzNzNq2fYyC67p+AHbh+iYKrqpqAnwE+Ok/8Dr6b+AtwArsu6Wq8/P9wQXHTETEOdcTkWl3YGYjub/8ANrnvguZ++ozAAAAAElFTkSuQmCC" />'''
|
|
|
|
TOOL_PLAIN_TEXT = '''<img onclick="toggle_plain_text();" alt="" title="Toggle Plain" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AofFg8dF9eGSAAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAANRJREFUOMvdkTFOgkEQhResaeQScAsplIRGDgKttnsIOYIUm1hQa/HfQiMFcIBt9k/UTWa/sRkbspKfzviS7eZ7M/uec39WbdsOgU/gI6V0ebZBKeVeTaWUu7PgpmkugD3wZW8XQuh3NhCRuaoq8AisVVVF5LazAfBi0JWITMzsuROccx4b8O6cc977HrAFyDmPumxfHQf3EyjwcBKOMQ6ApL8ISDHGwanqljb4VLlsY5ctqrD99c3Cm1aamZn5q/e+V6vuxgb2tc5DCH3gYAuu3f/RNzmJ99G3cZ53AAAAAElFTkSuQmCC" />'''
|
|
|
|
TOOL_PRINT = '''<img onclick="page_print();" alt="" title="Print" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AofFhAl8o8wSAAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAAQZJREFUOMulkzFSgjEUhL8X/xJ/iBegkAtQ6A08gIUehFthYykOw9BzBmwcaxGoZW1eZjIx/sC4TTJv8ja7mxfDIcmAAWDUIeDLzJQXm2w/AFZOkB8yoAU+gHtJ7yVJUnAlaS1pJOm6WN8kjSUtJQ1dLQChIvMATIEXXw9e3wMT4NnV/rKQsAcegAvgG9g5wcwv7OU51QgugSegD2yBO+DWmyLw+leINQXyW5VeoQj4qIIcW+CxPFwjSLKtEnAZOkGSSYruL3QMUxq0AERJUZKl5pV7bjsmMR+qHfAJ3DReDB4cwKLiv0R0S5Yy6ANz37ecgSZ7nui1zYm9G0B2wi+k63fyX/wA0b9vjF8iB3oAAAAASUVORK5CYII=" />'''
|
|
|
|
TOOL_ANNOTATION = '''<img onclick="toggle_annotations();" alt="" title="Toggle Annotations" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AofFhAIt1BsPQAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAALVJREFUOMvNkkESgjAUQ/Or+8IN9A7ewHN7FRgvwIB7eW6+WGsRRjZm05n+Jn+aRNoIyy8Ak1QVZkjqzYyiQEKsJF0kxUxgkHSW1Eu6mdn9bStwABqgA0Y+MfqsBU7ALie3M8SS0NU5JqD2zWvIqUgD1MF9iCVDF8yPkixsjTF4PIOfa/Hi/GhiO5mYJHH0mL4ROzdvIu+TAoW8ddm30iJNjTQgevOqpMLPx8NilWe6X3z8n3gAfmBJ5rRJVyQAAAAASUVORK5CYII=" />'''
|
|
|
|
TOOL_DUMP_THEME = '''<img onclick="dump_theme();" alt="" title="Download" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AofFhAWTV9RXgAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAAJtJREFUOMvdk9ENwyAQQ5+rDBA6QZbI/gN0h3YE2gXi/lykhABN1b9aQkh3+CEwiEK2BYyAyhbwlORtceCoEbgBqahnYI65C1CYr43eThd+1B8Ahkp0qXZZa8/2LlIFIG2i676DmDMwS8pDcZzW7tt4DbwOr8/2ZPthe3FbS6yZ4thfQdrmE5DP5g7kvLkCucdomtWDRJzUvvGqN6JK1cOooSjlAAAAAElFTkSuQmCC" />'''
|
|
|
|
TOOL_WRAPPING = '''<img onclick="toggle_wrapping();" alt="" title="Toggle Wrapping" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AsBFiYl9jWoIQAAAAxpVFh0Q29tbWVudAAAAAAAvK6ymQAAAP1JREFUOMudk0FuwkAMRZ+jbCGZXADlKvS8HKG9Qa8Q1K5LSLpG+ixwUmdKQMLSSDOeb/v7e8bITJIBNWD5FTCYmaLT7gTWwDtQZQlG4A0YYiILwTvgwxNsgV+vOuEm3wDsga+ZjaQkqZN0kdT7vpXU+Grd1zumk5Rm6g44STr6PjmriEl+d3RsK8li9T/nimXFOkmp8P4mwcZc5YXit7vRjxVgBQ/MK1aPWBWu9Jw1A9c+mZ0nW7AFdLevwKCR9BPE/SdiaWaSNADntdb9jXz6eQt8L15lGFM+vsarRevjtMqg7pkXrHghZiFs+QS8xmwDHIC9PXsHK197/t5XQswlGeOCYgkAAAAASUVORK5CYII=" />'''
|
|
|
|
TOOLBAR = '''<div id="toolbarhide"><div id="toolbar">%(options)s</div></div>'''
|
|
|
|
ANNOTATE_OPEN = '''<span onclick="toggle_annotations();" class="tooltip_hotspot" onmouseover="tooltip.show(%(comment)s);" onmouseout="tooltip.hide();">%(code)s'''
|
|
|
|
ANNOTATE_CLOSE = '''</span>'''
|
|
|
|
BODY_START = '''<body class="code_page code_text"><pre class="code_page">'''
|
|
|
|
FILE_INFO = '''<tr><td colspan="2" style="background: %(bgcolor)s"><div id="file_info"><span style="color: %(color)s">%(date_time)s %(file)s\n\n</span></div></td></tr>'''
|
|
|
|
TABLE_START = '''<table cellspacing="0" cellpadding="0" class="code_page">'''
|
|
|
|
LINE = (
|
|
'<tr>' +
|
|
'<td valign="top" id="L_%(table)d_%(line_id)d" class="code_text code_gutter" style="background: %(bgcolor)s">' +
|
|
'<span style="color: %(color)s;">%(line)s </span>' +
|
|
'</td>' +
|
|
'<td valign="top" class="code_text code_line" style="background-color: %(pad_color)s;">' +
|
|
'<div id="C_%(table)d_%(code_id)d">%(code)s\n</div>' +
|
|
'</td>' +
|
|
'</tr>'
|
|
)
|
|
|
|
CODE = '''<span class="%(class)s" style="background-color: %(highlight)s; color: %(color)s;">%(content)s</span>'''
|
|
ANNOTATION_CODE = '''<span style="background-color: %(highlight)s;"><a href="javascript:void();" class="annotation"><span class="%(class)s annotation" style="color: %(color)s;">%(content)s</span></a></span>'''
|
|
|
|
TABLE_END = '''</table>'''
|
|
|
|
ROW_START = '''<tr><td>'''
|
|
|
|
ROW_END = '''</td></tr>'''
|
|
|
|
DIVIDER = '''<span style="color: %(color)s">\n...\n\n</span>'''
|
|
|
|
ANNOTATION_TBL_START = (
|
|
'<div id="comment_list" style="display:none"><div id="comment_wrapper">' +
|
|
'<table id="comment_table">' +
|
|
'<tr><th>Line/Col</th><th>Comments' +
|
|
'<a href="javascript:void(0)" class="table_close" onclick="toggle_annotations();return false;">(close)</a>'
|
|
'</th></tr>'
|
|
)
|
|
|
|
ANNOTATION_TBL_END = '''</table></div></div>'''
|
|
|
|
ANNOTATION_ROW = (
|
|
'<tr>' +
|
|
'<td class="annotation_link">' +
|
|
'<a href="javascript:void(0)" onclick="scroll_to_line(\'C_%(table)d_%(row)d\');return false;">%(link)s</a>' +
|
|
'</td>' +
|
|
'<td class="annotation_comment"><div class="annotation_comment">%(comment)s</div></td>' +
|
|
'<tr>'
|
|
)
|
|
|
|
ANNOTATION_FOOTER = (
|
|
'<tr><td colspan=2>' +
|
|
'<div class="table_footer"><label>Position </label>' +
|
|
'<select id="dock" size="1" onchange="dock_table();">' +
|
|
'<option value="0" selected="selected">center</option>' +
|
|
'<option value="1">top</option>' +
|
|
'<option value="2">bottom</option>' +
|
|
'<option value="3">left</option>' +
|
|
'<option value="4">right</option>' +
|
|
'<option value="5">top left</option>' +
|
|
'<option value="6">top right</option>' +
|
|
'<option value="7">bottom left</option>' +
|
|
'<option value="8">bottom right</option>' +
|
|
'</select>' +
|
|
'</div>' +
|
|
'</td></tr>'
|
|
)
|
|
|
|
BODY_END = '''</pre>%(toolbar)s\n%(js)s\n</body>\n</html>\n'''
|
|
|
|
INCLUDE_THEME = \
|
|
'''
|
|
<script type="text/javascript">
|
|
%(jscode)s
|
|
plist.color_scheme = %(theme)s;
|
|
|
|
function dump_theme() {
|
|
extract_theme('%(name)s');
|
|
}
|
|
</script>
|
|
'''
|
|
|
|
TOGGLE_LINE_OPTIONS = \
|
|
'''
|
|
<script type="text/javascript">
|
|
%(jscode)s
|
|
|
|
page_line_info.wrap = false;
|
|
page_line_info.ranges = [%(ranges)s];
|
|
page_line_info.wrap_size = %(wrap_size)d;
|
|
page_line_info.tables = %(tables)s;
|
|
page_line_info.header = %(header)s;
|
|
page_line_info.gutter = %(gutter)s;
|
|
</script>
|
|
'''
|
|
|
|
AUTO_PRINT = \
|
|
'''
|
|
<script type="text/javascript">
|
|
document.getElementsByTagName('body')[0].onload = function (e) { page_print(); self.onload = null; };
|
|
</script>
|
|
'''
|
|
|
|
WRAP = \
|
|
'''
|
|
<script type="text/javascript">
|
|
toggle_wrapping();
|
|
</script>
|
|
'''
|
|
|
|
HTML_JS_WRAP = \
|
|
'''
|
|
<script type="text/javascript">
|
|
%(jscode)s
|
|
</script>
|
|
'''
|
|
|
|
|
|
def getjs(file_name):
|
|
code = ""
|
|
try:
|
|
with open(path.join(JS_DIR, file_name), "r") as f:
|
|
code = f.read()
|
|
except:
|
|
pass
|
|
return code
|
|
|
|
|
|
def getcss(file_name, options):
|
|
code = ""
|
|
final_code = ""
|
|
last_pt = 0
|
|
keys = '|'.join(options.keys())
|
|
replace = re.compile("/\\* *%(" + keys + ")% * \\*/")
|
|
|
|
try:
|
|
with open(path.join(CSS_DIR, file_name), "r") as f:
|
|
code = f.read()
|
|
for m in replace.finditer(code):
|
|
final_code += code[last_pt:m.start()] + options[m.group(1)]
|
|
last_pt = m.end()
|
|
final_code += code[last_pt:]
|
|
except:
|
|
pass
|
|
|
|
return final_code
|
|
|
|
|
|
class ExportHtmlPanelCommand(sublime_plugin.WindowCommand):
|
|
def execute(self, value):
|
|
if value >= 0:
|
|
view = self.window.active_view()
|
|
if view != None:
|
|
ExportHtml(view).run(**self.args[value])
|
|
|
|
def run(self):
|
|
options = sublime.load_settings(PACKAGE_SETTINGS).get("html_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 ExportHtmlCommand(sublime_plugin.WindowCommand):
|
|
def run(self, **kwargs):
|
|
view = self.window.active_view()
|
|
if view != None:
|
|
ExportHtml(view).run(**kwargs)
|
|
|
|
|
|
class ExportHtml(object):
|
|
def __init__(self, view):
|
|
self.view = view
|
|
|
|
def process_inputs(self, **kwargs):
|
|
return {
|
|
"numbers": bool(kwargs.get("numbers", False)),
|
|
"highlight_selections": bool(kwargs.get("highlight_selections", False)),
|
|
"browser_print": bool(kwargs.get("browser_print", False)),
|
|
"color_scheme": kwargs.get("color_scheme", None),
|
|
"wrap": kwargs.get("wrap", None),
|
|
"multi_select": bool(kwargs.get("multi_select", False)),
|
|
"style_gutter": bool(kwargs.get("style_gutter", True)),
|
|
"no_header": bool(kwargs.get("no_header", False)),
|
|
"date_time_format": kwargs.get("date_time_format", "%m/%d/%y %I:%M:%S"),
|
|
"show_full_path": bool(kwargs.get("show_full_path", True)),
|
|
"toolbar": kwargs.get("toolbar", ["plain_text", "gutter", "wrapping", "print", "annotation", "theme"]),
|
|
"save_location": kwargs.get("save_location", None),
|
|
"time_stamp": kwargs.get("time_stamp", "_%m%d%y%H%M%S"),
|
|
"clipboard_copy": bool(kwargs.get("clipboard_copy", False)),
|
|
"view_open": bool(kwargs.get("view_open", False)),
|
|
"shift_brightness": bool(kwargs.get("shift_brightness", False)),
|
|
"filter": kwargs.get("filter", "")
|
|
}
|
|
|
|
def setup(self, **kwargs):
|
|
path_packages = sublime.packages_path()
|
|
|
|
# Get get general document preferences from sublime preferences
|
|
eh_settings = sublime.load_settings(PACKAGE_SETTINGS)
|
|
settings = sublime.load_settings('Preferences.sublime-settings')
|
|
alternate_font_size = eh_settings.get("alternate_font_size", False)
|
|
alternate_font_face = eh_settings.get("alternate_font_face", False)
|
|
self.font_size = settings.get('font_size', 10) if alternate_font_size == False else alternate_font_size
|
|
self.font_face = settings.get('font_face', 'Consolas') if alternate_font_face == False else alternate_font_face
|
|
self.tab_size = settings.get('tab_size', 4)
|
|
self.padd_top = settings.get('line_padding_top', 0)
|
|
self.padd_bottom = settings.get('line_padding_bottom', 0)
|
|
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.date_time_format = kwargs["date_time_format"]
|
|
self.time = time.localtime()
|
|
self.show_full_path = kwargs["show_full_path"]
|
|
self.highlight_selections = kwargs["highlight_selections"]
|
|
self.browser_print = kwargs["browser_print"]
|
|
self.auto_wrap = kwargs["wrap"] != None and int(kwargs["wrap"]) > 0
|
|
self.wrap = 900 if not self.auto_wrap else int(kwargs["wrap"])
|
|
self.hl_continue = None
|
|
self.curr_hl = None
|
|
self.sels = []
|
|
self.multi_select = self.check_sel() if kwargs["multi_select"] and not kwargs["highlight_selections"] else False
|
|
self.size = self.view.size()
|
|
self.pt = 0
|
|
self.end = 0
|
|
self.curr_row = 0
|
|
self.tables = 0
|
|
self.curr_annot = None
|
|
self.curr_comment = None
|
|
self.annotations = self.get_annotations()
|
|
self.annot_num = -1
|
|
self.new_annot = False
|
|
self.open_annot = False
|
|
self.no_header = kwargs["no_header"]
|
|
self.annot_tbl = []
|
|
self.toolbar = kwargs["toolbar"]
|
|
self.toolbar_orientation = "block" if eh_settings.get("toolbar_orientation", "horizontal") == "vertical" else "inline-block"
|
|
self.matched = {}
|
|
self.ebground = self.bground
|
|
self.dark_lumens = None
|
|
self.lumens_limit = float(eh_settings.get("bg_min_lumen_threshold", 62))
|
|
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))
|
|
|
|
fname = self.view.file_name()
|
|
if fname == None or not path.exists(fname):
|
|
fname = "Untitled"
|
|
self.file_name = fname
|
|
|
|
# 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.scheme_file = path.basename(colour_scheme)
|
|
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'), True, True)
|
|
self.fground = self.strip_transparency(colour_settings.get("foreground", '#000000'))
|
|
self.sbground = self.strip_transparency(colour_settings.get("selection", self.fground), True)
|
|
self.sfground = self.strip_transparency(colour_settings.get("selectionForeground", None))
|
|
self.gbground = self.strip_transparency(colour_settings.get("gutter", self.bground)) if kwargs["style_gutter"] else self.bground
|
|
self.gfground = self.strip_transparency(colour_settings.get("gutterForeground", self.fground), True) if kwargs["style_gutter"] else self.fground
|
|
|
|
self.highlights = []
|
|
if self.highlight_selections:
|
|
for sel in self.view.sel():
|
|
if not sel.empty():
|
|
self.highlights.append(sel)
|
|
|
|
# Create scope colors mapping from color scheme file
|
|
self.colours = {self.view.scope_name(self.end).split(' ')[0]: {"color": self.fground, "bgcolor": None, "style": None}}
|
|
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)
|
|
bgcolour = item['settings'].get('background', 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 or bgcolour != None):
|
|
self.colours[scope] = {
|
|
"color": self.strip_transparency(colour),
|
|
"bgcolor": self.strip_transparency(bgcolour, True),
|
|
"style": style
|
|
}
|
|
|
|
self.shift_brightness = kwargs["shift_brightness"] and self.dark_lumens is not None and self.dark_lumens < self.lumens_limit
|
|
if self.shift_brightness:
|
|
self.color_adjust()
|
|
|
|
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 color_adjust(self):
|
|
factor = 1 + ((self.lumens_limit - self.dark_lumens) / 255.0) if self.shift_brightness else None
|
|
for k, v in self.colours.items():
|
|
fg, bg = v["color"], v["bgcolor"]
|
|
if v["color"] is not None:
|
|
self.colours[k]["color"] = self.apply_color_change(v["color"], factor)
|
|
if v["bgcolor"] is not None:
|
|
self.colours[k]["bgcolor"] = self.apply_color_change(v["bgcolor"], factor)
|
|
self.bground = self.apply_color_change(self.bground, factor)
|
|
self.fground = self.apply_color_change(self.fground, factor)
|
|
self.sbground = self.apply_color_change(self.sbground, factor)
|
|
if self.sfground is not None:
|
|
self.sfground = self.apply_color_change(self.sfground, factor)
|
|
self.gbground = self.apply_color_change(self.gbground, factor)
|
|
self.gfground = self.apply_color_change(self.gfground, factor)
|
|
|
|
def apply_color_change(self, color, shift_factor):
|
|
rgba = RGBA(color)
|
|
if shift_factor is not None:
|
|
rgba.brightness(shift_factor)
|
|
return rgba.get_rgb()
|
|
|
|
def get_tools(self, tools, use_annotation, use_wrapping):
|
|
toolbar_options = {
|
|
"gutter": TOOL_GUTTER,
|
|
"print": TOOL_PRINT,
|
|
"plain_text": TOOL_PLAIN_TEXT,
|
|
"annotation": TOOL_ANNOTATION if use_annotation else "",
|
|
"theme": TOOL_DUMP_THEME,
|
|
"wrapping": TOOL_WRAPPING if use_wrapping else ""
|
|
}
|
|
t_opt = ""
|
|
toolbar_element = ""
|
|
|
|
if len(tools):
|
|
for t in tools:
|
|
if t in toolbar_options:
|
|
t_opt += toolbar_options[t]
|
|
toolbar_element = TOOLBAR % {"options": t_opt}
|
|
return toolbar_element
|
|
|
|
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")
|
|
if track_darkness:
|
|
lumens = rgba.luminance()
|
|
if self.dark_lumens is None or lumens < self.dark_lumens:
|
|
self.dark_lumens = lumens
|
|
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 self.highlight_selections 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 print_line(self, line, num):
|
|
html_line = LINE % {
|
|
"line_id": num,
|
|
"color": self.gfground,
|
|
"bgcolor": self.gbground,
|
|
"line": str(num).rjust(self.gutter_pad).replace(" ", ' '),
|
|
"code_id": num,
|
|
"code": line,
|
|
"table": self.tables,
|
|
"pad_color": self.ebground or self.bground
|
|
}
|
|
|
|
return html_line
|
|
|
|
def guess_colour(self, pt, the_key):
|
|
the_colour = self.fground
|
|
the_bgcolour = None
|
|
the_style = set([])
|
|
if the_key in self.matched:
|
|
the_colour = self.matched[the_key]["color"]
|
|
the_style = self.matched[the_key]["style"]
|
|
the_bgcolour = self.matched[the_key]["bgcolor"]
|
|
else:
|
|
best_match_bg = 0
|
|
best_match_fg = 0
|
|
best_match_style = 0
|
|
for key in self.colours:
|
|
match = self.view.score_selector(pt, key)
|
|
if self.colours[key]["color"] is not None and match > best_match_fg:
|
|
best_match_fg = match
|
|
the_colour = self.colours[key]["color"]
|
|
if self.colours[key]["style"] is not None and match > best_match_style:
|
|
best_match_style = match
|
|
for s in self.colours[key]["style"]:
|
|
the_style.add(s)
|
|
if self.colours[key]["bgcolor"] is not None and match > best_match_bg:
|
|
best_match_bg = match
|
|
the_bgcolour = self.colours[key]["bgcolor"]
|
|
self.matched[the_key] = {"color": the_colour, "bgcolor": the_bgcolour, "style": the_style}
|
|
if len(the_style) == 0:
|
|
the_style = "normal"
|
|
else:
|
|
the_style = ' '.join(the_style)
|
|
return the_colour, the_style, the_bgcolour
|
|
|
|
def write_header(self, the_html):
|
|
header = HTML_HEADER % {
|
|
"title": path.basename(self.file_name),
|
|
"css": getcss(
|
|
'export.css',
|
|
{
|
|
"font_size": str(self.font_size),
|
|
"font_face": '"' + self.font_face + '"',
|
|
"page_bg": self.bground,
|
|
"gutter_bg": self.gbground,
|
|
"body_fg": self.fground,
|
|
"display_mode": 'table-cell' if self.numbers else 'none',
|
|
"dot_color": self.fground,
|
|
"toolbar_orientation": self.toolbar_orientation
|
|
}
|
|
),
|
|
"js": INCLUDE_THEME % {
|
|
"jscode": getjs('plist.js'),
|
|
"theme": json.dumps(self.plist_file, sort_keys=True, indent=4, separators=(',', ': ')).encode('raw_unicode_escape'),
|
|
"name": self.scheme_file,
|
|
}
|
|
}
|
|
the_html.write(header)
|
|
|
|
def convert_view_to_html(self, the_html):
|
|
for line in self.view.split_by_newlines(sublime.Region(self.pt, self.size)):
|
|
self.size = line.end()
|
|
empty = not bool(line.size())
|
|
line = self.convert_line_to_html(the_html, empty)
|
|
the_html.write(self.print_line(line, self.curr_row))
|
|
self.curr_row += 1
|
|
|
|
def html_encode(self, text):
|
|
# Format text to HTML
|
|
encode_table = {
|
|
'&': '&',
|
|
'>': '>',
|
|
'<': '<',
|
|
'\t': ' ' * self.tab_size,
|
|
'\n': ''
|
|
}
|
|
|
|
return re.sub(
|
|
r'(?!\s($|\S))\s',
|
|
' ',
|
|
''.join(
|
|
encode_table.get(c, c) for c in text
|
|
).encode('ascii', 'xmlcharrefreplace')
|
|
)
|
|
|
|
def get_annotations(self):
|
|
annotations = get_annotations(self.view)
|
|
comments = []
|
|
for x in range(0, int(annotations["count"])):
|
|
region = annotations["annotations"]["html_annotation_%d" % x]["region"]
|
|
comments.append((region, annotations["annotations"]["html_annotation_%d" % x]["comment"]))
|
|
comments.sort()
|
|
return comments
|
|
|
|
def annotate_text(self, line, the_colour, the_bgcolour, the_style, empty):
|
|
pre_text = None
|
|
annot_text = None
|
|
post_text = None
|
|
start = None
|
|
|
|
# Pretext Check
|
|
if self.pt >= self.curr_annot.begin():
|
|
# Region starts with an annotation
|
|
start = self.pt
|
|
else:
|
|
# Region has text before annoation
|
|
pre_text = self.html_encode(self.view.substr(sublime.Region(self.pt, self.curr_annot.begin())))
|
|
start = self.curr_annot.begin()
|
|
|
|
if self.end == self.curr_annot.end():
|
|
# Region ends annotation
|
|
annot_text = self.html_encode(self.view.substr(sublime.Region(start, self.end)))
|
|
self.curr_annot = None
|
|
elif self.end > self.curr_annot.end():
|
|
# Region has text following annotation
|
|
annot_text = self.html_encode(self.view.substr(sublime.Region(start, self.curr_annot.end())))
|
|
post_text = self.html_encode(self.view.substr(sublime.Region(self.curr_annot.end(), self.end)))
|
|
self.curr_annot = None
|
|
else:
|
|
# Region ends but annotation is not finished
|
|
annot_text = self.html_encode(self.view.substr(sublime.Region(start, self.end)))
|
|
self.curr_annot = sublime.Region(self.end, self.curr_annot.end())
|
|
|
|
# Print the separate parts pre text, annotation, post text
|
|
if pre_text != None:
|
|
self.format_text(line, pre_text, the_colour, the_bgcolour, the_style, empty)
|
|
if annot_text != None:
|
|
self.format_text(line, annot_text, the_colour, the_bgcolour, the_style, empty, annotate=True)
|
|
if self.curr_annot == None:
|
|
self.curr_comment = None
|
|
if post_text != None:
|
|
self.format_text(line, post_text, the_colour, the_bgcolour, the_style, empty)
|
|
|
|
def add_annotation_table_entry(self):
|
|
row, col = self.view.rowcol(self.annot_pt)
|
|
self.annot_tbl.append(
|
|
(
|
|
self.tables, self.curr_row, "Line %d Col %d" % (row + 1, col + 1),
|
|
self.curr_comment.encode('ascii', 'xmlcharrefreplace')
|
|
)
|
|
)
|
|
self.annot_pt = None
|
|
|
|
def format_text(self, line, text, the_colour, the_bgcolour, the_style, empty, annotate=False):
|
|
if empty:
|
|
text = ' '
|
|
else:
|
|
the_style += " real_text"
|
|
|
|
if the_bgcolour is None:
|
|
the_bgcolour = self.bground
|
|
|
|
if annotate:
|
|
code = ANNOTATION_CODE % {"highlight": the_bgcolour, "color": the_colour, "content": text, "class": the_style}
|
|
else:
|
|
code = CODE % {"highlight": the_bgcolour, "color": the_colour, "content": text, "class": the_style}
|
|
|
|
if annotate:
|
|
if self.curr_annot != None and not self.open_annot:
|
|
# Open an annotation
|
|
if self.annot_pt != None:
|
|
self.add_annotation_table_entry()
|
|
if self.new_annot:
|
|
self.annot_num += 1
|
|
self.new_annot = False
|
|
code = ANNOTATE_OPEN % {"code": code, "comment": str(self.annot_num)}
|
|
self.open_annot = True
|
|
elif self.curr_annot == None:
|
|
if self.open_annot:
|
|
# Close an annotation
|
|
code += ANNOTATE_CLOSE
|
|
self.open_annot = False
|
|
else:
|
|
# Do a complete annotation
|
|
if self.annot_pt != None:
|
|
self.add_annotation_table_entry()
|
|
if self.new_annot:
|
|
self.annot_num += 1
|
|
self.new_annot = False
|
|
code = (
|
|
ANNOTATE_OPEN % {"code": code, "comment": str(self.annot_num)} +
|
|
ANNOTATE_CLOSE
|
|
)
|
|
line.append(code)
|
|
|
|
def convert_line_to_html(self, the_html, empty):
|
|
line = []
|
|
hl_done = False
|
|
|
|
# Continue highlight form last line
|
|
if self.hl_continue != None:
|
|
self.curr_hl = self.hl_continue
|
|
self.hl_continue = None
|
|
|
|
while self.end <= self.size:
|
|
# Get next highlight region
|
|
if self.highlight_selections and self.curr_hl == None and len(self.highlights) > 0:
|
|
self.curr_hl = self.highlights.pop(0)
|
|
|
|
# See if we are starting a highlight region
|
|
if self.curr_hl != None and self.pt == self.curr_hl.begin():
|
|
# 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:
|
|
# Kick out if we hit a highlight region
|
|
if self.end == self.curr_hl.end():
|
|
break
|
|
self.end += 1
|
|
if self.end < self.curr_hl.end():
|
|
if self.end >= self.size:
|
|
self.hl_continue = sublime.Region(self.end, self.curr_hl.end())
|
|
else:
|
|
self.curr_hl = sublime.Region(self.end, self.curr_hl.end())
|
|
else:
|
|
hl_done = True
|
|
if hl_done and empty:
|
|
the_colour, the_style, the_bgcolour = self.guess_colour(self.pt, scope_name)
|
|
elif self.sfground is None:
|
|
the_colour, the_style, _ = self.guess_colour(self.pt, scope_name)
|
|
the_bgcolour = self.sbground
|
|
else:
|
|
the_colour, the_style = self.sfground, "normal"
|
|
the_bgcolour = self.sbground
|
|
else:
|
|
# 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:
|
|
# Kick out if we hit a highlight region
|
|
if self.curr_hl != None and self.end == self.curr_hl.begin():
|
|
break
|
|
self.end += 1
|
|
the_colour, the_style, the_bgcolour = self.guess_colour(self.pt, scope_name)
|
|
|
|
# Get new annotation
|
|
if (self.curr_annot == None or self.curr_annot.end() < self.pt) and len(self.annotations):
|
|
self.curr_annot, self.curr_comment = self.annotations.pop(0)
|
|
self.annot_pt = self.curr_annot[0]
|
|
while self.pt > self.curr_annot[1]:
|
|
if len(self.annotations):
|
|
self.curr_annot, self.curr_comment = self.annotations.pop(0)
|
|
self.annot_pt = self.curr_annot[0]
|
|
else:
|
|
self.curr_annot = None
|
|
self.curr_comment = None
|
|
break
|
|
self.new_annot = True
|
|
self.curr_annot = sublime.Region(self.curr_annot[0], self.curr_annot[1])
|
|
|
|
region = sublime.Region(self.pt, self.end)
|
|
if self.curr_annot != None and region.intersects(self.curr_annot):
|
|
# Apply annotation within the text and format the text
|
|
self.annotate_text(line, the_colour, the_bgcolour, the_style, empty)
|
|
else:
|
|
# Normal text formatting
|
|
tidied_text = self.html_encode(self.view.substr(region))
|
|
self.format_text(line, tidied_text, the_colour, the_bgcolour, the_style, empty)
|
|
|
|
if hl_done:
|
|
# Clear highlight flags and variables
|
|
hl_done = False
|
|
self.curr_hl = None
|
|
|
|
# Continue walking through line
|
|
self.pt = self.end
|
|
self.end = self.pt + 1
|
|
|
|
# Close annotation if open at end of line
|
|
if self.open_annot:
|
|
line.append(ANNOTATE_CLOSE % {"comment": self.curr_comment})
|
|
self.open_annot = False
|
|
|
|
# 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)
|
|
_, _, self.ebground = self.guess_colour(self.pt, end_key)
|
|
|
|
# Join line segments
|
|
return ''.join(line)
|
|
|
|
def write_body(self, the_html):
|
|
processed_rows = ""
|
|
the_html.write(BODY_START)
|
|
|
|
the_html.write(TABLE_START)
|
|
if not self.no_header:
|
|
# Write file name
|
|
date_time = time.strftime(self.date_time_format, self.time)
|
|
the_html.write(
|
|
FILE_INFO % {
|
|
"bgcolor": self.bground,
|
|
"color": self.fground,
|
|
"date_time": date_time,
|
|
"file": self.file_name if self.show_full_path else path.basename(self.file_name)
|
|
}
|
|
)
|
|
|
|
the_html.write(ROW_START)
|
|
the_html.write(TABLE_START)
|
|
# 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)
|
|
processed_rows += "[" + str(self.curr_row) + ","
|
|
self.convert_view_to_html(the_html)
|
|
count += 1
|
|
self.tables = count
|
|
processed_rows += str(self.curr_row) + "],"
|
|
|
|
if count < total:
|
|
the_html.write(TABLE_END)
|
|
the_html.write(ROW_END)
|
|
the_html.write(ROW_START)
|
|
the_html.write(DIVIDER % {"color": self.fground})
|
|
the_html.write(ROW_END)
|
|
the_html.write(ROW_START)
|
|
the_html.write(TABLE_START)
|
|
else:
|
|
self.setup_print_block(self.view.sel()[0])
|
|
processed_rows += "[" + str(self.curr_row) + ","
|
|
self.convert_view_to_html(the_html)
|
|
processed_rows += str(self.curr_row) + "],"
|
|
self.tables += 1
|
|
|
|
the_html.write(TABLE_END)
|
|
the_html.write(ROW_END)
|
|
the_html.write(TABLE_END)
|
|
|
|
js_options = []
|
|
if len(self.annot_tbl):
|
|
self.add_comments_table(the_html)
|
|
js_options.append(HTML_JS_WRAP % {"jscode": getjs('annotation.js')})
|
|
|
|
# Write javascript snippets
|
|
js_options.append(HTML_JS_WRAP % {"jscode": getjs('print.js')})
|
|
js_options.append(HTML_JS_WRAP % {"jscode": getjs('plaintext.js')})
|
|
js_options.append(TOGGLE_LINE_OPTIONS % {
|
|
"jscode": getjs('lines.js'),
|
|
"wrap_size": self.wrap,
|
|
"ranges": processed_rows.rstrip(','),
|
|
"tables": self.tables,
|
|
"header": ("false" if self.no_header else "true"),
|
|
"gutter": ('true' if self.numbers else 'false')
|
|
}
|
|
)
|
|
if self.auto_wrap:
|
|
js_options.append(WRAP)
|
|
|
|
if self.browser_print:
|
|
js_options.append(AUTO_PRINT)
|
|
|
|
# Write empty line to allow copying of last line and line number without issue
|
|
the_html.write(BODY_END % {"js": ''.join(js_options), "toolbar": self.get_tools(self.toolbar, len(self.annot_tbl), self.auto_wrap)})
|
|
|
|
def add_comments_table(self, the_html):
|
|
the_html.write(ANNOTATION_TBL_START)
|
|
the_html.write(''.join([ANNOTATION_ROW % {"table": t, "row": r, "link": l, "comment": c} for t, r, l, c in self.annot_tbl]))
|
|
the_html.write(ANNOTATION_FOOTER)
|
|
the_html.write(ANNOTATION_TBL_END)
|
|
|
|
def run(self, **kwargs):
|
|
inputs = self.process_inputs(**kwargs)
|
|
self.setup(**inputs)
|
|
|
|
save_location = inputs["save_location"]
|
|
time_stamp = inputs["time_stamp"]
|
|
|
|
if save_location is not None:
|
|
fname = self.view.file_name()
|
|
if (
|
|
((fname == None or not path.exists(fname)) and save_location == ".") or
|
|
not path.exists(save_location)
|
|
or not path.isdir(save_location)
|
|
):
|
|
html_file = ".html"
|
|
save_location = None
|
|
elif save_location == ".":
|
|
html_file = "%s%s.html" % (fname, time.strftime(time_stamp, self.time))
|
|
elif fname is None or not path.exists(fname):
|
|
html_file = path.join(save_location, "Untitled%s.html" % time.strftime(time_stamp, self.time))
|
|
else:
|
|
html_file = path.join(save_location, "%s%s.html" % (path.basename(fname), time.strftime(time_stamp, self.time)))
|
|
else:
|
|
html_file = ".html"
|
|
|
|
if save_location is not None:
|
|
open_html = lambda x: open(x, "w")
|
|
else:
|
|
open_html = lambda x: tempfile.NamedTemporaryFile(delete=False, suffix=x)
|
|
|
|
with open_html(html_file) as the_html:
|
|
self.write_header(the_html)
|
|
self.write_body(the_html)
|
|
if inputs["clipboard_copy"]:
|
|
the_html.seek(0)
|
|
sublime.set_clipboard(the_html.read())
|
|
sublime.status_message("Export to HTML: copied to clipboard")
|
|
|
|
if inputs["view_open"]:
|
|
self.view.window().open_file(the_html.name)
|
|
else:
|
|
# Open in web browser; check return code, if failed try webbrowser
|
|
status = desktop.open(the_html.name, status=True)
|
|
if not status:
|
|
webbrowser.open(the_html.name, new=2)
|