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 get_zen_doctype(context, default='html'): ''' Tests the document to see if it is CSS or XSL; for use with zen coding actions to determine type of snippets to use ''' doc_type = default css_exts = ['css', 'less'] xsl_exts = ['xsl', 'xslt'] path = context.path() if path is not None: pos = path.rfind('.') if pos != -1: pos += 1 ext = path[pos:] if ext in css_exts: doc_type = 'css' elif ext in xsl_exts: doc_type = 'xsl' # No luck with the extension; check for inline style tags if doc_type == 'html': range = get_range(context) cursor = range.location + range.length content = context.string() start, end = html_matcher.match(content, cursor) tag = html_matcher.last_match['opening_tag'] if tag is not None: tag = tag.name if tag == 'style': doc_type = 'css' return doc_type
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 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 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 act(controller, bundle, options): context = tea.get_context(controller) direction = tea.get_option(options, 'direction', 'out') # Since input is always a selection of some kind, check if we have one rng = tea.get_range(context) cursor = rng.location + rng.length range_start, range_end = rng.location, rng.location + rng.length content = context.string() old_open_tag = html_matcher.last_match['opening_tag'] old_close_tag = html_matcher.last_match['closing_tag'] if direction.lower() == '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 _start, _end = html_matcher.find(content, old_open_tag.end + 1) if _start == old_open_tag.end and _end == old_close_tag.start: start, end = html_matcher.match(content, old_open_tag.end + 1) else: start, end = old_open_tag.end, old_close_tag.start else: start, end = old_open_tag.end, old_close_tag.start else: new_cursor = content.find('<', old_open_tag.end, old_close_tag.start) search_pos = new_cursor != -1 and new_cursor + 1 or old_open_tag.end start, end = html_matcher.match(content, search_pos) else: start, end = html_matcher.match(content, cursor) if start is not None: new_range = tea.new_range(start, end - start) tea.set_selected_range(context, new_range) return True else: return False
def testXhtml(self): self.assertEqual((11, 16), html_matcher.match(xhtml_string, 12)); self.assertEqual((3, 25), html_matcher.match(xhtml_string, 8)); self.assertEqual((32, 38), html_matcher.match(xhtml_string, 36)); self.assertEqual((46, 85), html_matcher.match(xhtml_string, 70)); self.assertEqual((3, 113), html_matcher.match(xhtml_string, 43)); self.assertEqual((89, 105), html_matcher.match(xhtml_string, 99)); self.assertEqual((12, 52), html_matcher.match(xhtml_string2, 39)); self.assertEqual((6, 59), html_matcher.match(xhtml_string2, 52)); self.assertEqual((6, 59), html_matcher.match(xhtml_string2, 57)); self.assertEqual((0, 66), html_matcher.match(xhtml_string2, 3)); self.assertEqual((39, 52), html_matcher.match(xhtml_string2, 45)); self.assertEqual((66, 97), html_matcher.match(xhtml_string2, 95)); self.assertEqual((23, 52), html_matcher.match(xsl_string, 32)); self.assertEqual((62, 91), html_matcher.match(xsl_string, 76)); self.assertEqual((3, 105), html_matcher.match(xhtml_string3, 77)); self.assertEqual((25, 56), html_matcher.match(xhtml_string3, 49));
def testXhtml(self): self.assertEqual((11, 16), html_matcher.match(xhtml_string, 12)) self.assertEqual((3, 25), html_matcher.match(xhtml_string, 8)) self.assertEqual((32, 38), html_matcher.match(xhtml_string, 36)) self.assertEqual((46, 85), html_matcher.match(xhtml_string, 70)) self.assertEqual((3, 113), html_matcher.match(xhtml_string, 43)) self.assertEqual((89, 105), html_matcher.match(xhtml_string, 99)) self.assertEqual((12, 52), html_matcher.match(xhtml_string2, 39)) self.assertEqual((6, 59), html_matcher.match(xhtml_string2, 52)) self.assertEqual((6, 59), html_matcher.match(xhtml_string2, 57)) self.assertEqual((0, 66), html_matcher.match(xhtml_string2, 3)) self.assertEqual((39, 52), html_matcher.match(xhtml_string2, 45)) self.assertEqual((66, 97), html_matcher.match(xhtml_string2, 95)) self.assertEqual((23, 52), html_matcher.match(xsl_string, 32)) self.assertEqual((62, 91), html_matcher.match(xsl_string, 76)) self.assertEqual((3, 105), html_matcher.match(xhtml_string3, 77)) self.assertEqual((25, 56), html_matcher.match(xhtml_string3, 49))
def wrap(self, context, abbr, profile_name='xhtml'): # Set up the config variables zen_settings = settings_loader.load_settings() zen.update_settings(zen_settings) zen.newline = self.safe_str(tea.get_line_ending(context)) zen_settings['variables']['indentation'] = self.safe_str(tea.get_indentation_string(context)) # This allows us to use smart incrementing tab stops in zen snippets point_ix = [0] def place_ins_point(text): if not point_ix[0]: point_ix[0] += 1 return '$0' else: return '' zen.insertion_point = place_ins_point text, rng = tea.selection_and_range(context) if not text: # no selection, find matching tag content = context.string() start, end = html_matcher.match(content, rng.location) if start is None: # nothing to wrap return False def is_space(char): return char.isspace() or char in r'\n\r' # narrow down selection until first non-space character while start < end: if not is_space(content[start]): break start += 1 while end > start: end -= 1 if not is_space(content[end]): end += 1 break rng = tea.new_range(start, end - start) text = tea.get_selection(context, rng) # Fetch the doctype based on file extension doc_type = tea.get_zen_doctype(context) text = self.unindent(context, text) # Damn Python's encodings! Have to convert string to ascii before wrapping # and then back to utf-8 result = zen.wrap_with_abbreviation(self.safe_str(abbr), self.safe_str(text), doc_type, profile_name) result = unicode(result, 'utf-8') result = tea.indent_snippet(context, result, rng) result = tea.clean_line_endings(context, result) cursor_loc = result.find('$0') if cursor_loc != -1: select_range = tea.new_range(cursor_loc + rng.location, 0) result = result.replace('$0', '') tea.insert_text_and_select(context, result, rng, select_range) else: tea.insert_text(context, result, rng)