Example #1
0
 def _on_item_activated(item, *args):
     assert isinstance(item, QtWidgets.QTreeWidgetItem)
     data = item.data(0, QtCore.Qt.UserRole)
     try:
         l = data['line']
     except TypeError:
         return  # file item or root item
     start = data['start']
     lenght = data['end'] - start
     if data is not None:
         # open editor and go to line/column
         e = editor.open_file(data['path'], data['line'], data['start'])
         if e is None:
             return
         # select text
         helper = TextHelper(e)
         try:
             cursor = helper.select_lines(start=l, end=l)
         except AttributeError:
             _logger().debug('failed to select occurent line in editor, not'
                             ' a subclass of QPlainTextEdit')
         else:
             assert isinstance(cursor, QtGui.QTextCursor)
             cursor.movePosition(cursor.StartOfBlock)
             cursor.movePosition(cursor.Right, cursor.MoveAnchor, start)
             cursor.movePosition(cursor.Right, cursor.KeepAnchor, lenght)
             e.setTextCursor(cursor)
Example #2
0
 def _on_mouse_released(self, event):
     """ mouse pressed callback """
     if event.button() == 1 and self._deco:
         cursor = TextHelper(self.editor).word_under_mouse_cursor()
         if cursor and cursor.selectedText():
             self._timer.request_job(
                 self.word_clicked.emit, cursor)
    def _check_word_cursor(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(self.editor).word_under_cursor()

        request_data = {
            'code': self.editor.toPlainText(),
            'line': tc.blockNumber(),
            'column': tc.columnNumber(),
            'path': self.editor.file.path,
            'encoding': self.editor.file.encoding
        }
        try:
            self.editor.backend.send_request(
                workers.goto_assignments, request_data,
                on_receive=self._on_results_available)
        except NotRunning:
            pass
Example #4
0
 def _select_word_under_mouse_cursor(self):
     """ Selects the word under the mouse cursor. """
     cursor = TextHelper(self.editor).word_under_mouse_cursor()
     if (self._previous_cursor_start != cursor.selectionStart() and
             self._previous_cursor_end != cursor.selectionEnd()):
         self._remove_decoration()
         self._add_decoration(cursor)
     self._previous_cursor_start = cursor.selectionStart()
     self._previous_cursor_end = cursor.selectionEnd()
Example #5
0
 def _on_mouse_moved(self, event):
     """ mouse moved callback """
     if event.modifiers() & QtCore.Qt.ControlModifier:
         cursor = TextHelper(self.editor).word_under_mouse_cursor()
         if not self._cursor or cursor.position() != self._cursor.position():
             self._check_word_cursor(cursor)
         self._cursor = cursor
     else:
         self._cursor = None
         self._clear_selection()
Example #6
0
 def _on_key_pressed(self, event):
     helper = TextHelper(self.editor)
     indent = helper.line_indent() * ' '
     if self.editor.textCursor().positionInBlock() == len(indent):
         self.QUOTES_FORMATS['"'] = '%s:'
     else:
         self.QUOTES_FORMATS['"'] = '%s'
     self.QUOTES_FORMATS['{'] = '\n' + indent + '%s'
     self.QUOTES_FORMATS['['] = '\n' + indent + '%s'
     super(AutoCompleteMode, self)._on_key_pressed(event)
Example #7
0
 def _on_action_quick_doc_triggered(self):
     tc = TextHelper(self.editor).word_under_cursor(select_whole_word=True)
     request_data = {
         'code': self.editor.toPlainText(),
         'line': tc.blockNumber(),
         'column': tc.columnNumber(),
         'path': self.editor.file.path,
         'encoding': self.editor.file.encoding
     }
     self.editor.backend.send_request(
         quick_doc, request_data, on_receive=self._on_results_available)
 def _in_method_call(self):
     helper = TextHelper(self.editor)
     line_nbr = helper.current_line_nbr() - 1
     expected_indent = helper.line_indent() - 4
     while line_nbr >= 0:
         text = helper.line_text(line_nbr)
         indent = len(text) - len(text.lstrip())
         if indent == expected_indent and 'class' in text:
             return True
         line_nbr -= 1
     return False
Example #9
0
def test_dynamic_folding_insert_section():
    # test insert section under empty data division
    # data division (which is not a fold trigger initially) block will become a fold trigger
    editor = CobolCodeEdit()
    editor.setPlainText(section_code, '', '')
    th = TextHelper(editor)
    block = editor.document().findBlockByNumber(2)
    assert TextBlockHelper.is_fold_trigger(block) is False
    cursor = th.goto_line(2, column=len('       DATA DIVISION.'))
    cursor.insertText('\n       WORKING-STORAGE SECTION.')
    assert TextBlockHelper.is_fold_trigger(block) is True
Example #10
0
def test_add_decoration(editor):
    helper = TextHelper(editor)
    helper.goto_line(2, 2)
    cursor = helper.word_under_cursor(select_whole_word=True)
    deco = TextDecoration(cursor)
    deco.set_as_bold()
    deco.set_as_underlined(QtGui.QColor("#FF0000"))
    editor.decorations.append(deco)
    assert not editor.decorations.append(deco)
    assert deco.contains_cursor(cursor)
    # keep editor clean for next tests
    editor.decorations.clear()
 def _on_post_key_pressed(self, event):
     if not event.isAccepted() and not self._ignore_post:
         txt = event.text()
         next_char = TextHelper(self.editor).get_right_character()
         if txt in self.MAPPING:
             to_insert = self.MAPPING[txt]
             if (not next_char or next_char in self.MAPPING.keys() or
                     next_char in self.MAPPING.values() or
                     next_char.isspace()):
                 TextHelper(self.editor).insert_text(
                     self.QUOTES_FORMATS[txt] % to_insert)
     self._ignore_post = False
Example #12
0
 def _on_item_clicked(self, item):
     """
     Go to the item position in the editor.
     """
     if item:
         name = item.data(0, QtCore.Qt.UserRole)
         if name:
             go = name.block.blockNumber()
             helper = TextHelper(self._editor)
             if helper.current_line_nbr() != go:
                 helper.goto_line(go, column=name.column)
             self._editor.setFocus()
Example #13
0
def test_clear_decoration(editor):
    # should work even when there are no more decorations
    helper = TextHelper(editor)
    editor.decorations.clear()
    cursor = helper.word_under_cursor(select_whole_word=True)
    deco = TextDecoration(cursor)
    deco.set_as_bold()
    deco.set_as_underlined(QtGui.QColor("#FF0000"))
    editor.decorations.append(deco)
    assert not editor.decorations.append(deco)
    editor.decorations.clear()
    assert editor.decorations.append(deco)
    assert editor.decorations.remove(deco)
Example #14
0
def test_remove_decoration(editor):
    helper = TextHelper(editor)
    TextHelper(editor).goto_line(1, 2)
    cursor = helper.word_under_cursor(select_whole_word=True)
    deco = TextDecoration(cursor)
    deco.set_as_bold()
    deco.set_as_underlined(QtGui.QColor("#FF0000"))
    editor.decorations.append(deco)
    assert editor.decorations.remove(deco)
    # already removed, return False
    assert not editor.decorations.remove(deco)
    assert editor.decorations.append(deco)
    # keep editor clean for next tests
    editor.decorations.clear()
Example #15
0
 def _on_bt_toggled(self, *ar):
     if not self._lock:
         self._lock = True
         for w in self._widgets:
             if w == self.sender():
                 break
         if self._toggled:
             self._toggled.setChecked(False)
         self._toggled = w
         th = TextHelper(self.editor)
         line = w.scope.blockNumber()
         th.goto_line(line, column=th.line_indent(line))
         self._lock = False
         self.editor.setFocus()
Example #16
0
 def _compute_offsets(self):
     original_tc = self.editor.textCursor()
     tc = self.editor.textCursor()
     start = tc.selectionStart()
     end = tc.selectionEnd()
     tc.setPosition(start)
     start_line = tc.blockNumber()
     tc.setPosition(end)
     end_line = tc.blockNumber()
     th = TextHelper(self.editor)
     th.select_lines(start=start_line, end=end_line, apply_selection=True)
     source = th.selected_text()
     results = get_field_infos(source, self.editor.free_format)
     self.editor.setTextCursor(original_tc)
     self.pic_infos_available.emit(results)
Example #17
0
 def _computeOffsets(self):
     original_tc = self.editor.textCursor()
     tc = self.editor.textCursor()
     assert isinstance(tc, QTextCursor)
     start = tc.selectionStart()
     end = tc.selectionEnd()
     tc.setPosition(start)
     start_line = tc.blockNumber() + 1
     tc.setPosition(end)
     end_line = tc.blockNumber() + 1
     th = TextHelper(self.editor)
     th.select_lines(start=start_line, end=end_line, apply_selection=True)
     source = th.selected_text()
     self.picInfosAvailable.emit(get_field_infos(source))
     self.editor.setTextCursor(original_tc)
Example #18
0
def test_dynamic_folding_insert_end_if():
    # test insert section under empty data division
    # data division (which is not a fold trigger initially) block will become a fold trigger
    editor = CobolCodeEdit()
    editor.setPlainText(end_if_code, '', '')
    th = TextHelper(editor)
    block = editor.document().findBlockByNumber(4)
    first, last = FoldScope(block).get_range()
    assert first == 4
    assert last == 5
    cursor = th.goto_line(5, column=len('          DISPLAY "FOO"'))
    cursor.insertText('\n       END-IF')
    first, last = FoldScope(block).get_range()
    assert first == 4
    assert last == 6
    editor.close()
Example #19
0
 def _on_post_key_pressed(self, event):
     if not event.isAccepted() and not self._ignore_post:
         txt = event.text()
         trav = self.editor.textCursor()
         assert isinstance(trav, QtGui.QTextCursor)
         trav.movePosition(trav.Left, trav.MoveAnchor, 2)
         literal = TextHelper(self.editor).is_comment_or_string(trav)
         if not literal:
             next_char = TextHelper(self.editor).get_right_character()
             if txt in self.MAPPING:
                 to_insert = self.MAPPING[txt]
                 if (not next_char or next_char in self.MAPPING.keys() or
                         next_char in self.MAPPING.values() or
                         next_char.isspace()):
                     TextHelper(self.editor).insert_text(
                         self.QUOTES_FORMATS[txt] % to_insert)
     self._ignore_post = False
Example #20
0
def test_occurrences(editor, underlined):
    editor.file.open(__file__)
    mode = get_mode(editor)
    mode.underlined = underlined
    assert len(mode._decorations) == 0
    assert mode.delay == 1000
    TextHelper(editor).goto_line(16, 7)
    QTest.qWait(2000)
    assert len(mode._decorations) == 22
Example #21
0
 def _goto_symbol(self):
     try:
         self._cached_cursor_position = TextHelper(
             api.editor.get_current_editor()).cursor_position()
     except (TypeError, AttributeError):
         pass  # no current editor or not a code edit
     else:
         self._locator.mode = self._locator.MODE_GOTO_SYMBOL
         self._show_locator()
Example #22
0
def test_action_search_triggered2(editor):
    panel = get_panel(editor)
    # second search with the same text
    tc = TextHelper(editor).word_under_mouse_cursor()
    editor.setTextCursor(tc)
    panel.on_search()
    editor.show()
    QTest.qWait(1000)
    assert panel.isVisible()
 def perform_word_selection(self, event=None):
     """
     Performs word selection
     :param event: QMouseEvent
     """
     self.editor.setTextCursor(
         TextHelper(self.editor).word_under_cursor(True))
     if event:
         event.accept()
Example #24
0
def test_request_completion(editor):
    mode = get_mode(editor)
    QTest.qWait(1000)
    if editor.backend.running:
        editor.backend.stop()
    # starts the server after the request to test the retry on NotConnected
    # mechanism
    TextHelper(editor).goto_line(0)
    QTest.qWait(2000)
    mode.request_completion()
    editor.backend.start(server_path())
    QTest.qWait(1000)
    assert editor.backend.running is True
    # now this should work
    TextHelper(editor).goto_line(3)
    QTest.qWait(100)
    assert mode.request_completion() is True
    QTest.qWait(100)
Example #25
0
 def _goto_error_msg(self, msg):
     """
     Opens an editor and goes to the error line.
     """
     if os.path.exists(msg.path):
         self.app.file.open_file(msg.path)
         if msg.status:
             TextHelper(self.app.edit.current_editor).goto_line(msg.line)
             self.app.edit.current_editor.setFocus(True)
Example #26
0
 def perform_extended_selection(self, event=None):
     """
     Performs extended word selection.
     :param event: QMouseEvent
     """
     TextHelper(self.editor).select_extended_word(
         continuation_chars=self.continuation_characters)
     if event:
         event.accept()
def test_successive_requests(editor):
    mode = get_mode(editor)
    QTest.qWait(1000)
    TextHelper(editor).goto_line(3)
    # only the first request should be accepted
    ret1 = mode.request_completion()
    ret2 = mode.request_completion()
    assert ret1 is True
    assert ret2 is True
Example #28
0
    def on_goto_out_of_doc(self, assignment):
        """
        Open the a new tab when goto goes out of the current document.

        :param assignment: Destination
        """
        editor = self.open_file(assignment.module_path)
        if editor:
            TextHelper(editor).goto_line(assignment.line, assignment.column)
def test_line_selection(editor):
    QTest.qWait(1000)
    mode = get_mode(editor)
    TextHelper(editor).goto_line(0, 2)
    QTest.qWait(1000)
    mode.perform_line_selection()
    assert editor.textCursor().selectedText(
    ) == 'from pyqode.qt import QtCore, QtGui'
    QTest.qWait(1000)
Example #30
0
 def mouseMoveEvent(self, event):
     # Requests a tooltip if the cursor is currently over a marker.
     line = TextHelper(self.editor).line_nbr_from_position(event.pos().y())
     if line:
         markers = self.marker_for_line(line)
         text = '\n'.join([
             marker.description for marker in markers if marker.description
         ])
         if len(markers):
             if self._previous_line != line:
                 top = TextHelper(self.editor).line_pos_from_number(
                     markers[0].line)
                 if top:
                     self._job_runner.request_job(self._display_tooltip,
                                                  text, top)
         else:
             self._job_runner.cancel_requests()
         self._previous_line = line
Example #31
0
    def requestGoTo(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(self.editor).word_under_cursor(
                select_whole_word=True)
        symbol = tc.selectedText()
        analyser = getattr(self.editor, "analyserMode")
        if analyser:
            node = analyser.root_node.find(symbol)
            if node:
                self._definition = node
                QTimer.singleShot(100, self._goToDefinition)
Example #32
0
def test_calltips(editor):
    editor.clear()
    mode = get_mode(editor)
    assert editor.backend.connected
    assert mode is not None
    editor.setPlainText("open(__file_")
    TextHelper(editor).goto_line(1, len('open(__file_'))
    QTest.keyPress(editor, ',')
    QTest.keyRelease(editor, ',')
    QTest.qWait(1000)
 def _is_last_chard_end_of_word(self):
     try:
         tc = TextHelper(self.editor).word_under_cursor()
         tc.setPosition(tc.position())
         tc.movePosition(tc.StartOfLine, tc.KeepAnchor)
         l = tc.selectedText()
         last_char = l[len(l) - 1]
         seps = self.editor.word_separators
         symbols = [",", " ", "("]
         return last_char in seps and last_char not in symbols
     except IndexError:
         return False
Example #34
0
 def _on_key_pressed(self, event):
     txt = event.text()
     cursor = self.editor.textCursor()
     from pyqode.qt import QtGui
     assert isinstance(cursor, QtGui.QTextCursor)
     if cursor.hasSelection():
         # quoting of selected text
         if event.text() in self.MAPPING.keys():
             first = event.text()
             last = self.MAPPING[event.text()]
             cursor.insertText(self.SELECTED_QUOTES_FORMATS[event.text()] %
                               (first, cursor.selectedText(), last))
             self.editor.setTextCursor(cursor)
             event.accept()
         else:
             self._ignore_post = True
         return
     next_char = TextHelper(self.editor).get_right_character()
     self.logger.debug('next char: %s', next_char)
     ignore = False
     if event.key() == QtCore.Qt.Key_Backspace:
         # get the character that will get deleted
         tc = self.editor.textCursor()
         pos = tc.position()
         tc.movePosition(tc.Left)
         tc.movePosition(tc.Right, tc.KeepAnchor)
         del_char = tc.selectedText()
         if del_char in self.MAPPING and \
                 self.MAPPING[del_char] == next_char:
             tc.beginEditBlock()
             tc.movePosition(tc.Right, tc.KeepAnchor)
             tc.insertText('')
             tc.setPosition(pos - 2)
             tc.endEditBlock()
             self.editor.setTextCursor(tc)
             ignore = True
     elif (txt and next_char == txt
           and (next_char in self.MAPPING or txt in self.AVOID_DUPLICATES)):
         ignore = True
     if ignore:
         event.accept()
         TextHelper(self.editor).clear_selection()
         TextHelper(self.editor).move_right()
Example #35
0
    def _get_indent(self, cursor):
        """
        Return the indentation text (a series of spaces or tabs)

        :param cursor: QTextCursor

        :returns: Tuple (text before new line, text after new line)
        """
        indent = TextHelper(self.editor).line_indent() * ' '
        return "", indent
Example #36
0
 def _get_indent(self, cursor):
     prev_line_text = TextHelper(self.editor).current_line_text()
     if not self.editor.free_format:
         prev_line_text = " " * 7 + prev_line_text[7:]
     diff = len(prev_line_text) - len(prev_line_text.lstrip())
     post_indent = " " * diff
     min_column = self.editor.indenter_mode.min_column
     if len(post_indent) < min_column:
         post_indent = min_column * " "
     # all regex are upper cases
     text = cursor.block().text().upper()
     # raise indentation level
     patterns = [regex.PARAGRAPH_PATTERN, regex.STRUCT_PATTERN, regex.BRANCH_START, regex.LOOP_PATTERN]
     for ptrn in patterns:
         if ptrn.indexIn(text) != -1:
             post_indent += self.editor.tab_length * " "
             return "", post_indent
     # use the previous line indentation
     return "", post_indent
 def inner_action(*args):
     """ Inner action: open file """
     # cache cursor position before reloading so that the cursor position
     # is restored automatically after reload has finished.
     # See OpenCobolIDE/OpenCobolIDE#97
     Cache().set_cursor_position(
         self.editor.file.path,
         TextHelper(self.editor).cursor_position())
     self.editor.file.open(self.editor.file.path)
     self.file_reloaded.emit()
 def _on_replace_triggered(self):
     text = ''
     if editor.get_current_editor() is not None:
         text = TextHelper(editor.get_current_editor()).selected_text()
     search_settings = _DlgFindReplace.replace(self.main_window,
                                               text_to_find=text)
     if search_settings is not None:
         self._remove_dock()
         self._replace = True
         self._start_search_in_path(search_settings)
Example #39
0
def open_editor(path, line):
    global editor_windows
    print(path, line)
    editor = PyCodeEdit()
    # prevent the restoration of cursor position which will reset the position
    # we will set after opening the file
    editor.file.restore_cursor = False
    editor.file.open(path)
    TextHelper(editor).goto_line(line)
    editor.show()
    editor_windows.append(editor)
Example #40
0
 def _update_status_bar(self, editor):
     if editor:
         l, c = TextHelper(editor).cursor_position()
         self.lbl_cursor_pos.setText('%d:%d' % (l + 1, c + 1))
         self.lbl_encoding.setText(editor.file.encoding)
         self.lbl_filename.setText(editor.file.path)
         self.lbl_interpreter.setText(sys.executable)
     else:
         self.lbl_encoding.clear()
         self.lbl_filename.clear()
         self.lbl_cursor_pos.clear()
def test_forbidden_characters(editor):
    code = '''{
"widget": {
    "debug": "on",
    "window": {
        "title": "Sample Konfabulator Widget",
        "name": "main_window",
        "width":
        "height": 500
    },
}
}
    '''
    editor.setPlainText(code)
    assert editor.toPlainText() == code
    TextHelper(editor).goto_line(6, column=16, move=True)
    QTest.qWait(100)
    assert TextHelper(editor).current_line_text() == '        "width":'
    QTest.keyPress(editor, '\'')
    assert TextHelper(editor).current_line_text() == '        "width":\''
Example #42
0
def test_occurrences(editor):
    for underlined in [True, False]:
        editor.file.open(__file__)
        assert editor.backend.running is True
        mode = get_mode(editor)
        mode.underlined = underlined
        assert len(mode._decorations) == 0
        assert mode.delay == 1000
        TextHelper(editor).goto_line(16, 7)
        QTest.qWait(2000)
        assert len(mode._decorations) > 0
Example #43
0
def test_calltips_with_closing_paren(editor):
    editor.clear()
    mode = get_mode(editor)
    assert editor.backend.connected
    assert mode is not None
    editor.setPlainText("open")
    TextHelper(editor).goto_line(1, len('open'))
    QTest.keyPress(editor, '(')
    QTest.keyRelease(editor, '(')
    QTest.qWait(1000)
    mode._display_tooltip(None, 0)
Example #44
0
 def color(self, value):
     self._color = value
     self._pen = QtGui.QPen(self._color)
     TextHelper(self.editor).mark_whole_doc_dirty()
     self.editor.repaint()
     if self.editor:
         for clone in self.editor.clones:
             try:
                 clone.modes.get(self.__class__).color = value
             except KeyError:
                 # this should never happen since we're working with clones
                 pass
Example #45
0
def test_matching(editor):
    """
    Moves the text cursor in a few places to execute all statements in
    matcher.py.

    """
    mode = get_mode(editor)
    # before [
    TextHelper(editor).goto_line(1, 14)
    mode.do_symbols_matching()
    # after ]
    TextHelper(editor).goto_line(4, 1)
    mode.do_symbols_matching()
    # before {
    TextHelper(editor).goto_line(5, 14)
    mode.do_symbols_matching()
    # after }
    TextHelper(editor).goto_line(8, 1)
    mode.do_symbols_matching()
    # before (
    TextHelper(editor).goto_line(9, 16)
    mode.do_symbols_matching()
    # after )
    TextHelper(editor).goto_line(12, 1)
    mode.do_symbols_matching()
 def _on_find_triggered(self):
     text = ''
     if editor.get_current_editor() is not None:
         try:
             text = TextHelper(editor.get_current_editor()).selected_text()
         except AttributeError:
             text = ''
     search_settings = _DlgFindReplace.find(self.main_window,
                                            text_to_find=text)
     if search_settings is not None:
         self._remove_dock()
         self._replace = False
         self._start_search_in_path(search_settings)
Example #47
0
def test_goto_out_of_doc(editor):
    global out
    out = False
    editor.clear()
    code = "import logging\nlogging.basicConfig()"
    editor.setPlainText(code)
    mode = get_mode(editor)
    TextHelper(editor).goto_line(1, len('logging.basicConfig()') - 4)
    mode.out_of_doc.connect(_on_out_of_doc)
    assert out is False
    mode.request_goto()
    QTest.qWait(5000)
    assert out is True
Example #48
0
def test_symbol_pos(editor):
    """
    Moves the text cursor in a few places to execute all statements in
    matcher.py.

    """
    mode = get_mode(editor)
    editor.file.open(__file__)
    # move to ')'
    cursor = TextHelper(editor).goto_line(12, 1, move=False)
    l, c = mode.symbol_pos(cursor)
    assert l == 9
    assert c == 17
Example #49
0
def test_action_search_triggered(editor):
    panel = get_panel(editor)
    # select word under cursor
    tc = TextHelper(editor).word_under_mouse_cursor()
    editor.setTextCursor(tc)
    panel.on_actionSearch_triggered()
    assert panel.isVisible()
    QTest.qWait(1000)
    panel.checkBoxCase.setChecked(True)
    panel.checkBoxWholeWords.setChecked(True)
    panel.on_actionSearch_triggered()
    assert panel.isVisible()
    QTest.qWait(1000)
Example #50
0
def test_multiple_results(editor):
    global flg_multi
    editor.clear()
    code = "import os\nos.path.abspath('..')"
    editor.setPlainText(code)
    mode = get_mode(editor)
    TextHelper(editor).goto_line(1, 4)
    QTest.qWait(5000)
    mode.request_goto()
    assert flg_multi is False
    QtCore.QTimer.singleShot(1000, accept_dlg)
    QTest.qWait(5000)
    assert flg_multi is True
Example #51
0
 def _get_indent(self, cursor):
     prev_line_text = TextHelper(self.editor).current_line_text()
     if not self.editor.free_format:
         prev_line_text = ' ' * 7 + prev_line_text[7:]
     diff = len(prev_line_text) - len(prev_line_text.lstrip())
     post_indent = ' ' * diff
     min_column = self.editor.indenter_mode.min_column
     if len(post_indent) < min_column:
         post_indent = min_column * ' '
     # all regex are upper cases
     text = cursor.block().text().upper()
     # raise indentation level
     patterns = [
         regex.PARAGRAPH_PATTERN, regex.STRUCT_PATTERN, regex.BRANCH_START,
         regex.LOOP_PATTERN
     ]
     for ptrn in patterns:
         if ptrn.indexIn(text) != -1:
             post_indent += self.editor.tab_length * ' '
             return '', post_indent
     # use the previous line indentation
     return '', post_indent
 def _is_last_chard_end_of_word(self):
     try:
         tc = TextHelper(self.editor).word_under_cursor()
         tc.setPosition(tc.position())
         tc.movePosition(tc.StartOfLine, tc.KeepAnchor)
         l = tc.selectedText()
         last_char = l[len(l) - 1]
         seps = self.editor.word_separators
         symbols = [",", " ", "("]
         return last_char in seps and last_char not in symbols
     except IndexError:
         return False
Example #53
0
 def _on_occurrence_activated(item):
     assert isinstance(item, QtWidgets.QTreeWidgetItem)
     data = item.data(0, QtCore.Qt.UserRole)
     try:
         l = data['line']
     except TypeError:
         return  # file item or root item
     l = data['line']
     start = data['start']
     lenght = data['end'] - start
     if data is not None:
         # open editor and go to line/column
         editor = api.editor.open_file(
             data['path'], data['line'], data['start'])
         if editor is None:
             return
         # select text
         helper = TextHelper(editor)
         cursor = helper.select_lines(start=l, end=l)
         cursor.movePosition(cursor.StartOfBlock)
         cursor.movePosition(cursor.Right, cursor.MoveAnchor, start)
         cursor.movePosition(cursor.Right, cursor.KeepAnchor, lenght)
         editor.setTextCursor(cursor)
    def request_goto(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(self.editor).word_under_cursor()
        if not self._pending:
            request_data = {
                'code': self.editor.toPlainText(),
                'line': tc.blockNumber() + 1,
                'column': tc.columnNumber(),
                'path': self.editor.file.path,
                'encoding': self.editor.file.encoding
            }
            self.editor.backend.send_request(
                workers.goto_assignments, request_data,
                on_receive=self._on_results_available)
            self._pending = True
        self.editor.set_mouse_cursor(QtCore.Qt.WaitCursor)
 def _on_post_key_pressed(self, event):
     # if we are in disabled cc, use the parent implementation
     helper = TextHelper(self.editor)
     column = helper.current_column_nbr()
     usd = self.editor.textCursor().block().userData()
     if usd:
         for start, end in usd.cc_disabled_zones:
             if (start <= column < end - 1 and
                     not helper.current_line_text(
                         ).lstrip().startswith('"""')):
                 return
         prev_line = helper.line_text(helper.current_line_nbr() - 1)
         is_below_fct_or_class = "def" in prev_line or "class" in prev_line
         if (event.text() == '"' and
                 '""' == helper.current_line_text().strip() and
                 (is_below_fct_or_class or column == 2)):
             self._insert_docstring(prev_line, is_below_fct_or_class)
         elif (event.text() == "(" and
                 helper.current_line_text().lstrip().startswith("def ")):
             self._handle_fct_def()
         else:
             super(PyAutoCompleteMode, self)._on_post_key_pressed(event)
Example #56
0
 def _auto_import(self):
     helper = TextHelper(self.editor)
     name = helper.word_under_cursor(select_whole_word=True).selectedText()
     if name:
         import_stmt = 'import %s' % name
     else:
         import_stmt = ''
     import_stmt, status = QtWidgets.QInputDialog.getText(
         self.editor, _('Add import'), _('Import statement:'),
         QtWidgets.QLineEdit.Normal, import_stmt)
     if status:
         sh = self.editor.syntax_highlighter
         line_number = sh.import_statements[0].blockNumber()
         for stmt in sh.import_statements:
             if stmt.text() == import_stmt:
                 # same import already exists
                 return
         l, c = helper.cursor_position()
         cursor = helper.goto_line(line_number)
         cursor.insertText(import_stmt + '\n')
         helper.goto_line(l + 1, c)
class PyAutoIndentMode(AutoIndentMode):
    """
    Customised :class:`pyqode.core.modes.AutoIndentMode` for python
    that tries its best to follow the pep8 indentation guidelines.
    """
    def __init__(self):
        super(PyAutoIndentMode, self).__init__()

    def on_install(self, editor):
        super().on_install(editor)
        self._helper = TextHelper(editor)

    def has_two_empty_line_before(self, tc):
        ln = tc.blockNumber()
        limit = ln - 1
        while ln > limit:
            if self._helper.line_text(ln).strip() != "":
                return False
            ln -= 1
        return True

    def has_unclosed_paren(self, tc):
        ln = tc.blockNumber()
        while ln >= 0:
            line = self._helper.line_text(ln)
            if line.count("(") > line.count(")"):
                return True
            ln -= 1
        return False

    def is_in_string_def(self, full_line, column):
        count = 0
        char = "'"
        for i in range(len(full_line)):
            if full_line[i] == "'" or full_line[i] == '"':
                count += 1
            if full_line[i] == '"' and i < column:
                char = '"'
        count_after_col = 0
        for i in range(column, len(full_line)):
            if full_line[i] == "'" or full_line[i] == '"':
                count_after_col += 1
        return count % 2 == 0 and count_after_col % 2 == 1, char

    def is_paren_open(self, paren):
        return (paren.character == "(" or paren.character == "["
                or paren.character == '{')

    def is_paren_closed(self, paren):
        return (paren.character == ")" or paren.character == "]"
                or paren.character == '}')

    def get_full_line(self, tc):
        tc2 = QTextCursor(tc)
        tc2.select(QTextCursor.LineUnderCursor)
        full_line = tc2.selectedText()
        return full_line

    def parens_count_for_block(self, col, block):
        data = block.userData()
        nb_open = 0
        nb_closed = 0
        lists = [data.parentheses, data.braces, data.square_brackets]
        for symbols in lists:
            for paren in symbols:
                if self.is_paren_open(paren):
                    if not col:
                        return -1, -1
                    nb_open += 1
                if paren.position >= col and self.is_paren_closed(paren):
                    nb_closed += 1
        return nb_closed, nb_open

    def between_paren(self, tc, col):
        nb_closed, nb_open = self.parens_count_for_block(col, tc.block())
        block = tc.block().next()
        while nb_open == nb_closed == 0 and block.isValid():
            nb_closed, nb_open = self.parens_count_for_block(nb_open, block)
            block = block.next()
        # if not, is there an non closed paren on the next lines.
        parens = {'(': 0, '{': 0, '[': 0}
        matching = {')': '(', '}': '{', ']': '['}
        rparens = {')': 0, '}': 0, ']': 0}
        rmatching = {'(': ')', '{': '}', '[': ']'}
        if nb_open != nb_closed:
            # look down
            if nb_open > nb_closed:
                operation = self._next_block
                down = True
            else:
                operation = self._prev_block
                down = False
            block = tc.block()
            # block = operation(tc.block())
            offset = col
            while block.isValid():
                data = block.userData()
                lists = [data.parentheses, data.braces, data.square_brackets]
                for symbols in lists:
                    for paren in symbols:
                        if paren.position < offset and down:
                            continue
                        if paren.position >= offset and not down:
                            continue
                        if self.is_paren_open(paren):
                            parens[paren.character] += 1
                            rparens[rmatching[paren.character]] -= 1
                            if (operation == self._prev_block and
                                    rparens[rmatching[paren.character]] < 0):
                                return True
                        if self.is_paren_closed(paren):
                            rparens[paren.character] += 1
                            parens[matching[paren.character]] -= 1
                            if (operation == self._next_block and
                                    parens[matching[paren.character]] < 0):
                                return True
                block = operation(block)
                offset = 0 if down else len(block.text())
        elif nb_open > 0:
            return True
        return False

    def _next_block(self, b):
        return b.next()

    def _prev_block(self, b):
        return b.previous()

    def is_in_comment(self, column, tc, full_line):
        use_parent_impl = False
        usd = tc.block().userData()
        for start, end in usd.cc_disabled_zones:
            if start < column < end:
                string = full_line[start:end]
                if not ((string.startswith("'") or string.startswith('"')) and
                        (string.endswith("'") or string.endswith('"'))):
                    use_parent_impl = True
                    break
        return use_parent_impl

    def get_last_word(self, tc):
        tc2 = QTextCursor(tc)
        tc2.movePosition(QTextCursor.Left, 1)
        tc2.movePosition(QTextCursor.WordLeft, tc.KeepAnchor)
        # tc2.movePosition(QTextCursor.Right, tc.KeepAnchor,
        #                  self.editor.cursorPosition[1])
        return tc2.selectedText().strip()

    def get_indent_of_opening_paren(self, tc, column):
        # find last closed paren
        pos = None
        data = tc.block().userData()
        tc2 = QTextCursor(tc)
        tc2.movePosition(tc2.StartOfLine, tc2.MoveAnchor)
        for paren in reversed(data.parentheses):
            if paren.character == ')':
                column = paren.position
                pos = tc2.position() + column + 1
                break
        if pos:
            tc2 = QTextCursor(tc)
            tc2.setPosition(pos)
            ol, oc = self.editor.modes.get(SymbolMatcherMode).symbol_pos(
                tc2, '(', 0)
            line = self._helper.line_text(ol)
            return len(line) - len(line.lstrip())
        return None

    def get_last_open_paren_pos(self, tc, column):
        pos = None
        char = None
        ln = tc.blockNumber() + 1
        tc_trav = QTextCursor(tc)
        while ln >= 1:
            tc_trav.movePosition(tc_trav.StartOfLine, tc_trav.MoveAnchor)
            data = tc_trav.block().userData()
            lists = [data.parentheses, data.braces, data.square_brackets]
            for symbols in lists:
                for paren in reversed(symbols):
                    if paren.position < column:
                        if self.is_paren_open(paren):
                            if paren.position > column:
                                continue
                            else:
                                pos = tc_trav.position() + paren.position
                                char = paren.character
                                # ensure it does not have a closing paren on
                                # the same line
                                tc3 = QTextCursor(tc)
                                tc3.setPosition(pos)
                                l, c = self.editor.modes.get(
                                    SymbolMatcherMode).symbol_pos(tc3, ')')
                                if l == ln and c < column:
                                    continue
                                return pos, char
            # check previous line
            tc_trav.movePosition(tc_trav.Up, tc_trav.MoveAnchor)
            ln = tc_trav.blockNumber() + 1
            column = len(self._helper.line_text(ln))
        return pos, char

    def get_parent_pos(self, tc, column):
        pos, char = self.get_last_open_paren_pos(tc, column)
        if char == '(':
            ptype = 0
            closingchar = ')'
        elif char == '[':
            ptype = 1
            closingchar = ']'
        elif char == '{':
            ptype = 2
            closingchar = '}'
        tc2 = QTextCursor(tc)
        tc2.setPosition(pos)
        ol, oc = self.editor.modes.get(SymbolMatcherMode).symbol_pos(
            tc2, char, ptype)
        cl, cc = self.editor.modes.get(SymbolMatcherMode).symbol_pos(
            tc2, closingchar, ptype)
        return (ol, oc), (cl, cc)

    def get_next_char(self, tc):
        tc2 = QTextCursor(tc)
        tc2.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor)
        char = tc2.selectedText()
        return char

    def handle_indent_after_paren(self, column, line, fullline, tc):
        """
        Handle indent between symbols such as parenthesis, braces,...
        """
        # elements might be separated by ',' 'or' 'and'
        next_char = self.get_next_char(tc)
        nextcharisclosingsymbol = next_char in [']', ')', '}']
        (oL, oC), (cL, cC) = self.get_parent_pos(tc, column)
        closingline = self._helper.line_text(cL)
        openingline = self._helper.line_text(oL)
        openingline = re.sub(r'".*"', "", openingline)
        openingline = re.sub(r"'.*'", "", openingline)
        openingindent = len(openingline) - len(openingline.lstrip())
        tokens = [t.strip() for t in re.split(', |and |or ',
                                              line[oC:column]) if t]

        # align with first token pos
        if len(closingline) > cC and closingline[cC] == ":":
            post = openingindent * " " + 8 * " "
        else:
            # press enter before a '}', ']', ')'
            # which close an affectation (tuple, list , dict)
            if nextcharisclosingsymbol and re.match('.*=[\s][\W].*',
                                                    openingline):
                post = openingindent * " "
            else:
                # align elems in list, tuple, dict
                if re.match('.*=[\s][\W].*', openingline) and oL - cL == 1:
                    post = openingindent * " " + 4 * " "
                # align elems in fct declaration (we align with first
                # token)
                else:
                    if len(tokens):
                        post = oC * " "
                    else:
                        post = openingindent * " " + 4 * " "
        pre = ""
        in_string_def, char = self.is_in_string_def(fullline, column)
        if in_string_def:
            pre = char
            post += char
        return pre, post

    def at_block_start(self, tc, line):
        """
        Improve QTextCursor.atBlockStart to ignore spaces
        """
        if tc.atBlockStart():
            return True
        column = tc.columnNumber()
        indentation = len(line) - len(line.lstrip())
        return column <= indentation

    def at_block_end(self, tc, fullline):
        if tc.atBlockEnd():
            return True
        column = tc.columnNumber()
        return column >= len(fullline.rstrip()) - 1

    def _get_indent(self, cursor):
        pos = cursor.position()
        ln, column = self._helper.cursor_position()
        fullline = self.get_full_line(cursor)
        line = fullline[:column]
        # no indent
        if pos == 0 or column == 0:
            return "", ""
        pre, post = AutoIndentMode._get_indent(self, cursor)
        if self.at_block_start(cursor, line):
            if self.has_two_empty_line_before(cursor):
                post = post[:-4]
            return pre, post
        # return pressed in comments
        if self.is_in_comment(column, cursor, fullline):
            if line.strip().startswith("#") and column != len(fullline):
                post += '# '
            return pre, post
        elif self.between_paren(cursor, column):
            try:
                pre, post = self.handle_indent_after_paren(
                    column, line, fullline, cursor)
            except TypeError:
                return pre, post
        else:
            lastword = self.get_last_word(cursor)
            inStringDef, char = self.is_in_string_def(fullline, column)
            if inStringDef:
                # the string might be between paren if multiline
                # check if there a at least a non closed paren on the previous
                # lines
                if self.has_unclosed_paren(cursor):
                    pre = char
                else:
                    pre = '" \\'
                    post += 4 * ' '
                if fullline.endswith(':'):
                    post += 4 * " "
                post += char
            elif fullline.rstrip().endswith(":") and \
                    lastword.rstrip().endswith(':') and \
                    self.at_block_end(cursor, fullline):
                try:
                    indent = (self.get_indent_of_opening_paren(cursor, column)
                              + 4)
                    if indent:
                        post = indent * " "
                except TypeError:
                    kw = ["if", "class", "def", "while", "for", "else", "elif",
                          "except", "finally", "try"]
                    l = fullline
                    ln = cursor.blockNumber()

                    def check_kw_in_line(kwds, lparam):
                        for kwd in kwds:
                            if kwd in lparam:
                                return True
                        return False

                    while not check_kw_in_line(kw, l) and ln:
                        ln -= 1
                        l = self._helper.line_text(ln)
                    indent = (len(l) - len(l.lstrip())) * " "
                    indent += 4 * " "
                    post = indent
            elif line.endswith("\\"):
                # increment indent
                post += 4 * " "
            elif fullline.endswith(")") and lastword.endswith(')'):
                # find line where the open braces can be found and align with
                # that line
                indent = self.get_indent_of_opening_paren(cursor, column)
                if indent:
                    post = indent * " "
            elif ("\\" not in fullline and "#" not in fullline and
                  fullline.strip() and not fullline.endswith(')') and
                  not self.at_block_end(cursor, fullline)):
                if lastword and lastword[-1] != " ":
                    pre += " \\"
                else:
                    pre += '\\'
                post += 4 * " "
                if fullline.endswith(':'):
                    post += 4 * " "
            elif (lastword == "return" or lastword == "pass"):
                post = post[:-4]

        return pre, post
 def on_install(self, editor):
     super().on_install(editor)
     self._helper = TextHelper(editor)
class PyAutoIndentMode(AutoIndentMode):
    """ Automatically indents text, respecting the PEP8 conventions.

    Customised :class:`pyqode.core.modes.AutoIndentMode` for python
    that tries its best to follow the pep8 indentation guidelines.

    """
    def __init__(self):
        super(PyAutoIndentMode, self).__init__()
        self._helper = None

    def on_install(self, editor):
        super(PyAutoIndentMode, self).on_install(editor)
        self._helper = TextHelper(editor)

    def _get_indent(self, cursor):
        ln, column = self._helper.cursor_position()
        fullline = self._get_full_line(cursor)
        line = fullline[:column]
        pre, post = AutoIndentMode._get_indent(self, cursor)
        if self._at_block_start(cursor, line):
            return pre, post
        # return pressed in comments
        c2 = QTextCursor(cursor)
        if c2.atBlockEnd():
            c2.movePosition(c2.Left)
        if (self._helper.is_comment_or_string(
                c2, formats=['comment', 'docstring']) or
                fullline.endswith('"""')):
            if line.strip().startswith("#") and column != len(fullline):
                post += '# '
            return pre, post
        # between parens
        elif self._between_paren(cursor, column):
            return self._handle_indent_between_paren(
                column, line, (pre, post), cursor)
        else:
            lastword = self._get_last_word(cursor)
            lastwordu = self._get_last_word_unstripped(cursor)
            in_string_def, char = self._is_in_string_def(fullline, column)
            if in_string_def:
                post, pre = self._handle_indent_inside_string(
                    char, cursor, fullline, post)
            elif (fullline.rstrip().endswith(":") and
                    lastword.rstrip().endswith(':') and
                    self._at_block_end(cursor, fullline)):
                post = self._handle_new_scope_indentation(
                    cursor, fullline)
            elif line.endswith("\\"):
                # if user typed \ and press enter -> indent is always
                # one level higher
                post += self.editor.tab_length * " "
            elif (fullline.endswith((')', '}', ']')) and
                    lastword.endswith((')', '}', ']'))):
                post = self._handle_indent_after_paren(cursor, post)
            elif ("\\" not in fullline and "#" not in fullline and
                  not self._at_block_end(cursor, fullline)):
                post, pre = self._handle_indent_in_statement(
                    fullline, lastwordu, post, pre)
            elif ((self._at_block_end(cursor, fullline) and
                    fullline.strip().startswith('return ')) or
                    lastword == "pass"):
                post = post[:-self.editor.tab_length]
        return pre, post

    @staticmethod
    def _is_in_string_def(full_line, column):
        count = 0
        char = "'"
        for i in range(len(full_line)):
            if full_line[i] == "'" or full_line[i] == '"':
                count += 1
            if full_line[i] == '"' and i < column:
                char = '"'
        count_after_col = 0
        for i in range(column, len(full_line)):
            if full_line[i] == "'" or full_line[i] == '"':
                count_after_col += 1
        return count % 2 == 0 and count_after_col % 2 == 1, char

    @staticmethod
    def _is_paren_open(paren):
        return (paren.character == "(" or paren.character == "["
                or paren.character == '{')

    @staticmethod
    def _is_paren_closed(paren):
        return (paren.character == ")" or paren.character == "]"
                or paren.character == '}')

    @staticmethod
    def _get_full_line(tc):
        tc2 = QTextCursor(tc)
        tc2.select(QTextCursor.LineUnderCursor)
        full_line = tc2.selectedText()
        return full_line

    def _parens_count_for_block(self, col, block):
        open_p = []
        closed_p = []
        lists = get_block_symbol_data(self.editor, block)
        for symbols in lists:
            for paren in symbols:
                if paren.position >= col:
                    continue
                if self._is_paren_open(paren):
                    if not col:
                        return -1, -1, [], []
                    open_p.append(paren)
                if self._is_paren_closed(paren):
                    closed_p.append(paren)
        return len(open_p), len(closed_p), open_p, closed_p

    def _between_paren(self, tc, col):
        try:
            self.editor.modes.get('SymbolMatcherMode')
        except KeyError:
            return False
        block = tc.block()
        nb_open = nb_closed = 0
        while block.isValid() and block.text().strip():
            o, c, _, _ = self._parens_count_for_block(col, block)
            nb_open += o
            nb_closed += c
            block = block.previous()
            col = len(block.text())
        return nb_open > nb_closed

    @staticmethod
    def _get_last_word(tc):
        tc2 = QTextCursor(tc)
        tc2.movePosition(QTextCursor.Left, tc.KeepAnchor, 1)
        tc2.movePosition(QTextCursor.WordLeft, tc.KeepAnchor)
        return tc2.selectedText().strip()

    @staticmethod
    def _get_last_word_unstripped(tc):
        tc2 = QTextCursor(tc)
        tc2.movePosition(QTextCursor.Left, tc.KeepAnchor, 1)
        tc2.movePosition(QTextCursor.WordLeft, tc.KeepAnchor)
        return tc2.selectedText()

    def _get_indent_of_opening_paren(self, tc):
        tc.movePosition(tc.Left, tc.KeepAnchor)
        char = tc.selectedText()
        tc.movePosition(tc.Right, tc.MoveAnchor)
        mapping = {
            ')': (OPEN, PAREN),
            ']': (OPEN, SQUARE),
            '}': (OPEN, BRACE)
        }
        try:
            character, char_type = mapping[char]
        except KeyError:
            return None
        else:
            ol, oc = self.editor.modes.get(SymbolMatcherMode).symbol_pos(
                tc, character, char_type)
            line = self._helper.line_text(ol)
            return len(line) - len(line.lstrip())

    def _get_first_open_paren(self, tc, column):
        pos = None
        char = None
        ln = tc.blockNumber()
        tc_trav = QTextCursor(tc)
        mapping = {
            '(': (CLOSE, PAREN),
            '[': (CLOSE, SQUARE),
            '{': (CLOSE, BRACE)
        }
        while ln >= 0 and tc.block().text().strip():
            tc_trav.movePosition(tc_trav.StartOfLine, tc_trav.MoveAnchor)
            lists = get_block_symbol_data(self.editor, tc_trav.block())
            all_symbols = []
            for symbols in lists:
                all_symbols += [s for s in symbols]
            symbols = sorted(all_symbols, key=lambda x: x.position)
            for paren in reversed(symbols):
                if paren.position < column:
                    if self._is_paren_open(paren):
                        if paren.position > column:
                            continue
                        else:
                            pos = tc_trav.position() + paren.position
                            char = paren.character
                            # ensure it does not have a closing paren on
                            # the same line
                            tc3 = QTextCursor(tc)
                            tc3.setPosition(pos)
                            try:
                                ch, ch_type = mapping[paren.character]
                                l, c = self.editor.modes.get(
                                    SymbolMatcherMode).symbol_pos(
                                    tc3, ch, ch_type)
                            except KeyError:
                                continue
                            if l == ln and c < column:
                                continue
                            return pos, char
            # check previous line
            tc_trav.movePosition(tc_trav.Up, tc_trav.MoveAnchor)
            ln = tc_trav.blockNumber()
            column = len(self._helper.line_text(ln))
        return pos, char

    def _get_paren_pos(self, tc, column):
        pos, char = self._get_first_open_paren(tc, column)
        mapping = {'(': PAREN, '[': SQUARE, '{': BRACE}
        tc2 = QTextCursor(tc)
        tc2.setPosition(pos)
        import sys
        ol, oc = self.editor.modes.get(SymbolMatcherMode).symbol_pos(
            tc2, OPEN, mapping[char])
        cl, cc = self.editor.modes.get(SymbolMatcherMode).symbol_pos(
            tc2, CLOSE, mapping[char])
        return (ol, oc), (cl, cc)

    @staticmethod
    def _get_next_char(tc):
        tc2 = QTextCursor(tc)
        tc2.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor)
        char = tc2.selectedText()
        return char

    @staticmethod
    def _get_prev_char(tc):
        tc2 = QTextCursor(tc)
        tc2.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor)
        char = tc2.selectedText()
        while char == ' ':
            tc2.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor)
            char = tc2.selectedText()
        return char.strip()

    def _handle_indent_between_paren(self, column, line, parent_impl, tc):
        """
        Handle indent between symbols such as parenthesis, braces,...
        """
        pre, post = parent_impl
        next_char = self._get_next_char(tc)
        prev_char = self._get_prev_char(tc)
        prev_open = prev_char in ['[', '(', '{']
        next_close = next_char in [']', ')', '}']
        (open_line, open_symbol_col), (close_line, close_col) = \
            self._get_paren_pos(tc, column)
        open_line_txt = self._helper.line_text(open_line)
        open_line_indent = len(open_line_txt) - len(open_line_txt.lstrip())
        if prev_open:
            post = (open_line_indent + self.editor.tab_length) * ' '
        elif next_close and prev_char != ',':
            post = open_line_indent * ' '
        elif tc.block().blockNumber() == open_line:
            post = open_symbol_col * ' '

        # adapt indent if cursor on closing line and next line have same
        # indent -> PEP8 compliance
        if close_line and close_col:
            txt = self._helper.line_text(close_line)
            bn = tc.block().blockNumber()
            flg = bn == close_line
            next_indent = self._helper.line_indent(bn + 1) * ' '
            if flg and txt.strip().endswith(':') and next_indent == post:
                # | look at how the previous line ( ``':'):`` ) was
                # over-indented, this is actually what we are trying to
                # achieve here
                post += self.editor.tab_length * ' '

        # breaking string
        if next_char in ['"', "'"]:
            tc.movePosition(tc.Left)
        is_string = self._helper.is_comment_or_string(tc, formats=['string'])
        if next_char in ['"', "'"]:
            tc.movePosition(tc.Right)
        if is_string:
            trav = QTextCursor(tc)
            while self._helper.is_comment_or_string(
                    trav, formats=['string']):
                trav.movePosition(trav.Left)
            trav.movePosition(trav.Right)
            symbol = '%s' % self._get_next_char(trav)
            pre += symbol
            post += symbol

        return pre, post

    @staticmethod
    def _at_block_start(tc, line):
        """
        Improve QTextCursor.atBlockStart to ignore spaces
        """
        if tc.atBlockStart():
            return True
        column = tc.columnNumber()
        indentation = len(line) - len(line.lstrip())
        return column <= indentation

    @staticmethod
    def _at_block_end(tc, fullline):
        if tc.atBlockEnd():
            return True
        column = tc.columnNumber()
        return column >= len(fullline.rstrip()) - 1

    def _handle_indent_inside_string(self, char, cursor, fullline, post):
        # break string with a '\' at the end of the original line, always
        # breaking strings enclosed by parens is done in the
        # _handle_between_paren method
        n = self.editor.tab_length
        pre = '%s \\' % char
        post += n * ' '
        if fullline.endswith(':'):
            post += n * " "
        post += char
        return post, pre

    def _handle_new_scope_indentation(self, cursor, fullline):
        try:
            indent = (self._get_indent_of_opening_paren(cursor) +
                      self.editor.tab_length)
            post = indent * " "
        except TypeError:
            # e.g indent is None (meaning the line does not ends with ):, ]:
            # or }:
            kw = ["if", "class", "def", "while", "for", "else", "elif",
                  "except", "finally", "try"]
            l = fullline
            ln = cursor.blockNumber()

            def check_kw_in_line(kwds, lparam):
                for kwd in kwds:
                    if kwd in lparam:
                        return True
                return False

            while not check_kw_in_line(kw, l) and ln:
                ln -= 1
                l = self._helper.line_text(ln)
            indent = (len(l) - len(l.lstrip())) * " "
            indent += self.editor.tab_length * " "
            post = indent
        return post

    def _handle_indent_after_paren(self, cursor, post):
        indent = self._get_indent_of_opening_paren(cursor)
        if indent is not None:
            post = indent * " "
        return post

    def _handle_indent_in_statement(self, fullline, lastword, post, pre):
        if lastword and lastword[-1] != " ":
            pre += " \\"
        else:
            pre += '\\'
        post += self.editor.tab_length * " "
        if fullline.endswith(':'):
            post += self.editor.tab_length * " "
        return post, pre
 def on_install(self, editor):
     super(PyAutoIndentMode, self).on_install(editor)
     self._helper = TextHelper(editor)