def select_scope_name(editor=wingapi.kArgEditor):
    '''
    Select the name of the function or class that the cursor is currently on.
    
    Suggested key combination: `Alt-Colon`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    document_text = shared.get_text(document)
    with shared.SelectionRestorer(editor):    
        editor.ExecuteCommand('select-scope')
        scope_start, scope_end = editor.GetSelection()
        
    scope_contents = document_text[scope_start : scope_end]
    match = _scope_name_regex.match(scope_contents)
    if not match:
        return
    stuff_before_scope_name, scope_name = match.groups()
    scope_name_start = scope_start + len(stuff_before_scope_name)
    scope_name_end = scope_name_start + len(scope_name)
    assert document_text[scope_name_position :
                             scope_name_position+len(scope_name)] == scope_name
    
    with shared.UndoableAction(document):
        editor.SetSelection(scope_name_start, scope_name_end)
def _get_rhs_positions(document):
    matches = _get_matches(document)
    document_text = shared.get_text(document)
    stripper = lambda (start, end): \
        shared.strip_segment_from_whitespace_and_newlines(document_text,
                                                          start, end)
    return map(stripper, (match.span('rhs') for match in matches))
def previous_brace_match(editor=wingapi.kArgEditor):
    '''
    Select the previous pair of braces.
    
    Similar to Wing's built-in `brace-match`, except it goes backwards instead
    of going forwards. Goes to the nearest pair of braces, whether it's (), [],
    or {} that's before the current caret position, and selects those braces
    including all their content.
    
    Known limitations: Misses some pairs of braces. Doesn't know to ignore
    braces found in strings.
    
    Suggested key combination: `Ctrl-Bracketleft`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    document_text = shared.get_text(document)
    _, caret_position = editor.GetSelection()
    closing_brace_position = max((
        document_text.rfind(')', 0, caret_position - 1), 
        document_text.rfind(']', 0, caret_position - 1), 
        document_text.rfind('}', 0, caret_position - 1)
    ))
    if closing_brace_position == -1:
        return
    new_position = closing_brace_position
    editor.SetSelection(new_position, new_position)
    editor.ExecuteCommand('brace-match')
def previous_brace_match(editor=wingapi.kArgEditor):
    '''
    Select the previous pair of braces.
    
    Similar to Wing's built-in `brace-match`, except it goes backwards instead
    of going forwards. Goes to the nearest pair of braces, whether it's (), [],
    or {} that's before the current caret position, and selects those braces
    including all their content.
    
    Known limitations: Misses some pairs of braces. Doesn't know to ignore
    braces found in strings.
    
    Suggested key combination: `Ctrl-Bracketleft`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    document_text = shared.get_text(document)
    _, caret_position = editor.GetSelection()
    closing_brace_position = max(
        (document_text.rfind(')', 0, caret_position - 1),
         document_text.rfind(']', 0, caret_position - 1),
         document_text.rfind('}', 0, caret_position - 1)))
    if closing_brace_position == -1:
        return
    new_position = closing_brace_position
    editor.SetSelection(new_position, new_position)
    editor.ExecuteCommand('brace-match')
def _get_rhs_positions(document):
    matches = _get_matches(document)
    document_text = shared.get_text(document)
    stripper = lambda (start, end): \
        shared.strip_segment_from_whitespace_and_newlines(document_text,
                                                          start, end)
    return map(stripper, (match.span('rhs') for match in matches))
def django_toggle_between_view_and_template():
    '''
    Toggle between a view file in Django and the corresponding template file.
    
    If you're currently viewing a template file, this'll open the corresponding
    view file. If you're currently viewing a view file, this'll open the
    corresponding template file.
    
    This assumes that the view file has the word `view` in it and has a
    definition for the class attribute `template_name`.
    
    Suggested key combination: `Insert Quoteleft` (i.e. backtick)
    '''
    
    app = wingapi.gApplication
    editor = app.GetActiveEditor()
    document = editor.GetDocument()
    project = app.GetProject()
    all_file_paths = project.GetAllFiles()
    document_text = shared.get_text(document)
    file_path = document.GetFilename()
    folder, file_name = os.path.split(file_path)
    
    
    if file_name.endswith('.py'):
        match = template_name_pattern.search(document_text)
        if match:
            template_partial_file_path = match.group(1)
            matching_file_paths = [
                file_path for file_path in all_file_paths
                if template_partial_file_path in file_path.replace('\\', '/')
            ]
            if matching_file_paths:
                matching_file_path = matching_file_paths[0]
                app.OpenEditor(matching_file_path, raise_window=True)
    elif template_file_pattern.match(file_name):
        short_file_path = shorten_template_file_path_pattern.match(
                                         file_path.replace('\\', '/')).group(1)
        specifies_our_template_pattern = re.compile(
            r'''template_name *= * ['"]%s['"]''' % re.escape(short_file_path)
        )
        all_view_file_paths = [
            file_path for file_path in all_file_paths
            if view_file_path_pattern.search(file_path)
        ]
            
        matching_file_paths_iterator = (
            file_path for file_path in all_view_file_paths if
            specifies_our_template_pattern.search(
                                            shared.get_file_content(file_path))
        )
        try:
            matching_file_path = next(matching_file_paths_iterator)
        except StopIteration:
            return
        app.OpenEditor(matching_file_path, raise_window=True)
def _get_span_of_opening_parenthesis(document, position):
    assert isinstance(document, wingapi.CAPIDocument)
    document_text = shared.get_text(document)
    if not document_text[position] == '(': raise Exception
    for i in range(1, len(document_text) - position):
        portion = document_text[position:position+i+1]
        if portion.count('(') == portion.count(')'):
            if not portion[-1] == ')': raise Exception
            return (position, position + i + 1)
    else:
        return (position, position)
def django_toggle_between_view_and_template():
    '''
    Toggle between a view file in Django and the corresponding template file.
    
    If you're currently viewing a template file, this'll open the corresponding
    view file. If you're currently viewing a view file, this'll open the
    corresponding template file.
    
    This assumes that the view file has the word `view` in it and has a
    definition for the class attribute `template_name`.
    
    Suggested key combination: `Insert Quoteleft` (i.e. backtick)
    '''

    app = wingapi.gApplication
    editor = app.GetActiveEditor()
    document = editor.GetDocument()
    project = app.GetProject()
    all_file_paths = project.GetAllFiles()
    document_text = shared.get_text(document)
    file_path = document.GetFilename()
    folder, file_name = os.path.split(file_path)

    if file_name.endswith('.py'):
        match = template_name_pattern.search(document_text)
        if match:
            template_partial_file_path = match.group(1)
            matching_file_paths = [
                file_path for file_path in all_file_paths
                if template_partial_file_path in file_path.replace('\\', '/')
            ]
            if matching_file_paths:
                matching_file_path = matching_file_paths[0]
                app.OpenEditor(matching_file_path, raise_window=True)
    elif template_file_pattern.match(file_name):
        short_file_path = shorten_template_file_path_pattern.match(
            file_path.replace('\\', '/')).group(1)
        specifies_our_template_pattern = re.compile(
            r'''template_name *= * ['"]%s['"]''' % re.escape(short_file_path))
        all_view_file_paths = [
            file_path for file_path in all_file_paths
            if view_file_path_pattern.search(file_path)
        ]

        matching_file_paths_iterator = (
            file_path for file_path in all_view_file_paths
            if specifies_our_template_pattern.search(
                shared.get_file_content(file_path)))
        try:
            matching_file_path = next(matching_file_paths_iterator)
        except StopIteration:
            return
        app.OpenEditor(matching_file_path, raise_window=True)
def _get_matches_for_arguments(document, truncate=None):
    assert isinstance(document, wingapi.CAPIDocument)
    document_text = shared.get_text(document)
    if truncate:
        truncate_position, truncate_radius = truncate
        if len(document_text) > truncate_radius * 2:
            old_document_text = document_text
            document_text = (
                ' ' * abs(truncate_position - truncate_radius) +
                document_text[truncate_position-truncate_radius
                                            :truncate_position+truncate_radius]
            )
    return tuple(match for match in
                 invocation_pattern_for_arguments.finditer(document_text)
                 if not keyword.iskeyword(match.groups()[0]))
def guess_class_name():
    '''
    Guess the class name based on file name, and replace class name.
    
    Imagine you had a file `foo_manager.py` with a class `FooManager` defined
    in it, and you duplicated it, calling the new file `bar_grokker.py`. If you
    run `guess-class-name`, it'll replace all instances of `FooManager` in your
    new file to `BarGrokker`.
    
    (It finds the original class name by taking the first class defined in the
    file. If the file defined multiple classes, you might get the wrong
    results.)
    
    Suggested key combination: `Insert Ctrl-C`
    '''
    
    app = wingapi.gApplication
    editor = app.GetActiveEditor()
    document = editor.GetDocument()
    file_name_without_extension = \
                        os.path.split(document.GetFilename())[-1].split('.')[0]
    
    guessed_class_name = \
                   shared.lower_case_to_camel_case(file_name_without_extension)
    
    document_text = shared.get_text(document)
    
    
    matches = tuple(re.finditer(existing_class_name_pattern, document_text))
    if not matches:
        return
    match = matches[-1]
    existing_class_name = match.group(1)
    
    n_occurrences = document_text.count(existing_class_name
                                        )
    with shared.SelectionRestorer(editor):
        with shared.UndoableAction(document):
            document.SetText(
                document_text.replace(existing_class_name, guessed_class_name)
            )
            
    app.SetStatusMessage('Replaced %s occurrences' % n_occurrences)
    
def _get_argument_positions(document, limit_to_keywords=False, truncate=None):
    argument_batch_positions = _get_argument_batch_positions(document,
                                                             truncate=truncate)
    document_text = shared.get_text(document)
    raw_argument_positions = tuple(itertools.chain(
        *(_argpos(
            document_text[argument_batch_position[0]:
                                                   argument_batch_position[1]],
            document_offset=argument_batch_position[0],
            limit_to_keywords=limit_to_keywords
        )
                       for argument_batch_position in argument_batch_positions)
    ))
    argument_positions = map(
        lambda (start, end):
            shared.strip_segment_from_whitespace_and_newlines(document_text,
                                                              start, end),
        raw_argument_positions
    )
    return argument_positions
def guess_class_name():
    '''
    Guess the class name based on file name, and replace class name.
    
    Imagine you had a file `foo_manager.py` with a class `FooManager` defined
    in it, and you duplicated it, calling the new file `bar_grokker.py`. If you
    run `guess-class-name`, it'll replace all instances of `FooManager` in your
    new file to `BarGrokker`.
    
    (It finds the original class name by taking the first class defined in the
    file. If the file defined multiple classes, you might get the wrong
    results.)
    
    Suggested key combination: `Insert Ctrl-C`
    '''

    app = wingapi.gApplication
    editor = app.GetActiveEditor()
    document = editor.GetDocument()
    file_name_without_extension = \
                        os.path.split(document.GetFilename())[-1].split('.')[0]

    guessed_class_name = \
                   shared.lower_case_to_camel_case(file_name_without_extension)

    document_text = shared.get_text(document)

    matches = tuple(re.finditer(existing_class_name_pattern, document_text))
    if not matches:
        return
    match = matches[-1]
    existing_class_name = match.group(1)

    n_occurrences = document_text.count(existing_class_name)
    with shared.SelectionRestorer(editor):
        with shared.UndoableAction(document):
            document.SetText(
                document_text.replace(existing_class_name, guessed_class_name))

    app.SetStatusMessage('Replaced %s occurrences' % n_occurrences)
Example #13
0
def flip(editor=wingapi.kArgEditor):
    '''
    Flip between opposite words.
    
    Put the caret on a word like `True` or `start` or `new` and watch it change
    into `False` or `end` or `old`.
    
    Suggested key combination: `Insert P`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    assert isinstance(document, wingapi.CAPIDocument)
    
    with shared.UndoableAction(document):
        word, word_start_position = _is_any_word_on_caret(
            shared.get_text(document), 
            editor.GetSelection()[0], 
            all_words
        )
        if not word:
            return
        
        for first_word, second_word in flip_pairs:
            if first_word == word:
                new_word = second_word
                break
            elif second_word == word:
                new_word = first_word
                break
            else:
                continue
        else:
            raise RuntimeError
        
        with shared.SelectionRestorer(editor):
            document.DeleteChars(word_start_position,
                                 word_start_position + len(word) - 1)
            document.InsertChars(word_start_position, new_word)
Example #14
0
def flip(editor=wingapi.kArgEditor):
    '''
    Flip between opposite words.
    
    Put the caret on a word like `True` or `start` or `new` and watch it change
    into `False` or `end` or `old`.
    
    Suggested key combination: `Insert P`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    assert isinstance(document, wingapi.CAPIDocument)

    with shared.UndoableAction(document):
        word, word_start_position = _is_any_word_on_caret(
            shared.get_text(document),
            editor.GetSelection()[0], all_words)
        if not word:
            return

        for first_word, second_word in flip_pairs:
            if first_word == word:
                new_word = second_word
                break
            elif second_word == word:
                new_word = first_word
                break
            else:
                continue
        else:
            raise RuntimeError

        with shared.SelectionRestorer(editor):
            document.DeleteChars(word_start_position,
                                 word_start_position + len(word) - 1)
            document.InsertChars(word_start_position, new_word)
def _get_matches(document):
    assert isinstance(document, wingapi.CAPIDocument)
    document_text = shared.get_text(document)
    return tuple(pattern.finditer(document_text))
Example #16
0
def _get_all_number_positions(editor):
    text = shared.get_text(editor.GetDocument())
    matches = tuple(number_pattern.finditer(text))
    return tuple((match.start(), match.end()) for match in matches)
def _get_matches(document):
    assert isinstance(document, wingapi.CAPIDocument)
    document_text = shared.get_text(document)
    return tuple(match for match in invocation_pattern.finditer(document_text)
                 if not keyword.iskeyword(match.groups()[0]))
def _get_scope_name_positions(document):
    document_text = shared.get_text(document)
    matches = _scope_name_pattern.finditer(document_text)
    return tuple(match.span(1) for match in matches)
def for_thing_in_things(editor=wingapi.kArgEditor, comprehension=False):
    '''
    Turn `things` into `for thing in things:`.
    
    Type any pluarl word, like `bananas` or `directories`. Then run this
    script, and you get `for directory in directories`.
    
    This also works for making `range(number)` into `for i in range(number):`.
    
    Note: The `:` part is added only on Windows.
    
    If `comprehension=True`, inputs `for thing in things` without the colon and
    puts the caret before instead of after.
    
    Suggested key combination: `Insert Ctrl-F`
                               `Insert Shift-F` for `comprehension=True`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    
    document_text = shared.get_text(document)
    
    assert isinstance(document, wingapi.CAPIDocument)
    
    with shared.UndoableAction(document):
        end_position, _ = editor.GetSelection()
        line_number = document.GetLineNumberFromPosition(end_position)
        line_start = document.GetLineStart(line_number)
        line_end = document.GetLineEnd(line_number)
        line_contents = document.GetCharRange(line_start, line_end)
        editor.SetSelection(end_position, end_position)
        if ')' in document.GetCharRange(end_position - 1, end_position + 1) \
                                                 and 'range(' in line_contents:
            
            start_position = document_text.find('range(', line_start)
            end_position = document_text.find(
                ')',
                start_position,
                min((line_end, end_position + 1))
            ) + 1
        else:
            wingapi.gApplication.ExecuteCommand('backward-word')
            start_position, _ = editor.GetSelection()
            

        print(start_position, end_position)
        base_text = document.GetCharRange(start_position, end_position)
        print(base_text)
        ### Analyzing base text: ##############################################
        #                                                                     #
        if range_pattern.match(base_text):
            variable_name = 'i'
        elif base_text.endswith('s'):
            variable_name = shared.plural_word_to_singular_word(base_text)
        #                                                                     #
        ### Finished analyzing base text. #####################################
        
        segment_to_insert = 'for %s in ' % variable_name
        if comprehension:
            segment_to_insert = ' %s' % segment_to_insert
        document.InsertChars(start_position, segment_to_insert)
        
        if comprehension:
            editor.SetSelection(start_position, start_position)
        else:
            editor.ExecuteCommand('end-of-line')
        
            if shared.autopy_available:
                import autopy.key
                autopy.key.tap(':')        
def _get_matches(document):
    assert isinstance(document, wingapi.CAPIDocument)
    document_text = shared.get_text(document)
    return tuple(pattern.finditer(document_text))
Example #21
0
def for_thing_in_things(editor=wingapi.kArgEditor, app=wingapi.kArgApplication,
                        comprehension=False):
    '''
    Turn `things` into `for thing in things:`.
    
    Type any pluarl word, like `bananas` or `directories`. Then run this
    script, and you get `for directory in directories`.
    
    This also works for making `range(number)` into `for i in range(number):`.
    
    Note: The `:` part is added only on Windows.
    
    If `comprehension=True`, inputs `for thing in things` without the colon and
    puts the caret before instead of after.
    
    Suggested key combination: `Insert Ctrl-F`
                               `Insert Shift-F` for `comprehension=True`
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    document = editor.GetDocument()
    
    document_text = shared.get_text(document)
    
    assert isinstance(document, wingapi.CAPIDocument)
    
    with shared.UndoableAction(document):
        end_position, _ = editor.GetSelection()
        line_number = document.GetLineNumberFromPosition(end_position)
        line_start = document.GetLineStart(line_number)
        line_end = document.GetLineEnd(line_number)
        line_contents = document.GetCharRange(line_start, line_end)
        editor.SetSelection(end_position, end_position)
        if ')' in document.GetCharRange(end_position - 1, end_position + 1) \
                                                 and 'range(' in line_contents:
            
            text_start_position = document_text.find('range(', line_start)
            if document_text[text_start_position - 1] == 'x':
                text_start_position -= 1
            end_position = document_text.find(
                ')',
                text_start_position,
                min((line_end, end_position + 1))
            ) + 1
        else:
            wingapi.gApplication.ExecuteCommand('backward-word')
            text_start_position, _ = editor.GetSelection()
            wingapi.gApplication.ExecuteCommand('forward-word')
            wingapi.gApplication.ExecuteCommand('select-expression')
            shared.strip_selection_if_single_line(editor)
            expression_start_position, _ = editor.GetSelection()
            

        base_text = document.GetCharRange(text_start_position, end_position)
        ### Analyzing base text: ##############################################
        #                                                                     #
        if range_pattern.match(base_text):
            variable_name = 'i'
        elif base_text.endswith('s'):
            variable_name = shared.plural_word_to_singular_word(base_text)
        else:
            raise Exception('This text doesn\'t work: %s' % base_text)
        #                                                                     #
        ### Finished analyzing base text. #####################################
        
        segment_to_insert = 'for %s in ' % variable_name
        
        with shared.SelectionRestorer(editor):
            app.ExecuteCommand('beginning-of-line-text(toggle=False)')
            home_position, _ = editor.GetSelection()
            
        if comprehension:
            segment_to_insert = ' %s' % segment_to_insert
            document.InsertChars(expression_start_position, segment_to_insert)
            editor.SetSelection(expression_start_position,
                                expression_start_position)
        else:
            document.InsertChars(home_position, segment_to_insert)            
            editor.ExecuteCommand('end-of-line')        
            if shared.autopy_available:
                import autopy.key
                autopy.key.tap(':')
def _find_string_from_position(editor, position, multiline=False):
    '''
    Given a character in the document known to be in a string, find its string.
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    assert _is_position_on_string(editor, position)
    document_start = 0
    document_end = editor.GetDocument().GetLength()
    start_marker = end_marker = position
    while end_marker < document_end and \
                            _is_position_on_string(editor, end_marker+1):
        end_marker += 1
    while start_marker > document_start and \
                          _is_position_on_string(editor, start_marker-1):
        start_marker -= 1
            
    if start_marker > document_start:
        assert not _is_position_on_string(editor, start_marker-1)
    if end_marker < document_end:
        assert not _is_position_on_string(editor, end_marker+1)
    
    if multiline:
        string_ranges = [(start_marker, end_marker)]
        document_text = shared.get_text(editor.GetDocument())
        
        ### Scanning backward: ################################################
        #                                                                     #
        while True:
            start_of_first_string = string_ranges[0][0]
            # No backwards regex search in `re` yet, doing it manually:
            for i in range(start_of_first_string - 1, 0, -1):
                if document_text[i] not in string.whitespace:
                    candidate_end_of_additional_string = i
                    print('Candidate: %s %s' % (i, document_text[i]))
                    break
            else:
                break    
                    
            if _is_position_on_string(editor,
                       candidate_end_of_additional_string, try_previous=False):
                string_ranges.insert(
                    0, 
                    _find_string_from_position(
                        editor,
                        candidate_end_of_additional_string
                    )
                )
                continue
            else:
                break
        #                                                                     #
        ### Finished scanning backward. #######################################

        ### Scanning forward: #################################################
        #                                                                     #
        while True:
            end_of_last_string = string_ranges[-1][1]
            search_result = re.search('\S',
                                      document_text[end_of_last_string:])
            if search_result:
                candidate_start_of_additional_string = \
                                   end_of_last_string + search_result.span()[0]
                if _is_position_on_string(editor,
                                          candidate_start_of_additional_string,
                                          try_previous=False):
                    string_ranges.append(
                        _find_string_from_position(
                            editor,
                            candidate_start_of_additional_string
                        )
                    )
                    continue
                    
            # (This is like an `else` clause for both the above `if`s.)
            return tuple(string_ranges)
        #                                                                     #
        ### Finished scanning forward. ########################################
        
        
    else: # not multiline
        return (start_marker, end_marker)
Example #23
0
def _find_string_from_position(editor, position, multiline=False):
    '''
    Given a character in the document known to be in a string, find its string.
    '''
    assert isinstance(editor, wingapi.CAPIEditor)
    assert _is_position_on_string(editor, position)
    document_start = 0
    document_end = editor.GetDocument().GetLength()
    start_marker = end_marker = position
    while end_marker < document_end and \
                            _is_position_on_string(editor, end_marker+1):
        end_marker += 1
    while start_marker > document_start and \
                          _is_position_on_string(editor, start_marker-1):
        start_marker -= 1

    if start_marker > document_start:
        assert not _is_position_on_string(editor, start_marker - 1)
    if end_marker < document_end:
        assert not _is_position_on_string(editor, end_marker + 1)

    if multiline:
        string_ranges = [(start_marker, end_marker)]
        document_text = shared.get_text(editor.GetDocument())

        ### Scanning backward: ################################################
        #                                                                     #
        while True:
            start_of_first_string = string_ranges[0][0]
            # No backwards regex search in `re` yet, doing it manually:
            for i in range(start_of_first_string - 1, 0, -1):
                if document_text[i] not in string.whitespace:
                    candidate_end_of_additional_string = i
                    print('Candidate: %s %s' % (i, document_text[i]))
                    break
            else:
                break

            if _is_position_on_string(editor,
                                      candidate_end_of_additional_string,
                                      try_previous=False):
                string_ranges.insert(
                    0,
                    _find_string_from_position(
                        editor, candidate_end_of_additional_string))
                continue
            else:
                break
        #                                                                     #
        ### Finished scanning backward. #######################################

        ### Scanning forward: #################################################
        #                                                                     #
        while True:
            end_of_last_string = string_ranges[-1][1]
            search_result = re.search('\S', document_text[end_of_last_string:])
            if search_result:
                candidate_start_of_additional_string = \
                                   end_of_last_string + search_result.span()[0]
                if _is_position_on_string(editor,
                                          candidate_start_of_additional_string,
                                          try_previous=False):
                    string_ranges.append(
                        _find_string_from_position(
                            editor, candidate_start_of_additional_string))
                    continue

            # (This is like an `else` clause for both the above `if`s.)
            return tuple(string_ranges)
        #                                                                     #
        ### Finished scanning forward. ########################################

    else:  # not multiline
        return (start_marker, end_marker)
def _get_scope_name_positions(document):
    document_text = shared.get_text(document)
    matches = _scope_name_pattern.finditer(document_text)
    return tuple(match.span(1) for match in matches)
def _get_all_constant_positions(editor):
    text = shared.get_text(editor.GetDocument())
    matches = tuple(constant_pattern.finditer(text))
    return tuple((match.start(), match.end()) for match in matches)