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, 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)
Example #3
0
    def get_current_line(self):
        """
		Returns content of current line
		@return: str
		"""
        rng = tea.get_ranges(self._context)[0]
        text, rng = tea.get_line(self._context, rng)
        return text
Example #4
0
    def get_current_line(self):
        """
		Returns content of current line
		@return: str
		"""
        rng = tea.get_ranges(self._context)[0]
        text, rng = tea.get_line(self._context, rng)
        return text
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)
Example #6
0
    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))
		"""
        rng = tea.get_ranges(self._context)[0]
        text, rng = tea.get_line(self._context, rng)
        return rng.location, rng.location + rng.length
Example #7
0
    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))
		"""
        rng = tea.get_ranges(self._context)[0]
        text, rng = tea.get_line(self._context, rng)
        return rng.location, rng.location + rng.length
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
Example #9
0
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)
Example #10
0
def act(context, direction='out', mode='auto'):
    zen_target = 'html, html *, xml, xml *'
    if (mode.lower() == 'auto' and tea.cursor_in_zone(context, zen_target)) or \
       mode.lower() == 'zen':
        # HTML or XML, so use Zen-coding's excellent balancing commands
        
        # Using this method rather than tea.get_single_range() is better
        # because it won't cause the action to fail if there's more than
        # one selection
        editor = ZenEditor(context)
        action_name = 'match_pair_inward' if direction == 'in' else 'match_pair_outward'
        return zen_coding.run_action(action_name, editor)
    else:
        # No HTML or XML, so we'll rely on itemizers
        ranges = tea.get_ranges(context)
        targets = []
        for range in ranges:
            if direction.lower() == 'in':
                item = tea.get_item_for_range(context, range)
                if item is None:
                    # No item, so jump to next iteration
                    continue
                new_range = item.range()
                if new_range.location == range.location and \
                   new_range.length == range.length:
                    items = item.childItems()
                    if len(items) > 0:
                        new_range = items[0].range()
                targets.append(new_range)
            else:
                item = tea.get_item_parent_for_range(context, range)
                if item is None:
                    continue
                targets.append(item.range())
        
        # Set the selections, and return
        if len(targets) > 0:
            context.setSelectedRanges_([NSValue.valueWithRange_(range) for range in targets])
            return True
        else:
            return False
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 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
Example #14
0
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)
Example #15
0
 def get_caret_pos(self):
     """ Returns current caret position """
     range = tea.get_ranges(self._context)[0]
     return range.location
Example #16
0
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
Example #17
0
 def get_caret_pos(self):
     """ Returns current caret position """
     range = tea.get_ranges(self._context)[0]
     return range.location