I am writing a method, show_spelling_errors(), that loops through custom objects kept in a list self.tokens and uses properties of each object to change the color of some font in a Qt TextEdit widget. show_spelling_errors() is called by another method which is connected to the textChanged signal in Qt so it will run any time the user types into the widget. The method definition is shown below:
def show_spelling_errors(self):
try:
cursor = self.textEdit.textCursor()
incorrect_format = cursor.charFormat()
incorrect_format.setForeground(QtCore.Qt.GlobalColor.red)
for token in self.tokens:
if not token.is_spelled_correctly:
print("is spelled correctly: " + str(token.is_spelled_correctly))
cursor.movePosition(cursor.MoveOperation.Start, cursor.MoveMode.MoveAnchor)
cursor.movePosition(cursor.MoveOperation.NextWord, cursor.MoveMode.MoveAnchor, token.word_index)
cursor.movePosition(cursor.MoveOperation.EndOfWord, cursor.MoveMode.KeepAnchor)
print("selection start " + str(cursor.selectionStart()))
print("selection end " + str(cursor.selectionEnd()))
# cursor.setCharFormat(incorrect_format)
cursor.clearSelection()
If I run the code exactly as above, things work as expected. However, if I uncomment the penultimate line (which is what actually should change the font color), The loop no longer terminates but rather loops endlessly over the first member of self.tokens and then eventually causes a stack overflow. I'm very confused that the inclusion of this statement in the loop can cause the behavior of the loop (which I thought should be unrelated?) to change this way.
Edit: Below is the code needed to reproduce this behavior
from PyQt6.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout
from PyQt6 import QtCore
import sys
import re
import traceback
from spellchecker import SpellChecker
class Token:
def __init__(self, chars, spell, start=0, word_index=0):
self.word_index = word_index
self.content = chars
self.token_length = len(chars)
self.start_pos = start
self.end_pos = start + self.token_length
self.is_spelled_correctly = len(spell.unknown([chars])) < 1
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("QTextEdit")
self.resize(300, 270)
self.spell = SpellChecker()
self.textEdit = QTextEdit()
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
self.setLayout(layout)
self.tokens = None
self.textEdit.textChanged.connect(self.handle_text)
def handle_text(self):
self.tokenize_text()
self.show_spelling_errors()
def show_spelling_errors(self):
try:
cursor = self.textEdit.textCursor()
incorrect_format = cursor.charFormat()
incorrect_format.setForeground(QtCore.Qt.GlobalColor.red)
for token in self.tokens:
if not token.is_spelled_correctly:
print("is spelled correctly: " + str(token.is_spelled_correctly))
cursor.movePosition(cursor.MoveOperation.Start, cursor.MoveMode.MoveAnchor)
cursor.movePosition(cursor.MoveOperation.NextWord, cursor.MoveMode.MoveAnchor, token.word_index)
cursor.movePosition(cursor.MoveOperation.EndOfWord, cursor.MoveMode.KeepAnchor)
print("selection start " + str(cursor.selectionStart()))
print("selection end " + str(cursor.selectionEnd()))
cursor.setCharFormat(incorrect_format)
cursor.clearSelection()
except:
traceback.print_exc()
def tokenize_text(self):
try:
print("tokenizing...")
text = self.textEdit.toPlainText()
text_seps = re.findall(' .', text)
current_pos = 0
start_positions = [current_pos]
for sep in text_seps:
current_pos = text.find(sep, current_pos) + 1
start_positions.append(current_pos)
self.tokens = [
Token(string, self.spell, start, word_ind) for
word_ind, (start, string) in
enumerate(zip(start_positions, text.split()))
]
except:
traceback.print_exc()
app = QApplication([])
win = TextEditDemo()
win.show()
sys.exit(app.exec())