def act(controller, bundle, options): context = tea.get_context(controller) snippet = tea.get_option(options, 'snippet', '') maintain_selection = tea.get_option(options, 'maintain_selection', False) text, range = tea.selection_and_range(context) snippet = tea.indent_snippet(context, snippet, range) snippet = tea.clean_line_endings(context, snippet) # Set up target selection sel_loc = snippet.find('$SELECTED_TEXT') cursor_loc = snippet.find('$0') if maintain_selection: select_range = tea.new_range(sel_loc + range.location, range.length) elif cursor_loc != -1: select_range = tea.new_range(snippet.find('$0') + range.location, 0) else: select_range = None snippet = snippet.replace('$SELECTED_TEXT', text) snippet = snippet.replace('$0', '') if select_range is not None: tea.insert_text_and_select(context, snippet, range, select_range) else: tea.insert_text(context, snippet, range)
def act(controller, bundle, options): ''' Required action method input dictates what should be trimmed: - None (default): falls back to alternate - selection: ignores lines if they exist, just trims selection - selected_lines: each line in the selection alternate dictates what to fall back on - None (default): will do nothing if input is blank - line: will trim the line the caret is on - all_lines: all lines in the document trim dictates what part of the text should be trimmed: - both (default) - start - end If respect_indent is True, indent characters (as defined in preferences) at the beginning of the line will be left untouched. ''' context = tea.get_context(controller) input = tea.get_option(options, 'input') alternate = tea.get_option(options, 'alternate') trim = tea.get_option(options, 'trim', 'both') respect_indent = tea.get_option(options, 'respect_indent', False) discard_empty = tea.get_option(options, 'discard_empty', False) # Since input is always a selection of some kind, check if we have one range = tea.get_range(context) if (range.length == 0) or input is None: if alternate.lower() == 'line': text, range = tea.get_line(context) text = tea.trim(context, text, False, trim, respect_indent, True, discard_empty) elif alternate.lower() == 'all_lines': range = tea.new_range(0, context.string().length()) text = tea.get_selection(context, range) text = tea.trim(context, text, True, trim, respect_indent, True, discard_empty) else: if input.lower() == 'selected_lines': parse_lines = True else: parse_lines = False text = tea.get_selection(context, range) text = tea.trim(context, text, parse_lines, trim, respect_indent, True, discard_empty) tea.insert_text(context, text, range) new_range = tea.new_range(range.location, len(text)) tea.set_selected_range(context, new_range)
def get_line_after_and_range(context, range = None): '''Get the full line immediately after the current (or supplied) range''' line_ending = tea.get_line_ending(context) len_line_ending = len(line_ending) if range is None: range = tea.get_range(context) content = context.string() start = range.location + range.length if not is_line_ending(content, start - len_line_ending, line_ending): start = content.find(line_ending, start) if start == -1: return None else: start += len_line_ending end = content.find(line_ending, start) if end == -1: end = len(content) else: end += len_line_ending start = max(0, start) end = min(end, len(content)) line_range = tea.new_range(start, end - start) return tea.get_selection(context, line_range), line_range
def get_line_after_and_range(context, range=None): '''Get the full line immediately after the current (or supplied) range''' line_ending = tea.get_line_ending(context) len_line_ending = len(line_ending) if range is None: range = tea.get_range(context) content = context.string() start = range.location + range.length if not is_line_ending(content, start - len_line_ending, line_ending): start = content.find(line_ending, start) if start == -1: return None else: start += len_line_ending end = content.find(line_ending, start) if end == -1: end = len(content) else: end += len_line_ending start = max(0, start) end = min(end, len(content)) line_range = tea.new_range(start, end - start) return tea.get_selection(context, line_range), line_range
def act(context, actionObject, operation='entab'): def replacements(match): '''Utility function for replacing items''' return match.group(0).replace(search, replace) spaces = int(actionObject.userInput().stringValue()) if operation == 'entab': target = re.compile(r'^(\t* +\t*)+', re.MULTILINE) search = ' ' * spaces replace = '\t' else: target = re.compile(r'^( *\t+ *)+', re.MULTILINE) search = '\t' replace = ' ' * spaces insertions = tea.new_recipe() ranges = tea.get_ranges(context) if len(ranges) == 1 and ranges[0].length == 0: # No selection, use the document ranges[0] = tea.new_range(0, context.string().length()) for range in ranges: text = tea.get_selection(context, range) # Non-Unix line endings will bork things; convert them text = tea.unix_line_endings(text) text = re.sub(target, replacements, text) if tea.get_line_ending(context) != '\n': text = tea.clean_line_endings(context, text) insertions.addReplacementString_forRange_(text, range) insertions.setUndoActionName_(operation.title()) context.applyTextRecipe_(insertions) return True
def replace_content(self, value, start=None, end=None, undo_name="Replace content"): """ 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 """ if start is None: start = 0 if end is None: end = len(self.get_content()) rng = tea.new_range(start, end - start) value = self.add_placeholders(value) tea.insert_snippet_over_range(self._context, value, rng, undo_name)
def act(context, direction=None, remove_duplicates=False, undo_name=None): ''' Required action method This only allows a single selection (enforced through the utility functions) then sorts the lines, either ascending or descending. Theoretically we could allow discontiguous selections; might be useful? ''' # Check if there is a selection, otherwise take all lines ranges = tea.get_ranges(context) if len(ranges) == 1 and ranges[0].length == 0: range = tea.new_range(0, context.string().length()) text = tea.get_selection(context, range) else: text, range = tea.get_single_selection(context, True) if text == None: return False # Split the text into lines, not maintaining the linebreaks lines = text.splitlines(False) # Remove duplicates if set if remove_duplicates: if direction is None: seen = {} result = [] for x in lines: if x in seen: continue seen[x] = 1 result.append(x) lines = result else: lines = list(set(lines)) # Sort lines ascending or descending if direction == 'asc' or direction == 'desc': lines.sort() if direction == 'desc': lines.reverse() # If direction is random, shuffle lines if direction == 'random': random.shuffle(lines) # Join lines to one string linebreak = tea.get_line_ending(context) sortedText = unicode.join(linebreak, lines) # Add final linebreak if selected text has one if text.endswith(linebreak): sortedText += linebreak # Paste the text return tea.insert_text_over_range(context, sortedText, range, undo_name)
def act(context, default=None, prefix_selection=False, suffix_selection=False, undo_name=None, **syntaxes): ''' Required action method Inserts arbitrary text over all selections; specific text can be syntax-specific (same procedure as Wrap Selection In Link) If you set prefix_selection to true, the inserted text will precede any selected text; if suffix_selection is true it will follow any selected text; if both are true it will wrap the text ''' # Grab the ranges ranges = tea.get_ranges(context) # Set up our text recipe insertions = tea.new_recipe() for range in ranges: if prefix_selection or suffix_selection: # Get the selected text text = tea.get_selection(context, range) if prefix_selection: text = '$INSERT' + text if suffix_selection: text += '$INSERT' # If empty selection, only insert one if text == '$INSERT$INSERT': text = '$INSERT' else: text = '$INSERT' # Check for zone-specific insertion insert = tea.select_from_zones(context, range, default, **syntaxes) text = text.replace('$INSERT', insert) text = text.replace('$TOUCH', '') # Insert the text, or replace the selected text if range.length is 0: insertions.addInsertedString_forIndex_(text, range.location) else: insertions.addReplacementString_forRange_(text, range) # Set undo name and run the recipe if undo_name != None: insertions.setUndoActionName_(undo_name) reset_cursor = False if len(ranges) is 1 and ranges[0].length is 0: # Thanks to addInsertedString's wonkiness, we have to reset the cursor reset_cursor = True # Espresso beeps if I return True or False; hence this weirdness return_val = context.applyTextRecipe_(insertions) if reset_cursor: new_range = tea.new_range(ranges[0].location + len(text), 0) tea.set_selected_range(context, new_range) return return_val
def replace_content(self, value, start=None, end=None, no_indent=False): """ 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 """ if start is None: start = 0 if end is None: end = len(self.get_content()) rng = tea.new_range(start, end - start) value = self.add_placeholders(value) if not no_indent: value = zencoding.utils.pad_string(value, get_line_padding(self.get_current_line())) sel_start, sel_end, value = self.preprocess_text(value) if sel_start is not None: select_range = tea.new_range(sel_start + rng.location, sel_end - sel_start) tea.insert_text_and_select(self._context, value, rng, select_range) else: tea.insert_text(self._context, value, rng)
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) """ if end is None: end = start new_range = tea.new_range(start, end - start) tea.set_selected_range(self._context, new_range)
def act(context, input=None, alternate=None, trim='both', respect_indent=False, undo_name=None): ''' Required action method input dictates what should be trimmed: - None (default): falls back to alternate - selection: ignores lines if they exist, just trims selection - selected_lines: each line in the selection alternate dictates what to fall back on - None (default): will do nothing if input is blank - line: will trim the line the caret is on - all_lines: all lines in the document trim dictates what part of the text should be trimmed: - both (default) - start - end If respect_indent is True, indent characters (as defined in preferences) at the beginning of the line will be left untouched. ''' # Since input is always a selection of some kind, check if we have one ranges = tea.get_ranges(context) insertions = tea.new_recipe() if (len(ranges) == 1 and ranges[0].length == 0) or input is None: if alternate == 'line': text, range = tea.get_line(context, ranges[0]) text = tea.trim(context, text, False, trim, respect_indent) elif alternate == 'all_lines': range = tea.new_range(0, context.string().length()) text = tea.get_selection(context, range) text = tea.trim(context, text, True, trim, respect_indent) insertions.addReplacementString_forRange_(text, range) else: if input == 'selected_lines': parse_lines = True else: parse_lines = False for range in ranges: text = tea.get_selection(context, range) text = tea.trim(context, text, parse_lines, trim, respect_indent) insertions.addReplacementString_forRange_(text, range) if undo_name != None: insertions.setUndoActionName_(undo_name) return context.applyTextRecipe_(insertions)
def act(controller, bundle, options): # Grab the context context = tea.get_context(controller) image = find_image(context) if image: size = get_image_size(context, image['tag']) if size: new_tag = replace_or_append(image['tag'], 'width', size['width']) new_tag = replace_or_append(new_tag, 'height', size['height']) rng = tea.get_range(context) tea.insert_text(context, new_tag, tea.new_range(image['start'], image['end'] - image['start'])) tea.set_selected_range(context, rng) return True return False
def act(controller, bundle, options): # Grab the context context = tea.get_context(controller) # Setup the options fallback = tea.get_option(options, 'fallback', '') snippet = tea.get_option(options, 'snippet', '$URL') # Get the clipboard contents, parse for a URL process = subprocess.Popen(['pbpaste'], stdout=subprocess.PIPE) clipboard, error = process.communicate(None) # Construct the default link url = format_hyperlink(clipboard, fallback) # Grab the selected text and range text, range = tea.selection_and_range(context) # Parse the snippet for $SELECTED_TEXT placeholder sel_loc = snippet.find('$SELECTED_TEXT') if sel_loc != -1: replace_text = True prefix = snippet[0:sel_loc] suffix = snippet[sel_loc+14:] else: replace_text = False prefix = snippet suffix = '' prefix = prefix.replace('$URL', url) suffix = suffix.replace('$URL', url) if replace_text: replacement = prefix + text + suffix else: replacement = prefix url_loc = replacement.find(url) if url_loc == -1: url_loc = len(replacement) url_len = 0 else: url_len = len(url) newrange = tea.new_range(url_loc + range.location, url_len) tea.insert_text_and_select(context, replacement, range, newrange)
def lines_and_range(context, range=None): '''Get the range of the full lines containing the current (or supplied) range''' line_ending = tea.get_line_ending(context) len_line_ending = len(line_ending) if range is None: range = tea.get_range(context) content = context.string() start, end = range.location, range.location + range.length if not is_line_ending(content, start - len_line_ending, line_ending): start = content.rfind(line_ending, 0, start) if start == -1: start = 0 else: start += len_line_ending # select to the end of the line (if it's not already selected) if not is_line_ending(content, end, line_ending): # edge case: cursor is at start of line and more than one line selected: if not is_line_ending(content, end - len_line_ending, line_ending) or len( content[start:end].split(line_ending)) <= 1: end = content.find(line_ending, end) if end == -1: end = len(content) else: end += len_line_ending # edge case: empty line, not selected elif is_line_ending(content, end - len_line_ending, line_ending): if len(content[start:end].split(line_ending)) <= 1: end = content.find(line_ending, end) if end == -1: end = len(content) else: end += len_line_ending else: end += len_line_ending start = max(0, start) end = min(end, len(content)) line_range = tea.new_range(start, end - start) return tea.get_selection(context, line_range), line_range
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 lines_and_range(context, range = None): '''Get the range of the full lines containing the current (or supplied) range''' line_ending = tea.get_line_ending(context) len_line_ending = len(line_ending) if range is None: range = tea.get_range(context) content = context.string() start, end = range.location, range.location + range.length if not is_line_ending(content, start - len_line_ending, line_ending): start = content.rfind(line_ending, 0, start) if start == -1: start = 0 else: start += len_line_ending # select to the end of the line (if it's not already selected) if not is_line_ending(content, end, line_ending): # edge case: cursor is at start of line and more than one line selected: if not is_line_ending(content, end - len_line_ending, line_ending) or len(content[start:end].split(line_ending)) <= 1: end = content.find(line_ending, end) if end == -1: end = len(content) else: end += len_line_ending # edge case: empty line, not selected elif is_line_ending(content, end - len_line_ending, line_ending): if len(content[start:end].split(line_ending)) <= 1: end = content.find(line_ending, end) if end == -1: end = len(content) else: end += len_line_ending else: end += len_line_ending start = max(0, start) end = min(end, len(content)) line_range = tea.new_range(start, end - start) return tea.get_selection(context, line_range), line_range
def act(context, type="named", wrap="$HEX", undo_name=None): """ Required action method Type can be: named: named HTML entities, with high value numeric entities if no name numeric: numeric HTML entities hex: hexadecimal encoding; use the 'wrap' option for specific output Wrap will be used if type is 'hex' and will replace $HEX with the actual hex value. For example '\u$HEX' will be result in something like '\u0022' """ ranges = tea.get_ranges(context) if len(ranges) == 1 and ranges[0].length == 0: # We've got one empty range; make sure it's not at the # beginning of the document if ranges[0].location > 0: # Set the new target range to the character before the cursor ranges[0] = tea.new_range(ranges[0].location - 1, 1) else: return False # Since we're here we've got something to work with insertions = tea.new_recipe() for range in ranges: text = tea.get_selection(context, range) if type == "named": # Convert any characters we can into named HTML entities text = tea.named_entities(text) elif type == "numeric": # Convert any characters we can into numeric HTML entities text = tea.numeric_entities(text, type) elif type == "hex": # Convert characters to hex via numeric entities text = tea.numeric_entities(text) text = tea.entities_to_hex(text, wrap) insertions.addReplacementString_forRange_(text, range) if undo_name is not None: insertions.setUndoActionName_(undo_name) return context.applyTextRecipe_(insertions)
def get_line_before_and_range(context, range = None): '''Get the full line immediately before the current (or supplied) range''' line_ending = tea.get_line_ending(context) if range is None: range = tea.get_range(context) content = context.string() end = content.rfind(line_ending, 0, range.location) if end == -1: return None else: end = end + len(line_ending) start = content.rfind(line_ending, 0, end - len(line_ending)) if start == -1: start = 0 else: start += len(line_ending) start = max(0, start) end = min(end, len(content)) line_range = tea.new_range(start, end - start) return tea.get_selection(context, line_range), line_range
def get_line_before_and_range(context, range=None): '''Get the full line immediately before the current (or supplied) range''' line_ending = tea.get_line_ending(context) if range is None: range = tea.get_range(context) content = context.string() end = content.rfind(line_ending, 0, range.location) if end == -1: return None else: end = end + len(line_ending) start = content.rfind(line_ending, 0, end - len(line_ending)) if start == -1: start = 0 else: start += len(line_ending) start = max(0, start) end = min(end, len(content)) line_range = tea.new_range(start, end - start) return tea.get_selection(context, line_range), line_range
def replace_content(self, value, start=None, end=None, no_indent=False, undo_name='Replace content'): """ 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 """ if start is None: start = 0 if end is None: end = len(self.get_content()) self.set_caret_pos(start) rng = tea.new_range(start, end - start) value = self.add_placeholders(value) tea.insert_snippet_over_range(self._context, value, rng, undo_name, not no_indent)
def didEndSheet_returnCode_contextInfo_(self, sheet, code, info): def replacements(match): '''Utility function for replacing items''' return match.group(0).replace(self.search, self.replace) if code == 1: # Leave sheet open with "processing" spinner self.spinner.startAnimation_(self) spaces = int(self.numSpaces.stringValue()) if self.action == 'entab': target = re.compile(r'^(\t* +\t*)+', re.MULTILINE) self.search = ' ' * spaces self.replace = '\t' else: target = re.compile(r'^( *\t+ *)+', re.MULTILINE) self.search = '\t' self.replace = ' ' * spaces insertions = tea.new_recipe() ranges = tea.get_ranges(self.context) if len(ranges) == 1 and ranges[0].length == 0: # No selection, use the document ranges[0] = tea.new_range(0, self.context.string().length()) for range in ranges: text = tea.get_selection(self.context, range) # Non-Unix line endings will bork things; convert them text = tea.unix_line_endings(text) text = re.sub(target, replacements, text) if tea.get_line_ending(self.context) != '\n': text = tea.clean_line_endings(self.context, text) insertions.addReplacementString_forRange_(text, range) insertions.setUndoActionName_(self.action.title()) self.context.applyTextRecipe_(insertions) self.spinner.stopAnimation_(self) sheet.orderOut_(self)
def performActionWithContext_error_(self, context): ''' Gathers the necessary info, populates the environment, and runs the script ''' def execute(file, input): '''Utility function for running the script''' script = subprocess.Popen( [file], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) return script.communicate(str(input)) if self.script is None: tea.log('No script found') return False # Environment variables that won't change with repetition os.putenv('E_SUGARPATH', self.bundle_path) filepath = context.documentContext().fileURL() if filepath is not None: os.putenv('E_FILENAME', filepath.path().lastPathComponent()) if filepath.isFileURL(): os.putenv( 'E_DIRECTORY', filepath.path().stringByDeletingLastPathComponent() ) os.putenv('E_FILEPATH', filepath.path()) root = tea.get_root_zone(context) if root is False: root = '' os.putenv('E_ROOT_ZONE', root) # Set up the preferences prefs = tea.get_prefs(context) os.putenv('E_SOFT_TABS', str(prefs.insertsSpacesForTab())) os.putenv('E_TAB_SIZE', str(prefs.numberOfSpacesForTab())) os.putenv('E_LINE_ENDING', prefs.lineEndingString()) os.putenv('E_XHTML', tea.get_tag_closestring(context)) # Set up the user-defined shell variables defaults = NSUserDefaults.standardUserDefaults() for item in defaults.arrayForKey_('TEAShellVariables'): if 'variable' in item and item['variable'] != '': os.putenv(item['variable'], item['value']) # Initialize our common variables recipe = tea.new_recipe() ranges = tea.get_ranges(context) # Check the user script folder for overrides file = os.path.join(os.path.expanduser( '~/Library/Application Support/Espresso/TEA/Scripts/' ), self.script) if not os.path.exists(file): file = os.path.join(self.bundle_path, 'TEA', self.script) if not os.path.exists(file): # File doesn't exist in the bundle, either, so something is screwy return tea.say( context, 'Error: could not find script', 'TEA could not find the script associated with this action. '\ 'Please contact the Sugar developer, or make sure it is '\ 'installed here:\n\n'\ '~/Library/Application Support/Espresso/TEA/Scripts' ) # There's always at least one range; this thus supports multiple # discontinuous selections for range in ranges: # These environment variables may change with repetition, so reset os.putenv('E_SELECTED_TEXT', str(context.string().substringWithRange_(range)) ) word, wordrange = tea.get_word(context, range) os.putenv('E_CURRENT_WORD', str(word)) os.putenv('E_CURRENT_LINE', str(context.string().substringWithRange_( context.lineStorage().lineRangeForRange_(range) )) ) os.putenv( 'E_LINENUMBER', str(context.lineStorage().lineNumberForIndex_(range.location)) ) os.putenv('E_LINEINDEX', str( range.location - \ context.lineStorage().lineStartIndexForIndex_lineNumber_( range.location, None ) )) active = tea.get_active_zone(context, range) if active is False: active = '' os.putenv('E_ACTIVE_ZONE', str(active)) # Setup STDIN and track the source source = 'input' if self.input == 'selection': input = tea.get_selection(context, range) if input == '': if self.alt == 'document': input = context.string() elif self.alt == 'line': input, range = tea.get_line(context, range) # For this usage, we don't want to pass the final linebreak input = input[:-1] range = tea.new_range(range.location, range.length-1) elif self.alt == 'word': input, range = tea.get_word(context, range) elif self.alt == 'character': input, range = tea.get_character(context, range) source = 'alt' elif self.input == 'document': input = context.string() else: input = '' # Run the script try: output, error = execute(file, input) except: # Most likely cause of failure is lack of executable status try: os.chmod(file, 0755) output, error = execute(file, input) except: # Failed to execute completely, so exit with error return tea.say( context, 'Error: cannot execute script', 'Error: could not execute the script. Please contact '\ 'the Sugar author.' ) # Log errors if error: tea.log(str(error)) # Process the output output = output.decode('utf-8') if self.output == 'document' or \ (source == 'alt' and self.alt == 'document'): docrange = tea.new_range(0, context.string().length()) recipe.addReplacementString_forRange_(output, docrange) break elif self.output == 'text': recipe.addReplacementString_forRange_(output, range) elif self.output == 'snippet': recipe.addDeletedRange_(range) break # If no output, we don't need to go any further if self.output is None: return True # Made it here, so apply the recipe and return if self.undo is not None: recipe.setUndoActionName_(self.undo) recipe.prepare() if recipe.numberOfChanges() > 0: response = context.applyTextRecipe_(recipe) else: response = True if self.output == 'snippet': response = tea.insert_snippet(context, output) return response
def act(controller, bundle, options): context = tea.get_context(controller) # Get the options alpha_numeric = tea.get_option(options, 'alpha_numeric', True) extra_characters = tea.get_option(options, 'extra_characters', '_-') bidirectional = tea.get_option(options, 'bidirectional', True) snippet = tea.get_option(options, 'snippet', '<$SELECTED_TEXT>$0</$WORD>') mode = tea.get_option(options, 'mode', '') # Fetch the word range = context.selectedRange() word, new_range = tea.get_word_or_selection(context, range, alpha_numeric, extra_characters, bidirectional) if word == '': # No word, so nothing further to do return False # If we're using $WORD, make sure the word is just a word if snippet.find('$WORD') >= 0: fullword = word word = tea.parse_word(word) if word is None: word = '' else: fullword = word # Process that sucker! if mode == 'zen' and fullword.find(' ') < 0: # Explicitly load zen settings zen_settings = settings_loader.load_settings() zen_core.update_settings(zen_settings) # Set up the config variables zen_core.newline = tea.get_line_ending(context) zen_settings['variables']['indentation'] = 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_core.insertion_point = place_ins_point # Determine doctype as best we can based on file extension doc_type = tea.get_zen_doctype(context) # Prepare the snippet snippet = zen_core.expand_abbreviation(fullword, doc_type, 'xhtml') elif mode == 'zen' and tea.is_selfclosing(word): # Self-closing, so construct the snippet from scratch snippet = '<' + fullword if fullword == word and not fullword in ['br', 'hr']: snippet += ' $0 />' else: snippet += ' />$0' # Indent the snippet snippet = tea.indent_snippet(context, snippet, new_range) snippet = tea.clean_line_endings(context, snippet) # Special replacement in case we're using $WORD snippet = snippet.replace('$WORD', word) snippet = snippet.replace('$SELECTED_TEXT', fullword) cursor_loc = snippet.find('$0') if cursor_loc != -1: select_range = tea.new_range(cursor_loc + new_range.location, 0) snippet = snippet.replace('$0', '') tea.insert_text_and_select(context, snippet, new_range, select_range) else: tea.insert_text(context, snippet, new_range)
def act(context, direction=None, remove_duplicates=False, undo_name=None): """ Required action method This sorts the selected lines (or document, if no selection) either ascending, descending, or randomly. """ # Check if there is a selection, otherwise take all lines ranges = tea.get_ranges(context) if len(ranges) == 1 and ranges[0].length == 0: ranges = [tea.new_range(0, context.string().length())] # Setup the text recipe recipe = tea.new_recipe() for range in ranges: text = tea.get_selection(context, range) # A blank range means we have only one range and it's empty # so we can't do any sorting if text == "": return False # Split the text into lines, not maintaining the linebreaks lines = text.splitlines(False) # Remove duplicates if set if remove_duplicates: if direction is None: seen = {} result = [] for x in lines: if x in seen: continue seen[x] = 1 result.append(x) lines = result else: lines = list(set(lines)) # Sort lines ascending or descending if direction == "asc" or direction == "desc": lines.sort() if direction == "desc": lines.reverse() # If direction is random, shuffle lines if direction == "random": random.shuffle(lines) # Join lines to one string linebreak = tea.get_line_ending(context) sortedText = unicode.join(linebreak, lines) # Add final linebreak if selected text has one if text.endswith(linebreak): sortedText += linebreak # Insert the text recipe.addReplacementString_forRange_(sortedText, range) if undo_name is not None: recipe.setUndoActionName_(undo_name) # Apply the recipe return context.applyTextRecipe_(recipe)
def act(context, target=None, source=None, trim=False, discard_indent=False, search_string=None, regex=False): ''' Required action method target dictates what we're looking for: - text - if unspecified, simply selects the source source dictates how to gather the string to search for: - word (word under the caret) - line (line under the caret) - if unspecified, defaults to selection Setting trim=True will cause the source to be trimmed Setting discard_indent=True will cause leading whitespace to be trimmed (unnecessary unless trim=True) search_string will set the string to search for if target is text or zone - $SELECTED_TEXT will be replaced with the source text Setting regex=True will cause search_string to be evaluated as regex ''' range = tea.get_ranges(context)[0] if source == 'word': text, range = tea.get_word(context, range) elif source == 'line': text, range = tea.get_line(context, range) elif range.length > 0: text = tea.get_selection(context, range) # Make sure that we've got some text, even if it's an empty string if text is None: text = '' # Trim the source if trim: if discard_indent: trimmed = tea.trim(context, text, False) else: trimmed = tea.trim(context, text, False, 'end') start = text.find(trimmed) if start != -1: start = range.location + start length = len(trimmed) if source == 'line': # We don't want the linebreak if we're trimming length = length - 1 range = tea.new_range(start, length) text = trimmed if target is not None and text: if search_string is not None: search = search_string.replace('$SELECTED_TEXT', text) else: search = text # Find the start and end points of the substring start = end = None if regex: match = re.search(r'(' + search + r')', context.string()) if match: # Get the start and end points start, end = match.span(1) else: start = context.string().find(search) if start != -1: end = start + len(search) else: start = None # Construct the new target range if start is not None and end is not None: range = tea.new_range(start, end - start) # Set the new range tea.set_selected_range(context, range) return True
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)
def act(controller, bundle, options): ''' Required action method target dictates what we're looking for: - text - if unspecified, simply selects the source source dictates how to gather the string to search for: - word (word under the caret) - line (line under the caret) - if unspecified, defaults to selection Setting trim=True will cause the source to be trimmed Setting discard_indent=True will cause leading whitespace to be trimmed (unnecessary unless trim=True) search_string will set the string to search for if target is text or zone - $SELECTED_TEXT will be replaced with the source text Setting regex=True will cause search_string to be evaluated as regex ''' context = tea.get_context(controller) target = tea.get_option(options, 'target') source = tea.get_option(options, 'source') trim = tea.get_option(options, 'trim', False) discard_indent = tea.get_option(options, 'discard_indent', False) search_string = tea.get_option(options, 'search_string') regex = tea.get_option(options, 'regex', False) range = tea.get_range(context) if source == 'word': text, range = tea.get_word(context, range) elif source == 'line': text, range = tea.get_line(context) elif range.length > 0: text = tea.get_selection(context, range) # Trim the source if trim: if discard_indent: trimmed = tea.trim(context, text, False, preserve_linebreaks=False) else: trimmed = tea.trim(context, text, False, 'end', preserve_linebreaks=False) start = text.find(trimmed) if start != -1: start = range.location + start length = len(trimmed) if source == 'line' and trimmed[-1:] in ['\r\n', '\r', '\n']: # We don't want the linebreak if we're trimming length = length - 1 range = tea.new_range(start, length) text = trimmed if target is not None and text: if search_string is not None: search = search_string.replace('$SELECTED_TEXT', text) else: search = text # Find the start and end points of the substring start = end = None if regex: match = re.search(r'(' + search + r')', context.string()) if match: # Get the start and end points start, end = match.span(1) else: start = context.string().find(search) if start != -1: end = start + len(search) else: start = None # Construct the new target range if start is not None and end is not None: range = tea.new_range(start, end - start) # Set the new range tea.set_selected_range(context, range)