def act(context, default=None, undo_name=None, **syntaxes): ''' Required action method default parameter is not a snippet, but should contain the $SELECTED_TEXT placeholder ''' # Get the selected ranges ranges = tea.get_ranges(context) if len(ranges) is 1: # Since we've only got one selection we can use a snippet range = ranges[0] insertion = tea.select_from_zones(context, range, default, **syntaxes) # Make sure the range is actually a selection if range.length > 0: text = tea.get_selection(context, range) snippet = '${1:' + insertion.replace('$SELECTED_TEXT', '${2:$SELECTED_TEXT}') + '}$0' else: # Not a selection, just wrap the cursor text = '' snippet = insertion.replace('$SELECTED_TEXT', '$1') + '$0' snippet = tea.construct_snippet(text, snippet) return tea.insert_snippet_over_range(context, snippet, range, undo_name) # Since we're here, it must not have been a single selection insertions = tea.new_recipe() for range in ranges: insertion = tea.select_from_zones(context, range, default, **syntaxes) text = tea.get_selection(context, range) text = insertion.replace('$SELECTED_TEXT', text) insertions.addReplacementString_forRange_(text, range) if undo_name is not None: insertions.setUndoActionName_(undo_name) return context.applyTextRecipe_(insertions)
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 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(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 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 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 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(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)