feat(SublimeText2.EditorPackages): cache packages

This commit is contained in:
Iristyle
2013-04-04 08:55:15 -04:00
parent d65666cdfc
commit c3efdad2c2
274 changed files with 26863 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
*.pyc
*.cache

View File

@@ -0,0 +1,362 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Print Color</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#FFFFFF</string>
<key>caret</key>
<string>#000000</string>
<key>foreground</key>
<string>#000000</string>
<key>invisibles</key>
<string>#3B3A32</string>
<key>lineHighlight</key>
<string>#2E2E2E22</string>
<key>selection</key>
<string>#34A7BD</string>
<key>selectionForeground</key>
<string>#FFFFFF</string>
<key>inactiveSelection</key>
<string>#9D550FAA</string>
<key>inactiveSelectionForeground</key>
<string>#6ac4d6</string>
<key>findHighlight</key>
<string>#FFE792</string>
<key>findHighlightForeground</key>
<string>#000000</string>
<key>activeGuide</key>
<string>#34A7BD</string>
<key>gutterForeground</key>
<string>#858585</string>
<key>gutter</key>
<string>#E5E5E5</string>
<key>bracketsForeground</key>
<string>#F8F8F2A5</string>
<key>bracketsOptions</key>
<string>underline</string>
<key>bracketContentsForeground</key>
<string>#F8F8F2A5</string>
<key>bracketContentsOptions</key>
<string>underline</string>
<key>tagsOptions</key>
<string>stippled_underline</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#A5A5A5</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8F8634</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number</string>
<key>scope</key>
<string>constant.numeric</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7C4FCD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Built-in constant</string>
<key>scope</key>
<string>constant.language</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7C4FCD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>User-defined constant</string>
<key>scope</key>
<string>constant.character, constant.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7C4FCD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<string>keyword</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#C70040</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<string>storage</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#C70040</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage type</string>
<key>scope</key>
<string>storage.type</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#34A7BD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class name</string>
<key>scope</key>
<string>entity.name.class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>underline</string>
<key>foreground</key>
<string>#427E00</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inherited class</string>
<key>scope</key>
<string>entity.other.inherited-class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic underline</string>
<key>foreground</key>
<string>#427E00</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function name</string>
<key>scope</key>
<string>entity.name.function</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#427E00</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function argument</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#CB6500</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag name</string>
<key>scope</key>
<string>entity.name.tag</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#C70040</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#427E00</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library function</string>
<key>scope</key>
<string>support.function</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#34A7BD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library constant</string>
<key>scope</key>
<string>support.constant</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#34A7BD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library class/type</string>
<key>scope</key>
<string>support.type, support.class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#34A7BD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library variable</string>
<key>scope</key>
<string>support.other.variable</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#C70040</string>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#F8F8F0</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid deprecated</string>
<key>scope</key>
<string>invalid.deprecated</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#7C4FCD</string>
<key>foreground</key>
<string>#F8F8F0</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>JSON String</string>
<key>scope</key>
<string>meta.structure.dictionary.json string.quoted.double.json</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8F8634</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#C70040</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#427E00</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>diff.changed</string>
<key>scope</key>
<string>markup.changed</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8F8634</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>22808317-0a5a-4b87-baea-5aeee17bf295</string>
</dict>
</plist>

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Print Grayscale</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#FFFFFF</string>
<key>caret</key>
<string>#000000</string>
<key>foreground</key>
<string>#000000</string>
<key>invisibles</key>
<string>#323232</string>
<key>lineHighlight</key>
<string>#2E2E2E22</string>
<key>selection</key>
<string>#666666</string>
<key>selectionForeground</key>
<string>#FFFFFF</string>
<key>inactiveSelection</key>
<string>#888888</string>
<key>findHighlight</key>
<string>#666666</string>
<key>findHighlightForeground</key>
<string>#000000</string>
<key>activeGuide</key>
<string>#888888</string>
<key>gutterForeground</key>
<string>#000000</string>
<key>gutter</key>
<string>#E5E5E5</string>
<key>bracketsForeground</key>
<string>#000000</string>
<key>bracketsOptions</key>
<string>underline</string>
<key>bracketContentsForeground</key>
<string>#000000</string>
<key>bracketContentsOptions</key>
<string>underline</string> -->
<key>tagsOptions</key>
<string>stippled_underline</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#A5A5A5</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage type</string>
<key>scope</key>
<string>storage.type</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#000000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class name</string>
<key>scope</key>
<string>entity.name.class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>underline</string>
<key>foreground</key>
<string>#000000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inherited class</string>
<key>scope</key>
<string>entity.other.inherited-class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic underline</string>
<key>foreground</key>
<string>#000000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function argument</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#000000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library class/type</string>
<key>scope</key>
<string>support.type, support.class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#000000</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>07379361-ce49-4e6c-a03f-26378a7a2131</string>
</dict>
</plist>

View File

@@ -0,0 +1,26 @@
[
{ "caption": "-" },
{
"caption": "Export",
"children":
[
{ "command": "export_html_panel", "caption": "HTML" },
{"command": "export_bbcode_panel", "caption": "BBCode"}
]
},
{
"caption": "Annotations",
"children":
[
{ "command": "enable_annotation_mode", "caption": "Enable Annotation Mode" },
{ "command": "disable_annotation_mode", "caption": "Disable Annotation Mode" },
{ "caption": "-" },
{ "command": "add_annotation", "caption": "Add Annotation" },
{ "command": "edit_annotation", "caption": "Edit Annotation" },
{ "command": "delete_annotations", "caption": "Delete Annotation(s)" },
{ "command": "clear_annotations", "caption": "Delete All Annotations" },
{ "command": "show_annotation_comment", "caption": "Show Annotation Comment" }
]
},
{ "caption": "-"}
]

View File

@@ -0,0 +1,35 @@
[
// Export to HTML
{
"caption": "Export to HTML: Show Export Menu",
"command": "export_html_panel"
},
{
"caption": "Export to BBCode: Show Export Menu",
"command": "export_bbcode_panel"
},
{
"caption": "Export to HTML: Toggle Annotation Mode",
"command": "toggle_annotation_html_mode"
},
{
"caption": "Export to HTML: Add Annotation",
"command": "add_annotation"
},
{
"caption": "Export to HTML: Edit Annotation",
"command": "edit_annotation"
},
{
"caption": "Export to HTML: Delete Annotation(s)",
"command": "delete_annotations"
},
{
"caption": "Export to HTML: Delete All Annotations",
"command": "clear_annotations"
},
{
"caption": "Export to HTML: Show Annotation Comment",
"command": "show_annotation_comment"
}
]

View File

@@ -0,0 +1,352 @@
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)

View File

@@ -0,0 +1,907 @@
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"><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">' +
'<span style="color: %(color)s;">%(line)s&nbsp;</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,
"line": str(num).rjust(self.gutter_pad).replace(" ", '&nbsp;'),
"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 = {
'&': '&amp;',
'>': '&gt;',
'<': '&lt;',
'\t': '&nbsp;' * self.tab_size,
' ': '&nbsp;',
'\n': ''
}
return ''.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 = '&nbsp;'
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 % {
"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)

View File

@@ -0,0 +1,180 @@
{
// By default, the current color scheme is used
// You can define a string path to an alternate default
// Color scheme here, it must be relative to the
// Example:
// "alternate_scheme": "Packages/ExportHtml/themes/Print-Color.tmTheme"
"alternate_scheme": false,
// By default, ExportHtml uses your current font_face and font_size.
// You can change this setting to always use this value. By default,
// the setting is a literal false, but you can change it to an actual
// font_face to enable this setting.
"alternate_font_face": false,
// By default, ExportHtml uses your current font_face and font_size.
// You can change this setting to always use this value. By default,
// the setting is a literal false, but you can change it to an actual
// font_size to enable this setting.
"alternate_font_size": false,
//Path to linux Python 2.6 lib
"linux_python2.6_lib": "/usr/lib/python2.6/lib-dynload",
// Minimum allowable size for a selection to be accepted for only the selection to be exproted
// Multi-select will also ignore selections whose size is less than this.
"valid_selection_size": 4,
// Scope to use for color for annotations in a Sublime Text view
"annotation_highlight_scope": "comment",
// Style to use for for annotations in a Sublime Text view
// (outline|solid)
"annotation_highlight_style": "outline",
// Orientation that the toolbar will be rendered with.
// (horizontal|vertical)
"toolbar_orientation": "horizontal",
// Define configurations for the drop down export menu
"html_panel": [
// Browser print color (selections and multi-selections allowed)
{
"Browser Print - Color": {
"numbers": true,
"wrap": 900,
"browser_print": true,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme",
"style_gutter": false
}
},
// Browser print black and white (selections and multi-selections allowed)
{
"Browser Print - Grayscale": {
"numbers": true,
"wrap": 900,
"browser_print": true,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Grayscale.tmTheme",
"style_gutter": false
}
},
// Browser print color; highlight selections(selections and multi-selections allowed)
// Background colors are disabled in browser printing by default, so they will not
// print until background color printing is enabled
{
"Browser Print - Color (Selection Highlights)": {
"numbers": true,
"wrap": 900,
"browser_print": true,
"highlight_selections": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme",
"style_gutter": false
}
},
// Browser print black and white; highlight selections(selections and multi-selections allowed)
// Background colors are disabled in browser printing by default, so they will not
// print until background color printing is enabled
{
"Browser Print - Grayscale (Selection Highlights)": {
"numbers": true,
"wrap": 900,
"browser_print": true,
"highlight_selections": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Grayscale.tmTheme",
"style_gutter": false
}
},
// Browser view color (selections and multi-selections allowed)
{
"Browser View - Color": {
"numbers": true,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme"
}
},
// Browser view black and white (selections and multi-selections allowed)
{
"Browser View - Grayscale": {
"numbers": true,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Grayscale.tmTheme"
}
},
// Browser view color; highlight selections(selections and multi-selections allowed)
{
"Browser View - Color (Selection Highlights)": {
"numbers": true,
"highlight_selections": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme"
}
},
// Browser view black and white; highlight selections(selections and multi-selections allowed)
{
"Browser View - Grayscale (Selection Highlights)": {
"numbers": true,
"highlight_selections": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Grayscale.tmTheme"
}
},
// Sublime view grayscale HTML source; (selections and multi-selections allowed)
{
"Sublime View - Color": {
"view_open": true,
"numbers": true,
"wrap": 900,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme"
}
},
// Sublime view grayscale HTML source; (selections and multi-selections allowed)
{
"Sublime View - Grayscale": {
"view_open": true,
"numbers": true,
"wrap": 900,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Grayscale.tmTheme"
}
}
],
"bbcode_panel": [
{
"To Clipboard - Format as BBCode": {
"numbers": false,
"multi_select": true
}
},
{
"To Clipboard - Format as BBCode (show gutter)": {
"numbers": true,
"multi_select": true
}
},
{
"Sublime View - Show BBCode in View": {
"numbers": false,
"multi_select": true,
"view_open": true
}
},
{
"Sublime View - Show BBCode in View (show gutter)": {
"numbers": false,
"multi_select": true,
"view_open": true
}
}
]
}

View File

@@ -0,0 +1,285 @@
#!/usr/bin/env python
"""
Simple desktop integration for Python. This module provides desktop environment
detection and resource opening support for a selection of common and
standardised desktop environments.
Copyright (C) 2005, 2006, 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
--------
Desktop Detection
-----------------
To detect a specific desktop environment, use the get_desktop function.
To detect whether the desktop environment is standardised (according to the
proposed DESKTOP_LAUNCH standard), use the is_standard function.
Opening URLs
------------
To open a URL in the current desktop environment, relying on the automatic
detection of that environment, use the desktop.open function as follows:
desktop.open("http://www.python.org")
To override the detected desktop, specify the desktop parameter to the open
function as follows:
desktop.open("http://www.python.org", "KDE") # Insists on KDE
desktop.open("http://www.python.org", "GNOME") # Insists on GNOME
Without overriding using the desktop parameter, the open function will attempt
to use the "standard" desktop opening mechanism which is controlled by the
DESKTOP_LAUNCH environment variable as described below.
The DESKTOP_LAUNCH Environment Variable
---------------------------------------
The DESKTOP_LAUNCH environment variable must be shell-quoted where appropriate,
as shown in some of the following examples:
DESKTOP_LAUNCH="kdialog --msgbox" Should present any opened URLs in
their entirety in a KDE message box.
(Command "kdialog" plus parameter.)
DESKTOP_LAUNCH="my\ opener" Should run the "my opener" program to
open URLs.
(Command "my opener", no parameters.)
DESKTOP_LAUNCH="my\ opener --url" Should run the "my opener" program to
open URLs.
(Command "my opener" plus parameter.)
Details of the DESKTOP_LAUNCH environment variable convention can be found here:
http://lists.freedesktop.org/archives/xdg/2004-August/004489.html
Other Modules
-------------
The desktop.dialog module provides support for opening dialogue boxes.
The desktop.windows module permits the inspection of desktop windows.
"""
__version__ = "0.4"
import os
import sys
# Provide suitable process creation functions.
try:
import subprocess
def _run(cmd, shell, wait):
opener = subprocess.Popen(cmd, shell=shell)
if wait: opener.wait()
return opener.pid
def _readfrom(cmd, shell):
opener = subprocess.Popen(cmd, shell=shell, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
opener.stdin.close()
return opener.stdout.read()
def _status(cmd, shell):
opener = subprocess.Popen(cmd, shell=shell)
opener.wait()
return opener.returncode == 0
except ImportError:
import popen2
def _run(cmd, shell, wait):
opener = popen2.Popen3(cmd)
if wait: opener.wait()
return opener.pid
def _readfrom(cmd, shell):
opener = popen2.Popen3(cmd)
opener.tochild.close()
opener.childerr.close()
return opener.fromchild.read()
def _status(cmd, shell):
opener = popen2.Popen3(cmd)
opener.wait()
return opener.poll() == 0
import commands
# Private functions.
def _get_x11_vars():
"Return suitable environment definitions for X11."
if not os.environ.get("DISPLAY", "").strip():
return "DISPLAY=:0.0 "
else:
return ""
def _is_xfce():
"Return whether XFCE is in use."
# XFCE detection involves testing the output of a program.
try:
return _readfrom(_get_x11_vars() + "xprop -root _DT_SAVE_MODE", shell=1).strip().endswith(' = "xfce4"')
except OSError:
return 0
def _is_x11():
"Return whether the X Window System is in use."
return os.environ.has_key("DISPLAY")
# Introspection functions.
def get_desktop():
"""
Detect the current desktop environment, returning the name of the
environment. If no environment could be detected, None is returned.
"""
if os.environ.has_key("KDE_FULL_SESSION") or \
os.environ.has_key("KDE_MULTIHEAD"):
return "KDE"
elif os.environ.has_key("GNOME_DESKTOP_SESSION_ID") or \
os.environ.has_key("GNOME_KEYRING_SOCKET"):
return "GNOME"
elif sys.platform == "darwin":
return "Mac OS X"
elif hasattr(os, "startfile"):
return "Windows"
elif _is_xfce():
return "XFCE"
# KDE, GNOME and XFCE run on X11, so we have to test for X11 last.
if _is_x11():
return "X11"
else:
return None
def use_desktop(desktop):
"""
Decide which desktop should be used, based on the detected desktop and a
supplied 'desktop' argument (which may be None). Return an identifier
indicating the desktop type as being either "standard" or one of the results
from the 'get_desktop' function.
"""
# Attempt to detect a desktop environment.
detected = get_desktop()
# Start with desktops whose existence can be easily tested.
if (desktop is None or desktop == "standard") and is_standard():
return "standard"
elif (desktop is None or desktop == "Windows") and detected == "Windows":
return "Windows"
# Test for desktops where the overriding is not verified.
elif (desktop or detected) == "KDE":
return "KDE"
elif (desktop or detected) == "GNOME":
return "GNOME"
elif (desktop or detected) == "XFCE":
return "XFCE"
elif (desktop or detected) == "Mac OS X":
return "Mac OS X"
elif (desktop or detected) == "X11":
return "X11"
else:
return None
def is_standard():
"""
Return whether the current desktop supports standardised application
launching.
"""
return os.environ.has_key("DESKTOP_LAUNCH")
# Activity functions.
def open(url, desktop=None, wait=0, status=False):
"""
Open the 'url' in the current desktop's preferred file browser. If the
optional 'desktop' parameter is specified then attempt to use that
particular desktop environment's mechanisms to open the 'url' instead of
guessing or detecting which environment is being used.
Suggested values for 'desktop' are "standard", "KDE", "GNOME", "XFCE",
"Mac OS X", "Windows" where "standard" employs a DESKTOP_LAUNCH environment
variable to open the specified 'url'. DESKTOP_LAUNCH should be a command,
possibly followed by arguments, and must have any special characters
shell-escaped.
The process identifier of the "opener" (ie. viewer, editor, browser or
program) associated with the 'url' is returned by this function. If the
process identifier cannot be determined, None is returned.
An optional 'wait' parameter is also available for advanced usage and, if
'wait' is set to a true value, this function will wait for the launching
mechanism to complete before returning (as opposed to immediately returning
as is the default behaviour).
"""
# Decide on the desktop environment in use.
desktop_in_use = use_desktop(desktop)
if desktop_in_use == "standard":
arg = "".join([os.environ["DESKTOP_LAUNCH"], commands.mkarg(url)])
return _run(arg, 1, wait)
elif desktop_in_use == "Windows":
# NOTE: This returns None in current implementations.
os.startfile(url)
return True if status else None
elif desktop_in_use == "KDE":
cmd = ["kfmclient", "exec", url]
elif desktop_in_use == "GNOME":
cmd = ["gnome-open", url]
elif desktop_in_use == "XFCE":
cmd = ["exo-open", url]
elif desktop_in_use == "Mac OS X":
cmd = ["open", url]
elif desktop_in_use == "X11" and os.environ.has_key("BROWSER"):
cmd = [os.environ["BROWSER"], url]
# Finish with an error where no suitable desktop was identified.
else:
raise OSError, "Desktop '%s' not supported (neither DESKTOP_LAUNCH nor os.startfile could be used)" % desktop_in_use
if status:
return _status(cmd, 0)
else:
return _run(cmd, 0, wait)
# vim: tabstop=4 expandtab shiftwidth=4

View File

@@ -0,0 +1,549 @@
#!/usr/bin/env python
"""
Simple desktop dialogue box support for Python.
Copyright (C) 2007, 2009 Paul Boddie <paul@boddie.org.uk>
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
--------
Opening Dialogue Boxes (Dialogs)
--------------------------------
To open a dialogue box (dialog) in the current desktop environment, relying on
the automatic detection of that environment, use the appropriate dialogue box
class:
question = desktop.dialog.Question("Are you sure?")
result = question.open()
To override the detected desktop, specify the desktop parameter to the open
function as follows:
question.open("KDE") # Insists on KDE
question.open("GNOME") # Insists on GNOME
The dialogue box options are documented in each class's docstring.
Available dialogue box classes are listed in the desktop.dialog.available
attribute.
Supported desktop environments are listed in the desktop.dialog.supported
attribute.
"""
from desktop import use_desktop, _run, _readfrom, _status
class _wrapper:
def __init__(self, handler):
self.handler = handler
class _readvalue(_wrapper):
def __call__(self, cmd, shell):
return self.handler(cmd, shell).strip()
class _readinput(_wrapper):
def __call__(self, cmd, shell):
return self.handler(cmd, shell)[:-1]
class _readvalues_kdialog(_wrapper):
def __call__(self, cmd, shell):
result = self.handler(cmd, shell).strip().strip('"')
if result:
return result.split('" "')
else:
return []
class _readvalues_zenity(_wrapper):
def __call__(self, cmd, shell):
result = self.handler(cmd, shell).strip()
if result:
return result.split("|")
else:
return []
class _readvalues_Xdialog(_wrapper):
def __call__(self, cmd, shell):
result = self.handler(cmd, shell).strip()
if result:
return result.split("/")
else:
return []
# Dialogue parameter classes.
class String:
"A generic parameter."
def __init__(self, name):
self.name = name
def convert(self, value, program):
return [value or ""]
class Strings(String):
"Multiple string parameters."
def convert(self, value, program):
return value or []
class StringPairs(String):
"Multiple string parameters duplicated to make identifiers."
def convert(self, value, program):
l = []
for v in value:
l.append(v)
l.append(v)
return l
class StringKeyword:
"A keyword parameter."
def __init__(self, keyword, name):
self.keyword = keyword
self.name = name
def convert(self, value, program):
return [self.keyword + "=" + (value or "")]
class StringKeywords:
"Multiple keyword parameters."
def __init__(self, keyword, name):
self.keyword = keyword
self.name = name
def convert(self, value, program):
l = []
for v in value or []:
l.append(self.keyword + "=" + v)
return l
class Integer(String):
"An integer parameter."
defaults = {
"width" : 40,
"height" : 15,
"list_height" : 10
}
scale = 8
def __init__(self, name, pixels=0):
String.__init__(self, name)
if pixels:
self.factor = self.scale
else:
self.factor = 1
def convert(self, value, program):
if value is None:
value = self.defaults[self.name]
return [str(int(value) * self.factor)]
class IntegerKeyword(Integer):
"An integer keyword parameter."
def __init__(self, keyword, name, pixels=0):
Integer.__init__(self, name, pixels)
self.keyword = keyword
def convert(self, value, program):
if value is None:
value = self.defaults[self.name]
return [self.keyword + "=" + str(int(value) * self.factor)]
class Boolean(String):
"A boolean parameter."
values = {
"kdialog" : ["off", "on"],
"zenity" : ["FALSE", "TRUE"],
"Xdialog" : ["off", "on"]
}
def convert(self, value, program):
values = self.values[program]
if value:
return [values[1]]
else:
return [values[0]]
class MenuItemList(String):
"A menu item list parameter."
def convert(self, value, program):
l = []
for v in value:
l.append(v.value)
l.append(v.text)
return l
class ListItemList(String):
"A radiolist/checklist item list parameter."
def __init__(self, name, status_first=0):
String.__init__(self, name)
self.status_first = status_first
def convert(self, value, program):
l = []
for v in value:
boolean = Boolean(None)
status = boolean.convert(v.status, program)
if self.status_first:
l += status
l.append(v.value)
l.append(v.text)
if not self.status_first:
l += status
return l
# Dialogue argument values.
class MenuItem:
"A menu item which can also be used with radiolists and checklists."
def __init__(self, value, text, status=0):
self.value = value
self.text = text
self.status = status
# Dialogue classes.
class Dialogue:
commands = {
"KDE" : "kdialog",
"GNOME" : "zenity",
"XFCE" : "zenity", # NOTE: Based on observations with Xubuntu.
"X11" : "Xdialog"
}
def open(self, desktop=None):
"""
Open a dialogue box (dialog) using a program appropriate to the desktop
environment in use.
If the optional 'desktop' parameter is specified then attempt to use
that particular desktop environment's mechanisms to open the dialog
instead of guessing or detecting which environment is being used.
Suggested values for 'desktop' are "standard", "KDE", "GNOME",
"Mac OS X", "Windows".
The result of the dialogue interaction may be a string indicating user
input (for Input, Password, Menu, Pulldown), a list of strings
indicating selections of one or more items (for RadioList, CheckList),
or a value indicating true or false (for Question, Warning, Message,
Error).
Where a string value may be expected but no choice is made, an empty
string may be returned. Similarly, where a list of values is expected
but no choice is made, an empty list may be returned.
"""
# Decide on the desktop environment in use.
desktop_in_use = use_desktop(desktop)
# Get the program.
try:
program = self.commands[desktop_in_use]
except KeyError:
raise OSError, "Desktop '%s' not supported (no known dialogue box command could be suggested)" % desktop_in_use
# The handler is one of the functions communicating with the subprocess.
# Some handlers return boolean values, others strings.
handler, options = self.info[program]
cmd = [program]
for option in options:
if isinstance(option, str):
cmd.append(option)
else:
value = getattr(self, option.name, None)
cmd += option.convert(value, program)
return handler(cmd, 0)
class Simple(Dialogue):
def __init__(self, text, width=None, height=None):
self.text = text
self.width = width
self.height = height
class Question(Simple):
"""
A dialogue asking a question and showing response buttons.
Options: text, width (in characters), height (in characters)
Response: a boolean value indicating an affirmative response (true) or a
negative response
"""
name = "question"
info = {
"kdialog" : (_status, ["--yesno", String("text")]),
"zenity" : (_status, ["--question", StringKeyword("--text", "text")]),
"Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]),
}
class Warning(Simple):
"""
A dialogue asking a question and showing response buttons.
Options: text, width (in characters), height (in characters)
Response: a boolean value indicating an affirmative response (true) or a
negative response
"""
name = "warning"
info = {
"kdialog" : (_status, ["--warningyesno", String("text")]),
"zenity" : (_status, ["--warning", StringKeyword("--text", "text")]),
"Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]),
}
class Message(Simple):
"""
A message dialogue.
Options: text, width (in characters), height (in characters)
Response: a boolean value indicating an affirmative response (true) or a
negative response
"""
name = "message"
info = {
"kdialog" : (_status, ["--msgbox", String("text")]),
"zenity" : (_status, ["--info", StringKeyword("--text", "text")]),
"Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]),
}
class Error(Simple):
"""
An error dialogue.
Options: text, width (in characters), height (in characters)
Response: a boolean value indicating an affirmative response (true) or a
negative response
"""
name = "error"
info = {
"kdialog" : (_status, ["--error", String("text")]),
"zenity" : (_status, ["--error", StringKeyword("--text", "text")]),
"Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]),
}
class Menu(Simple):
"""
A menu of options, one of which being selectable.
Options: text, width (in characters), height (in characters),
list_height (in items), items (MenuItem objects)
Response: a value corresponding to the chosen item
"""
name = "menu"
info = {
"kdialog" : (_readvalue(_readfrom), ["--menu", String("text"), MenuItemList("items")]),
"zenity" : (_readvalue(_readfrom), ["--list", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
MenuItemList("items")]
),
"Xdialog" : (_readvalue(_readfrom), ["--stdout", "--menubox",
String("text"), Integer("height"), Integer("width"), Integer("list_height"), MenuItemList("items")]
),
}
item = MenuItem
number_of_titles = 2
def __init__(self, text, titles, items=None, width=None, height=None, list_height=None):
"""
Initialise a menu with the given heading 'text', column 'titles', and
optional 'items' (which may be added later), 'width' (in characters),
'height' (in characters) and 'list_height' (in items).
"""
Simple.__init__(self, text, width, height)
self.titles = ([""] * self.number_of_titles + titles)[-self.number_of_titles:]
self.items = items or []
self.list_height = list_height
def add(self, *args, **kw):
"""
Add an item, passing the given arguments to the appropriate item class.
"""
self.items.append(self.item(*args, **kw))
class RadioList(Menu):
"""
A list of radio buttons, one of which being selectable.
Options: text, width (in characters), height (in characters),
list_height (in items), items (MenuItem objects), titles
Response: a list of values corresponding to chosen items (since some
programs, eg. zenity, appear to support multiple default
selections)
"""
name = "radiolist"
info = {
"kdialog" : (_readvalues_kdialog(_readfrom), ["--radiolist", String("text"), ListItemList("items")]),
"zenity" : (_readvalues_zenity(_readfrom),
["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
ListItemList("items", 1)]
),
"Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--radiolist",
String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")]
),
}
number_of_titles = 3
class CheckList(Menu):
"""
A list of checkboxes, many being selectable.
Options: text, width (in characters), height (in characters),
list_height (in items), items (MenuItem objects), titles
Response: a list of values corresponding to chosen items
"""
name = "checklist"
info = {
"kdialog" : (_readvalues_kdialog(_readfrom), ["--checklist", String("text"), ListItemList("items")]),
"zenity" : (_readvalues_zenity(_readfrom),
["--list", "--checklist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
ListItemList("items", 1)]
),
"Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--checklist",
String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")]
),
}
number_of_titles = 3
class Pulldown(Menu):
"""
A pull-down menu of options, one of which being selectable.
Options: text, width (in characters), height (in characters),
items (list of values)
Response: a value corresponding to the chosen item
"""
name = "pulldown"
info = {
"kdialog" : (_readvalue(_readfrom), ["--combobox", String("text"), Strings("items")]),
"zenity" : (_readvalue(_readfrom),
["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"),
StringPairs("items")]
),
"Xdialog" : (_readvalue(_readfrom),
["--stdout", "--combobox", String("text"), Integer("height"), Integer("width"), Strings("items")]),
}
item = unicode
number_of_titles = 2
class Input(Simple):
"""
An input dialogue, consisting of an input field.
Options: text, input, width (in characters), height (in characters)
Response: the text entered into the dialogue by the user
"""
name = "input"
info = {
"kdialog" : (_readinput(_readfrom),
["--inputbox", String("text"), String("data")]),
"zenity" : (_readinput(_readfrom),
["--entry", StringKeyword("--text", "text"), StringKeyword("--entry-text", "data")]),
"Xdialog" : (_readinput(_readfrom),
["--stdout", "--inputbox", String("text"), Integer("height"), Integer("width"), String("data")]),
}
def __init__(self, text, data="", width=None, height=None):
Simple.__init__(self, text, width, height)
self.data = data
class Password(Input):
"""
A password dialogue, consisting of a password entry field.
Options: text, width (in characters), height (in characters)
Response: the text entered into the dialogue by the user
"""
name = "password"
info = {
"kdialog" : (_readinput(_readfrom),
["--password", String("text")]),
"zenity" : (_readinput(_readfrom),
["--entry", StringKeyword("--text", "text"), "--hide-text"]),
"Xdialog" : (_readinput(_readfrom),
["--stdout", "--password", "--inputbox", String("text"), Integer("height"), Integer("width")]),
}
class TextFile(Simple):
"""
A text file input box.
Options: filename, text, width (in characters), height (in characters)
Response: any text returned by the dialogue program (typically an empty
string)
"""
name = "textfile"
info = {
"kdialog" : (_readfrom, ["--textbox", String("filename"), Integer("width", pixels=1), Integer("height", pixels=1)]),
"zenity" : (_readfrom, ["--text-info", StringKeyword("--filename", "filename"), IntegerKeyword("--width", "width", pixels=1),
IntegerKeyword("--height", "height", pixels=1)]
),
"Xdialog" : (_readfrom, ["--stdout", "--textbox", String("filename"), Integer("height"), Integer("width")]),
}
def __init__(self, filename, text="", width=None, height=None):
Simple.__init__(self, text, width, height)
self.filename = filename
# Available dialogues.
available = [Question, Warning, Message, Error, Menu, CheckList, RadioList, Input, Password, Pulldown, TextFile]
# Supported desktop environments.
supported = Dialogue.commands.keys()
# vim: tabstop=4 expandtab shiftwidth=4

View File

@@ -0,0 +1,273 @@
#!/usr/bin/env python
"""
Simple desktop window enumeration for Python.
Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
--------
Finding Open Windows on the Desktop
-----------------------------------
To obtain a list of windows, use the desktop.windows.list function as follows:
windows = desktop.windows.list()
To obtain the root window, typically the desktop background, use the
desktop.windows.root function as follows:
root = desktop.windows.root()
Each window object can be inspected through a number of methods. For example:
name = window.name()
width, height = window.size()
x, y = window.position()
child_windows = window.children()
See the desktop.windows.Window class for more information.
"""
from desktop import _is_x11, _get_x11_vars, _readfrom, use_desktop
import re
# System functions.
def _xwininfo(identifier, action):
if identifier is None:
args = "-root"
else:
args = "-id " + identifier
s = _readfrom(_get_x11_vars() + "xwininfo %s -%s" % (args, action), shell=1)
# Return a mapping of keys to values for the "stats" action.
if action == "stats":
d = {}
for line in s.split("\n"):
fields = line.split(":")
if len(fields) < 2:
continue
key, value = fields[0].strip(), ":".join(fields[1:]).strip()
d[key] = value
return d
# Otherwise, return the raw output.
else:
return s
def _get_int_properties(d, properties):
results = []
for property in properties:
results.append(int(d[property]))
return results
# Finder functions.
def find_all(name):
return 1
def find_named(name):
return name is not None
def find_by_name(name):
return lambda n, t=name: n == t
# Window classes.
# NOTE: X11 is the only supported desktop so far.
class Window:
"A window on the desktop."
_name_pattern = re.compile(r':\s+\(.*?\)\s+[-0-9x+]+\s+[-0-9+]+$')
_absent_names = "(has no name)", "(the root window) (has no name)"
def __init__(self, identifier):
"Initialise the window with the given 'identifier'."
self.identifier = identifier
# Finder methods (from above).
self.find_all = find_all
self.find_named = find_named
self.find_by_name = find_by_name
def __repr__(self):
return "Window(%r)" % self.identifier
# Methods which deal with the underlying commands.
def _get_handle_and_name(self, text):
fields = text.strip().split(" ")
handle = fields[0]
# Get the "<name>" part, stripping off the quotes.
name = " ".join(fields[1:])
if len(name) > 1 and name[0] == '"' and name[-1] == '"':
name = name[1:-1]
if name in self._absent_names:
return handle, None
else:
return handle, name
def _get_this_handle_and_name(self, line):
fields = line.split(":")
return self._get_handle_and_name(":".join(fields[1:]))
def _get_descendant_handle_and_name(self, line):
match = self._name_pattern.search(line)
if match:
return self._get_handle_and_name(line[:match.start()].strip())
else:
raise OSError, "Window information from %r did not contain window details." % line
def _descendants(self, s, fn):
handles = []
adding = 0
for line in s.split("\n"):
if line.endswith("child:") or line.endswith("children:"):
if not adding:
adding = 1
elif adding and line:
handle, name = self._get_descendant_handle_and_name(line)
if fn(name):
handles.append(handle)
return [Window(handle) for handle in handles]
# Public methods.
def children(self, all=0):
"""
Return a list of windows which are children of this window. If the
optional 'all' parameter is set to a true value, all such windows will
be returned regardless of whether they have any name information.
"""
s = _xwininfo(self.identifier, "children")
return self._descendants(s, all and self.find_all or self.find_named)
def descendants(self, all=0):
"""
Return a list of windows which are descendants of this window. If the
optional 'all' parameter is set to a true value, all such windows will
be returned regardless of whether they have any name information.
"""
s = _xwininfo(self.identifier, "tree")
return self._descendants(s, all and self.find_all or self.find_named)
def find(self, callable):
"""
Return windows using the given 'callable' (returning a true or a false
value when invoked with a window name) for descendants of this window.
"""
s = _xwininfo(self.identifier, "tree")
return self._descendants(s, callable)
def name(self):
"Return the name of the window."
d = _xwininfo(self.identifier, "stats")
# Format is 'xwininfo: Window id: <handle> "<name>"
return self._get_this_handle_and_name(d["xwininfo"])[1]
def size(self):
"Return a tuple containing the width and height of this window."
d = _xwininfo(self.identifier, "stats")
return _get_int_properties(d, ["Width", "Height"])
def position(self):
"Return a tuple containing the upper left co-ordinates of this window."
d = _xwininfo(self.identifier, "stats")
return _get_int_properties(d, ["Absolute upper-left X", "Absolute upper-left Y"])
def displayed(self):
"""
Return whether the window is displayed in some way (but not necessarily
visible on the current screen).
"""
d = _xwininfo(self.identifier, "stats")
return d["Map State"] != "IsUnviewable"
def visible(self):
"Return whether the window is displayed and visible."
d = _xwininfo(self.identifier, "stats")
return d["Map State"] == "IsViewable"
def list(desktop=None):
"""
Return a list of windows for the current desktop. If the optional 'desktop'
parameter is specified then attempt to use that particular desktop
environment's mechanisms to look for windows.
"""
root_window = root(desktop)
window_list = [window for window in root_window.descendants() if window.displayed()]
window_list.insert(0, root_window)
return window_list
def root(desktop=None):
"""
Return the root window for the current desktop. If the optional 'desktop'
parameter is specified then attempt to use that particular desktop
environment's mechanisms to look for windows.
"""
# NOTE: The desktop parameter is currently ignored and X11 is tested for
# NOTE: directly.
if _is_x11():
return Window(None)
else:
raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
def find(callable, desktop=None):
"""
Find and return windows using the given 'callable' for the current desktop.
If the optional 'desktop' parameter is specified then attempt to use that
particular desktop environment's mechanisms to look for windows.
"""
return root(desktop).find(callable)
# vim: tabstop=4 expandtab shiftwidth=4

View File

@@ -0,0 +1,173 @@
'''
RGBA
Licensed under MIT
Copyright (c) 2012 Isaac Muse <isaacmuse@gmail.com>
'''
import re
from colorsys import rgb_to_hls, hls_to_rgb, rgb_to_hsv, hsv_to_rgb
RGB_CHANNEL_SCALE = 1.0 / 255.0
HUE_SCALE = 1.0 / 360.0
def clamp(value, mn, mx):
return max(min(value, mx), mn)
class RGBA(object):
r = None
g = None
b = None
a = None
color_pattern = re.compile(r"^#(?:([A-Fa-f\d]{6})([A-Fa-f\d]{2})?|([A-Fa-f\d]{3}))")
def __init__(self, s=None):
if s is None:
s = "#000000FF"
self.r, self.g, self.b, self.a = self._split_channels(s)
def _split_channels(self, s):
def alpha_channel(alpha):
return int(alpha, 16) if alpha else 0xFF
m = self.color_pattern.match(s)
assert(m is not None)
if m.group(1):
return int(s[1:3], 16), int(s[3:5], 16), int(s[5:7], 16), alpha_channel(m.group(2))
else:
return int(s[1] * 2, 16), int(s[2] * 2, 16), int(s[3] * 2, 16), 0xFF
def get_rgba(self):
return "#%02X%02X%02X%02X" % (self.r, self.g, self.b, self.a)
def get_rgb(self):
return "#%02X%02X%02X" % (self.r, self.g, self.b)
def apply_alpha(self, background="#000000AA"):
def tx_alpha(cf, af, cb, ab):
return int(abs(cf * (af / 255.0) + cb * (ab / 255.0) * (1 - (af / 255.0)))) & 0xFF
if self.a < 0xFF:
r, g, b, a = self._split_channels(background)
self.r, self.g, self.b = (tx_alpha(self.r, self.a, r, a), tx_alpha(self.g, self.a, g, a), tx_alpha(self.b, self.a, b, a))
return self.get_rgb()
def luminance(self):
return clamp(int(round(0.299 * self.r + 0.587 * self.g + 0.114 * self.b)), 0, 255)
def tohsv(self):
return rgb_to_hsv(self.r * RGB_CHANNEL_SCALE, self.g * RGB_CHANNEL_SCALE, self.b * RGB_CHANNEL_SCALE)
def fromhsv(self, h, s, v):
r, g, b = hsv_to_rgb(h, s, v)
self.r = int(round(r * 255)) & 0xFF
self.g = int(round(g * 255)) & 0xFF
self.b = int(round(b * 255)) & 0xFF
def tohls(self):
return rgb_to_hls(self.r * RGB_CHANNEL_SCALE, self.g * RGB_CHANNEL_SCALE, self.b * RGB_CHANNEL_SCALE)
def fromhls(self, h, l, s):
r, g, b = hls_to_rgb(h, l, s)
self.r = int(round(r * 255)) & 0xFF
self.g = int(round(g * 255)) & 0xFF
self.b = int(round(b * 255)) & 0xFF
def colorize(self, deg):
h, l, s = self.tohls()
h = clamp(deg * HUE_SCALE, 0.0, 1.0)
self.fromhls(h, l, s)
def hue(self, deg):
d = deg * HUE_SCALE
h, l, s = self.tohls()
h = h + d
while h > 1.0:
h -= 1.0
while h < 0.0:
h += 1.0
self.fromhls(h, l, s)
def invert(self):
self.r ^= 0xFF
self.g ^= 0xFF
self.b ^= 0xFF
def saturation(self, factor):
h, l, s = self.tohls()
s = clamp(s * factor, 0.0, 1.0)
self.fromhls(h, l, s)
def grayscale(self):
luminance = self.luminance() & 0xFF
self.r = luminance
self.g = luminance
self.b = luminance
def sepia(self):
r = clamp(int((self.r * .393) + (self.g * .769) + (self.b * .189)), 0, 255) & 0xFF
g = clamp(int((self.r * .349) + (self.g * .686) + (self.b * .168)), 0, 255) & 0xFF
b = clamp(int((self.r * .272) + (self.g * .534) + (self.b * .131)), 0, 255) & 0xFF
self.r, self.g, self.b = r, g, b
def brightness(self, factor):
# Caculate brightness based on RGB luminance.
# Maybe HLS or HSV brightness adjustment is better?
def get_overage(c):
if c < 0.0:
o = 0.0 + c
c = 0.0
elif c > 255.0:
o = c - 255.0
c = 255.0
else:
o = 0.0
return o, c
def distribute_overage(c, o, s):
channels = len(s)
if channels == 0:
return c
parts = o / len(s)
if "r" in s and "g" in s:
c = c[0] + parts, c[1] + parts, c[2]
elif "r" in s and "b" in s:
c = c[0] + parts, c[1], c[2] + parts
elif "g" in s and "b" in s:
c = c[0], c[1] + parts, c[2] + parts
elif "r" in s:
c = c[0] + parts, c[1], c[2]
elif "g" in s:
c = c[0], c[1] + parts, c[2]
else: # "b" in s:
c = c[0], c[1], c[2] + parts
return c
channels = ["r", "g", "b"]
total_lumes = clamp(self.luminance() + (255.0 * factor) - 255.0, 0.0, 255.0)
if total_lumes == 255:
# white
self.r, self.g, self.b = 0xFF, 0xFF, 0xFF
elif total_lumes == 0:
# black
self.r, self.g, self.b = 0x00, 0x00, 0x00
else:
# Adjust Brightness
pts = (total_lumes - 0.299 * self.r - 0.587 * self.g - 0.114 * self.b)
slots = set(channels)
components = [float(self.r) + pts, float(self.g) + pts, float(self.b) + pts]
count = 0
for c in channels:
overage, components[count] = get_overage(components[count])
if overage:
slots.remove(c)
components = list(distribute_overage(components, overage, slots))
count += 1
self.r = int(round(components[0])) & 0xFF
self.g = int(round(components[1])) & 0xFF
self.b = int(round(components[2])) & 0xFF

View File

@@ -0,0 +1,262 @@
import sublime
import sublime_plugin
PACKAGE_SETTINGS = "ExportHtml.sublime-settings"
def get_highlight_style():
style_flag = 0
settings = sublime.load_settings(PACKAGE_SETTINGS)
scope = settings.get("annotation_highlight_scope", "comment")
style = settings.get("annotation_highlight_style", "outline")
if style == "outline":
style_flag |= sublime.DRAW_OUTLINED
return scope, style_flag
def clean_invalid_regions(view, annotations):
deletions = 0
for x in range(0, int(annotations["count"])):
key_name = "html_annotation_%d" % x
regions = view.get_regions(key_name)
if len(regions) and not regions[0].empty():
annotations["annotations"]["html_annotation_%d" % x]["region"] = [regions[0].begin(), regions[0].end()]
if deletions:
new_key = "html_annotation_%d" % (x - deletions)
annotations["annotations"][new_key] = annotations["annotations"][key_name]
del annotations["annotations"][key_name]
new_region = annotations["annotations"][new_key]["region"]
view.erase_regions(key_name)
scope, style = get_highlight_style()
view.add_regions(
new_key,
[sublime.Region(new_region[0], new_region[1])],
scope,
"",
style
)
else:
del annotations["annotations"]["html_annotation_%d" % x]
annotations["count"] -= 1
deletions += 1
if len(regions):
view.erase_regions(key_name)
view.settings().set("annotation_comments", annotations)
def get_annotations(view):
annotations = view.settings().get("annotation_comments", {"count": 0, "annotations": {}})
clean_invalid_regions(view, annotations)
return annotations
def clear_annotations(view):
annotations = view.settings().get("annotation_comments", {"count": 0, "annotations": {}})
for x in range(0, int(annotations["count"])):
view.erase_regions("html_annotation_%d" % x)
view.settings().set("annotation_comments", {"count": 0, "annotations": {}})
def delete_annotations(view):
annotations = view.settings().get("annotation_comments", {"count": 0, "annotations": {}})
for sel in view.sel():
for x in range(0, int(annotations["count"])):
region = annotations["annotations"]["html_annotation_%d" % x]["region"]
annotation = sublime.Region(int(region[0]), int(region[1]))
if annotation.contains(sel):
view.erase_regions("html_annotation_%d" % x)
break
clean_invalid_regions(view, annotations)
def get_annotation_comment(view):
comment = None
annotations = view.settings().get("annotation_comments", {"count": 0, "annotations": {}})
if len(view.sel()):
sel = view.sel()[0]
for x in range(0, int(annotations["count"])):
region = annotations["annotations"]["html_annotation_%d" % x]["region"]
annotation = sublime.Region(int(region[0]), int(region[1]))
if annotation.contains(sel):
comment = annotations["annotations"]["html_annotation_%d" % x]["comment"]
return comment
def is_selection_in_annotation(view, first_only=False):
mode = view.settings().get("annotation_mode", False)
selection = False
if mode:
annotations = view.settings().get("annotation_comments", {"count": 0, "annotations": {}})
for sel in view.sel():
for x in range(0, int(annotations["count"])):
region = annotations["annotations"]["html_annotation_%d" % x]["region"]
annotation = sublime.Region(int(region[0]), int(region[1]))
if annotation.contains(sel):
selection = True
break
if first_only:
break
return mode and selection
def annotations_exist(view):
mode = view.settings().get("annotation_mode", False)
found = False
if mode:
annotations = view.settings().get("annotation_comments", {"count": 0, "annotations": {}})
if int(annotations["count"]):
found = True
return mode and found
def is_selected(view):
mode = view.settings().get("annotation_mode", False)
selected = not view.sel()[0].empty()
return mode and selected
class ShowAnnotationCommentCommand(sublime_plugin.TextCommand):
def is_visible(self):
return is_selection_in_annotation(self.view)
def run(self, edit):
comment = get_annotation_comment(self.view)
if comment != None:
sublime.message_dialog("Annotation Comment:\n\n%s" % comment)
sublime.set_clipboard(comment)
class ClearAnnotationsCommand(sublime_plugin.TextCommand):
def is_visible(self):
return annotations_exist(self.view)
def run(self, edit):
clear_annotations(self.view)
class DeleteAnnotationsCommand(sublime_plugin.TextCommand):
def is_visible(self):
return is_selection_in_annotation(self.view)
def run(self, edit):
delete_annotations(self.view)
class EnableAnnotationModeCommand(sublime_plugin.TextCommand):
def is_visible(self):
return not self.view.settings().get("annotation_mode", False)
def run(self, edit):
self.view.run_command("toggle_annotation_html_mode")
class DisableAnnotationModeCommand(sublime_plugin.TextCommand):
def is_visible(self):
return self.view.settings().get("annotation_mode", False)
def run(self, edit):
self.view.run_command("toggle_annotation_html_mode")
class ToggleAnnotationHtmlModeCommand(sublime_plugin.TextCommand):
def is_enabled(self):
return not self.view.settings().get('is_widget')
def run(self, edit):
mode = False if self.view.settings().get("annotation_mode", False) else True
self.view.settings().set("annotation_mode", mode)
if mode:
self.view.settings().set("annotation_read_mode", self.view.is_read_only())
self.view.set_read_only(True)
self.view.set_status("html_annotation_mode", "Annotation Mode: ON")
else:
clear_annotations(self.view)
self.view.set_read_only(self.view.settings().get("annotation_read_mode", False))
self.view.erase_status("html_annotation_mode")
class AddAnnotationCommand(sublime_plugin.TextCommand):
def is_visible(self):
return is_selected(self.view)
def run(self, edit):
AnnotateHtml(self.view).run()
class EditAnnotationCommand(sublime_plugin.TextCommand):
def is_visible(self):
return is_selection_in_annotation(self.view, first_only=True)
def run(self, edit):
AnnotateHtml(self.view).run()
class AnnotateHtml(object):
def __init__(self, view):
self.view = view
def subset_annotation_adjust(self):
subset = None
comment = ""
parent = None
intersect = False
for k, v in self.annotations["annotations"].items():
region = sublime.Region(int(v["region"][0]), int(v["region"][1]))
if region.contains(self.sel):
subset = region
comment = v["comment"]
parent = k
break
elif region.intersects(self.sel):
intersect = True
break
if subset != None:
self.sel = subset
return comment, parent, intersect
def add_annotation(self, s, view_id, subset):
window = sublime.active_window()
view = window.active_view() if window != None else None
if s != "" and view != None and view_id == view.id():
if subset == None:
idx = self.annotations["count"]
key_name = ("html_annotation_%d" % idx)
else:
key_name = subset
self.annotations["annotations"][key_name] = {
"region": [self.sel.begin(), self.sel.end()],
"comment": s
}
if subset == None:
self.annotations["count"] += 1
self.view.settings().set("annotation_comments", self.annotations)
scope, style = get_highlight_style()
self.view.add_regions(
key_name,
[self.sel],
scope,
"",
style
)
def annotation_panel(self, default_comment, subset):
view_id = self.view.id()
self.view.window().show_input_panel(
("Annotate region (%d, %d)" % (self.sel.begin(), self.sel.end())),
default_comment,
lambda x: self.add_annotation(x, view_id=view_id, subset=subset),
None,
None
)
def run(self):
self.sel = self.view.sel()[0]
self.annotations = get_annotations(self.view)
comment, subset, intersects = self.subset_annotation_adjust()
if not intersects:
self.annotation_panel(comment, subset)
else:
sublime.error_message("Cannot have intersecting annotation regions!")

View File

@@ -0,0 +1,40 @@
[
{
"caption": "Preferences",
"mnemonic": "n",
"id": "preferences",
"children":
[
{
"caption": "Package Settings",
"mnemonic": "P",
"id": "package-settings",
"children":
[
{
"caption": "ExportHTML",
"children":
[
{
"command": "open_file",
"args": {"file": "${packages}/ExportHtml/readme.md"},
"caption": "README"
},
{ "caption": "-" },
{
"command": "open_file",
"args": {"file": "${packages}/ExportHtml/ExportHtml.sublime-settings"},
"caption": "Settings Default"
},
{
"command": "open_file",
"args": {"file": "${packages}/User/ExportHtml.sublime-settings"},
"caption": "Settings User"
}
]
}
]
}
]
}
]

View File

@@ -0,0 +1,153 @@
/* General */
* html a:hover { background: transparent; }
body { color: /* %body_fg% */; }
pre { border: 0; margin: 0; padding: 0; }
td { padding: 0; }
table { border: 0; margin: 0; padding: 0; border-collapse: collapse; empty-cells: show; }
.code_text { font: /* %font_size% */pt /* %font_face% */, "Courier", Monospace; }
.code_page { background-color: /* %page_bg% */; }
.simple_code_page { background-color: white; color: black }
.code_gutter { display: /* %display_mode% */; background-color: /* %gutter_bg% */; padding-right: 10px; }
td.code_line div { width: 100%; }
span { display: inline-block; border: 0; margin: 0; }
span.bold { font-weight: bold; }
span.italic { font-style: italic; }
span.normal { font-style: normal; }
span.underline { text-decoration:underline; }
/* Wrapping */
div.wrap {
white-space: -moz-pre-wrap; /* Mozilla */
white-space: -hp-pre-wrap; /* HP printers */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: pre-wrap; /* CSS 2.1 */
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
word-wrap: break-word; /* IE */
}
div.wrap span { display: inline; }
/* Toolbar */
div#toolbarhide {
position: fixed;
top: 0px;
right: 0px;
padding-top: 5px;
padding-right: 10px;
}
div#toolbar {
visibility: hidden;
text-align: center;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
opacity: 0;
-webkit-transform: translateZ(0); /* webkit flicker fix */
-webkit-font-smoothing: antialiased; /* webkit text rendering fix */
-webkit-transition: all .25s ease-in;
-moz-transition: all .25s ease-in;
-ms-transition: all .25s ease-in;
-o-transition: all .25s ease-in;
transition: all .25s ease-in;
background-color:black;
color: white;
}
div#toolbarhide:hover div#toolbar {
visibility: visible;
opacity: .8;
-webkit-transition: all .25s ease-out;
-moz-transition: all .25s ease-out;
-ms-transition: all .25s ease-out;
-o-transition: all .25s ease-out;
transition: all .25s ease-out;
}
div#toolbar img {
display: /* %toolbar_orientation% */ ;
vertical-align: middle;
border: 0;
cursor: pointer;
height: 16px;
width: 16px;
padding: 5px;
}
/* tooltips */
#tooltip {
border-radius: 5px 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 5px 5px rgba(0, 0, 0, 0.1);
white-space: -moz-pre-wrap; /* Mozilla */
white-space: -hp-pre-wrap; /* HP printers */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: pre-wrap; /* CSS 2.1 */
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
word-wrap: break-word; /* IE */
position:absolute; display:block;
color: black;
padding: 0.8em 1em;
background: #FFFFE0; border: solid black;
font-family: Calibri, Tahoma, Geneva, sans-serif;
font-size: 10pt;
font-weight: bold;
}
a.annotation {
color: /* %body_fg% */;
outline: none;
text-decoration: underline;
}
span.annotation{ display: inline; }
.tooltip_hotspot { cursor:pointer; }
/* Annotation Table */
div#comment_list {
visibility: hidden;
display: none;
margin: auto auto;
text-align: center;
position: fixed;
z-index: 99;
left:0px;
top: 0px;
}
div#comment_wrapper {
max-height: 200px;
overflow: auto;
border: solid black;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 5px 5px rgba(0, 0, 0, 0.1);
}
table#comment_table {
border: thin solid; border-collapse: collapse;
color: #000000; background-color: #FFFFE0; margin: auto auto;
}
div.table_footer { text-align:right; font-size: 8pt; font-weight: bold;}
a.table_close { color: black; float: right; }
a.table_close:link { text-decoration: none; }
a.table_close:active { text-decoration: none; }
a.table_close:visited { text-decoration: none; }
a.table_close:hover { text-decoration: none; }
table#comment_table th, table#comment_table td { border: thin solid; padding: 5px; }
td.annotation_link { width: 60px; text-align: right; padding-right: 20px; }
.annotation_comment { width: 500px; }
div.annotation_comment {
float: left;
text-align: left;
white-space: -moz-pre-wrap; /* Mozilla */
white-space: -hp-pre-wrap; /* HP printers */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: pre-wrap; /* CSS 2.1 */
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
word-wrap: break-word; /* IE */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1,206 @@
/*jshint globalstrict: true*/
"use strict";
var win_attr = {
get_center : function (dim) {
var c = {
'x' : (win_attr.get_size('x')/2),
'y' : (win_attr.get_size('y')/2)
};
return ((dim) ? c[dim] : c);
},
get_size : function(dir) {
dir = (dir === 'x') ? 'Width' : 'Height';
return ((window['inner'+dir]) ?
window['inner'+dir] :
((window.document.documentElement && window.document.documentElement['client'+dir]) ?
window.document.documentElement['client'+dir] :
window.document.body['client'+dir]
)
);
}
},
position_el = {
center : function (el, dim) {
var c = win_attr.get_center(),
top = (c.y - (el.offsetHeight/2)),
left = (c.x - (el.offsetWidth/2));
if (dim == null || dim === 'y') el.style.top = (top < 0) ? 0 + 'px' : top + 'px';
if (dim == null || dim === 'x') el.style.left = (left < 0) ? 0 + 'px' : left + 'px';
},
set : function (el, x, y) {
var left, top;
if (typeof x === "undefined") x = null;
if (typeof y === "undefined") y = null;
if (y === 'center') {
position_el.center(el, 'y');
} else if (y === 'top') {
el.style.top = 0 + 'px';
} else if (y === 'bottom') {
top = (win_attr.get_size('y') - (el.offsetHeight));
el.style.top = (top < 0) ? 0 + 'px' : top + 'px';
} else if (y.match(/^[\d]+(%|px|em|mm|cm|in|pt|pc)$/) != null) {
el.style.top = y;
}
if (x === "center") {
position_el.center(el, 'x');
} else if (x === 'left') {
el.style.left = 0 + 'px';
} else if (x === 'right') {
left = (win_attr.get_size('x') - (el.offsetWidth));
el.style.left = (left < 0) ? 0 + 'px' : left + 'px';
} else if (x.match(/^[\d]+(%|px|em|mm|cm|in|pt|pc)$/) != null) {
el.style.left = x;
}
}
};
function position_table(el) {
var x, y,
sel = document.getElementById('dock'),
option = sel.options[sel.selectedIndex].value;
switch(option) {
case "0": x = 'center'; y = 'center'; break;
case "1": x = 'center'; y = 'top'; break;
case "2": x = 'center'; y = 'bottom'; break;
case "3": x = 'left'; y = 'center'; break;
case "4": x = 'right'; y = 'center'; break;
case "5": x = 'left'; y = 'top'; break;
case "6": x = 'right'; y = 'top'; break;
case "7": x = 'left'; y = 'bottom'; break;
case "8": x = 'right'; y = 'bottom'; break;
default: break;
}
setTimeout(function () {position_el.set(el, x, y); el.style.visibility = 'visible';}, 300);
}
function toggle_annotations() {
var comments_div = document.getElementById('comment_list'),
mode = comments_div.style.display;
if (mode == 'none') {
comments_div.style.display = 'block';
position_table(comments_div);
} else {
comments_div.style.visibility = 'hidden';
comments_div.style.display = 'none';
}
}
function dock_table() {
var comments_div = document.getElementById('comment_list');
position_table(comments_div);
}
function scroll_to_line(value) {
var pos = 0,
el = document.getElementById(value);
window.scrollTo(0, 0);
while(el) {
pos += el.offsetTop;
el = el.offsetParent;
}
pos -= win_attr.get_center('y');
if (pos < 0) {
pos = 0;
}
window.scrollTo(0, pos);
}
// Tooltips from http://www.scriptiny.com/2008/06/javascript-tooltip/
var tooltip = function() {
var id = 'tooltip',
top = 3,
left = 3,
maxw = 300,
speed = 10,
timer = 20,
endalpha = 95,
alpha = 0,
ie = document.all ? true : false,
tt, t, c, b, h;
return{
annotation_list: {},
init: function() {
var i, comment, comments, len;
comments = document.querySelectorAll("div.annotation_comment");
len = comments.length;
for (i = 0; i < len; i++) {
comment = comments[i];
if ("textContent" in comment) {
tooltip.annotation_list[i] = comment.textContent;
} else {
tooltip.annotation_list[i] = comment.innerText;
}
}
},
show:function(v, w) {
if(tt == null) {
tt = document.createElement('div');
tt.setAttribute('id', id);
document.body.appendChild(tt);
tt.style.opacity = 0;
tt.style.filter = 'alpha(opacity=0)';
document.onmousemove = this.pos;
}
tt.style.display = 'block';
tt.innerHTML = v in tooltip.annotation_list ? tooltip.annotation_list[v] : '?';
tt.style.width = w ? w + 'px' : 'auto';
if(!w && ie){
tt.style.width = tt.offsetWidth;
}
if(tt.offsetWidth > maxw){
tt.style.width = maxw + 'px';
}
h = parseInt(tt.offsetHeight, 10) + top;
clearInterval(tt.timer);
tooltip.instantshow(true);
// tt.timer = setInterval(function(){tooltip.fade(1);}, timer);
},
pos:function(e) {
var u = ie ? event.clientY + document.documentElement.scrollTop : e.pageY,
l = ie ? event.clientX + document.documentElement.scrollLeft : e.pageX;
tt.style.top = (u - h) + 'px';
tt.style.left = (l + left) + 'px';
},
instantshow: function(show) {
if (show === true) {
tt.style.opacity = endalpha * 0.01;
tt.style.filter = 'alpha(opacity=' + endalpha + ')';
} else {
tt.style.display = 'none';
}
},
fade:function(d) {
var a = alpha, i;
if((a != endalpha && d == 1) || (a !== 0 && d == -1)){
i = speed;
if(endalpha - a < speed && d == 1){
i = endalpha - a;
}else if(alpha < speed && d == -1){
i = a;
}
alpha = a + (i * d);
tt.style.opacity = alpha * 0.01;
tt.style.filter = 'alpha(opacity=' + alpha + ')';
}else{
clearInterval(tt.timer);
if(d == -1){
tt.style.display = 'none';
}
}
},
hide:function() {
clearInterval(tt.timer);
tooltip.instantshow(false);
// tt.timer = setInterval(function(){tooltip.fade(-1);},timer);
}
};
}();
tooltip.init();

View File

@@ -0,0 +1,87 @@
/*jshint globalstrict: true*/
"use strict";
var page_line_info = {
wrap: false,
ranges: null,
wrap_size: null,
tables: null,
header: null,
gutter: false
};
function wrap_code() {
var start, end, i, j, mode, idx,
width = 0, el;
if (page_line_info.header) {
document.getElementById("file_info").style.width = page_line_info.wrap_size + "px";
}
for (i = 1; i <= page_line_info.tables; i++) {
idx = i - 1;
start = page_line_info.ranges[idx][0];
end = page_line_info.ranges[idx][1];
for(j = start; j < end; j++) {
if (mode == null) {
mode = true;
if (page_line_info.gutter) {
width = document.getElementById("L_" + idx + "_" + j).offsetWidth;
}
}
el = document.getElementById("C_" + idx + "_" + j);
el.style.width = (page_line_info.wrap_size - width) + "px";
el.className = "wrap";
}
}
}
function toggle_gutter() {
var i, j, mode, rows, r, tbls, cells;
tbls = document.getElementsByTagName('table');
for (i = 1; i <= page_line_info.tables; i++) {
rows = tbls[i].getElementsByTagName('tr');
r = rows.length;
for (j = 0; j < r; j++) {
cells = rows[j].getElementsByTagName('td');
if (mode == null) {
if (page_line_info.gutter) {
mode = 'none';
page_line_info.gutter = false;
} else {
mode = 'table-cell';
page_line_info.gutter = true;
}
}
cells[0].style.display = mode;
}
}
if (page_line_info.wrap && mode != null) {
setTimeout(function() {wrap_code();}, 500);
}
}
function unwrap_code() {
var i, j, idx, start, end, el;
if (page_line_info.header) {
document.getElementById("file_info").style.width = "100%";
}
for (i = 1; i <= page_line_info.tables; i++) {
idx = i - 1;
start = page_line_info.ranges[idx][0];
end = page_line_info.ranges[idx][1];
for(j = start; j < end; j++) {
el = document.getElementById("C_" + idx + "_" + j);
el.style.width = "100%";
el.className = "";
}
}
}
function toggle_wrapping() {
if (page_line_info.wrap) {
page_line_info.wrap = false;
unwrap_code();
} else {
page_line_info.wrap = true;
wrap_code();
}
}

View File

@@ -0,0 +1,39 @@
/*jshint globalstrict: true*/
"use strict";
var plain_text_clone = null;
function toggle_plain_text() {
var lines = document.querySelectorAll("td.code_line"),
line_len = lines.length,
text = "",
plain_pre = document.querySelectorAll("pre.simple_code_page"),
orig_pre, pre, i, j, spans, span_len, span;
if (plain_pre.length > 0) {
document.body.removeChild(plain_pre[0]);
document.body.appendChild(plain_text_clone);
document.body.className = "code_page";
} else {
for (i = 0; i < line_len; i++) {
spans = lines[i].querySelectorAll("span.real_text");
span_len = spans.length;
for (j = 0; j < span_len; j++) {
span = spans[j];
if ("textContent" in span) {
text += span.textContent;
} else {
text += span.innerText;
}
}
text += "\n";
}
orig_pre = document.querySelectorAll("pre.code_page")[0];
plain_text_clone = orig_pre.cloneNode(true);
pre = document.createElement('pre');
pre.className = "simple_code_page";
pre.appendChild(document.createTextNode(text));
document.body.removeChild(orig_pre);
document.body.appendChild(pre);
document.body.className = "simple_code_page";
}
}

View File

@@ -0,0 +1,129 @@
/*jshint globalstrict: true*/
"use strict";
var plist = {
color_scheme: {},
content: "",
indentlevel: 0,
get: function(file_name) {
this.content = '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' +
'<plist version="1.0">\n' +
'<!-- ' + file_name + ' -->\n';
this.parsedict(this.color_scheme);
this.content += '</plist>\n';
return this.content;
},
isinstance: function(obj, s) {
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() === s;
},
sortkeys: function(obj) {
var sorted = {},
keys = [],
key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
keys.sort();
return keys;
},
indent: function() {
var i;
for (i = 0; i < this.indentlevel; i++) {
this.content += " ";
}
},
parsekey: function(k) {
this.indent();
this.content += '<key>' + k + '</key>\n';
},
parseitem: function(obj) {
if (this.isinstance(obj, "string")) {
this.parsestring(obj);
} else if (this.isinstance(obj, "array")) {
this.parsearray(obj);
} else if (this.isinstance(obj, "object")) {
this.parsedict(obj);
}
},
parsearray: function(obj) {
var i, len = obj.length;
this.indent();
this.content += '<array>\n';
this.indentlevel++;
for (i = 0; i < len; i++) {
this.parseitem(obj[i]);
}
this.indentlevel--;
this.indent();
this.content += '</array>\n';
},
parsestring: function(s) {
this.indent();
this.content += '<string>' + s + '</string>\n';
},
parsedict: function(obj) {
var keys = this.sortkeys(obj),
len = keys.length,
k, i;
this.indent();
this.content += '<dict>\n';
this.indentlevel++;
for (i = 0; i < len; i++)
{
k = keys[i];
this.parsekey(k);
this.parseitem(obj[k]);
}
this.indentlevel--;
this.indent();
this.content += '</dict>\n';
}
},
escape_html = {
safe_chars: {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
},
escape: function (s) {
return String(s).replace(/[&<>"'\/]/g, function (c) {return escape_html.safe_chars[c];});
}
};
function extract_theme(name) {
var text, wnd, doc,
a = document.createElement('a');
window.URL = window.URL || window.webkitURL;
if (window.Blob != null && a.download != null) {
text = new Blob([plist.get(name)], {'type':'application/octet-stream'});
a.href = window.URL.createObjectURL(text);
a.download = name;
a.click();
} else {
text = '<pre>' + escape_html.escape(plist.get(name)) + '</pre>',
wnd = window.open('', '_blank', "status=1,toolbar=0,scrollbars=1"),
doc = wnd.document;
doc.write(text);
doc.close();
wnd.focus();
}
}

View File

@@ -0,0 +1,15 @@
/*jshint globalstrict: true*/
"use strict";
function page_print() {
var element = document.getElementById("toolbarhide");
if (element != null) {
element.style.display = "none";
}
if (window.print) {
window.print();
}
if (element != null) {
element.style.display = "block";
}
}

View File

@@ -0,0 +1 @@
{"url": "https://github.com/facelessuser/ExportHtml", "version": "2013.03.29.23.39.49", "description": "Sublime Text - Export code to HTML for copying/printing/saving. Also, export code to BBCode for forum posts."}

View File

@@ -0,0 +1,220 @@
# About
This is a fork of agibsonsw's [PrintHtml](https://github.com/agibsonsw/PrintHtml) plugin. This plugin allows the exporting of a document in ST2 to an HTML file or to BBCode. It duplicates ST2's theme colors and font styles. You can play with the demo page that has actual html pages generated with this plugin [here](http://facelessuser.github.com/ExportHtml).
<img src="http://dl.dropbox.com/u/342698/ExportHtml/preview.png" border="0"/>
# Features
- Export to HTML using any tmTheme for syntax highlighting
- Can handle any language supported by ST2
- Supports bold and italic theme font styles as well
- Configurable output
- Format suitable for copying and pasting in emails
- 2 included tmTheme files for color and grayscale printing (but any can be used)
- Export only selections (multi-select supported)
- Export and show highlights (multi-select supported)
- Toggle gutter on/off in browser view
- Automatically open browser print dialog (optional)
- Enable/disable configurable word wrapping
- Configurable toolbar to appear in the generated webpage
# Usage: Exporting HTML
ExportHtml comes with a number of default commands available, but these can be overridden in the settings file. Or you can create commands directly outside of the settings file bound to the command palette, key bindings, or even the menu.
If adding a command to the settings file, it goes under the ```html_panel``` setting. These configurations will appear under the ```Export to HTML: Show Export Menu``` command palette command.
```javascript
// Define configurations for the drop down export menu
"html_panel": [
// Browser print color (selections and multi-selections allowed)
{
"Browser Print - Color": {
"numbers": true,
"wrap": 900,
"browser_print": true,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme",
"style_gutter": false
}
}
]
```
The name of the command is the key value, and then you add the parameters you wish to specify. You can use any combination of settings below.
- numbers (boolean): Display line numbers in the gutter.
- style_gutter (boolean): Style gutter with theme backgrounds and foregrounds, or just use the default background/foreground. Default is ```true```.
- multi_select (boolean): If multiple regions are selected in a document, only export what is under those selections. By default only the first selection is recognized. Default is ```false```
- highlight_selections (boolean): Highlights all selections in HTML output using the themes selection colors. Multi-select option will be ignored if this is set ```true```. Default is ```false```
- wrap (integer): Define the allowable size in px to wrap lines at. By default wrapping is not used.
- color_scheme (string): The color scheme (tmTheme) file you would like to use. By default the current color scheme file is used, or the the alternate default color scheme if defined in the setting ```alternate_scheme```.
- clipboard_copy (boolean): Copy html to the clipboard after generation. Default is ```false```.
- browser_print (boolean): When opening in the web browser, also open the brower's print dialog. This will be ignored if ```view_open``` is ```true```. Default is ```false```.
- view_open (boolean): Open HTML in a Sublime Text tab instead of the web browser. Default is ```false```.
- no_header (boolean): Do not display file name, date, and time at the top of the HTML document. Default is ```false```.
- date_time_format (string): String denoting the format for date and time when displaying header. Please see Python's documentation on ```time.strftime``` for detailed info on formatting syntax. Default is ```"%m/%d/%y %I:%M:%S"```
- show_full_path (boolean): Show full path for filename when displaying header. Default is ```true```
- save_location (string): Path to save html file. If the file is wanted in the same file as the original, use ".". Otherwise, use the absolute path to where the file is desired. If there is an issue determining where to save the file, or the path does not exist, the OS temp folder will be used. Default is ```None``` (use temp folder).
- time_stamp (string): Configure the time stamp of saved html when using ```save_location```. To remove time stamps, just set to an empty string ```""```. Please see Python's documentation on ```time.strftime``` for detailed info on formatting syntax. Default is ```"_%m%d%y%H%M%S"```
- toolbar (array of strings): Option to display a toolbar with to access features in a generated HTML. This setting is an array of keywords that represent the icons in the toolbar to show. Valid keywords include ```gutter```, ```print```, ```plain_text```, ```annotation```, ```theme```, and ```wrapping```. Toolbar will appear when you mouse over the uppert right corner of the window of the generated html. Default enables all.
- filter (string): Filters to use on the theme's colors. The string is a sequence of filters separated by ```;```. The accepted filters are ```grayscale```, ```invert```, ```sepia```, ```brightness```,, ```saturation```, ```hue```, and ```colorize```. ```brightness``` and ```saturation``` requires a float parameter to specify to what magnitude the filter should be applied at. ```hue``` and ```colorize``` take a float that represents a degree. ```hue``` shifts the hue via the degree given (can accept negative degrees); hues will wrap if they extend past 0 degrees or 360 degrees. Example: ```"filter": "sepia;invert;brightness(1.1);saturation(1.3);"```. Default is ```""```.
- shift_brightness (bool): This setting shifts the entire theme's brightness if a background color's luminace is below the global setting ```bg_min_lumen_threshold```. This was added to solve an issue that I had when copying dark themes into an outlook email; if a html span had a background that was too dark, the foreground would just be white. This allows me to not have to worry about how dark the theme is, and probably serves very little use besides that.
If you wish to bind a command to a key combination etc., the same settings as above can be used.
Example:
```javascript
{
"keys": ["ctrl+alt+n"],
"command": "export_html",
"args": {
"numbers": true,
"wrap": 900,
"browser_print": true,
"multi_select": true,
"color_scheme": "Packages/ExportHtml/ColorSchemes/Print-Color.tmTheme",
"style_gutter": false
}
}
```
When viewing the HTML in your web browser, regardless of the gutter settings, the gutter can be toggled to show or be hidden using the toolbar.
# Usage: Exporting BBCode
ExportHtml can also export selected code as BBCode for posting in forums. Exporting BBCode is very similar to exporting HTML code.
If adding a command to the settings file, it goes under the ```bbcode_panel``` setting. These configurations will appear under the ```Export to BBCode: Show Export Menu``` command palette command.
```javascript
// Define configurations for the drop down export menu
"bbcode_panel": [
{
"To Clipboard - Format as BBCode": {
"numbers": false,
"multi_select": true
}
}
]
```
The name of the command is the key value, and then you add the parameters you wish to specify. You can use any combination of settings below.
- numbers (boolean): Display line numbers in the gutter.
- multi_select (boolean): If multiple regions are selected in a document, only export what is under those selections. By default only the first selection is recognized. Default is ```false```
- color_scheme (string): The color scheme (tmTheme) file you would like to use. By default the current color scheme file is used, or the the alternate default color scheme if defined in the setting ```alternate_scheme```.
- clipboard_copy (boolean): Copy BBCode to the clipboard after generation. Default is ```true```.
- view_open (boolean): Open txt file of BBCode in a Sublime Text tab. Default is ```false```.
- no_header (boolean): Do not display file name, date, and time at the top of the HTML document. Default is ```false```.
If you wish to bind a command to a key combination etc., the same settings as above can be used.
Example:
```javascript
{
"keys": ["ctrl+alt+n"],
"command": "export_bbcode",
"args": {
"numbers": false,
"multi_select": true
}
}
```
# Usage: Annotations (HTML only)
Annotations are comments you can make on selected text. When the HTML is generated, the selected text will be underlined, and when the mouse hovers over them, a tooltip will appear with your comment.
<img src="http://dl.dropbox.com/u/342698/ExportHtml/annotation_preview.png" border="0"/>
In order to use annotations, you must enter into an "Annotation Mode". This puts your file in a read only state. At this point, you can select text and create annotations using the annotation commands provided. When you leave the "Annotation Mode", all annotations will be lost. So you must print before leaving annotation mode.
You can access the annotation commands from the command palette or from the context menu.
The commands are as follows:
- Enable Annotation Mode: Turn annotation mode on.
- Disable Annotation Mode: Turn annotation mode off.
- Annotate Selection: Annote the given selection (no multi-select support currently).
- Delete Annotation(s): Delete the annotation region the the cursor resides in (multi-select support).
- Delete All Annotations: Delete all annotation regions.
- Show Annotation Comment: Show the annotation comment of the region under the cursor.
You can navigate the annotations in the generate HTML by using a jump table. You can show the jump table at any time by selecting the annotation button in the toolbar. You can also click any annotation to show the jump table as well. If it gets in the way, you can dock it in a different location.
<img src="http://dl.dropbox.com/u/342698/ExportHtml/annotation_table_preview.png" border="0"/>
# Settings File options
- alternate_scheme (string or false): Defines a default theme to be used if a theme is not specified in a command. When this is false, the current Sublime Text theme in use is used.
- alternate_font_size (int or false): Define an alternate font_size to use by default instead of the current one in use. Use the current one in use if set to a literal ```false```. Default is ```false```.
- alternate_font_face (string or false): Define an alternate font_face to use by default instead of the current one in use. Use the current one in use if set to a literal ```false```. Default is ```false```.
- valid_selection_size (integer): Minimum allowable size for a selection to be accepted for only the selection to be printed.
- linux_python2.6_lib (string): If you are on linux and Sublime Text is not including your Python 2.6 library folder, you can try and configure it here.
- html_panel (array of commands): Define export configurations to appear under the ```Export to HTML: Show Export Menu``` command palette command.
- bbcode_panel (array of commands): Define export configurations to appear under the ```Export to BBCode: Show Export Menu``` command palette command.
#Credits
- agibsonsw: Original idea and algorithm for the plugin
- Paul Boddie: Desktop module for open files in web browser cross platform
- Print-Color and Print-Grayscale tmThemes were derived from Monokai Bright
#Version 0.5.7
- Better tooltips for annotations (they now follow the mouse)
- Remove workaround to fix gaps in background color (it is recommended to just use a reliable font like Courier)
- Change method of underlining annotations to work in wrap mode and non-wrap mode and with background colors
- Fix for CSS in annotation table not handling comment overflow
#Version 0.5.6
- Expose filters to ExportBbcode
- Port transparency simulation to ExportBbcode
- Add hue and colorize filters
#Version 0.5.5
- Various bug fixes
- Add color filters that can be applied to a theme
- Add shift_brightness to solve an issue I had with copying the html of very dark themes into Outlook at work
#Version 0.5.0
- Added ability to define path to save generated html to a specific folder with optional timestamp
- If selection foreground is not defined, use normal colors for text.
- Click annotations to show annotation jump table
- Removed shortcut actions
- Themes are now embedded in the html and can be extracted
- Added toggle plain text option and toggle wrapping (if enababled)
- Added toolbar to print, download theme, disable toggle wrapping (if enabled), toggle annotation jump table (if annotations available), toggle plain text, and toggle gutter
- Exposed toolbar options in configuration (can define any toolbar item to appear)
- Split out javascript into separate files
- Improved and fixed javascript issues
# Version 0.4.1
- Add date_time_format and show_full_path options
- Some internal adjustments
# Version 0.4.0
- Fix regression with option numbers = false
- Fix issue where if transparency was included in hex color, color would not render
- Fix regression where annotation table would not show
# Version 0.3.2
- Allow alternate font size and face via the settings file
- Tweak annotation jump table style and code
# Version 0.3.1
- Position annotation jump table in different locations via drop down list
# Version 0.3.0
- Add annotation jump table for the HTML. Show table with "alt+double_click"
# Version 0.2.0
- Fix issue where html is opened twice
- New annotation feature
- New export to BBCode
- Rename PrintHTML to ExportHTML
- Fix HTML Title (display actual file name of content)
- Update documentation
# Version 0.1.1
- Fix status returnd as None for Windows
# Version 0.1.0
- Initial release