Beispiel #1
0
class IdePanel(QPlainTextEdit):
    def __init__(self):
        QPlainTextEdit.__init__(self)
        self.setWordWrapMode(QTextOption.NoWrap)
        self.setFont(QFont("monospace", 10))
        self.setCursorWidth(2)
        self.installEventFilter(self)

        self.cursorPositionChanged.connect(self.showHelp)

        self.completer = QCompleter(self)
        self.completer.setWidget(self)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.activated.connect(self.insertCompletion)

        auto_complete = QShortcut(QKeySequence("Ctrl+Space"), self)
        auto_complete.activated.connect(self.activateCompleter)

        copy_line = QShortcut(QKeySequence("Ctrl+D"), self)
        copy_line.activated.connect(self.duplicateLine)

        select_fragment = QShortcut(QKeySequence("Ctrl+J"), self)
        select_fragment.activated.connect(self.selectFragment)

    def showHelp(self):
        text_cursor = self.textCursor()
        user_data = text_cursor.block().userData()

        if user_data is not None:
            configuration_line = user_data.configuration_line

            if configuration_line.keyword().hasKeywordDefinition():
                HelpCenter.getHelpCenter("ERT").setHelpMessageLink("config/" + configuration_line.documentationLink())

    def getText(self):
        return self.document().toPlainText()

    def eventFilter(self, qobject, qevent):
        if qobject == self and qevent.type() == QEvent.ToolTip:
            text_cursor = self.cursorForPosition(qevent.pos())
            pos = text_cursor.positionInBlock()

            user_data = text_cursor.block().userData()
            if user_data is not None:
                #: :type: ConfigurationLine
                configuration_line = user_data.configuration_line
                # if configuration_line.keyword().hasKeywordDefinition():
                #     print(configuration_line.keyword().keywordDefinition().documentation)

                if pos in configuration_line.keyword():
                    self.setToolTip(configuration_line.validationStatusForToken(configuration_line.keyword()).message())
                else:
                    for argument in configuration_line.arguments():
                        if pos in argument:
                            self.setToolTip(configuration_line.validationStatusForToken(argument).message())

            else:
                self.setToolTip("")

        return QPlainTextEdit.eventFilter(self, qobject, qevent)

    def activateCompleter(self):
        text_cursor = self.textCursor()
        block = self.document().findBlock(text_cursor.position())
        position_in_block = text_cursor.positionInBlock()

        self.selectWordUnderCursor(text_cursor)
        word = unicode(text_cursor.selectedText())

        user_data = block.userData()

        self.completer.setCompletionPrefix(word)

        show_completer = False
        if user_data is None:
            self.completer.setModel(QStringListModel(self.handler_names))
            show_completer = True

        else:
            keyword = user_data.keyword
            options = keyword.handler.parameterOptions(keyword, word, position_in_block)

            if len(options) == 1:
                self.insertCompletion(options[0])
            elif len(options) > 1:
                self.completer.setModel(QStringListModel(options))
                if self.completer.completionCount() == 1:
                    self.insertCompletion(self.completer.currentCompletion())
                else:
                    show_completer = True

        if show_completer:
            rect = self.cursorRect(text_cursor)
            rect.setWidth(
                self.completer.popup().sizeHintForColumn(0)
                + self.completer.popup().verticalScrollBar().sizeHint().width()
            )
            self.completer.complete(rect)

    def keyPressEvent(self, qkeyevent):
        if self.completer.popup().isVisible():
            dead_keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab]
            if qkeyevent.key() in dead_keys:
                qkeyevent.ignore()
                return

        if qkeyevent.modifiers() == Qt.ShiftModifier:
            if qkeyevent.key() & Qt.Key_Delete == Qt.Key_Delete:
                self.deleteLine()

        QPlainTextEdit.keyPressEvent(self, qkeyevent)

    def insertCompletion(self, string):
        text_cursor = self.textCursor()
        self.selectWordUnderCursor(text_cursor)
        text_cursor.insertText(string)

    def isCursorInSpace(self):
        text_cursor = self.textCursor()
        if text_cursor.positionInBlock() > 0:
            text_cursor.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor)
            text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)

        if text_cursor.positionInBlock() < text_cursor.block().length() - 1:
            text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)

        if unicode(text_cursor.selectedText()).strip() == "":
            return True

        return False

    def selectWordUnderCursor(self, text_cursor):
        if not self.isCursorInSpace():
            # text_cursor.select(QTextCursor.WordUnderCursor)

            # pattern = "[\s|\v|\f|\n|\r|\t|\xe2\x80\xa8|\xe2\x80\xa9]"
            # pattern = "[\\s|\\xe2\\x80\\xa9|\\xe2\\x80\\xa8]"

            block_start = 0
            block_end = text_cursor.block().length()

            cursor_pos = text_cursor.positionInBlock()
            pos = cursor_pos
            pattern = u"[\s\u2029\u2028]"
            while pos >= block_start:
                text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor)
                text = text_cursor.selectedText()
                if re.search(pattern, text):
                    break
                pos -= 1

            text_cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor)

            while pos < block_end:
                text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
                text = text_cursor.selectedText()
                if re.search(pattern, text):
                    break
                pos += 1

            text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor)

            # pattern = "[\\s]"
            # start = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindBackward | QTextDocument.FindCaseSensitively)
            # end = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindCaseSensitively)
            # block_end_pos = text_cursor.block().position() + text_cursor.block().length()
            #
            # text_cursor.setPosition(start.position(), QTextCursor.MoveAnchor)
            # # text_cursor.setPosition(min(block_end_pos, end.position() - 1), QTextCursor.KeepAnchor)
            # text_cursor.setPosition(end.position() - 1, QTextCursor.KeepAnchor)

    def deleteLine(self):
        text_cursor = self.textCursor()
        text_cursor.beginEditBlock()
        text_cursor.select(QTextCursor.LineUnderCursor)
        text_cursor.removeSelectedText()
        text_cursor.deletePreviousChar()

        text_cursor.movePosition(QTextCursor.NextBlock)
        text_cursor.movePosition(QTextCursor.StartOfLine)
        self.setTextCursor(text_cursor)
        text_cursor.endEditBlock()

    def duplicateLine(self):
        text_cursor = self.textCursor()
        text_cursor.beginEditBlock()
        text_cursor.select(QTextCursor.LineUnderCursor)
        text = text_cursor.selectedText()
        text_cursor.movePosition(QTextCursor.EndOfLine)
        text_cursor.insertBlock()
        text_cursor.insertText(text)
        text_cursor.endEditBlock()

    def selectFragment(self):
        text_cursor = self.textCursor()
        self.selectWordUnderCursor(text_cursor)
        self.setTextCursor(text_cursor)
Beispiel #2
0
class TagCompleterWidget(QObject):
    """
    widget in lineEdit-style with integrated qcompleter
    """ 
    def __init__(self, max_tags, expiry_prefix=None, tag_list=None, parent=None, separator=",", show_datestamp=False):
        
        QWidget.__init__(self, parent)
        
        self.__completer_active = False
        self.__max_tags = max_tags
        self.__tag_separator = separator
        self.__tag_list = tag_list
        self.__parent = parent
        self.__tag_line = QLineEdit(self.__parent)
        #self.__tag_line = TagLineEdit(self.__parent)
        self.__show_datestamp = show_datestamp
        self.__datestamp_format = TsConstants.DATESTAMP_FORMAT_DAY
        self.__expiry_prefix = expiry_prefix
        
        ## flag, if the line should be checked of emptiness
        self.__check_not_empty = False
        self.__check_tag_limit = False
        
        self.__restricted_vocabulary = False
        
        ## the latest activated suggestion 
        self.__activated_text = None
        # value of the actual datestamp
        self.__datestamp = None
        self.__datestamp_hidden = False
        
        self.__completer = QCompleter(self.__tag_list, self);
        self.__completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.__completer.setWidget(self.__tag_line)
        
        #self.__handle_datestamp()
        
        self.connect(self.__tag_line, SIGNAL("textChanged(QString)"), self.__text_changed_by_user)
        self.connect(self.__completer, SIGNAL("activated(QString)"), self.__text_activated)
        self.connect(self.__completer, SIGNAL("highlighted(QString)"), self.__text_highlighted)

    def __text_highlighted(self, item_name):
        """
        a suggestion has been selected in the dropdownbox
        """
        # set this variable to True just to know, that 
        # this value comes from the completer and not from the user 
        self.__completer_active = True
        self.__text_selected(item_name)
        self.__completer_active = False

    def __handle_datestamp(self, is_hidden):
        """
        if the show_datestamp flag is set to True, provide an automatic datestamp on the tagline 
        """
        if self.__show_datestamp:
            self.__datestamp = time.strftime(self.__datestamp_format)
            if not is_hidden:
                self.__tag_line.clear()
                self.__tag_line.setText(self.__datestamp)

    def set_datestamp_format(self, format, is_hidden):
        self.__datestamp_format = format
        self.__datestamp_hidden = is_hidden
        self.__handle_datestamp(is_hidden)
    
    def show_datestamp(self, show):
        self.__show_datestamp = show
        self.__handle_datestamp(show)

    def clear_line(self):
        """
        clear the tagline ... 
        if auto datestamp is set to "on" a fresh stamp will be placed into the tagline 
        """
        self.__tag_line.clear()
        if self.__show_datestamp:
            self.__handle_datestamp(self.__datestamp_hidden)
    def set_check_not_empty(self, check_necessary):
        """
        set this to True, if there should be sent a signal that indicates 
        that the tagline is not empty anymore 
        """
        self.__check_not_empty = True
    
    def set_restricted_vocabulary(self, is_restricted):
        """
        use True/False to turn the restricted function on/off
        """
        self.__restricted_vocabulary = is_restricted
    
    def select_line(self):
        """
        select the tagline ... 
        """
        self.__tag_line.selectAll()
        self.__tag_line.setFocus(QtCore.Qt.OtherFocusReason)

    def __text_changed_by_user(self, text):
        # create a QByteArray in utf8
        all_text = text.toUtf8()
        # make a python string out of it
        all_text = str(all_text)
        # convert the python string tu unicode utf-8
        all_text = unicode(all_text, "utf-8")
        if self.__check_not_empty:
            if all_text is not None and all_text != "":
                self.emit(QtCore.SIGNAL("line_empty"), False)
            else:
                self.emit(QtCore.SIGNAL("line_empty"), True)
        
        text = all_text[:self.__tag_line.cursorPosition()]
        
        ## remove whitespace and filter out duplicates by using a set
        tag_set = set([])
        for tag in all_text.split(self.__tag_separator):
            strip_tag = tag.strip()
            if strip_tag != "":
                tag_set.add(strip_tag)
        max_tags = self.__max_tags
        if self.__datestamp_hidden:
            max_tags = max_tags - 1;
        ## do not proceed if the max tag count is reached
        if len(tag_set) > max_tags:
            self.emit(QtCore.SIGNAL("tag_limit_reached"), True)
            self.__check_tag_limit = True
            return
        else:
            if self.__check_tag_limit:
                self.emit(QtCore.SIGNAL("tag_limit_reached"), False)
                self.__check_tag_limit = False
        
        prefix = text.split(self.__tag_separator)[-1].strip()
        if not self.__completer_active:
            self.__update_completer(tag_set, prefix)
    
    def __update_completer(self, tag_set, completion_prefix):
        if self.__tag_list is None:
            return
        tags = list(set(self.__tag_list).difference(tag_set))
        #tags = list(self.__tag_list)

        model = QStringListModel(tags, self)
        self.__completer.setModel(model)
        self.__completer.setCompletionPrefix(completion_prefix)
        
        if self.__restricted_vocabulary:
            self.__check_vocabulary(tag_set, completion_prefix)
            
        if completion_prefix.strip() != '':
            ## use the default completion algorithm
            self.__completer.complete()
    
    def __check_finished_tags(self, typed_tags_list):
        """
        use this method to control all typed tags. this means all tags terminated with a comma 
        """
        pass

    def __check_in_completion_list(self, tag):
        """
        if a written tag equals a tag of the completion list - the tag will be removed from the completion list
        so the completer will return a completion count of 0 for this tag.
        in this case there would be displayed an error message at the dialog (when controlled vocab is activated)
        so check manually, if the provided tag is contained in the suggestion_list
        """
        #for sug_tag in self.__tag_list:
        #    if sug_tag == tag:
        #        return True
        #return False
        return tag in self.__tag_list
    
    def __check_vocabulary(self, tag_set, completion_prefix):
        """
        have a look at the entered tag to be completed.
        if restricted vocabulary is turned on:
        datestamps do not have to be checked. 
        """
        
        not_allowed_tags_count = 0
        no_completion_found = False
        stripped_text = unicode(self.__tag_line.text()).strip()
        ##when a tag separator is on the last position, there should have been entered a new tag
        ##check this tag for its correctness
        if len(stripped_text) > 0:
            ##check if all written tags are allowed (incl. datestamps an expiry tags)
            for tag in tag_set:
                ## tag can be a datestamp -> OK
                if not SpecialCharHelper.is_datestamp(tag) and tag != "":
                    ## tag can be an expiry tag -> OK
#                    if self.__expiry_prefix is not None and not SpecialCharHelper.is_expiry_tag(self.__expiry_prefix, tag):
                    if self.__expiry_prefix is None or not SpecialCharHelper.is_partial_expiry_tag(self.__expiry_prefix, tag):
                        if unicode(tag) not in self.__tag_list:
                            not_allowed_tags_count += 1
                         
        if(completion_prefix.strip() == ""):
            ## if the prefix is an empty string - manually set the completion_count to 0
            ## because the completer would return the whole number of tags in its suggestion list
            completion_count = 0
        else:   
            completion_count = self.__completer.completionCount()
                            
        if self.__restricted_vocabulary and completion_count == 0:
            ## additionally check if the prefix equals a tag from the suggestion list
            ## this has to be done, because we do not get a completionCount > 0 if the prefix equals a given tag 
            #if completion_prefix not in self.__tag_list:
            if completion_prefix is not None and len(completion_prefix) > 0 and completion_prefix.strip() != "":
                ## just send the signal if the tag is no datestamp AND if it is no full tag
                if not SpecialCharHelper.is_datestamp(completion_prefix) and not self.__check_in_completion_list(completion_prefix):
                    if not SpecialCharHelper.is_partial_expiry_tag(self.__expiry_prefix, completion_prefix):
                        no_completion_found = True

        ## there are tags (terminated with comma) which are not in the allowed tag_list 
        if not_allowed_tags_count > 1:
            self.emit(QtCore.SIGNAL("no_completion_found"), True)
            return
            
            
        if not_allowed_tags_count > 0:
            ## in this case the user has entered a not allowed tag and terminated it with a comma to mark it as a tag
            ## the completion count is 0 because there is nothing after the last comma in the taglist 
            if completion_count == 0:
                self.emit(QtCore.SIGNAL("no_completion_found"), True)
                return
            ## it could be the case, that the user is still typing an allowed tag
            ## so check, if the completer has a possible completion
            ## if not -> send the signal 
            if no_completion_found:
                self.emit(QtCore.SIGNAL("no_completion_found"), True)
                return
        ## everytime there is no completion found, emit the signal
        elif no_completion_found:
            self.emit(QtCore.SIGNAL("no_completion_found"), True)
            return
        ## in this case everything is fine
        self.emit(QtCore.SIGNAL("no_completion_found"), False)
    
    def __text_selected(self, text):
        self.__activated_text = text
        
        cursor_pos = self.__tag_line.cursorPosition()
        before_text = unicode(self.__tag_line.text())[:cursor_pos]
        #after_text = unicode(self.__tag_line.text())[cursor_pos:]
        prefix_len = len(before_text.split(self.__tag_separator)[-1].strip())

        self.__tag_line.setText("%s%s" % (before_text[:cursor_pos - prefix_len], text))
        self.__tag_line.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
        
    def __text_activated(self, text):
        """
        a suggestion has been choosen by the user
        """
        self.__text_selected(text)
        self.emit(QtCore.SIGNAL("activated"))

    def get_tag_list(self):
        tag_string = unicode(self.__tag_line.text())
        result = set([])
        tag_list = tag_string.split(self.__tag_separator)
        for tag in tag_list:
            strip_tag = tag.strip()
            if strip_tag != "":
                result.add(strip_tag)
        # if the datestamp is hidden, add it manually to the taglist
        if self.__datestamp_hidden:
            result.add(self.__datestamp)
        return result
    
    def get_tag_line(self):
        return self.__tag_line
    
    def get_completer(self):
        return self.__completer
    
    def set_enabled(self, enable):
        self.__tag_line.setEnabled(enable)
        
    def set_text(self, text):
        self.__tag_line.setText(text)
    
    def set_tag_completion_list(self, tag_list):
        self.__tag_list = tag_list
        self.__completer.setModel(QtGui.QStringListModel(QtCore.QStringList(tag_list)))
        
    def get_tag_completion_list(self):
        return self.__tag_list

    def is_empty(self):
        if self.__tag_line.text() == "":
            return True
        else:
            return False
    
    def set_place_holder_text(self, text):
        self.__tag_line.setPlaceholderText(text)
Beispiel #3
0
class IdePanel(QPlainTextEdit):
    def __init__(self):
        QPlainTextEdit.__init__(self)
        self.setWordWrapMode(QTextOption.NoWrap)
        self.setFont(QFont("monospace", 10))
        self.setCursorWidth(2)
        self.installEventFilter(self)

        self.cursorPositionChanged.connect(self.showHelp)

        self.completer = QCompleter(self)
        self.completer.setWidget(self)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.activated.connect(self.insertCompletion)


        auto_complete = QShortcut(QKeySequence("Ctrl+Space"), self)
        auto_complete.activated.connect(self.activateCompleter)

        copy_line = QShortcut(QKeySequence("Ctrl+D"), self)
        copy_line.activated.connect(self.duplicateLine)

        select_fragment = QShortcut(QKeySequence("Ctrl+J"), self)
        select_fragment.activated.connect(self.selectFragment)

    def showHelp(self):
        text_cursor = self.textCursor()
        user_data = text_cursor.block().userData()

        if user_data is not None:
            configuration_line = user_data.configuration_line

            if configuration_line.keyword().hasKeywordDefinition():
                HelpCenter.getHelpCenter("ERT").setHelpMessageLink("config/" + configuration_line.documentationLink())


    def getText(self):
        return self.document().toPlainText()


    def eventFilter(self, qobject, qevent):
        if qobject == self and qevent.type() == QEvent.ToolTip:
            text_cursor = self.cursorForPosition(qevent.pos())
            pos = text_cursor.positionInBlock()

            user_data = text_cursor.block().userData()
            if user_data is not None:
                #: :type: ConfigurationLine
                configuration_line = user_data.configuration_line
                # if configuration_line.keyword().hasKeywordDefinition():
                #     print(configuration_line.keyword().keywordDefinition().documentation)

                if pos in configuration_line.keyword():
                    self.setToolTip(configuration_line.validationStatusForToken(configuration_line.keyword()).message())
                else:
                    for argument in configuration_line.arguments():
                        if pos in argument:
                            self.setToolTip(configuration_line.validationStatusForToken(argument).message())

            else:
                self.setToolTip("")


        return QPlainTextEdit.eventFilter(self, qobject, qevent)


    def activateCompleter(self):
        text_cursor = self.textCursor()
        block = self.document().findBlock(text_cursor.position())
        position_in_block = text_cursor.positionInBlock()



        self.selectWordUnderCursor(text_cursor)
        word = unicode(text_cursor.selectedText())

        user_data = block.userData()

        self.completer.setCompletionPrefix(word)

        show_completer = False
        if user_data is None:
            self.completer.setModel(QStringListModel(self.handler_names))
            show_completer = True

        else:
            keyword = user_data.keyword
            options = keyword.handler.parameterOptions(keyword, word, position_in_block)

            if len(options) == 1:
                self.insertCompletion(options[0])
            elif len(options) > 1:
                self.completer.setModel(QStringListModel(options))
                if self.completer.completionCount() == 1:
                    self.insertCompletion(self.completer.currentCompletion())
                else:
                    show_completer = True


        if show_completer:
            rect = self.cursorRect(text_cursor)
            rect.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width())
            self.completer.complete(rect)

    def keyPressEvent(self, qkeyevent):
        if self.completer.popup().isVisible():
            dead_keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab]
            if qkeyevent.key() in dead_keys:
                qkeyevent.ignore()
                return

        if qkeyevent.modifiers() == Qt.ShiftModifier:
            if qkeyevent.key() & Qt.Key_Delete == Qt.Key_Delete:
                self.deleteLine()

        QPlainTextEdit.keyPressEvent(self, qkeyevent)


    def insertCompletion(self, string):
        text_cursor = self.textCursor()
        self.selectWordUnderCursor(text_cursor)
        text_cursor.insertText(string)

    def isCursorInSpace(self):
        text_cursor = self.textCursor()
        if text_cursor.positionInBlock() > 0:
            text_cursor.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor)
            text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)

        if text_cursor.positionInBlock() < text_cursor.block().length() - 1:
            text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)

        if unicode(text_cursor.selectedText()).strip() == "":
            return True

        return False

    def selectWordUnderCursor(self, text_cursor):
        if not self.isCursorInSpace():
            # text_cursor.select(QTextCursor.WordUnderCursor)

            # pattern = "[\s|\v|\f|\n|\r|\t|\xe2\x80\xa8|\xe2\x80\xa9]"
            # pattern = "[\\s|\\xe2\\x80\\xa9|\\xe2\\x80\\xa8]"

            block_start = 0
            block_end = text_cursor.block().length()

            cursor_pos = text_cursor.positionInBlock()
            pos = cursor_pos
            pattern = u"[\s\u2029\u2028]"
            while pos >= block_start:
                text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor)
                text = text_cursor.selectedText()
                if re.search(pattern, text):
                    break
                pos -= 1

            text_cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor)

            while pos < block_end:
                text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
                text = text_cursor.selectedText()
                if re.search(pattern, text):
                    break
                pos += 1

            text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor)

            # pattern = "[\\s]"
            # start = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindBackward | QTextDocument.FindCaseSensitively)
            # end = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindCaseSensitively)
            # block_end_pos = text_cursor.block().position() + text_cursor.block().length()
            #
            # text_cursor.setPosition(start.position(), QTextCursor.MoveAnchor)
            # # text_cursor.setPosition(min(block_end_pos, end.position() - 1), QTextCursor.KeepAnchor)
            # text_cursor.setPosition(end.position() - 1, QTextCursor.KeepAnchor)


    def deleteLine(self):
        text_cursor = self.textCursor()
        text_cursor.beginEditBlock()
        text_cursor.select(QTextCursor.LineUnderCursor)
        text_cursor.removeSelectedText()
        text_cursor.deletePreviousChar()

        text_cursor.movePosition(QTextCursor.NextBlock)
        text_cursor.movePosition(QTextCursor.StartOfLine)
        self.setTextCursor(text_cursor)
        text_cursor.endEditBlock()

    def duplicateLine(self):
        text_cursor = self.textCursor()
        text_cursor.beginEditBlock()
        text_cursor.select(QTextCursor.LineUnderCursor)
        text = text_cursor.selectedText()
        text_cursor.movePosition(QTextCursor.EndOfLine)
        text_cursor.insertBlock()
        text_cursor.insertText(text)
        text_cursor.endEditBlock()


    def selectFragment(self):
        text_cursor = self.textCursor()
        self.selectWordUnderCursor(text_cursor)
        self.setTextCursor(text_cursor)