feat(SublimeText2.EditorPackages): cache packages
This commit is contained in:
2
EthanBrown.SublimeText2.EditorPackages/tools/PackageCache/ExportHtml/.gitignore
vendored
Normal file
2
EthanBrown.SublimeText2.EditorPackages/tools/PackageCache/ExportHtml/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.pyc
|
||||
*.cache
|
@@ -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>
|
@@ -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>
|
@@ -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": "-"}
|
||||
]
|
@@ -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"
|
||||
}
|
||||
]
|
@@ -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)
|
@@ -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 </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(" ", ' '),
|
||||
"code_id": num,
|
||||
"code": line,
|
||||
"table": self.tables,
|
||||
"pad_color": self.ebground or self.bground
|
||||
}
|
||||
|
||||
return html_line
|
||||
|
||||
def guess_colour(self, pt, the_key):
|
||||
the_colour = self.fground
|
||||
the_bgcolour = None
|
||||
the_style = set([])
|
||||
if the_key in self.matched:
|
||||
the_colour = self.matched[the_key]["color"]
|
||||
the_style = self.matched[the_key]["style"]
|
||||
the_bgcolour = self.matched[the_key]["bgcolor"]
|
||||
else:
|
||||
best_match_bg = 0
|
||||
best_match_fg = 0
|
||||
best_match_style = 0
|
||||
for key in self.colours:
|
||||
match = self.view.score_selector(pt, key)
|
||||
if self.colours[key]["color"] is not None and match > best_match_fg:
|
||||
best_match_fg = match
|
||||
the_colour = self.colours[key]["color"]
|
||||
if self.colours[key]["style"] is not None and match > best_match_style:
|
||||
best_match_style = match
|
||||
for s in self.colours[key]["style"]:
|
||||
the_style.add(s)
|
||||
if self.colours[key]["bgcolor"] is not None and match > best_match_bg:
|
||||
best_match_bg = match
|
||||
the_bgcolour = self.colours[key]["bgcolor"]
|
||||
self.matched[the_key] = {"color": the_colour, "bgcolor": the_bgcolour, "style": the_style}
|
||||
if len(the_style) == 0:
|
||||
the_style = "normal"
|
||||
else:
|
||||
the_style = ' '.join(the_style)
|
||||
return the_colour, the_style, the_bgcolour
|
||||
|
||||
def write_header(self, the_html):
|
||||
header = HTML_HEADER % {
|
||||
"title": path.basename(self.file_name),
|
||||
"css": getcss(
|
||||
'export.css',
|
||||
{
|
||||
"font_size": str(self.font_size),
|
||||
"font_face": '"' + self.font_face + '"',
|
||||
"page_bg": self.bground,
|
||||
"gutter_bg": self.gbground,
|
||||
"body_fg": self.fground,
|
||||
"display_mode": 'table-cell' if self.numbers else 'none',
|
||||
"dot_color": self.fground,
|
||||
"toolbar_orientation": self.toolbar_orientation
|
||||
}
|
||||
),
|
||||
"js": INCLUDE_THEME % {
|
||||
"jscode": getjs('plist.js'),
|
||||
"theme": json.dumps(self.plist_file, sort_keys=True, indent=4, separators=(',', ': ')).encode('raw_unicode_escape'),
|
||||
"name": self.scheme_file,
|
||||
}
|
||||
}
|
||||
the_html.write(header)
|
||||
|
||||
def convert_view_to_html(self, the_html):
|
||||
for line in self.view.split_by_newlines(sublime.Region(self.pt, self.size)):
|
||||
self.size = line.end()
|
||||
empty = not bool(line.size())
|
||||
line = self.convert_line_to_html(the_html, empty)
|
||||
the_html.write(self.print_line(line, self.curr_row))
|
||||
self.curr_row += 1
|
||||
|
||||
def html_encode(self, text):
|
||||
# Format text to HTML
|
||||
encode_table = {
|
||||
'&': '&',
|
||||
'>': '>',
|
||||
'<': '<',
|
||||
'\t': ' ' * self.tab_size,
|
||||
' ': ' ',
|
||||
'\n': ''
|
||||
}
|
||||
|
||||
return ''.join(encode_table.get(c, c) for c in text).encode('ascii', 'xmlcharrefreplace')
|
||||
|
||||
def get_annotations(self):
|
||||
annotations = get_annotations(self.view)
|
||||
comments = []
|
||||
for x in range(0, int(annotations["count"])):
|
||||
region = annotations["annotations"]["html_annotation_%d" % x]["region"]
|
||||
comments.append((region, annotations["annotations"]["html_annotation_%d" % x]["comment"]))
|
||||
comments.sort()
|
||||
return comments
|
||||
|
||||
def annotate_text(self, line, the_colour, the_bgcolour, the_style, empty):
|
||||
pre_text = None
|
||||
annot_text = None
|
||||
post_text = None
|
||||
start = None
|
||||
|
||||
# Pretext Check
|
||||
if self.pt >= self.curr_annot.begin():
|
||||
# Region starts with an annotation
|
||||
start = self.pt
|
||||
else:
|
||||
# Region has text before annoation
|
||||
pre_text = self.html_encode(self.view.substr(sublime.Region(self.pt, self.curr_annot.begin())))
|
||||
start = self.curr_annot.begin()
|
||||
|
||||
if self.end == self.curr_annot.end():
|
||||
# Region ends annotation
|
||||
annot_text = self.html_encode(self.view.substr(sublime.Region(start, self.end)))
|
||||
self.curr_annot = None
|
||||
elif self.end > self.curr_annot.end():
|
||||
# Region has text following annotation
|
||||
annot_text = self.html_encode(self.view.substr(sublime.Region(start, self.curr_annot.end())))
|
||||
post_text = self.html_encode(self.view.substr(sublime.Region(self.curr_annot.end(), self.end)))
|
||||
self.curr_annot = None
|
||||
else:
|
||||
# Region ends but annotation is not finished
|
||||
annot_text = self.html_encode(self.view.substr(sublime.Region(start, self.end)))
|
||||
self.curr_annot = sublime.Region(self.end, self.curr_annot.end())
|
||||
|
||||
# Print the separate parts pre text, annotation, post text
|
||||
if pre_text != None:
|
||||
self.format_text(line, pre_text, the_colour, the_bgcolour, the_style, empty)
|
||||
if annot_text != None:
|
||||
self.format_text(line, annot_text, the_colour, the_bgcolour, the_style, empty, annotate=True)
|
||||
if self.curr_annot == None:
|
||||
self.curr_comment = None
|
||||
if post_text != None:
|
||||
self.format_text(line, post_text, the_colour, the_bgcolour, the_style, empty)
|
||||
|
||||
def add_annotation_table_entry(self):
|
||||
row, col = self.view.rowcol(self.annot_pt)
|
||||
self.annot_tbl.append(
|
||||
(
|
||||
self.tables, self.curr_row, "Line %d Col %d" % (row + 1, col + 1),
|
||||
self.curr_comment.encode('ascii', 'xmlcharrefreplace')
|
||||
)
|
||||
)
|
||||
self.annot_pt = None
|
||||
|
||||
def format_text(self, line, text, the_colour, the_bgcolour, the_style, empty, annotate=False):
|
||||
if empty:
|
||||
text = ' '
|
||||
else:
|
||||
the_style += " real_text"
|
||||
|
||||
if the_bgcolour is None:
|
||||
the_bgcolour = self.bground
|
||||
|
||||
if annotate:
|
||||
code = ANNOTATION_CODE % {"highlight": the_bgcolour, "color": the_colour, "content": text, "class": the_style}
|
||||
else:
|
||||
code = CODE % {"highlight": the_bgcolour, "color": the_colour, "content": text, "class": the_style}
|
||||
|
||||
if annotate:
|
||||
if self.curr_annot != None and not self.open_annot:
|
||||
# Open an annotation
|
||||
if self.annot_pt != None:
|
||||
self.add_annotation_table_entry()
|
||||
if self.new_annot:
|
||||
self.annot_num += 1
|
||||
self.new_annot = False
|
||||
code = ANNOTATE_OPEN % {"code": code, "comment": str(self.annot_num)}
|
||||
self.open_annot = True
|
||||
elif self.curr_annot == None:
|
||||
if self.open_annot:
|
||||
# Close an annotation
|
||||
code += ANNOTATE_CLOSE
|
||||
self.open_annot = False
|
||||
else:
|
||||
# Do a complete annotation
|
||||
if self.annot_pt != None:
|
||||
self.add_annotation_table_entry()
|
||||
if self.new_annot:
|
||||
self.annot_num += 1
|
||||
self.new_annot = False
|
||||
code = (
|
||||
ANNOTATE_OPEN % {"code": code, "comment": str(self.annot_num)} +
|
||||
ANNOTATE_CLOSE
|
||||
)
|
||||
line.append(code)
|
||||
|
||||
def convert_line_to_html(self, the_html, empty):
|
||||
line = []
|
||||
hl_done = False
|
||||
|
||||
# Continue highlight form last line
|
||||
if self.hl_continue != None:
|
||||
self.curr_hl = self.hl_continue
|
||||
self.hl_continue = None
|
||||
|
||||
while self.end <= self.size:
|
||||
# Get next highlight region
|
||||
if self.highlight_selections and self.curr_hl == None and len(self.highlights) > 0:
|
||||
self.curr_hl = self.highlights.pop(0)
|
||||
|
||||
# See if we are starting a highlight region
|
||||
if self.curr_hl != None and self.pt == self.curr_hl.begin():
|
||||
# Get text of like scope up to a highlight
|
||||
scope_name = self.view.scope_name(self.pt)
|
||||
while self.view.scope_name(self.end) == scope_name and self.end < self.size:
|
||||
# Kick out if we hit a highlight region
|
||||
if self.end == self.curr_hl.end():
|
||||
break
|
||||
self.end += 1
|
||||
if self.end < self.curr_hl.end():
|
||||
if self.end >= self.size:
|
||||
self.hl_continue = sublime.Region(self.end, self.curr_hl.end())
|
||||
else:
|
||||
self.curr_hl = sublime.Region(self.end, self.curr_hl.end())
|
||||
else:
|
||||
hl_done = True
|
||||
if hl_done and empty:
|
||||
the_colour, the_style, the_bgcolour = self.guess_colour(self.pt, scope_name)
|
||||
elif self.sfground is None:
|
||||
the_colour, the_style, _ = self.guess_colour(self.pt, scope_name)
|
||||
the_bgcolour = self.sbground
|
||||
else:
|
||||
the_colour, the_style = self.sfground, "normal"
|
||||
the_bgcolour = self.sbground
|
||||
else:
|
||||
# Get text of like scope up to a highlight
|
||||
scope_name = self.view.scope_name(self.pt)
|
||||
while self.view.scope_name(self.end) == scope_name and self.end < self.size:
|
||||
# Kick out if we hit a highlight region
|
||||
if self.curr_hl != None and self.end == self.curr_hl.begin():
|
||||
break
|
||||
self.end += 1
|
||||
the_colour, the_style, the_bgcolour = self.guess_colour(self.pt, scope_name)
|
||||
|
||||
# Get new annotation
|
||||
if (self.curr_annot == None or self.curr_annot.end() < self.pt) and len(self.annotations):
|
||||
self.curr_annot, self.curr_comment = self.annotations.pop(0)
|
||||
self.annot_pt = self.curr_annot[0]
|
||||
while self.pt > self.curr_annot[1]:
|
||||
if len(self.annotations):
|
||||
self.curr_annot, self.curr_comment = self.annotations.pop(0)
|
||||
self.annot_pt = self.curr_annot[0]
|
||||
else:
|
||||
self.curr_annot = None
|
||||
self.curr_comment = None
|
||||
break
|
||||
self.new_annot = True
|
||||
self.curr_annot = sublime.Region(self.curr_annot[0], self.curr_annot[1])
|
||||
|
||||
region = sublime.Region(self.pt, self.end)
|
||||
if self.curr_annot != None and region.intersects(self.curr_annot):
|
||||
# Apply annotation within the text and format the text
|
||||
self.annotate_text(line, the_colour, the_bgcolour, the_style, empty)
|
||||
else:
|
||||
# Normal text formatting
|
||||
tidied_text = self.html_encode(self.view.substr(region))
|
||||
self.format_text(line, tidied_text, the_colour, the_bgcolour, the_style, empty)
|
||||
|
||||
if hl_done:
|
||||
# Clear highlight flags and variables
|
||||
hl_done = False
|
||||
self.curr_hl = None
|
||||
|
||||
# Continue walking through line
|
||||
self.pt = self.end
|
||||
self.end = self.pt + 1
|
||||
|
||||
# Close annotation if open at end of line
|
||||
if self.open_annot:
|
||||
line.append(ANNOTATE_CLOSE % {"comment": self.curr_comment})
|
||||
self.open_annot = False
|
||||
|
||||
# Get the color for the space at the end of a line
|
||||
if self.end < self.view.size():
|
||||
end_key = self.view.scope_name(self.pt)
|
||||
_, _, self.ebground = self.guess_colour(self.pt, end_key)
|
||||
|
||||
# Join line segments
|
||||
return ''.join(line)
|
||||
|
||||
def write_body(self, the_html):
|
||||
processed_rows = ""
|
||||
the_html.write(BODY_START)
|
||||
|
||||
the_html.write(TABLE_START)
|
||||
if not self.no_header:
|
||||
# Write file name
|
||||
date_time = time.strftime(self.date_time_format, self.time)
|
||||
the_html.write(
|
||||
FILE_INFO % {
|
||||
"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)
|
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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!")
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@@ -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.
Binary file not shown.
After Width: | Height: | Size: 392 B |
@@ -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();
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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: {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
"/": '/'
|
||||
},
|
||||
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();
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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."}
|
@@ -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
|
Reference in New Issue
Block a user