Search code examples
pythonqtexteditpyqt6qtextdocument

how to setup the QTextDocument.FindFlag to set the search mode to search across multiple lines in PyQt6


If the search text is spread across two lines, separated by '\n' in a QTextEdit widget, how to find it using the find() function, how to setup the QTextDocument.FindFlag to set the search mode to search across multiple lines.

from PyQt6.QtWidgets import QWidget, QApplication, QMainWindow, QTextEdit, QPushButton, QVBoxLayout
from PyQt6.QtGui import QTextCursor, QTextDocument
import sys

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.textEdit = QTextEdit()
        self.textEdit.setText('A\nB')

        self.pushButton = QPushButton("Find")
        self.pushButton.clicked.connect(self.findText)

        vbox = QVBoxLayout()
        vbox.addWidget(self.textEdit)
        vbox.addWidget(self.pushButton)

        self.setLayout(vbox)


    def findText(self):
        search_text = 'A\nB'

        # Set the QTextDocument.FindFlag to search across multiple lines
        search_flag = QTextDocument.FindFlag(0)
        search_flag |= QTextDocument.FindFlag(1)

        # Find the text using the QTextCursor
        cursor = self.textEdit.document().find(search_text, QTextCursor(), search_flag)

        if not cursor.isNull():
            self.textEdit.setTextCursor(cursor)
            self.textEdit.ensureCursorVisible()
        else:
            print("Could not find the search text")


def except_hook(cls, exception, traceback):
    sys.__excepthook__(cls, exception, traceback)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.excepthook = except_hook
    sys.exit(app.exec())

but the code finds nothing, please verify


Solution

  • It does not work because you are trying to search for a "new line" character, but that does not exist in the QTextDocument: when using setText(), the given text is interpreted as either plain text or HTML and converted in a QTextDocument structure.

    The new line character actually creates a new text block (a paragraph), similarly to the <p></p> tags used in HTML. In fact, if you try to print toHtml() of the text edit, you will see the following:

    <p style="...">A</p>
    <p style="...">B</p>
    

    When find() is called, it only searches within the text boundaries of each QTextBlock (including the overloaded functions that search within a single paragraph).

    The only way to do what you want to achieve is to find a pattern that includes the letter at the end of a paragraph, and this can only be done by using a regular expression. Once you've found a match, you then need to go to the next block and ensure that the first letter matches the rest of the search pattern.

    Note that the above is not the same when using a line break (<br>).

    In that case, QTextDocument uses the special character \u2028, which only creates a line break within the same text block. In that case, you can use a simple string search considering the above character.

    To sum up all this:

        def findText(self):
            doc = self.textEdit.document()
            cursor = doc.find('A\u2028B') # for <br>
            if cursor.isNull():
                while True:
                    cursor = doc.find(QRegExp('A$'), cursor)
                    if cursor.isNull() or cursor.atEnd():
                        return
                    postCursor = QTextCursor(cursor)
                    postCursor.movePosition(postCursor.MoveOperation.NextBlock)
                    postCursor.select(postCursor.SelectionType.LineUnderCursor)
                    if postCursor.selectedText().startswith('B'):
                        break
                cursor.setPosition(
                    postCursor.selectionEnd(), cursor.MoveMode.KeepAnchor)
    
            if not cursor.isNull():
                self.textEdit.setTextCursor(cursor)