All set. Best of Sparkup + Zen Coding for now

This commit is contained in:
Kenneth Reitz
2010-05-04 21:15:17 -04:00
parent 4c1ce34413
commit 451bc1f0f6
37 changed files with 5307 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>command</key>
<string>#!/usr/bin/env python
import sys; import os; sys.path.append(os.getenv('TM_BUNDLE_SUPPORT')); import sparkup
# You may change these options to your liking.
# Those starting with # are comments (disabled).
options = {
'textmate': True,
'no-last-newline': True,
#'start-guide-format': 'Begin %s',
#'end-guide-format': 'End %s',
}
sparkup.Router().start(options=options)</string>
<key>fallbackInput</key>
<string>line</string>
<key>input</key>
<string>selection</string>
<key>keyEquivalent</key>
<string>@e</string>
<key>name</key>
<string>Sizzle Expand</string>
<key>output</key>
<string>insertAsSnippet</string>
<key>scope</key>
<string>text.html</string>
<key>uuid</key>
<string>73A48D2B-D843-42A1-A288-0D1A6380043B</string>
</dict>
</plist>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>input</key>
<string>selection</string>
<key>name</key>
<string>Sparkup Expand</string>
<key>output</key>
<string>replaceSelectedText</string>
<key>uuid</key>
<string>EF1F0ADB-1A4D-411A-9BDB-FF9837072CB2</string>
</dict>
</plist>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>command</key>
<string>cd "$TM_BUNDLE_PATH"
echo "&lt;pre&gt;"
[ -d ".git" ] &amp;&amp; git pull
[ ! -d .git ] &amp;&amp; echo "You must install the bundle using Git in order to update via this command. More info: http://github.com/kennethreitz/kCode.tmBundle"
osascript -e 'tell app "TextMate" to reload bundles'
echo "&lt;/pre&gt;"</string>
<key>input</key>
<string>selection</string>
<key>name</key>
<string>Update Bundle via Git</string>
<key>output</key>
<string>discard</string>
<key>uuid</key>
<string>FC3E137B-6E92-40C9-BAF6-ED13182D7269</string>
</dict>
</plist>
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>commands</key>
<array>
<dict>
<key>argument</key>
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>command</key>
<string>python "$TM_BUNDLE_SUPPORT"/balance_tag.py</string>
<key>input</key>
<string>document</string>
<key>keyEquivalent</key>
<string>@d</string>
<key>name</key>
<string>start</string>
<key>output</key>
<string>showAsTooltip</string>
<key>uuid</key>
<string>zen-balance-outward-1</string>
</dict>
<key>command</key>
<string>executeCommandWithOptions:</string>
</dict>
<dict>
<key>command</key>
<string>findNext:</string>
</dict>
</array>
<key>keyEquivalent</key>
<string>@d</string>
<key>name</key>
<string>Balance Tag</string>
<key>uuid</key>
<string>zen-balance-outward-2</string>
</dict>
</plist>
+61
View File
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>commands</key>
<array>
<dict>
<key>argument</key>
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>command</key>
<string>python "$TM_BUNDLE_SUPPORT"/remove_tag.py</string>
<key>input</key>
<string>document</string>
<key>keyEquivalent</key>
<string>@K</string>
<key>name</key>
<string>start</string>
<key>output</key>
<string>showAsTooltip</string>
<key>uuid</key>
<string>zen-remove-tag-1</string>
</dict>
<key>command</key>
<string>executeCommandWithOptions:</string>
</dict>
<dict>
<key>command</key>
<string>findNext:</string>
</dict>
<dict>
<key>argument</key>
<dict>
<key>beforeRunningCommand</key>
<string>nop</string>
<key>command</key>
<string>python "$TM_BUNDLE_SUPPORT"/applescript.py</string>
<key>input</key>
<string>document</string>
<key>keyEquivalent</key>
<string>@K</string>
<key>name</key>
<string>start</string>
<key>output</key>
<string>showAsTooltip</string>
<key>uuid</key>
<string>zen-remove-tag-2</string>
</dict>
<key>command</key>
<string>executeCommandWithOptions:</string>
</dict>
</array>
<key>keyEquivalent</key>
<string>$@</string>
<key>name</key>
<string>Remove Tag</string>
<key>uuid</key>
<string>zen-remove-tag-3</string>
</dict>
</plist>
View File
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from zen_editor import ZenEditor
editor = ZenEditor()
editor.run_applescript()
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from zencoding import zen_core as zen_coding
from zen_editor import ZenEditor
editor = ZenEditor()
zen_coding.run_action('match_pair', editor)
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from zencoding import zen_core as zen_coding
from zen_editor import ZenEditor
editor = ZenEditor()
zen_coding.run_action('remove_tag', editor)
+1087
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+203
View File
@@ -0,0 +1,203 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
High-level editor interface that communicates with TextMate editor.
In order to work correctly, you should set set the commands
input to “Entire Document”
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
import os
import sys
import zencoding.zen_core as zen
import subprocess
import re
class ZenEditor():
def __init__(self, context=None):
self._content = ''
"Editor's content"
self.apple_script = os.path.join(os.getenv('TM_BUNDLE_SUPPORT'), 'pasteboard.scpt')
zen.set_newline(os.getenv('TM_LINE_ENDING', zen.get_newline()))
self.set_context(context)
def _get_selected_text(self):
"Returns selected text"
return os.getenv('TM_SELECTED_TEXT', '')
def set_context(self, context=None):
"""
Setup underlying editor context. You should call this method
<code>before</code> using any Zen Coding action.
@param context: context object
"""
self._content = sys.stdin.read()
def get_selection_range(self):
"""
Returns character indexes of selected text
@return: list of start and end indexes
"""
line_num = int(os.getenv('TM_INPUT_START_LINE', os.getenv('TM_LINE_NUMBER', 1)))
head_lines = self.get_content().splitlines(True)[0:line_num - 1]
head_len = len(''.join(head_lines))
start, end = self.get_current_line_range()
return start + head_len, end + head_len
def create_selection(self, start, end=None):
"""
Creates selection from <code>start</code> to <code>end</code> character
indexes. If <code>end</code> is ommited, this method should place caret
and <code>start</code> index
"""
self.set_caret_pos(start)
if end is not None:
selected_text = self.get_content()[start:end]
# copy selected text to Mac OS' pasteboard to use it
# as a part of macros sequence for 'find next' action
subprocess.Popen(['pbcopy', '-pboard', 'find'], stdin=subprocess.PIPE).communicate(selected_text)
def get_current_line_range(self):
"""
Returns current line's start and end indexes
@return: list of start and end indexes
@example
start, end = zen_editor.get_current_line_range();
print('%s, %s' % (start, end))
"""
start = int(os.getenv('TM_INPUT_START_LINE_INDEX', os.getenv('TM_LINE_INDEX', 0)))
return start, start + len(self._get_selected_text())
def get_caret_pos(self):
""" Returns current caret position """
return self.get_selection_range()[0]
def set_caret_pos(self, pos):
"""
Set new caret position
@type pos: int
"""
# figure out line and column vars
head = zen.split_by_lines(self.get_content()[0:pos])
line = max(len(head), 1)
column = pos - len(zen.get_newline().join(head[0:-1]))
subprocess.Popen(['open', 'txmt://open/?line=%d&column=%d' % (line, column)]).communicate()
def get_current_line(self):
"""
Returns content of current line
@return: str
"""
return os.getenv('TM_CURRENT_LINE', '')
def replace_content(self, value, start=None, end=None):
"""
Replace editor's content or it's part (from <code>start</code> to
<code>end</code> index). If <code>value</code> contains
<code>caret_placeholder</code>, the editor will put caret into
this position. If you skip <code>start</code> and <code>end</code>
arguments, the whole target's content will be replaced with
<code>value</code>.
If you pass <code>start</code> argument only,
the <code>value</code> will be placed at <code>start</code> string
index of current content.
If you pass <code>start</code> and <code>end</code> arguments,
the corresponding substring of current target's content will be
replaced with <code>value</code>
@param value: Content you want to paste
@type value: str
@param start: Start index of editor's content
@type start: int
@param end: End index of editor's content
@type end: int
"""
# For content replacement we need to use macro syntaxt.
# First, create selection and then write AppleScript file that
# will replace selected text with new one
if start is None: start = 0
if end is None: end = len(self.get_content())
self.create_selection(start, end)
value = self.add_placeholders(value)
fp = open(self.apple_script, 'w')
fp.write('tell application "TextMate" to insert "%s" with as snippet' % (value.replace('\\', '\\\\').replace('"', '\\"'),))
fp.close()
def get_content(self):
"""
Returns editor's content
@return: str
"""
return self._content
def get_syntax(self):
"""
Returns current editor's syntax mode
@return: str
"""
scope = os.getenv('TM_SCOPE')
default_type = 'html'
doc_type = None
try:
if 'xsl' in scope:
doc_type = 'xsl'
else:
doc_type = re.findall(r'\bhtml|css|xml|haml\b', scope)[-1]
except:
doc_type = default_type
if not doc_type: doc_type = default_type
return doc_type
def get_profile_name(self):
"""
Returns current output profile name (@see zen_coding#setup_profile)
@return {String}
"""
return 'xhtml'
def run_applescript(self):
"""
TextMate-specific action that calls AppleScript defined in
<code>replace_content()</code> method which replaces selected text
with new one
"""
if os.path.exists(self.apple_script):
subprocess.Popen(['osascript', self.apple_script], stderr=subprocess.PIPE).communicate()
os.remove(self.apple_script)
def prompt(self, title):
"""
Prompt user with CocoaDialog
@param title: Popup title
@return: str
"""
args = ['CocoaDialog', 'standard-inputbox', '--title', title, '‑‑nonewline']
p = subprocess.Popen(args, stdout=subprocess.PIPE).communicate()
output = p[0].splitlines()
if output[0] == '2' or not output[1]:
return None
else:
return output[1]
def add_placeholders(self, text):
_ix = [0]
def get_ix(m):
_ix[0] += 1
return '$%s' % _ix[0]
text = re.sub(r'\$', '\\$', text)
return re.sub(zen.get_caret_placeholder(), get_ix, text)
Binary file not shown.
View File
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
import os.path
import sys
# import all filters
__sub_modules = []
__prefix = 'zencoding.filters'
__filter_dir = os.path.dirname(__file__)
sys.path.append(__filter_dir)
filter_map = {}
for file in os.listdir(__filter_dir):
name, ext = os.path.splitext(file)
if ext.lower() == '.py':
__sub_modules.append(name)
__filters = __import__(__prefix, globals(), locals(), __sub_modules)
for key in dir(__filters):
__module = getattr(__filters, key)
if hasattr(__module, '__name__') and __module.__name__.startswith(__prefix + '.') and hasattr(__module, 'process'):
if hasattr(__module, 'alias'):
filter_map[__module.alias] = __module.process
else:
filter_map[__module.__name__[len(__prefix) + 1:]] = __module.process
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Comment important tags (with 'id' and 'class' attributes)
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
from zencoding import zen_core as zen_coding
alias = 'c'
"Filter name alias (if not defined, ZC will use module name)"
def add_comments(node, i):
"""
Add comments to tag
@type node: ZenNode
@type i: int
"""
id_attr = node.get_attribute('id')
class_attr = node.get_attribute('class')
nl = zen_coding.get_newline()
if id_attr or class_attr:
comment_str = ''
padding = node.parent and node.parent.padding or ''
if id_attr: comment_str += '#' + id_attr
if class_attr: comment_str += '.' + class_attr
node.start = node.start.replace('<', '<!-- ' + comment_str + ' -->' + nl + padding + '<', 1)
node.end = node.end.replace('>', '>' + nl + padding + '<!-- /' + comment_str + ' -->', 1)
# replace counters
node.start = zen_coding.replace_counter(node.start, i + 1)
node.end = zen_coding.replace_counter(node.end, i + 1)
def process(tree, profile):
if profile['tag_nl'] is False:
return tree
for i, item in enumerate(tree.children):
if item.is_block():
add_comments(item, i)
process(item, profile)
return tree
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Filter for escaping unsafe XML characters: <, >, &
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
import re
alias = 'e'
"Filter name alias (if not defined, ZC will use module name)"
char_map = {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;'
}
re_chars = re.compile(r'[<>&]')
def escape_chars(text):
return re_chars.sub(lambda m: char_map[m.group(0)], text)
def process(tree, profile=None):
for item in tree.children:
item.start = escape_chars(item.start)
item.end = escape_chars(item.end)
process(item)
return tree
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Format CSS properties: add space after property name:
padding:0; -> padding: 0;
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
import re
alias = 'fc'
"Filter name alias (if not defined, ZC will use module name)"
re_css_prop = re.compile(r'([\w\-]+\s*:)\s*')
def process(tree, profile):
for item in tree.children:
# CSS properties are always snippets
if item.type == 'snippet':
item.start = re_css_prop.sub(r'\1 ', item.start)
process(item, profile)
return tree
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generic formatting filter: creates proper indentation for each tree node,
placing "%s" placeholder where the actual output should be. You can use
this filter to preformat tree and then replace %s placeholder to whatever you
need. This filter should't be called directly from editor as a part
of abbreviation.
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
"""
import re
from zencoding import zen_core as zen_coding
alias = '_format'
"Filter name alias (if not defined, ZC will use module name)"
child_token = '${child}'
placeholder = '%s'
def get_newline():
return zen_coding.get_newline()
def get_indentation():
return zen_coding.get_indentation()
def has_block_sibling(item):
"""
Test if passed node has block-level sibling element
@type item: ZenNode
@return: bool
"""
return item.parent and item.parent.has_block_children()
def is_very_first_child(item):
"""
Test if passed itrem is very first child of the whole tree
@type tree: ZenNode
"""
return item.parent and not item.parent.parent and not item.previous_sibling
def should_break_line(node, profile):
"""
Need to add line break before element
@type node: ZenNode
@type profile: dict
@return: bool
"""
if not profile['inline_break']:
return False
# find toppest non-inline sibling
while node.previous_sibling and node.previous_sibling.is_inline():
node = node.previous_sibling
if not node.is_inline():
return False
# calculate how many inline siblings we have
node_count = 1
node = node.next_sibling
while node:
if node.is_inline():
node_count += 1
else:
break
node = node.next_sibling
return node_count >= profile['inline_break']
def should_break_child(node, profile):
"""
Need to add newline because <code>item</code> has too many inline children
@type node: ZenNode
@type profile: dict
@return: bool
"""
# we need to test only one child element, because
# has_block_children() method will do the rest
return node.children and should_break_line(node.children[0], profile)
def process_snippet(item, profile, level=0):
"""
Processes element with <code>snippet</code> type
@type item: ZenNode
@type profile: dict
@param level: Depth level
@type level: int
"""
data = item.source.value;
if not data:
# snippet wasn't found, process it as tag
return process_tag(item, profile, level)
item.start = placeholder
item.end = placeholder
padding = item.parent.padding if item.parent else get_indentation() * level
if not is_very_first_child(item):
item.start = get_newline() + padding + item.start
# adjust item formatting according to last line of <code>start</code> property
parts = data.split(child_token)
lines = zen_coding.split_by_lines(parts[0] or '')
padding_delta = get_indentation()
if len(lines) > 1:
m = re.match(r'^(\s+)', lines[-1])
if m:
padding_delta = m.group(1)
item.padding = padding + padding_delta
return item
def process_tag(item, profile, level=0):
"""
Processes element with <code>tag</code> type
@type item: ZenNode
@type profile: dict
@param level: Depth level
@type level: int
"""
if not item.name:
# looks like it's a root element
return item
item.start = placeholder
item.end = placeholder
is_unary = item.is_unary() and not item.children
# formatting output
if profile['tag_nl'] is not False:
padding = item.parent.padding if item.parent else get_indentation() * level
force_nl = profile['tag_nl'] is True
should_break = should_break_line(item, profile)
# formatting block-level elements
if ((item.is_block() or should_break) and item.parent) or force_nl:
# snippet children should take different formatting
if not item.parent or (item.parent.type != 'snippet' and not is_very_first_child(item)):
item.start = get_newline() + padding + item.start
if item.has_block_children() or should_break_child(item, profile) or (force_nl and not is_unary):
item.end = get_newline() + padding + item.end
if item.has_tags_in_content() or (force_nl and not item.has_children() and not is_unary):
item.start += get_newline() + padding + get_indentation()
elif item.is_inline() and has_block_sibling(item) and not is_very_first_child(item):
item.start = get_newline() + padding + item.start
item.padding = padding + get_indentation()
return item
def process(tree, profile, level=0):
"""
Processes simplified tree, making it suitable for output as HTML structure
@type item: ZenNode
@type profile: dict
@param level: Depth level
@type level: int
"""
for item in tree.children:
if item.type == 'tag':
item = process_tag(item, profile, level)
else:
item = process_snippet(item, profile, level)
if item.content:
item.content = zen_coding.pad_string(item.content, item.padding)
process(item, profile, level + 1)
return tree
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Filter that produces HAML tree
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
from zencoding import zen_core as zen_coding
child_token = '${child}'
def make_attributes_string(tag, profile):
"""
Creates HTML attributes string from tag according to profile settings
@type tag: ZenNode
@type profile: dict
"""
# make attribute string
attrs = ''
attr_quote = profile['attr_quotes'] == 'single' and "'" or '"'
cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or ''
# use short notation for ID and CLASS attributes
for a in tag.attributes:
name_lower = a['name'].lower()
if name_lower == 'id':
attrs += '#' + (a['value'] or cursor)
elif name_lower == 'class':
attrs += '.' + (a['value'] or cursor)
other_attrs = []
# process other attributes
for a in tag.attributes:
name_lower = a['name'].lower()
if name_lower != 'id' and name_lower != 'class':
attr_name = profile['attr_case'] == 'upper' and a['name'].upper() or name_lower
other_attrs.append(':' + attr_name + ' => ' + attr_quote + (a['value'] or cursor) + attr_quote)
if other_attrs:
attrs += '{' + ', '.join(other_attrs) + '}'
return attrs
def _replace(placeholder, value):
if placeholder:
return placeholder % value
else:
return value
def process_snippet(item, profile, level=0):
"""
Processes element with <code>snippet</code> type
@type item: ZenNode
@type profile: dict
@type level: int
"""
data = item.source.value
if not data:
# snippet wasn't found, process it as tag
return process_tag(item, profile, level)
tokens = data.split(child_token)
if len(tokens) < 2:
start = tokens[0]
end = ''
else:
start, end = tokens
padding = item.parent and item.parent.padding or ''
item.start = _replace(item.start, zen_coding.pad_string(start, padding))
item.end = _replace(item.end, zen_coding.pad_string(end, padding))
return item
def has_block_sibling(item):
"""
Test if passed node has block-level sibling element
@type item: ZenNode
@return: bool
"""
return item.parent and item.parent.has_block_children()
def process_tag(item, profile, level=0):
"""
Processes element with <code>tag</code> type
@type item: ZenNode
@type profile: dict
@type level: int
"""
if not item.name:
# looks like it's root element
return item
attrs = make_attributes_string(item, profile)
cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or ''
self_closing = ''
is_unary = item.is_unary() and not item.children
if profile['self_closing_tag'] and is_unary:
self_closing = '/'
# define tag name
tag_name = '%' + (profile['tag_case'] == 'upper' and item.name.upper() or item.name.lower())
if tag_name.lower() == '%div' and '{' not in attrs:
# omit div tag
tag_name = ''
item.end = ''
item.start = _replace(item.start, tag_name + attrs + self_closing)
if not item.children and not is_unary:
item.start += cursor
return item
def process(tree, profile, level=0):
"""
Processes simplified tree, making it suitable for output as HTML structure
@type tree: ZenNode
@type profile: dict
@type level: int
"""
if level == 0:
# preformat tree
tree = zen_coding.run_filters(tree, profile, '_format')
for i, item in enumerate(tree.children):
if item.type == 'tag':
process_tag(item, profile, level)
else:
process_snippet(item, profile, level)
# replace counters
item.start = zen_coding.replace_counter(item.start, i + 1)
item.end = zen_coding.replace_counter(item.end, i + 1)
process(item, profile, level + 1)
return tree
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Filter that produces HTML tree
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
from zencoding import zen_core as zen_coding
child_token = '${child}'
def make_attributes_string(tag, profile):
"""
Creates HTML attributes string from tag according to profile settings
@type tag: ZenNode
@type profile: dict
"""
# make attribute string
attrs = ''
attr_quote = profile['attr_quotes'] == 'single' and "'" or '"'
cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or ''
# process other attributes
for a in tag.attributes:
attr_name = profile['attr_case'] == 'upper' and a['name'].upper() or a['name'].lower()
attrs += ' ' + attr_name + '=' + attr_quote + (a['value'] or cursor) + attr_quote
return attrs
def _replace(placeholder, value):
if placeholder:
return placeholder % value
else:
return value
def process_snippet(item, profile, level):
"""
Processes element with <code>snippet</code> type
@type item: ZenNode
@type profile: dict
@type level: int
"""
data = item.source.value;
if not data:
# snippet wasn't found, process it as tag
return process_tag(item, profile, level)
tokens = data.split(child_token)
if len(tokens) < 2:
start = tokens[0]
end = ''
else:
start, end = tokens
padding = item.parent and item.parent.padding or ''
item.start = _replace(item.start, zen_coding.pad_string(start, padding))
item.end = _replace(item.end, zen_coding.pad_string(end, padding))
return item
def has_block_sibling(item):
"""
Test if passed node has block-level sibling element
@type item: ZenNode
@return: bool
"""
return item.parent and item.parent.has_block_children()
def process_tag(item, profile, level):
"""
Processes element with <code>tag</code> type
@type item: ZenNode
@type profile: dict
@type level: int
"""
if not item.name:
# looks like it's root element
return item
attrs = make_attributes_string(item, profile)
cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or ''
self_closing = ''
is_unary = item.is_unary() and not item.children
start= ''
end = ''
if profile['self_closing_tag'] == 'xhtml':
self_closing = ' /'
elif profile['self_closing_tag'] is True:
self_closing = '/'
# define opening and closing tags
tag_name = profile['tag_case'] == 'upper' and item.name.upper() or item.name.lower()
if is_unary:
start = '<' + tag_name + attrs + self_closing + '>'
item.end = ''
else:
start = '<' + tag_name + attrs + '>'
end = '</' + tag_name + '>'
item.start = _replace(item.start, start)
item.end = _replace(item.end, end)
if not item.children and not is_unary:
item.start += cursor
return item
def process(tree, profile, level=0):
"""
Processes simplified tree, making it suitable for output as HTML structure
@type tree: ZenNode
@type profile: dict
@type level: int
"""
if level == 0:
# preformat tree
tree = zen_coding.run_filters(tree, profile, '_format')
for i, item in enumerate(tree.children):
if item.type == 'tag':
process_tag(item, profile, level)
else:
process_snippet(item, profile, level)
# replace counters
item.start = zen_coding.replace_counter(item.start, i + 1)
item.end = zen_coding.replace_counter(item.end, i + 1)
process(item, profile, level + 1)
return tree
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Filter for trimming "select" attributes from some tags that contains
child elements
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
import re
tags = {
'xsl:variable': 1,
'xsl:with-param': 1
}
re_attr = re.compile(r'\s+select\s*=\s*([\'"]).*?\1')
def trim_attribute(node):
"""
Removes "select" attribute from node
@type node: ZenNode
"""
node.start = re_attr.sub('', node.start)
def process(tree, profile):
for item in tree.children:
if item.type == 'tag' and item.name.lower() in tags and item.children:
trim_attribute(item)
process(item, profile)
+273
View File
@@ -0,0 +1,273 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Context-independent xHTML pair matcher
Use method <code>match(html, start_ix)</code> to find matching pair.
If pair was found, this function returns a list of indexes where tag pair
starts and ends. If pair wasn't found, <code>None</code> will be returned.
The last matched (or unmatched) result is saved in <code>last_match</code>
dictionary for later use.
@author: Sergey Chikuyonok (serge.che@gmail.com)
'''
import re
start_tag = r'<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*)\s*(\/?)>'
end_tag = r'<\/([\w\:\-]+)[^>]*>'
attr = r'([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?'
"Last matched HTML pair"
last_match = {
'opening_tag': None, # Tag() or Comment() object
'closing_tag': None, # Tag() or Comment() object
'start_ix': -1,
'end_ix': -1
}
cur_mode = 'xhtml'
"Current matching mode"
def set_mode(new_mode):
global cur_mode
if new_mode != 'html': new_mode = 'xhtml'
cur_mode = new_mode
def make_map(elems):
"""
Create dictionary of elements for faster searching
@param elems: Elements, separated by comma
@type elems: str
"""
obj = {}
for elem in elems.split(','):
obj[elem] = True
return obj
# Empty Elements - HTML 4.01
empty = make_map("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
# Block Elements - HTML 4.01
block = make_map("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
# Inline Elements - HTML 4.01
inline = make_map("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
# Elements that you can, intentionally, leave open
# (and which close themselves)
close_self = make_map("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
# Attributes that have their values filled in disabled="disabled"
fill_attrs = make_map("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
#Special Elements (can contain anything)
# serge.che: parsing data inside <scipt> elements is a "feature"
special = make_map("style");
class Tag():
"""Matched tag"""
def __init__(self, match, ix):
"""
@type match: MatchObject
@param match: Matched HTML tag
@type ix: int
@param ix: Tag's position
"""
global cur_mode
name = match.group(1).lower()
self.name = name
self.full_tag = match.group(0)
self.start = ix
self.end = ix + len(self.full_tag)
self.unary = ( len(match.groups()) > 2 and bool(match.group(3)) ) or (name in empty and cur_mode == 'html')
self.type = 'tag'
self.close_self = (name in close_self and cur_mode == 'html')
class Comment():
"Matched comment"
def __init__(self, start, end):
self.start = start
self.end = end
self.type = 'comment'
def make_range(opening_tag=None, closing_tag=None, ix=0):
"""
Makes selection ranges for matched tag pair
@type opening_tag: Tag
@type closing_tag: Tag
@type ix: int
@return list
"""
start_ix, end_ix = -1, -1
if opening_tag and not closing_tag: # unary element
start_ix = opening_tag.start
end_ix = opening_tag.end
elif opening_tag and closing_tag: # complete element
if (opening_tag.start < ix and opening_tag.end > ix) or (closing_tag.start <= ix and closing_tag.end > ix):
start_ix = opening_tag.start
end_ix = closing_tag.end;
else:
start_ix = opening_tag.end
end_ix = closing_tag.start
return start_ix, end_ix
def save_match(opening_tag=None, closing_tag=None, ix=0):
"""
Save matched tag for later use and return found indexes
@type opening_tag: Tag
@type closing_tag: Tag
@type ix: int
@return list
"""
last_match['opening_tag'] = opening_tag;
last_match['closing_tag'] = closing_tag;
last_match['start_ix'], last_match['end_ix'] = make_range(opening_tag, closing_tag, ix)
return last_match['start_ix'] != -1 and (last_match['start_ix'], last_match['end_ix']) or (None, None)
def match(html, start_ix, mode='xhtml'):
"""
Search for matching tags in <code>html</code>, starting from
<code>start_ix</code> position. The result is automatically saved
in <code>last_match</code> property
"""
return _find_pair(html, start_ix, mode, save_match)
def find(html, start_ix, mode='xhtml'):
"""
Search for matching tags in <code>html</code>, starting from
<code>start_ix</code> position.
"""
return _find_pair(html, start_ix, mode)
def get_tags(html, start_ix, mode='xhtml'):
"""
Search for matching tags in <code>html</code>, starting from
<code>start_ix</code> position. The difference between
<code>match</code> function itself is that <code>get_tags</code>
method doesn't save matched result in <code>last_match</code> property
and returns array of opening and closing tags
This method is generally used for lookups
"""
return _find_pair(html, start_ix, mode, lambda op, cl=None, ix=0: (op, cl) if op and op.type == 'tag' else None)
def _find_pair(html, start_ix, mode='xhtml', action=make_range):
"""
Search for matching tags in <code>html</code>, starting from
<code>start_ix</code> position
@param html: Code to search
@type html: str
@param start_ix: Character index where to start searching pair
(commonly, current caret position)
@type start_ix: int
@param action: Function that creates selection range
@type action: function
@return: list
"""
forward_stack = []
backward_stack = []
opening_tag = None
closing_tag = None
html_len = len(html)
set_mode(mode)
def has_match(substr, start=None):
if start is None:
start = ix
return html.find(substr, start) == start
def find_comment_start(start_pos):
while start_pos:
if html[start_pos] == '<' and has_match('<!--', start_pos):
break
start_pos -= 1
return start_pos
# find opening tag
ix = start_ix - 1
while ix >= 0:
ch = html[ix]
if ch == '<':
check_str = html[ix:]
m = re.match(end_tag, check_str)
if m: # found closing tag
tmp_tag = Tag(m, ix)
if tmp_tag.start < start_ix and tmp_tag.end > start_ix: # direct hit on searched closing tag
closing_tag = tmp_tag
else:
backward_stack.append(tmp_tag)
else:
m = re.match(start_tag, check_str)
if m: # found opening tag
tmp_tag = Tag(m, ix);
if tmp_tag.unary:
if tmp_tag.start < start_ix and tmp_tag.end > start_ix: # exact match
return action(tmp_tag, None, start_ix)
elif backward_stack and backward_stack[-1].name == tmp_tag.name:
backward_stack.pop()
else: # found nearest unclosed tag
opening_tag = tmp_tag
break
elif check_str.startswith('<!--'): # found comment start
end_ix = check_str.find('-->') + ix + 3;
if ix < start_ix and end_ix >= start_ix:
return action(Comment(ix, end_ix))
elif ch == '-' and has_match('-->'): # found comment end
# search left until comment start is reached
ix = find_comment_start(ix)
ix -= 1
if not opening_tag:
return action(None)
# find closing tag
if not closing_tag:
ix = start_ix
while ix < html_len:
ch = html[ix]
if ch == '<':
check_str = html[ix:]
m = re.match(start_tag, check_str)
if m: # found opening tag
tmp_tag = Tag(m, ix);
if not tmp_tag.unary:
forward_stack.append(tmp_tag)
else:
m = re.match(end_tag, check_str)
if m: #found closing tag
tmp_tag = Tag(m, ix);
if forward_stack and forward_stack[-1].name == tmp_tag.name:
forward_stack.pop()
else: # found matched closing tag
closing_tag = tmp_tag;
break
elif has_match('<!--'): # found comment
ix += check_str.find('-->') + 3
continue
elif ch == '-' and has_match('-->'):
# looks like cursor was inside comment with invalid HTML
if not forward_stack or forward_stack[-1].type != 'comment':
end_ix = ix + 3
return action(Comment( find_comment_start(ix), end_ix ))
ix += 1
return action(opening_tag, closing_tag, start_ix)
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
my_zen_settings = {
'html': {
'abbreviations': {
'jq': '<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>',
'demo': '<div id="demo"></div>'
}
}
}
+161
View File
@@ -0,0 +1,161 @@
'''
Zen Coding's settings parser
Created on Jun 14, 2009
@author: sergey
'''
from copy import deepcopy
import re
import types
from zen_settings import zen_settings
_original_settings = deepcopy(zen_settings)
TYPE_ABBREVIATION = 'zen-tag',
TYPE_EXPANDO = 'zen-expando',
TYPE_REFERENCE = 'zen-reference';
""" Reference to another abbreviation or tag """
re_tag = r'^<([\w\-]+(?:\:[\w\-]+)?)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*)\s*(\/?)>'
"Regular expression for XML tag matching"
re_attrs = r'([\w\-]+)\s*=\s*([\'"])(.*?)\2'
"Regular expression for matching XML attributes"
class Entry:
"""
Unified object for parsed data
"""
def __init__(self, entry_type, key, value):
"""
@type entry_type: str
@type key: str
@type value: dict
"""
self.type = entry_type
self.key = key
self.value = value
def _make_expando(key, value):
"""
Make expando from string
@type key: str
@type value: str
@return: Entry
"""
return Entry(TYPE_EXPANDO, key, value)
def _make_abbreviation(key, tag_name, attrs, is_empty=False):
"""
Make abbreviation from string
@param key: Abbreviation key
@type key: str
@param tag_name: Expanded element's tag name
@type tag_name: str
@param attrs: Expanded element's attributes
@type attrs: str
@param is_empty: Is expanded element empty or not
@type is_empty: bool
@return: dict
"""
result = {
'name': tag_name,
'is_empty': is_empty
};
if attrs:
result['attributes'] = [];
for m in re.findall(re_attrs, attrs):
result['attributes'].append({
'name': m[0],
'value': m[2]
})
return Entry(TYPE_ABBREVIATION, key, result)
def _parse_abbreviations(obj):
"""
Parses all abbreviations inside dictionary
@param obj: dict
"""
for key, value in obj.items():
key = key.strip()
if key[-1] == '+':
# this is expando, leave 'value' as is
obj[key] = _make_expando(key, value)
else:
m = re.search(re_tag, value)
if m:
obj[key] = _make_abbreviation(key, m.group(1), m.group(2), (m.group(3) == '/'))
else:
# assume it's reference to another abbreviation
obj[key] = Entry(TYPE_REFERENCE, key, value)
def parse(settings):
"""
Parse user's settings. This function must be called *before* any activity
in zen coding (for example, expanding abbreviation)
@type settings: dict
"""
for p, value in settings.items():
if p == 'abbreviations':
_parse_abbreviations(value)
elif p == 'extends':
settings[p] = [v.strip() for v in value.split(',')]
elif type(value) == types.DictType:
parse(value)
def extend(parent, child):
"""
Recursevly extends parent dictionary with children's keys. Used for merging
default settings with user's
@type parent: dict
@type child: dict
"""
for p, value in child.items():
if type(value) == types.DictType:
if p not in parent:
parent[p] = {}
extend(parent[p], value)
else:
parent[p] = value
def create_maps(obj):
"""
Create hash maps on certain string properties of zen settings
@type obj: dict
"""
for p, value in obj.items():
if p == 'element_types':
for k, v in value.items():
if isinstance(v, str):
value[k] = [el.strip() for el in v.split(',')]
elif type(value) == types.DictType:
create_maps(value)
if __name__ == '__main__':
pass
def get_settings(user_settings=None):
"""
Main function that gather all settings and returns parsed dictionary
@param user_settings: A dictionary of user-defined settings
"""
settings = deepcopy(_original_settings)
create_maps(settings)
if user_settings:
user_settings = deepcopy(user_settings)
create_maps(user_settings)
extend(settings, user_settings)
# now we need to parse final set of settings
parse(settings)
return settings
Binary file not shown.
+645
View File
@@ -0,0 +1,645 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Middleware layer that communicates between editor and Zen Coding.
This layer describes all available Zen Coding actions, like
"Expand Abbreviation".
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
"""
from zencoding import zen_core as zen_coding
from zencoding import html_matcher
import re
def find_abbreviation(editor):
"""
Search for abbreviation in editor from current caret position
@param editor: Editor instance
@type editor: ZenEditor
@return: str
"""
start, end = editor.get_selection_range()
if start != end:
# abbreviation is selected by user
return editor.get_content()[start, end];
# search for new abbreviation from current caret position
cur_line_start, cur_line_end = editor.get_current_line_range()
return zen_coding.extract_abbreviation(editor.get_content()[cur_line_start:start])
def expand_abbreviation(editor, syntax=None, profile_name=None):
"""
Find from current caret position and expand abbreviation in editor
@param editor: Editor instance
@type editor: ZenEditor
@param syntax: Syntax type (html, css, etc.)
@type syntax: str
@param profile_name: Output profile name (html, xml, xhtml)
@type profile_name: str
@return: True if abbreviation was expanded successfully
"""
if syntax is None: syntax = editor.get_syntax()
if profile_name is None: profile_name = editor.get_profile_name()
range_start, caret_pos = editor.get_selection_range()
abbr = find_abbreviation(editor)
content = ''
if abbr:
content = zen_coding.expand_abbreviation(abbr, syntax, profile_name)
if content:
editor.replace_content(content, caret_pos - len(abbr), caret_pos)
return True
return False
def expand_abbreviation_with_tab(editor, syntax, profile_name='xhtml'):
"""
A special version of <code>expandAbbreviation</code> function: if it can't
find abbreviation, it will place Tab character at caret position
@param editor: Editor instance
@type editor: ZenEditor
@param syntax: Syntax type (html, css, etc.)
@type syntax: str
@param profile_name: Output profile name (html, xml, xhtml)
@type profile_name: str
"""
if not expand_abbreviation(editor, syntax, profile_name):
editor.replace_content(zen_coding.get_variable('indentation'), editor.get_caret_pos())
return True
def match_pair(editor, direction='out', syntax=None):
"""
Find and select HTML tag pair
@param editor: Editor instance
@type editor: ZenEditor
@param direction: Direction of pair matching: 'in' or 'out'.
@type direction: str
"""
direction = direction.lower()
if syntax is None: syntax = editor.get_profile_name()
range_start, range_end = editor.get_selection_range()
cursor = range_end
content = editor.get_content()
rng = None
old_open_tag = html_matcher.last_match['opening_tag']
old_close_tag = html_matcher.last_match['closing_tag']
if direction == 'in' and old_open_tag and range_start != range_end:
# user has previously selected tag and wants to move inward
if not old_close_tag:
# unary tag was selected, can't move inward
return False
elif old_open_tag.start == range_start:
if content[old_open_tag.end] == '<':
# test if the first inward tag matches the entire parent tag's content
_r = html_matcher.find(content, old_open_tag.end + 1, syntax)
if _r[0] == old_open_tag.end and _r[1] == old_close_tag.start:
rng = html_matcher.match(content, old_open_tag.end + 1, syntax)
else:
rng = (old_open_tag.end, old_close_tag.start)
else:
rng = (old_open_tag.end, old_close_tag.start)
else:
new_cursor = content[0:old_close_tag.start].find('<', old_open_tag.end)
search_pos = new_cursor + 1 if new_cursor != -1 else old_open_tag.end
rng = html_matcher.match(content, search_pos, syntax)
else:
rng = html_matcher.match(content, cursor, syntax)
if rng and rng[0] is not None:
editor.create_selection(rng[0], rng[1])
return True
else:
return False
def match_pair_inward(editor):
return match_pair(editor, 'in')
def match_pair_outward(editor):
return match_pair(editor, 'out')
def narrow_to_non_space(text, start, end):
"""
Narrow down text indexes, adjusting selection to non-space characters
@type text: str
@type start: int
@type end: int
@return: list
"""
# narrow down selection until first non-space character
while start < end:
if not text[start].isspace():
break
start += 1
while end > start:
end -= 1
if not text[end].isspace():
end += 1
break
return start, end
def wrap_with_abbreviation(editor, abbr, syntax=None, profile_name=None):
"""
Wraps content with abbreviation
@param editor: Editor instance
@type editor: ZenEditor
@param syntax: Syntax type (html, css, etc.)
@type syntax: str
@param profile_name: Output profile name (html, xml, xhtml)
@type profile_name: str
"""
if not abbr: return None
if syntax is None: syntax = editor.get_syntax()
if profile_name is None: profile_name = editor.get_profile_name()
start_offset, end_offset = editor.get_selection_range()
content = editor.get_content()
if start_offset == end_offset:
# no selection, find tag pair
rng = html_matcher.match(content, start_offset, profile_name)
if rng[0] is None: # nothing to wrap
return None
else:
start_offset, end_offset = rng
start_offset, end_offset = narrow_to_non_space(content, start_offset, end_offset)
line_bounds = get_line_bounds(content, start_offset)
padding = get_line_padding(content[line_bounds[0]:line_bounds[1]])
new_content = content[start_offset:end_offset]
result = zen_coding.wrap_with_abbreviation(abbr, unindent_text(new_content, padding), syntax, profile_name)
if result:
editor.replace_content(result, start_offset, end_offset)
return True
return False
def unindent(editor, text):
"""
Unindent content, thus preparing text for tag wrapping
@param editor: Editor instance
@type editor: ZenEditor
@param text: str
@return str
"""
return unindent_text(text, get_current_line_padding(editor))
def unindent_text(text, pad):
"""
Removes padding at the beginning of each text's line
@type text: str
@type pad: str
"""
lines = zen_coding.split_by_lines(text)
for i,line in enumerate(lines):
if line.startswith(pad):
lines[i] = line[len(pad):]
return zen_coding.get_newline().join(lines)
def get_current_line_padding(editor):
"""
Returns padding of current editor's line
@return str
"""
return get_line_padding(editor.get_current_line())
def get_line_padding(line):
"""
Returns padding of current editor's line
@return str
"""
m = re.match(r'^(\s+)', line)
return m and m.group(0) or ''
def find_new_edit_point(editor, inc=1, offset=0):
"""
Search for new caret insertion point
@param editor: Editor instance
@type editor: ZenEditor
@param inc: Search increment: -1 — search left, 1 — search right
@param offset: Initial offset relative to current caret position
@return: -1 if insertion point wasn't found
"""
cur_point = editor.get_caret_pos() + offset
content = editor.get_content()
max_len = len(content)
next_point = -1
re_empty_line = r'^\s+$'
def get_line(ix):
start = ix
while start >= 0:
c = content[start]
if c == '\n' or c == '\r': break
start -= 1
return content[start:ix]
while cur_point < max_len and cur_point > 0:
cur_point += inc
cur_char = content[cur_point]
next_char = content[cur_point + 1]
prev_char = content[cur_point - 1]
if cur_char in '"\'':
if next_char == cur_char and prev_char == '=':
# empty attribute
next_point = cur_point + 1
elif cur_char == '>' and next_char == '<':
# between tags
next_point = cur_point + 1
elif cur_char in '\r\n':
# empty line
if re.search(re_empty_line, get_line(cur_point - 1)):
next_point = cur_point
if next_point != -1: break
return next_point
def prev_edit_point(editor):
"""
Move caret to previous edit point
@param editor: Editor instance
@type editor: ZenEditor
"""
cur_pos = editor.get_caret_pos()
new_point = find_new_edit_point(editor, -1)
if new_point == cur_pos:
# we're still in the same point, try searching from the other place
new_point = find_new_edit_point(editor, -1, -2)
if new_point != -1:
editor.set_caret_pos(new_point)
return True
return False
def next_edit_point(editor):
"""
Move caret to next edit point
@param editor: Editor instance
@type editor: ZenEditor
"""
new_point = find_new_edit_point(editor, 1)
if new_point != -1:
editor.set_caret_pos(new_point)
return True
return False
def insert_formatted_newline(editor, mode='html'):
"""
Inserts newline character with proper indentation
@param editor: Editor instance
@type editor: ZenEditor
@param mode: Syntax mode (only 'html' is implemented)
@type mode: str
"""
caret_pos = editor.get_caret_pos()
nl = zen_coding.get_newline()
pad = zen_coding.get_variable('indentation')
if mode == 'html':
# let's see if we're breaking newly created tag
pair = html_matcher.get_tags(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name())
if pair[0] and pair[1] and pair[0]['type'] == 'tag' and pair[0]['end'] == caret_pos and pair[1]['start'] == caret_pos:
editor.replace_content(nl + pad + zen_coding.get_caret_placeholder() + nl, caret_pos)
else:
editor.replace_content(nl, caret_pos)
else:
editor.replace_content(nl, caret_pos)
return True
def select_line(editor):
"""
Select line under cursor
@param editor: Editor instance
@type editor: ZenEditor
"""
start, end = editor.get_current_line_range();
editor.create_selection(start, end)
return True
def go_to_matching_pair(editor):
"""
Moves caret to matching opening or closing tag
@param editor: Editor instance
@type editor: ZenEditor
"""
content = editor.get_content()
caret_pos = editor.get_caret_pos()
if content[caret_pos] == '<':
# looks like caret is outside of tag pair
caret_pos += 1
tags = html_matcher.get_tags(content, caret_pos, editor.get_profile_name())
if tags and tags[0]:
# match found
open_tag, close_tag = tags
if close_tag: # exclude unary tags
if open_tag['start'] <= caret_pos and open_tag['end'] >= caret_pos:
editor.set_caret_pos(close_tag['start'])
elif close_tag['start'] <= caret_pos and close_tag['end'] >= caret_pos:
editor.set_caret_pos(open_tag['start'])
return True
return False
def merge_lines(editor):
"""
Merge lines spanned by user selection. If there's no selection, tries to find
matching tags and use them as selection
@param editor: Editor instance
@type editor: ZenEditor
"""
start, end = editor.get_selection_range()
if start == end:
# find matching tag
pair = html_matcher.match(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name())
if pair and pair[0] is not None:
start, end = pair
if start != end:
# got range, merge lines
text = editor.get_content()[start:end]
lines = map(lambda s: re.sub(r'^\s+', '', s), zen_coding.split_by_lines(text))
text = re.sub(r'\s{2,}', ' ', ''.join(lines))
editor.replace_content(text, start, end)
editor.create_selection(start, start + len(text))
return True
return False
def toggle_comment(editor):
"""
Toggle comment on current editor's selection or HTML tag/CSS rule
@type editor: ZenEditor
"""
syntax = editor.get_syntax()
if syntax == 'css':
return toggle_css_comment(editor)
else:
return toggle_html_comment(editor)
def toggle_html_comment(editor):
"""
Toggle HTML comment on current selection or tag
@type editor: ZenEditor
@return: True if comment was toggled
"""
start, end = editor.get_selection_range()
content = editor.get_content()
if start == end:
# no selection, find matching tag
pair = html_matcher.get_tags(content, editor.get_caret_pos(), editor.get_profile_name())
if pair and pair[0]: # found pair
start = pair[0].start
end = pair[1] and pair[1].end or pair[0].end
return generic_comment_toggle(editor, '<!--', '-->', start, end)
def toggle_css_comment(editor):
"""
Simple CSS commenting
@type editor: ZenEditor
@return: True if comment was toggled
"""
start, end = editor.get_selection_range()
if start == end:
# no selection, get current line
start, end = editor.get_current_line_range()
# adjust start index till first non-space character
start, end = narrow_to_non_space(editor.get_content(), start, end)
return generic_comment_toggle(editor, '/*', '*/', start, end)
def search_comment(text, pos, start_token, end_token):
"""
Search for nearest comment in <code>str</code>, starting from index <code>from</code>
@param text: Where to search
@type text: str
@param pos: Search start index
@type pos: int
@param start_token: Comment start string
@type start_token: str
@param end_token: Comment end string
@type end_token: str
@return: None if comment wasn't found, list otherwise
"""
start_ch = start_token[0]
end_ch = end_token[0]
comment_start = -1
comment_end = -1
def has_match(tx, start):
return text[start:start + len(tx)] == tx
# search for comment start
while pos:
pos -= 1
if text[pos] == start_ch and has_match(start_token, pos):
comment_start = pos
break
if comment_start != -1:
# search for comment end
pos = comment_start
content_len = len(text)
while content_len >= pos:
pos += 1
if text[pos] == end_ch and has_match(end_token, pos):
comment_end = pos + len(end_token)
break
if comment_start != -1 and comment_end != -1:
return comment_start, comment_end
else:
return None
def generic_comment_toggle(editor, comment_start, comment_end, range_start, range_end):
"""
Generic comment toggling routine
@type editor: ZenEditor
@param comment_start: Comment start token
@type comment_start: str
@param comment_end: Comment end token
@type comment_end: str
@param range_start: Start selection range
@type range_start: int
@param range_end: End selection range
@type range_end: int
@return: bool
"""
content = editor.get_content()
caret_pos = [editor.get_caret_pos()]
new_content = None
def adjust_caret_pos(m):
caret_pos[0] -= len(m.group(0))
return ''
def remove_comment(text):
"""
Remove comment markers from string
@param {Sting} str
@return {String}
"""
text = re.sub(r'^' + re.escape(comment_start) + r'\s*', adjust_caret_pos, text)
return re.sub(r'\s*' + re.escape(comment_end) + '$', '', text)
def has_match(tx, start):
return content[start:start + len(tx)] == tx
# first, we need to make sure that this substring is not inside comment
comment_range = search_comment(content, caret_pos[0], comment_start, comment_end)
if comment_range and comment_range[0] <= range_start and comment_range[1] >= range_end:
# we're inside comment, remove it
range_start, range_end = comment_range
new_content = remove_comment(content[range_start:range_end])
else:
# should add comment
# make sure that there's no comment inside selection
new_content = '%s %s %s' % (comment_start, re.sub(re.escape(comment_start) + r'\s*|\s*' + re.escape(comment_end), '', content[range_start:range_end]), comment_end)
# adjust caret position
caret_pos[0] += len(comment_start) + 1
# replace editor content
if new_content is not None:
d = caret_pos[0] - range_start
new_content = new_content[0:d] + zen_coding.get_caret_placeholder() + new_content[d:]
editor.replace_content(unindent(editor, new_content), range_start, range_end)
return True
return False
def split_join_tag(editor, profile_name=None):
"""
Splits or joins tag, e.g. transforms it into a short notation and vice versa:
<div></div> → <div /> : join
<div /> → <div></div> : split
@param editor: Editor instance
@type editor: ZenEditor
@param profile_name: Profile name
@type profile_name: str
"""
caret_pos = editor.get_caret_pos()
profile = zen_coding.get_profile(profile_name or editor.get_profile_name())
caret = zen_coding.get_caret_placeholder()
# find tag at current position
pair = html_matcher.get_tags(editor.get_content(), caret_pos, profile_name or editor.get_profile_name())
if pair and pair[0]:
new_content = pair[0].full_tag
if pair[1]: # join tag
closing_slash = ''
if profile['self_closing_tag'] is True:
closing_slash = '/'
elif profile['self_closing_tag'] == 'xhtml':
closing_slash = ' /'
new_content = re.sub(r'\s*>$', closing_slash + '>', new_content)
# add caret placeholder
if len(new_content) + pair[0].start < caret_pos:
new_content += caret
else:
d = caret_pos - pair[0].start
new_content = new_content[0:d] + caret + new_content[d:]
editor.replace_content(new_content, pair[0].start, pair[1].end)
else: # split tag
nl = zen_coding.get_newline()
pad = zen_coding.get_variable('indentation')
# define tag content depending on profile
tag_content = profile['tag_nl'] is True and nl + pad + caret + nl or caret
new_content = '%s%s</%s>' % (re.sub(r'\s*\/>$', '>', new_content), tag_content, pair[0].name)
editor.replace_content(new_content, pair[0].start, pair[0].end)
return True
else:
return False
def get_line_bounds(text, pos):
"""
Returns line bounds for specific character position
@type text: str
@param pos: Where to start searching
@type pos: int
@return: list
"""
start = 0
end = len(text) - 1
# search left
for i in range(pos - 1, 0, -1):
if text[i] in '\n\r':
start = i + 1
break
# search right
for i in range(pos, len(text)):
if text[i] in '\n\r':
end = i
break
return start, end
def remove_tag(editor):
"""
Gracefully removes tag under cursor
@type editor: ZenEditor
"""
caret_pos = editor.get_caret_pos()
content = editor.get_content()
# search for tag
pair = html_matcher.get_tags(content, caret_pos, editor.get_profile_name())
if pair and pair[0]:
if not pair[1]:
# simply remove unary tag
editor.replace_content(zen_coding.get_caret_placeholder(), pair[0].start, pair[0].end)
else:
tag_content_range = narrow_to_non_space(content, pair[0].end, pair[1].start)
start_line_bounds = get_line_bounds(content, tag_content_range[0])
start_line_pad = get_line_padding(content[start_line_bounds[0]:start_line_bounds[1]])
tag_content = content[tag_content_range[0]:tag_content_range[1]]
tag_content = unindent_text(tag_content, start_line_pad)
editor.replace_content(zen_coding.get_caret_placeholder() + tag_content, pair[0].start, pair[1].end)
return True
else:
return False
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+128
View File
@@ -0,0 +1,128 @@
'''
High-level editor interface that communicates with underlying editor (like
Espresso, Coda, etc.) or browser.
Basically, you should call <code>set_context(obj)</code> method to
set up undelying editor context before using any other method.
This interface is used by <i>zen_actions.py</i> for performing different
actions like <b>Expand abbreviation</b>
@example
import zen_editor
zen_editor.set_context(obj);
//now you are ready to use editor object
zen_editor.get_selection_range();
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
'''
class ZenEditor():
def __init__(self):
pass
def set_context(self, context):
"""
Setup underlying editor context. You should call this method
<code>before</code> using any Zen Coding action.
@param context: context object
"""
pass
def get_selection_range(self):
"""
Returns character indexes of selected text
@return: list of start and end indexes
@example
start, end = zen_editor.get_selection_range();
print('%s, %s' % (start, end))
"""
return 0, 0
def create_selection(self, start, end=None):
"""
Creates selection from <code>start</code> to <code>end</code> character
indexes. If <code>end</code> is ommited, this method should place caret
and <code>start</code> index
@type start: int
@type end: int
@example
zen_editor.create_selection(10, 40)
# move caret to 15th character
zen_editor.create_selection(15)
"""
pass
def get_current_line_range(self):
"""
Returns current line's start and end indexes
@return: list of start and end indexes
@example
start, end = zen_editor.get_current_line_range();
print('%s, %s' % (start, end))
"""
return 0, 0
def get_caret_pos(self):
""" Returns current caret position """
return 0
def set_caret_pos(self, pos):
"""
Set new caret position
@type pos: int
"""
pass
def get_current_line(self):
"""
Returns content of current line
@return: str
"""
return ''
def replace_content(self, value, start=None, end=None):
"""
Replace editor's content or it's part (from <code>start</code> to
<code>end</code> index). If <code>value</code> contains
<code>caret_placeholder</code>, the editor will put caret into
this position. If you skip <code>start</code> and <code>end</code>
arguments, the whole target's content will be replaced with
<code>value</code>.
If you pass <code>start</code> argument only,
the <code>value</code> will be placed at <code>start</code> string
index of current content.
If you pass <code>start</code> and <code>end</code> arguments,
the corresponding substring of current target's content will be
replaced with <code>value</code>
@param value: Content you want to paste
@type value: str
@param start: Start index of editor's content
@type start: int
@param end: End index of editor's content
@type end: int
"""
pass
def get_content(self):
"""
Returns editor's content
@return: str
"""
return ''
def get_syntax(self):
"""
Returns current editor's syntax mode
@return: str
"""
return 'html'
def get_profile_name(self):
"""
Returns current output profile name (@see zen_coding#setup_profile)
@return {String}
"""
return 'xhtml'
+727
View File
@@ -0,0 +1,727 @@
"""
Zen Coding settings
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
"""
zen_settings = {
# Variables that can be placed inside snippets or abbreviations as ${variable}
# ${child} variable is reserved, don't use it
'variables': {
'lang': 'en',
'locale': 'en-US',
'charset': 'UTF-8',
'profile': 'xhtml',
# Inner element indentation
'indentation': '\t'
},
# common settings are used for quick injection of user-defined snippets
'common': {
},
'css': {
'extends': 'common',
'snippets': {
"@i": "@import url(|);",
"@m": "@media print {\n\t|\n}",
"@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}",
"!": "!important",
"pos": "position:|;",
"pos:s": "position:static;",
"pos:a": "position:absolute;",
"pos:r": "position:relative;",
"pos:f": "position:fixed;",
"t": "top:|;",
"t:a": "top:auto;",
"r": "right:|;",
"r:a": "right:auto;",
"b": "bottom:|;",
"b:a": "bottom:auto;",
"l": "left:|;",
"l:a": "left:auto;",
"z": "z-index:|;",
"z:a": "z-index:auto;",
"fl": "float:|;",
"fl:n": "float:none;",
"fl:l": "float:left;",
"fl:r": "float:right;",
"cl": "clear:|;",
"cl:n": "clear:none;",
"cl:l": "clear:left;",
"cl:r": "clear:right;",
"cl:b": "clear:both;",
"d": "display:|;",
"d:n": "display:none;",
"d:b": "display:block;",
"d:ib": "display:inline;",
"d:li": "display:list-item;",
"d:ri": "display:run-in;",
"d:cp": "display:compact;",
"d:tb": "display:table;",
"d:itb": "display:inline-table;",
"d:tbcp": "display:table-caption;",
"d:tbcl": "display:table-column;",
"d:tbclg": "display:table-column-group;",
"d:tbhg": "display:table-header-group;",
"d:tbfg": "display:table-footer-group;",
"d:tbr": "display:table-row;",
"d:tbrg": "display:table-row-group;",
"d:tbc": "display:table-cell;",
"d:rb": "display:ruby;",
"d:rbb": "display:ruby-base;",
"d:rbbg": "display:ruby-base-group;",
"d:rbt": "display:ruby-text;",
"d:rbtg": "display:ruby-text-group;",
"v": "visibility:|;",
"v:v": "visibility:visible;",
"v:h": "visibility:hidden;",
"v:c": "visibility:collapse;",
"ov": "overflow:|;",
"ov:v": "overflow:visible;",
"ov:h": "overflow:hidden;",
"ov:s": "overflow:scroll;",
"ov:a": "overflow:auto;",
"ovx": "overflow-x:|;",
"ovx:v": "overflow-x:visible;",
"ovx:h": "overflow-x:hidden;",
"ovx:s": "overflow-x:scroll;",
"ovx:a": "overflow-x:auto;",
"ovy": "overflow-y:|;",
"ovy:v": "overflow-y:visible;",
"ovy:h": "overflow-y:hidden;",
"ovy:s": "overflow-y:scroll;",
"ovy:a": "overflow-y:auto;",
"ovs": "overflow-style:|;",
"ovs:a": "overflow-style:auto;",
"ovs:s": "overflow-style:scrollbar;",
"ovs:p": "overflow-style:panner;",
"ovs:m": "overflow-style:move;",
"ovs:mq": "overflow-style:marquee;",
"zoo": "zoom:1;",
"cp": "clip:|;",
"cp:a": "clip:auto;",
"cp:r": "clip:rect(|);",
"bxz": "box-sizing:|;",
"bxz:cb": "box-sizing:content-box;",
"bxz:bb": "box-sizing:border-box;",
"bxsh": "box-shadow:|;",
"bxsh:n": "box-shadow:none;",
"bxsh:w": "-webkit-box-shadow:0 0 0 #000;",
"bxsh:m": "-moz-box-shadow:0 0 0 0 #000;",
"m": "margin:|;",
"m:a": "margin:auto;",
"m:0": "margin:0;",
"m:2": "margin:0 0;",
"m:3": "margin:0 0 0;",
"m:4": "margin:0 0 0 0;",
"mt": "margin-top:|;",
"mt:a": "margin-top:auto;",
"mr": "margin-right:|;",
"mr:a": "margin-right:auto;",
"mb": "margin-bottom:|;",
"mb:a": "margin-bottom:auto;",
"ml": "margin-left:|;",
"ml:a": "margin-left:auto;",
"p": "padding:|;",
"p:0": "padding:0;",
"p:2": "padding:0 0;",
"p:3": "padding:0 0 0;",
"p:4": "padding:0 0 0 0;",
"pt": "padding-top:|;",
"pr": "padding-right:|;",
"pb": "padding-bottom:|;",
"pl": "padding-left:|;",
"w": "width:|;",
"w:a": "width:auto;",
"h": "height:|;",
"h:a": "height:auto;",
"maw": "max-width:|;",
"maw:n": "max-width:none;",
"mah": "max-height:|;",
"mah:n": "max-height:none;",
"miw": "min-width:|;",
"mih": "min-height:|;",
"o": "outline:|;",
"o:n": "outline:none;",
"oo": "outline-offset:|;",
"ow": "outline-width:|;",
"os": "outline-style:|;",
"oc": "outline-color:#000;",
"oc:i": "outline-color:invert;",
"bd": "border:|;",
"bd+": "border:1px solid #000;",
"bd:n": "border:none;",
"bdbk": "border-break:|;",
"bdbk:c": "border-break:close;",
"bdcl": "border-collapse:|;",
"bdcl:c": "border-collapse:collapse;",
"bdcl:s": "border-collapse:separate;",
"bdc": "border-color:#000;",
"bdi": "border-image:url(|);",
"bdi:n": "border-image:none;",
"bdi:w": "-webkit-border-image:url(|) 0 0 0 0 stretch stretch;",
"bdi:m": "-moz-border-image:url(|) 0 0 0 0 stretch stretch;",
"bdti": "border-top-image:url(|);",
"bdti:n": "border-top-image:none;",
"bdri": "border-right-image:url(|);",
"bdri:n": "border-right-image:none;",
"bdbi": "border-bottom-image:url(|);",
"bdbi:n": "border-bottom-image:none;",
"bdli": "border-left-image:url(|);",
"bdli:n": "border-left-image:none;",
"bdci": "border-corner-image:url(|);",
"bdci:n": "border-corner-image:none;",
"bdci:c": "border-corner-image:continue;",
"bdtli": "border-top-left-image:url(|);",
"bdtli:n": "border-top-left-image:none;",
"bdtli:c": "border-top-left-image:continue;",
"bdtri": "border-top-right-image:url(|);",
"bdtri:n": "border-top-right-image:none;",
"bdtri:c": "border-top-right-image:continue;",
"bdbri": "border-bottom-right-image:url(|);",
"bdbri:n": "border-bottom-right-image:none;",
"bdbri:c": "border-bottom-right-image:continue;",
"bdbli": "border-bottom-left-image:url(|);",
"bdbli:n": "border-bottom-left-image:none;",
"bdbli:c": "border-bottom-left-image:continue;",
"bdf": "border-fit:|;",
"bdf:c": "border-fit:clip;",
"bdf:r": "border-fit:repeat;",
"bdf:sc": "border-fit:scale;",
"bdf:st": "border-fit:stretch;",
"bdf:ow": "border-fit:overwrite;",
"bdf:of": "border-fit:overflow;",
"bdf:sp": "border-fit:space;",
"bdl": "border-length:|;",
"bdl:a": "border-length:auto;",
"bdsp": "border-spacing:|;",
"bds": "border-style:|;",
"bds:n": "border-style:none;",
"bds:h": "border-style:hidden;",
"bds:dt": "border-style:dotted;",
"bds:ds": "border-style:dashed;",
"bds:s": "border-style:solid;",
"bds:db": "border-style:double;",
"bds:dtds": "border-style:dot-dash;",
"bds:dtdtds": "border-style:dot-dot-dash;",
"bds:w": "border-style:wave;",
"bds:g": "border-style:groove;",
"bds:r": "border-style:ridge;",
"bds:i": "border-style:inset;",
"bds:o": "border-style:outset;",
"bdw": "border-width:|;",
"bdt": "border-top:|;",
"bdt+": "border-top:1px solid #000;",
"bdt:n": "border-top:none;",
"bdtw": "border-top-width:|;",
"bdts": "border-top-style:|;",
"bdts:n": "border-top-style:none;",
"bdtc": "border-top-color:#000;",
"bdr": "border-right:|;",
"bdr+": "border-right:1px solid #000;",
"bdr:n": "border-right:none;",
"bdrw": "border-right-width:|;",
"bdrs": "border-right-style:|;",
"bdrs:n": "border-right-style:none;",
"bdrc": "border-right-color:#000;",
"bdb": "border-bottom:|;",
"bdb+": "border-bottom:1px solid #000;",
"bdb:n": "border-bottom:none;",
"bdbw": "border-bottom-width:|;",
"bdbs": "border-bottom-style:|;",
"bdbs:n": "border-bottom-style:none;",
"bdbc": "border-bottom-color:#000;",
"bdl": "border-left:|;",
"bdl+": "border-left:1px solid #000;",
"bdl:n": "border-left:none;",
"bdlw": "border-left-width:|;",
"bdls": "border-left-style:|;",
"bdls:n": "border-left-style:none;",
"bdlc": "border-left-color:#000;",
"bdrs": "border-radius:|;",
"bdtrrs": "border-top-right-radius:|;",
"bdtlrs": "border-top-left-radius:|;",
"bdbrrs": "border-bottom-right-radius:|;",
"bdblrs": "border-bottom-left-radius:|;",
"bg": "background:|;",
"bg+": "background:#FFF url(|) 0 0 no-repeat;",
"bg:n": "background:none;",
"bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='|x.png');",
"bgc": "background-color:#FFF;",
"bgi": "background-image:url(|);",
"bgi:n": "background-image:none;",
"bgr": "background-repeat:|;",
"bgr:n": "background-repeat:no-repeat;",
"bgr:x": "background-repeat:repeat-x;",
"bgr:y": "background-repeat:repeat-y;",
"bga": "background-attachment:|;",
"bga:f": "background-attachment:fixed;",
"bga:s": "background-attachment:scroll;",
"bgp": "background-position:0 0;",
"bgpx": "background-position-x:|;",
"bgpy": "background-position-y:|;",
"bgbk": "background-break:|;",
"bgbk:bb": "background-break:bounding-box;",
"bgbk:eb": "background-break:each-box;",
"bgbk:c": "background-break:continuous;",
"bgcp": "background-clip:|;",
"bgcp:bb": "background-clip:border-box;",
"bgcp:pb": "background-clip:padding-box;",
"bgcp:cb": "background-clip:content-box;",
"bgcp:nc": "background-clip:no-clip;",
"bgo": "background-origin:|;",
"bgo:pb": "background-origin:padding-box;",
"bgo:bb": "background-origin:border-box;",
"bgo:cb": "background-origin:content-box;",
"bgz": "background-size:|;",
"bgz:a": "background-size:auto;",
"bgz:ct": "background-size:contain;",
"bgz:cv": "background-size:cover;",
"c": "color:#000;",
"tbl": "table-layout:|;",
"tbl:a": "table-layout:auto;",
"tbl:f": "table-layout:fixed;",
"cps": "caption-side:|;",
"cps:t": "caption-side:top;",
"cps:b": "caption-side:bottom;",
"ec": "empty-cells:|;",
"ec:s": "empty-cells:show;",
"ec:h": "empty-cells:hide;",
"lis": "list-style:|;",
"lis:n": "list-style:none;",
"lisp": "list-style-position:|;",
"lisp:i": "list-style-position:inside;",
"lisp:o": "list-style-position:outside;",
"list": "list-style-type:|;",
"list:n": "list-style-type:none;",
"list:d": "list-style-type:disc;",
"list:c": "list-style-type:circle;",
"list:s": "list-style-type:square;",
"list:dc": "list-style-type:decimal;",
"list:dclz": "list-style-type:decimal-leading-zero;",
"list:lr": "list-style-type:lower-roman;",
"list:ur": "list-style-type:upper-roman;",
"lisi": "list-style-image:|;",
"lisi:n": "list-style-image:none;",
"q": "quotes:|;",
"q:n": "quotes:none;",
"q:ru": "quotes:'\00AB' '\00BB' '\201E' '\201C';",
"q:en": "quotes:'\201C' '\201D' '\2018' '\2019';",
"ct": "content:|;",
"ct:n": "content:normal;",
"ct:oq": "content:open-quote;",
"ct:noq": "content:no-open-quote;",
"ct:cq": "content:close-quote;",
"ct:ncq": "content:no-close-quote;",
"ct:a": "content:attr(|);",
"ct:c": "content:counter(|);",
"ct:cs": "content:counters(|);",
"coi": "counter-increment:|;",
"cor": "counter-reset:|;",
"va": "vertical-align:|;",
"va:sup": "vertical-align:super;",
"va:t": "vertical-align:top;",
"va:tt": "vertical-align:text-top;",
"va:m": "vertical-align:middle;",
"va:bl": "vertical-align:baseline;",
"va:b": "vertical-align:bottom;",
"va:tb": "vertical-align:text-bottom;",
"va:sub": "vertical-align:sub;",
"ta": "text-align:|;",
"ta:l": "text-align:left;",
"ta:c": "text-align:center;",
"ta:r": "text-align:right;",
"tal": "text-align-last:|;",
"tal:a": "text-align-last:auto;",
"tal:l": "text-align-last:left;",
"tal:c": "text-align-last:center;",
"tal:r": "text-align-last:right;",
"td": "text-decoration:|;",
"td:n": "text-decoration:none;",
"td:u": "text-decoration:underline;",
"td:o": "text-decoration:overline;",
"td:l": "text-decoration:line-through;",
"te": "text-emphasis:|;",
"te:n": "text-emphasis:none;",
"te:ac": "text-emphasis:accent;",
"te:dt": "text-emphasis:dot;",
"te:c": "text-emphasis:circle;",
"te:ds": "text-emphasis:disc;",
"te:b": "text-emphasis:before;",
"te:a": "text-emphasis:after;",
"th": "text-height:|;",
"th:a": "text-height:auto;",
"th:f": "text-height:font-size;",
"th:t": "text-height:text-size;",
"th:m": "text-height:max-size;",
"ti": "text-indent:|;",
"ti:-": "text-indent:-9999px;",
"tj": "text-justify:|;",
"tj:a": "text-justify:auto;",
"tj:iw": "text-justify:inter-word;",
"tj:ii": "text-justify:inter-ideograph;",
"tj:ic": "text-justify:inter-cluster;",
"tj:d": "text-justify:distribute;",
"tj:k": "text-justify:kashida;",
"tj:t": "text-justify:tibetan;",
"to": "text-outline:|;",
"to+": "text-outline:0 0 #000;",
"to:n": "text-outline:none;",
"tr": "text-replace:|;",
"tr:n": "text-replace:none;",
"tt": "text-transform:|;",
"tt:n": "text-transform:none;",
"tt:c": "text-transform:capitalize;",
"tt:u": "text-transform:uppercase;",
"tt:l": "text-transform:lowercase;",
"tw": "text-wrap:|;",
"tw:n": "text-wrap:normal;",
"tw:no": "text-wrap:none;",
"tw:u": "text-wrap:unrestricted;",
"tw:s": "text-wrap:suppress;",
"tsh": "text-shadow:|;",
"tsh+": "text-shadow:0 0 0 #000;",
"tsh:n": "text-shadow:none;",
"lh": "line-height:|;",
"whs": "white-space:|;",
"whs:n": "white-space:normal;",
"whs:p": "white-space:pre;",
"whs:nw": "white-space:nowrap;",
"whs:pw": "white-space:pre-wrap;",
"whs:pl": "white-space:pre-line;",
"whsc": "white-space-collapse:|;",
"whsc:n": "white-space-collapse:normal;",
"whsc:k": "white-space-collapse:keep-all;",
"whsc:l": "white-space-collapse:loose;",
"whsc:bs": "white-space-collapse:break-strict;",
"whsc:ba": "white-space-collapse:break-all;",
"wob": "word-break:|;",
"wob:n": "word-break:normal;",
"wob:k": "word-break:keep-all;",
"wob:l": "word-break:loose;",
"wob:bs": "word-break:break-strict;",
"wob:ba": "word-break:break-all;",
"wos": "word-spacing:|;",
"wow": "word-wrap:|;",
"wow:nm": "word-wrap:normal;",
"wow:n": "word-wrap:none;",
"wow:u": "word-wrap:unrestricted;",
"wow:s": "word-wrap:suppress;",
"lts": "letter-spacing:|;",
"f": "font:|;",
"f+": "font:1em Arial,sans-serif;",
"fw": "font-weight:|;",
"fw:n": "font-weight:normal;",
"fw:b": "font-weight:bold;",
"fw:br": "font-weight:bolder;",
"fw:lr": "font-weight:lighter;",
"fs": "font-style:|;",
"fs:n": "font-style:normal;",
"fs:i": "font-style:italic;",
"fs:o": "font-style:oblique;",
"fv": "font-variant:|;",
"fv:n": "font-variant:normal;",
"fv:sc": "font-variant:small-caps;",
"fz": "font-size:|;",
"fza": "font-size-adjust:|;",
"fza:n": "font-size-adjust:none;",
"ff": "font-family:|;",
"ff:s": "font-family:serif;",
"ff:ss": "font-family:sans-serif;",
"ff:c": "font-family:cursive;",
"ff:f": "font-family:fantasy;",
"ff:m": "font-family:monospace;",
"fef": "font-effect:|;",
"fef:n": "font-effect:none;",
"fef:eg": "font-effect:engrave;",
"fef:eb": "font-effect:emboss;",
"fef:o": "font-effect:outline;",
"fem": "font-emphasize:|;",
"femp": "font-emphasize-position:|;",
"femp:b": "font-emphasize-position:before;",
"femp:a": "font-emphasize-position:after;",
"fems": "font-emphasize-style:|;",
"fems:n": "font-emphasize-style:none;",
"fems:ac": "font-emphasize-style:accent;",
"fems:dt": "font-emphasize-style:dot;",
"fems:c": "font-emphasize-style:circle;",
"fems:ds": "font-emphasize-style:disc;",
"fsm": "font-smooth:|;",
"fsm:a": "font-smooth:auto;",
"fsm:n": "font-smooth:never;",
"fsm:aw": "font-smooth:always;",
"fst": "font-stretch:|;",
"fst:n": "font-stretch:normal;",
"fst:uc": "font-stretch:ultra-condensed;",
"fst:ec": "font-stretch:extra-condensed;",
"fst:c": "font-stretch:condensed;",
"fst:sc": "font-stretch:semi-condensed;",
"fst:se": "font-stretch:semi-expanded;",
"fst:e": "font-stretch:expanded;",
"fst:ee": "font-stretch:extra-expanded;",
"fst:ue": "font-stretch:ultra-expanded;",
"op": "opacity:|;",
"op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);",
"op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';",
"rz": "resize:|;",
"rz:n": "resize:none;",
"rz:b": "resize:both;",
"rz:h": "resize:horizontal;",
"rz:v": "resize:vertical;",
"cur": "cursor:|;",
"cur:a": "cursor:auto;",
"cur:d": "cursor:default;",
"cur:c": "cursor:crosshair;",
"cur:ha": "cursor:hand;",
"cur:he": "cursor:help;",
"cur:m": "cursor:move;",
"cur:p": "cursor:pointer;",
"cur:t": "cursor:text;",
"pgbb": "page-break-before:|;",
"pgbb:au": "page-break-before:auto;",
"pgbb:al": "page-break-before:always;",
"pgbb:l": "page-break-before:left;",
"pgbb:r": "page-break-before:right;",
"pgbi": "page-break-inside:|;",
"pgbi:au": "page-break-inside:auto;",
"pgbi:av": "page-break-inside:avoid;",
"pgba": "page-break-after:|;",
"pgba:au": "page-break-after:auto;",
"pgba:al": "page-break-after:always;",
"pgba:l": "page-break-after:left;",
"pgba:r": "page-break-after:right;",
"orp": "orphans:|;",
"wid": "widows:|;"
}
},
'html': {
'extends': 'common',
'filters': 'html',
'snippets': {
'cc:ie6': '<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->',
'cc:ie': '<!--[if IE]>\n\t${child}|\n<![endif]-->',
'cc:noie': '<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->',
'html:4t': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
'<html lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:4s': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
'<html lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:xt': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:xs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:xxs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:5': '<!DOCTYPE HTML>\n' +
'<html lang="${locale}">\n' +
'<head>\n' +
' <meta charset="${charset}">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>'
},
'abbreviations': {
'a': '<a href=""></a>',
'a:link': '<a href="http://|"></a>',
'a:mail': '<a href="mailto:|"></a>',
'abbr': '<abbr title=""></abbr>',
'acronym': '<acronym title=""></acronym>',
'base': '<base href="" />',
'bdo': '<bdo dir=""></bdo>',
'bdo:r': '<bdo dir="rtl"></bdo>',
'bdo:l': '<bdo dir="ltr"></bdo>',
'link:css': '<link rel="stylesheet" type="text/css" href="|style.css" media="all" />',
'link:print': '<link rel="stylesheet" type="text/css" href="|print.css" media="print" />',
'link:favicon': '<link rel="shortcut icon" type="image/x-icon" href="|favicon.ico" />',
'link:touch': '<link rel="apple-touch-icon" href="|favicon.png" />',
'link:rss': '<link rel="alternate" type="application/rss+xml" title="RSS" href="|rss.xml" />',
'link:atom': '<link rel="alternate" type="application/atom+xml" title="Atom" href="atom.xml" />',
'meta:utf': '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />',
'meta:win': '<meta http-equiv="Content-Type" content="text/html;charset=Win-1251" />',
'meta:compat': '<meta http-equiv="X-UA-Compatible" content="IE=7" />',
'style': '<style type="text/css"></style>',
'script': '<script type="text/javascript"></script>',
'script:src': '<script type="text/javascript" src=""></script>',
'img': '<img src="" alt="" />',
'iframe': '<iframe src="" frameborder="0"></iframe>',
'embed': '<embed src="" type="" />',
'object': '<object data="" type=""></object>',
'param': '<param name="" value="" />',
'map': '<map name=""></map>',
'area': '<area shape="" coords="" href="" alt="" />',
'area:d': '<area shape="default" href="" alt="" />',
'area:c': '<area shape="circle" coords="" href="" alt="" />',
'area:r': '<area shape="rect" coords="" href="" alt="" />',
'area:p': '<area shape="poly" coords="" href="" alt="" />',
'link': '<link rel="stylesheet" href="" />',
'form': '<form action=""></form>',
'form:get': '<form action="" method="get"></form>',
'form:post': '<form action="" method="post"></form>',
'label': '<label for=""></label>',
'input': '<input type="" />',
'input:hidden': '<input type="hidden" name="" />',
'input:h': '<input type="hidden" name="" />',
'input:text': '<input type="text" name="" id="" />',
'input:t': '<input type="text" name="" id="" />',
'input:search': '<input type="search" name="" id="" />',
'input:email': '<input type="email" name="" id="" />',
'input:url': '<input type="url" name="" id="" />',
'input:password': '<input type="password" name="" id="" />',
'input:p': '<input type="password" name="" id="" />',
'input:datetime': '<input type="datetime" name="" id="" />',
'input:date': '<input type="date" name="" id="" />',
'input:datetime-local': '<input type="datetime-local" name="" id="" />',
'input:month': '<input type="month" name="" id="" />',
'input:week': '<input type="week" name="" id="" />',
'input:time': '<input type="time" name="" id="" />',
'input:number': '<input type="number" name="" id="" />',
'input:color': '<input type="color" name="" id="" />',
'input:checkbox': '<input type="checkbox" name="" id="" />',
'input:c': '<input type="checkbox" name="" id="" />',
'input:radio': '<input type="radio" name="" id="" />',
'input:r': '<input type="radio" name="" id="" />',
'input:range': '<input type="range" name="" id="" />',
'input:file': '<input type="file" name="" id="" />',
'input:f': '<input type="file" name="" id="" />',
'input:submit': '<input type="submit" value="" />',
'input:s': '<input type="submit" value="" />',
'input:image': '<input type="image" src="" alt="" />',
'input:i': '<input type="image" src="" alt="" />',
'input:reset': '<input type="reset" value="" />',
'input:button': '<input type="button" value="" />',
'input:b': '<input type="button" value="" />',
'select': '<select name="" id=""></select>',
'option': '<option value=""></option>',
'textarea': '<textarea name="" id="" cols="30" rows="10"></textarea>',
'menu:context': '<menu type="context"></menu>',
'menu:c': '<menu type="context"></menu>',
'menu:toolbar': '<menu type="toolbar"></menu>',
'menu:t': '<menu type="toolbar"></menu>',
'video': '<video src=""></video>',
'audio': '<audio src=""></audio>',
'html:xml': '<html xmlns="http://www.w3.org/1999/xhtml"></html>',
'bq': '<blockquote></blockquote>',
'acr': '<acronym></acronym>',
'fig': '<figure></figure>',
'ifr': '<iframe></iframe>',
'emb': '<embed></embed>',
'obj': '<object></object>',
'src': '<source></source>',
'cap': '<caption></caption>',
'colg': '<colgroup></colgroup>',
'fst': '<fieldset></fieldset>',
'btn': '<button></button>',
'optg': '<optgroup></optgroup>',
'opt': '<option></option>',
'tarea': '<textarea></textarea>',
'leg': '<legend></legend>',
'sect': '<section></section>',
'art': '<article></article>',
'hdr': '<header></header>',
'ftr': '<footer></footer>',
'adr': '<address></address>',
'dlg': '<dialog></dialog>',
'str': '<strong></strong>',
'prog': '<progress></progress>',
'fset': '<fieldset></fieldset>',
'datag': '<datagrid></datagrid>',
'datal': '<datalist></datalist>',
'kg': '<keygen></keygen>',
'out': '<output></output>',
'det': '<details></details>',
'cmd': '<command></command>',
# expandos
'ol+': 'ol>li',
'ul+': 'ul>li',
'dl+': 'dl>dt+dd',
'map+': 'map>area',
'table+': 'table>tr>td',
'colgroup+': 'colgroup>col',
'colg+': 'colgroup>col',
'tr+': 'tr>td',
'select+': 'select>option',
'optgroup+': 'optgroup>option',
'optg+': 'optgroup>option'
},
'element_types': {
'empty': 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command',
'block_level': 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6',
'inline_level': 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'
}
},
'xsl': {
'extends': 'common,html',
'filters': 'html, xsl',
'abbreviations': {
'tm': '<xsl:template match="" mode=""></xsl:template>',
'tmatch': 'tm',
'tn': '<xsl:template name=""></xsl:template>',
'tname': 'tn',
'xsl:when': '<xsl:when test=""></xsl:when>',
'wh': 'xsl:when',
'var': '<xsl:variable name="">|</xsl:variable>',
'vare': '<xsl:variable name="" select=""/>',
'if': '<xsl:if test=""></xsl:if>',
'call': '<xsl:call-template name=""/>',
'attr': '<xsl:attribute name=""></xsl:attribute>',
'wp': '<xsl:with-param name="" select=""/>',
'par': '<xsl:param name="" select=""/>',
'val': '<xsl:value-of select=""/>',
'co': '<xsl:copy-of select=""/>',
'each': '<xsl:for-each select=""></xsl:for-each>',
'ap': '<xsl:apply-templates select="" mode=""/>',
# expandos
'choose+': 'xsl:choose>xsl:when+xsl:otherwise'
}
},
'haml': {
'filters': 'haml',
'extends': 'html'
}
}
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>kCode</string>
<key>ordering</key>
<array>
<string>zen-remove-tag-3</string>
<string>zen-balance-outward-2</string>
<string>EF1F0ADB-1A4D-411A-9BDB-FF9837072CB2</string>
<string>FC3E137B-6E92-40C9-BAF6-ED13182D7269</string>
</array>
<key>uuid</key>
<string>3F3B4BEF-F826-47EA-8575-876F1AEBB999</string>
</dict>
</plist>
+1
View File
@@ -0,0 +1 @@
sparkup