src/eric7/EricWidgets/EricSpellCheckedTextEdit.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
13 try: 13 try:
14 import enchant 14 import enchant
15 import enchant.tokenize 15 import enchant.tokenize
16 from enchant.errors import TokenizerNotFoundError, DictNotFoundError 16 from enchant.errors import TokenizerNotFoundError, DictNotFoundError
17 from enchant.utils import trim_suggestions 17 from enchant.utils import trim_suggestions
18
18 ENCHANT_AVAILABLE = True 19 ENCHANT_AVAILABLE = True
19 except ImportError: 20 except ImportError:
20 ENCHANT_AVAILABLE = False 21 ENCHANT_AVAILABLE = False
21 22
22 from PyQt6.QtCore import pyqtSlot, Qt, QCoreApplication 23 from PyQt6.QtCore import pyqtSlot, Qt, QCoreApplication
23 from PyQt6.QtGui import ( 24 from PyQt6.QtGui import (
24 QAction, QActionGroup, QSyntaxHighlighter, QTextBlockUserData, 25 QAction,
25 QTextCharFormat, QTextCursor 26 QActionGroup,
27 QSyntaxHighlighter,
28 QTextBlockUserData,
29 QTextCharFormat,
30 QTextCursor,
26 ) 31 )
27 from PyQt6.QtWidgets import QMenu, QTextEdit, QPlainTextEdit 32 from PyQt6.QtWidgets import QMenu, QTextEdit, QPlainTextEdit
28 33
29 if ENCHANT_AVAILABLE: 34 if ENCHANT_AVAILABLE:
30 class SpellCheckMixin(): 35
36 class SpellCheckMixin:
31 """ 37 """
32 Class implementing the spell-check mixin for the widget classes. 38 Class implementing the spell-check mixin for the widget classes.
33 """ 39 """
40
34 # don't show more than this to keep the menu manageable 41 # don't show more than this to keep the menu manageable
35 MaxSuggestions = 20 42 MaxSuggestions = 20
36 43
37 # default language to be used when no other is set 44 # default language to be used when no other is set
38 DefaultLanguage = None 45 DefaultLanguage = None
39 46
40 # default user lists 47 # default user lists
41 DefaultUserWordList = None 48 DefaultUserWordList = None
42 DefaultUserExceptionList = None 49 DefaultUserExceptionList = None
43 50
44 def __init__(self): 51 def __init__(self):
45 """ 52 """
46 Constructor 53 Constructor
47 """ 54 """
48 self.__highlighter = EnchantHighlighter(self.document()) 55 self.__highlighter = EnchantHighlighter(self.document())
50 # Start with a default dictionary based on the current locale 57 # Start with a default dictionary based on the current locale
51 # or the configured default language. 58 # or the configured default language.
52 spellDict = enchant.DictWithPWL( 59 spellDict = enchant.DictWithPWL(
53 SpellCheckMixin.DefaultLanguage, 60 SpellCheckMixin.DefaultLanguage,
54 SpellCheckMixin.DefaultUserWordList, 61 SpellCheckMixin.DefaultUserWordList,
55 SpellCheckMixin.DefaultUserExceptionList 62 SpellCheckMixin.DefaultUserExceptionList,
56 ) 63 )
57 except DictNotFoundError: 64 except DictNotFoundError:
58 try: 65 try:
59 # Use English dictionary if no locale dictionary is 66 # Use English dictionary if no locale dictionary is
60 # available or the default one could not be found. 67 # available or the default one could not be found.
61 spellDict = enchant.DictWithPWL( 68 spellDict = enchant.DictWithPWL(
62 "en", 69 "en",
63 SpellCheckMixin.DefaultUserWordList, 70 SpellCheckMixin.DefaultUserWordList,
64 SpellCheckMixin.DefaultUserExceptionList 71 SpellCheckMixin.DefaultUserExceptionList,
65 ) 72 )
66 except DictNotFoundError: 73 except DictNotFoundError:
67 # Still no dictionary could be found. Forget about spell 74 # Still no dictionary could be found. Forget about spell
68 # checking. 75 # checking.
69 spellDict = None 76 spellDict = None
70 77
71 self.__highlighter.setDict(spellDict) 78 self.__highlighter.setDict(spellDict)
72 79
73 def contextMenuEvent(self, evt): 80 def contextMenuEvent(self, evt):
74 """ 81 """
75 Protected method to handle context menu events to add a spelling 82 Protected method to handle context menu events to add a spelling
76 suggestions submenu. 83 suggestions submenu.
77 84
78 @param evt reference to the context menu event 85 @param evt reference to the context menu event
79 @type QContextMenuEvent 86 @type QContextMenuEvent
80 """ 87 """
81 menu = self.__createSpellcheckContextMenu(evt.pos()) 88 menu = self.__createSpellcheckContextMenu(evt.pos())
82 menu.exec(evt.globalPos()) 89 menu.exec(evt.globalPos())
83 90
84 def __createSpellcheckContextMenu(self, pos): 91 def __createSpellcheckContextMenu(self, pos):
85 """ 92 """
86 Private method to create the spell-check context menu. 93 Private method to create the spell-check context menu.
87 94
88 @param pos position of the mouse pointer 95 @param pos position of the mouse pointer
89 @type QPoint 96 @type QPoint
90 @return context menu with additional spell-check entries 97 @return context menu with additional spell-check entries
91 @rtype QMenu 98 @rtype QMenu
92 """ 99 """
93 menu = self.createStandardContextMenu(pos) 100 menu = self.createStandardContextMenu(pos)
94 101
95 # Add a submenu for setting the spell-check language and 102 # Add a submenu for setting the spell-check language and
96 # document format. 103 # document format.
97 menu.addSeparator() 104 menu.addSeparator()
98 self.__addRemoveEntry(self.__cursorForPosition(pos), menu) 105 self.__addRemoveEntry(self.__cursorForPosition(pos), menu)
99 menu.addMenu(self.__createLanguagesMenu(menu)) 106 menu.addMenu(self.__createLanguagesMenu(menu))
100 menu.addMenu(self.__createFormatsMenu(menu)) 107 menu.addMenu(self.__createFormatsMenu(menu))
101 108
102 # Try to retrieve a menu of corrections for the right-clicked word 109 # Try to retrieve a menu of corrections for the right-clicked word
103 spellMenu = self.__createCorrectionsMenu( 110 spellMenu = self.__createCorrectionsMenu(
104 self.__cursorForMisspelling(pos), menu) 111 self.__cursorForMisspelling(pos), menu
105 112 )
113
106 if spellMenu: 114 if spellMenu:
107 menu.insertSeparator(menu.actions()[0]) 115 menu.insertSeparator(menu.actions()[0])
108 menu.insertMenu(menu.actions()[0], spellMenu) 116 menu.insertMenu(menu.actions()[0], spellMenu)
109 117
110 return menu 118 return menu
111 119
112 def __createCorrectionsMenu(self, cursor, parent=None): 120 def __createCorrectionsMenu(self, cursor, parent=None):
113 """ 121 """
114 Private method to create a menu for corrections of the selected 122 Private method to create a menu for corrections of the selected
115 word. 123 word.
116 124
117 @param cursor reference to the text cursor 125 @param cursor reference to the text cursor
118 @type QTextCursor 126 @type QTextCursor
119 @param parent reference to the parent widget (defaults to None) 127 @param parent reference to the parent widget (defaults to None)
120 @type QWidget (optional) 128 @type QWidget (optional)
121 @return menu with corrections 129 @return menu with corrections
122 @rtype QMenu 130 @rtype QMenu
123 """ 131 """
124 if cursor is None: 132 if cursor is None:
125 return None 133 return None
126 134
127 text = cursor.selectedText() 135 text = cursor.selectedText()
128 suggestions = trim_suggestions( 136 suggestions = trim_suggestions(
129 text, self.__highlighter.dict().suggest(text), 137 text,
130 SpellCheckMixin.MaxSuggestions) 138 self.__highlighter.dict().suggest(text),
131 139 SpellCheckMixin.MaxSuggestions,
140 )
141
132 spellMenu = QMenu( 142 spellMenu = QMenu(
133 QCoreApplication.translate("SpellCheckMixin", 143 QCoreApplication.translate("SpellCheckMixin", "Spelling Suggestions"),
134 "Spelling Suggestions"), 144 parent,
135 parent) 145 )
136 for word in suggestions: 146 for word in suggestions:
137 act = spellMenu.addAction(word) 147 act = spellMenu.addAction(word)
138 act.setData((cursor, word)) 148 act.setData((cursor, word))
139 149
140 if suggestions: 150 if suggestions:
141 spellMenu.addSeparator() 151 spellMenu.addSeparator()
142 152
143 # add management entry 153 # add management entry
144 act = spellMenu.addAction(QCoreApplication.translate( 154 act = spellMenu.addAction(
145 "SpellCheckMixin", "Add to Dictionary")) 155 QCoreApplication.translate("SpellCheckMixin", "Add to Dictionary")
156 )
146 act.setData((cursor, text, "add")) 157 act.setData((cursor, text, "add"))
147 158
148 spellMenu.triggered.connect(self.__spellMenuTriggered) 159 spellMenu.triggered.connect(self.__spellMenuTriggered)
149 return spellMenu 160 return spellMenu
150 161
151 def __addRemoveEntry(self, cursor, menu): 162 def __addRemoveEntry(self, cursor, menu):
152 """ 163 """
153 Private method to create a menu entry to remove the word at the 164 Private method to create a menu entry to remove the word at the
154 menu position. 165 menu position.
155 166
156 @param cursor reference to the text cursor for the misspelled word 167 @param cursor reference to the text cursor for the misspelled word
157 @type QTextCursor 168 @type QTextCursor
158 @param menu reference to the context menu 169 @param menu reference to the context menu
159 @type QMenu 170 @type QMenu
160 """ 171 """
161 if cursor is None: 172 if cursor is None:
162 return 173 return
163 174
164 text = cursor.selectedText() 175 text = cursor.selectedText()
165 menu.addAction(QCoreApplication.translate( 176 menu.addAction(
166 "SpellCheckMixin", 177 QCoreApplication.translate(
167 "Remove '{0}' from Dictionary").format(text), 178 "SpellCheckMixin", "Remove '{0}' from Dictionary"
168 lambda: self.__addToUserDict(text, "remove")) 179 ).format(text),
169 180 lambda: self.__addToUserDict(text, "remove"),
181 )
182
170 def __createLanguagesMenu(self, parent=None): 183 def __createLanguagesMenu(self, parent=None):
171 """ 184 """
172 Private method to create a menu for selecting the spell-check 185 Private method to create a menu for selecting the spell-check
173 language. 186 language.
174 187
175 @param parent reference to the parent widget (defaults to None) 188 @param parent reference to the parent widget (defaults to None)
176 @type QWidget (optional) 189 @type QWidget (optional)
177 @return menu with spell-check languages 190 @return menu with spell-check languages
178 @rtype QMenu 191 @rtype QMenu
179 """ 192 """
180 curLanguage = self.__highlighter.dict().tag.lower() 193 curLanguage = self.__highlighter.dict().tag.lower()
181 languageMenu = QMenu( 194 languageMenu = QMenu(
182 QCoreApplication.translate("SpellCheckMixin", "Language"), 195 QCoreApplication.translate("SpellCheckMixin", "Language"), parent
183 parent) 196 )
184 languageActions = QActionGroup(languageMenu) 197 languageActions = QActionGroup(languageMenu)
185 198
186 for language in sorted(enchant.list_languages()): 199 for language in sorted(enchant.list_languages()):
187 act = QAction(language, languageActions) 200 act = QAction(language, languageActions)
188 act.setCheckable(True) 201 act.setCheckable(True)
189 act.setChecked(language.lower() == curLanguage) 202 act.setChecked(language.lower() == curLanguage)
190 act.setData(language) 203 act.setData(language)
191 languageMenu.addAction(act) 204 languageMenu.addAction(act)
192 205
193 languageMenu.triggered.connect(self.__setLanguage) 206 languageMenu.triggered.connect(self.__setLanguage)
194 return languageMenu 207 return languageMenu
195 208
196 def __createFormatsMenu(self, parent=None): 209 def __createFormatsMenu(self, parent=None):
197 """ 210 """
198 Private method to create a menu for selecting the document format. 211 Private method to create a menu for selecting the document format.
199 212
200 @param parent reference to the parent widget (defaults to None) 213 @param parent reference to the parent widget (defaults to None)
201 @type QWidget (optional) 214 @type QWidget (optional)
202 @return menu with document formats 215 @return menu with document formats
203 @rtype QMenu 216 @rtype QMenu
204 """ 217 """
205 formatMenu = QMenu( 218 formatMenu = QMenu(
206 QCoreApplication.translate("SpellCheckMixin", "Format"), 219 QCoreApplication.translate("SpellCheckMixin", "Format"), parent
207 parent) 220 )
208 formatActions = QActionGroup(formatMenu) 221 formatActions = QActionGroup(formatMenu)
209 222
210 curFormat = self.__highlighter.chunkers() 223 curFormat = self.__highlighter.chunkers()
211 for name, chunkers in ( 224 for name, chunkers in (
212 (QCoreApplication.translate("SpellCheckMixin", "Text"), 225 (QCoreApplication.translate("SpellCheckMixin", "Text"), []),
213 []), 226 (
214 (QCoreApplication.translate("SpellCheckMixin", "HTML"), 227 QCoreApplication.translate("SpellCheckMixin", "HTML"),
215 [enchant.tokenize.HTMLChunker]) 228 [enchant.tokenize.HTMLChunker],
229 ),
216 ): 230 ):
217 act = QAction(name, formatActions) 231 act = QAction(name, formatActions)
218 act.setCheckable(True) 232 act.setCheckable(True)
219 act.setChecked(chunkers == curFormat) 233 act.setChecked(chunkers == curFormat)
220 act.setData(chunkers) 234 act.setData(chunkers)
221 formatMenu.addAction(act) 235 formatMenu.addAction(act)
222 236
223 formatMenu.triggered.connect(self.__setFormat) 237 formatMenu.triggered.connect(self.__setFormat)
224 return formatMenu 238 return formatMenu
225 239
226 def __cursorForPosition(self, pos): 240 def __cursorForPosition(self, pos):
227 """ 241 """
228 Private method to create a text cursor selecting the word at the 242 Private method to create a text cursor selecting the word at the
229 given position. 243 given position.
230 244
231 @param pos position of the misspelled word 245 @param pos position of the misspelled word
232 @type QPoint 246 @type QPoint
233 @return text cursor for the word 247 @return text cursor for the word
234 @rtype QTextCursor 248 @rtype QTextCursor
235 """ 249 """
236 cursor = self.cursorForPosition(pos) 250 cursor = self.cursorForPosition(pos)
237 cursor.select(QTextCursor.SelectionType.WordUnderCursor) 251 cursor.select(QTextCursor.SelectionType.WordUnderCursor)
238 252
239 if cursor.hasSelection(): 253 if cursor.hasSelection():
240 return cursor 254 return cursor
241 else: 255 else:
242 return None 256 return None
243 257
244 def __cursorForMisspelling(self, pos): 258 def __cursorForMisspelling(self, pos):
245 """ 259 """
246 Private method to create a text cursor selecting the misspelled 260 Private method to create a text cursor selecting the misspelled
247 word. 261 word.
248 262
249 @param pos position of the misspelled word 263 @param pos position of the misspelled word
250 @type QPoint 264 @type QPoint
251 @return text cursor for the misspelled word 265 @return text cursor for the misspelled word
252 @rtype QTextCursor 266 @rtype QTextCursor
253 """ 267 """
254 cursor = self.cursorForPosition(pos) 268 cursor = self.cursorForPosition(pos)
255 misspelledWords = getattr(cursor.block().userData(), 269 misspelledWords = getattr(cursor.block().userData(), "misspelled", [])
256 "misspelled", []) 270
257
258 # If the cursor is within a misspelling, select the word 271 # If the cursor is within a misspelling, select the word
259 for (start, end) in misspelledWords: 272 for (start, end) in misspelledWords:
260 if start <= cursor.positionInBlock() <= end: 273 if start <= cursor.positionInBlock() <= end:
261 blockPosition = cursor.block().position() 274 blockPosition = cursor.block().position()
262 275
263 cursor.setPosition(blockPosition + start, 276 cursor.setPosition(
264 QTextCursor.MoveMode.MoveAnchor) 277 blockPosition + start, QTextCursor.MoveMode.MoveAnchor
265 cursor.setPosition(blockPosition + end, 278 )
266 QTextCursor.MoveMode.KeepAnchor) 279 cursor.setPosition(
280 blockPosition + end, QTextCursor.MoveMode.KeepAnchor
281 )
267 break 282 break
268 283
269 if cursor.hasSelection(): 284 if cursor.hasSelection():
270 return cursor 285 return cursor
271 else: 286 else:
272 return None 287 return None
273 288
274 def __correctWord(self, cursor, word): 289 def __correctWord(self, cursor, word):
275 """ 290 """
276 Private method to replace some misspelled text. 291 Private method to replace some misspelled text.
277 292
278 @param cursor reference to the text cursor for the misspelled word 293 @param cursor reference to the text cursor for the misspelled word
279 @type QTextCursor 294 @type QTextCursor
280 @param word replacement text 295 @param word replacement text
281 @type str 296 @type str
282 """ 297 """
283 cursor.beginEditBlock() 298 cursor.beginEditBlock()
284 cursor.removeSelectedText() 299 cursor.removeSelectedText()
285 cursor.insertText(word) 300 cursor.insertText(word)
286 cursor.endEditBlock() 301 cursor.endEditBlock()
287 302
288 def __addToUserDict(self, word, command): 303 def __addToUserDict(self, word, command):
289 """ 304 """
290 Private method to add a word to the user word or exclude list. 305 Private method to add a word to the user word or exclude list.
291 306
292 @param word text to be added 307 @param word text to be added
293 @type str 308 @type str
294 @param command command indicating the user dictionary type 309 @param command command indicating the user dictionary type
295 @type str 310 @type str
296 """ 311 """
298 dictionary = self.__highlighter.dict() 313 dictionary = self.__highlighter.dict()
299 if command == "add": 314 if command == "add":
300 dictionary.add(word) 315 dictionary.add(word)
301 elif command == "remove": 316 elif command == "remove":
302 dictionary.remove(word) 317 dictionary.remove(word)
303 318
304 self.__highlighter.rehighlight() 319 self.__highlighter.rehighlight()
305 320
306 @pyqtSlot(QAction) 321 @pyqtSlot(QAction)
307 def __spellMenuTriggered(self, act): 322 def __spellMenuTriggered(self, act):
308 """ 323 """
309 Private slot to handle a selection of the spell menu. 324 Private slot to handle a selection of the spell menu.
310 325
311 @param act reference to the selected action 326 @param act reference to the selected action
312 @type QAction 327 @type QAction
313 """ 328 """
314 data = act.data() 329 data = act.data()
315 if len(data) == 2: 330 if len(data) == 2:
316 # replace the misspelled word 331 # replace the misspelled word
317 self.__correctWord(*data) 332 self.__correctWord(*data)
318 333
319 elif len(data) == 3: 334 elif len(data) == 3:
320 # dictionary management action 335 # dictionary management action
321 _, word, command = data 336 _, word, command = data
322 self.__addToUserDict(word, command) 337 self.__addToUserDict(word, command)
323 338
324 @pyqtSlot(QAction) 339 @pyqtSlot(QAction)
325 def __setLanguage(self, act): 340 def __setLanguage(self, act):
326 """ 341 """
327 Private slot to set the selected language. 342 Private slot to set the selected language.
328 343
329 @param act reference to the selected action 344 @param act reference to the selected action
330 @type QAction 345 @type QAction
331 """ 346 """
332 language = act.data() 347 language = act.data()
333 self.setLanguage(language) 348 self.setLanguage(language)
334 349
335 @pyqtSlot(QAction) 350 @pyqtSlot(QAction)
336 def __setFormat(self, act): 351 def __setFormat(self, act):
337 """ 352 """
338 Private slot to set the selected document format. 353 Private slot to set the selected document format.
339 354
340 @param act reference to the selected action 355 @param act reference to the selected action
341 @type QAction 356 @type QAction
342 """ 357 """
343 chunkers = act.data() 358 chunkers = act.data()
344 self.__highlighter.setChunkers(chunkers) 359 self.__highlighter.setChunkers(chunkers)
345 360
346 def setFormat(self, formatName): 361 def setFormat(self, formatName):
347 """ 362 """
348 Public method to set the document format. 363 Public method to set the document format.
349 364
350 @param formatName name of the document format 365 @param formatName name of the document format
351 @type str 366 @type str
352 """ 367 """
353 self.__highlighter.setChunkers( 368 self.__highlighter.setChunkers(
354 [enchant.tokenize.HTMLChunker] 369 [enchant.tokenize.HTMLChunker] if format == "html" else []
355 if format == "html" else
356 []
357 ) 370 )
358 371
359 def dict(self): 372 def dict(self):
360 """ 373 """
361 Public method to get a reference to the dictionary in use. 374 Public method to get a reference to the dictionary in use.
362 375
363 @return reference to the current dictionary 376 @return reference to the current dictionary
364 @rtype enchant.Dict 377 @rtype enchant.Dict
365 """ 378 """
366 return self.__highlighter.dict() 379 return self.__highlighter.dict()
367 380
368 def setDict(self, spellDict): 381 def setDict(self, spellDict):
369 """ 382 """
370 Public method to set the dictionary to be used. 383 Public method to set the dictionary to be used.
371 384
372 @param spellDict reference to the spell-check dictionary 385 @param spellDict reference to the spell-check dictionary
373 @type emchant.Dict 386 @type emchant.Dict
374 """ 387 """
375 self.__highlighter.setDict(spellDict) 388 self.__highlighter.setDict(spellDict)
376 389
377 @pyqtSlot(str) 390 @pyqtSlot(str)
378 def setLanguage(self, language): 391 def setLanguage(self, language):
379 """ 392 """
380 Public slot to set the spellchecker language. 393 Public slot to set the spellchecker language.
381 394
382 @param language language to be set 395 @param language language to be set
383 @type str 396 @type str
384 """ 397 """
385 epwl = self.dict().pwl 398 epwl = self.dict().pwl
386 pwl = ( 399 pwl = epwl.provider.file if isinstance(epwl, enchant.Dict) else None
387 epwl.provider.file 400
388 if isinstance(epwl, enchant.Dict) else
389 None
390 )
391
392 epel = self.dict().pel 401 epel = self.dict().pel
393 pel = ( 402 pel = epel.provider.file if isinstance(epel, enchant.Dict) else None
394 epel.provider.file
395 if isinstance(epel, enchant.Dict) else
396 None
397 )
398 self.setLanguageWithPWL(language, pwl, pel) 403 self.setLanguageWithPWL(language, pwl, pel)
399 404
400 @pyqtSlot(str, str, str) 405 @pyqtSlot(str, str, str)
401 def setLanguageWithPWL(self, language, pwl, pel): 406 def setLanguageWithPWL(self, language, pwl, pel):
402 """ 407 """
403 Public slot to set the spellchecker language and associated user 408 Public slot to set the spellchecker language and associated user
404 word lists. 409 word lists.
405 410
406 @param language language to be set 411 @param language language to be set
407 @type str 412 @type str
408 @param pwl file name of the personal word list 413 @param pwl file name of the personal word list
409 @type str 414 @type str
410 @param pel file name of the personal exclude list 415 @param pel file name of the personal exclude list
420 except DictNotFoundError: 425 except DictNotFoundError:
421 # Still no dictionary could be found. Forget about spell 426 # Still no dictionary could be found. Forget about spell
422 # checking. 427 # checking.
423 spellDict = None 428 spellDict = None
424 self.__highlighter.setDict(spellDict) 429 self.__highlighter.setDict(spellDict)
425 430
426 @classmethod 431 @classmethod
427 def setDefaultLanguage(cls, language, pwl=None, pel=None): 432 def setDefaultLanguage(cls, language, pwl=None, pel=None):
428 """ 433 """
429 Class method to set the default spell-check language. 434 Class method to set the default spell-check language.
430 435
431 @param language language to be set as default 436 @param language language to be set as default
432 @type str 437 @type str
433 @param pwl file name of the personal word list 438 @param pwl file name of the personal word list
434 @type str 439 @type str
435 @param pel file name of the personal exclude list 440 @param pel file name of the personal exclude list
436 @type str 441 @type str
437 """ 442 """
438 with contextlib.suppress(DictNotFoundError): 443 with contextlib.suppress(DictNotFoundError):
439 cls.DefaultUserWordList = pwl 444 cls.DefaultUserWordList = pwl
440 cls.DefaultUserExceptionList = pel 445 cls.DefaultUserExceptionList = pel
441 446
442 # set default language only, if a dictionary is available 447 # set default language only, if a dictionary is available
443 enchant.Dict(language) 448 enchant.Dict(language)
444 cls.DefaultLanguage = language 449 cls.DefaultLanguage = language
445 450
446 class EnchantHighlighter(QSyntaxHighlighter): 451 class EnchantHighlighter(QSyntaxHighlighter):
447 """ 452 """
448 Class implementing a QSyntaxHighlighter subclass that consults a 453 Class implementing a QSyntaxHighlighter subclass that consults a
449 pyEnchant dictionary to highlight misspelled words. 454 pyEnchant dictionary to highlight misspelled words.
450 """ 455 """
451 TokenFilters = (enchant.tokenize.EmailFilter, 456
452 enchant.tokenize.URLFilter) 457 TokenFilters = (enchant.tokenize.EmailFilter, enchant.tokenize.URLFilter)
453 458
454 # Define the spell-check style once and just assign it as necessary 459 # Define the spell-check style once and just assign it as necessary
455 ErrorFormat = QTextCharFormat() 460 ErrorFormat = QTextCharFormat()
456 ErrorFormat.setUnderlineColor(Qt.GlobalColor.red) 461 ErrorFormat.setUnderlineColor(Qt.GlobalColor.red)
457 ErrorFormat.setUnderlineStyle( 462 ErrorFormat.setUnderlineStyle(
458 QTextCharFormat.UnderlineStyle.SpellCheckUnderline) 463 QTextCharFormat.UnderlineStyle.SpellCheckUnderline
459 464 )
465
460 def __init__(self, *args): 466 def __init__(self, *args):
461 """ 467 """
462 Constructor 468 Constructor
463 469
464 @param *args list of arguments for the QSyntaxHighlighter 470 @param *args list of arguments for the QSyntaxHighlighter
465 @type list 471 @type list
466 """ 472 """
467 QSyntaxHighlighter.__init__(self, *args) 473 QSyntaxHighlighter.__init__(self, *args)
468 474
469 self.__spellDict = None 475 self.__spellDict = None
470 self.__tokenizer = None 476 self.__tokenizer = None
471 self.__chunkers = [] 477 self.__chunkers = []
472 478
473 def chunkers(self): 479 def chunkers(self):
474 """ 480 """
475 Public method to get the chunkers in use. 481 Public method to get the chunkers in use.
476 482
477 @return list of chunkers in use 483 @return list of chunkers in use
478 @rtype list 484 @rtype list
479 """ 485 """
480 return self.__chunkers 486 return self.__chunkers
481 487
482 def setChunkers(self, chunkers): 488 def setChunkers(self, chunkers):
483 """ 489 """
484 Public method to set the chunkers to be used. 490 Public method to set the chunkers to be used.
485 491
486 @param chunkers chunkers to be used 492 @param chunkers chunkers to be used
487 @type list 493 @type list
488 """ 494 """
489 self.__chunkers = chunkers 495 self.__chunkers = chunkers
490 self.setDict(self.dict()) 496 self.setDict(self.dict())
491 497
492 def dict(self): 498 def dict(self):
493 """ 499 """
494 Public method to get the spelling dictionary in use. 500 Public method to get the spelling dictionary in use.
495 501
496 @return spelling dictionary 502 @return spelling dictionary
497 @rtype enchant.Dict 503 @rtype enchant.Dict
498 """ 504 """
499 return self.__spellDict 505 return self.__spellDict
500 506
501 def setDict(self, spellDict): 507 def setDict(self, spellDict):
502 """ 508 """
503 Public method to set the spelling dictionary to be used. 509 Public method to set the spelling dictionary to be used.
504 510
505 @param spellDict spelling dictionary 511 @param spellDict spelling dictionary
506 @type enchant.Dict 512 @type enchant.Dict
507 """ 513 """
508 if spellDict: 514 if spellDict:
509 try: 515 try:
510 self.__tokenizer = enchant.tokenize.get_tokenizer( 516 self.__tokenizer = enchant.tokenize.get_tokenizer(
511 spellDict.tag, 517 spellDict.tag,
512 chunkers=self.__chunkers, 518 chunkers=self.__chunkers,
513 filters=EnchantHighlighter.TokenFilters) 519 filters=EnchantHighlighter.TokenFilters,
520 )
514 except TokenizerNotFoundError: 521 except TokenizerNotFoundError:
515 # Fall back to the "good for most euro languages" 522 # Fall back to the "good for most euro languages"
516 # English tokenizer 523 # English tokenizer
517 self.__tokenizer = enchant.tokenize.get_tokenizer( 524 self.__tokenizer = enchant.tokenize.get_tokenizer(
518 chunkers=self.__chunkers, 525 chunkers=self.__chunkers,
519 filters=EnchantHighlighter.TokenFilters) 526 filters=EnchantHighlighter.TokenFilters,
527 )
520 else: 528 else:
521 self.__tokenizer = None 529 self.__tokenizer = None
522 530
523 self.__spellDict = spellDict 531 self.__spellDict = spellDict
524 532
525 self.rehighlight() 533 self.rehighlight()
526 534
527 def highlightBlock(self, text): 535 def highlightBlock(self, text):
528 """ 536 """
529 Public method to apply the text highlight. 537 Public method to apply the text highlight.
530 538
531 @param text text to be spell-checked 539 @param text text to be spell-checked
532 @type str 540 @type str
533 """ 541 """
534 """Overridden QSyntaxHighlighter method to apply the highlight""" 542 """Overridden QSyntaxHighlighter method to apply the highlight"""
535 if self.__spellDict is None or self.__tokenizer is None: 543 if self.__spellDict is None or self.__tokenizer is None:
536 return 544 return
537 545
538 # Build a list of all misspelled words and highlight them 546 # Build a list of all misspelled words and highlight them
539 misspellings = [] 547 misspellings = []
540 for (word, pos) in self.__tokenizer(text): 548 for (word, pos) in self.__tokenizer(text):
541 if not self.__spellDict.check(word): 549 if not self.__spellDict.check(word):
542 self.setFormat(pos, len(word), 550 self.setFormat(pos, len(word), EnchantHighlighter.ErrorFormat)
543 EnchantHighlighter.ErrorFormat)
544 misspellings.append((pos, pos + len(word))) 551 misspellings.append((pos, pos + len(word)))
545 552
546 # Store the list so the context menu can reuse this tokenization 553 # Store the list so the context menu can reuse this tokenization
547 # pass (Block-relative values so editing other blocks won't 554 # pass (Block-relative values so editing other blocks won't
548 # invalidate them) 555 # invalidate them)
549 data = QTextBlockUserData() 556 data = QTextBlockUserData()
550 data.misspelled = misspellings 557 data.misspelled = misspellings
551 self.setCurrentBlockUserData(data) 558 self.setCurrentBlockUserData(data)
552 559
553 else: 560 else:
554 561
555 class SpellCheckMixin(): 562 class SpellCheckMixin:
556 """ 563 """
557 Class implementing the spell-check mixin for the widget classes. 564 Class implementing the spell-check mixin for the widget classes.
558 """ 565 """
566
559 # 567 #
560 # This is just a stub to provide the same API as the enchant enabled 568 # This is just a stub to provide the same API as the enchant enabled
561 # one. 569 # one.
562 # 570 #
563 def __init__(self): 571 def __init__(self):
564 """ 572 """
565 Constructor 573 Constructor
566 """ 574 """
567 pass 575 pass
568 576
569 def setFormat(self, formatName): 577 def setFormat(self, formatName):
570 """ 578 """
571 Public method to set the document format. 579 Public method to set the document format.
572 580
573 @param formatName name of the document format 581 @param formatName name of the document format
574 @type str 582 @type str
575 """ 583 """
576 pass 584 pass
577 585
578 def dict(self): 586 def dict(self):
579 """ 587 """
580 Public method to get a reference to the dictionary in use. 588 Public method to get a reference to the dictionary in use.
581 589
582 @return reference to the current dictionary 590 @return reference to the current dictionary
583 @rtype enchant.Dict 591 @rtype enchant.Dict
584 """ 592 """
585 pass 593 pass
586 594
587 def setDict(self, spellDict): 595 def setDict(self, spellDict):
588 """ 596 """
589 Public method to set the dictionary to be used. 597 Public method to set the dictionary to be used.
590 598
591 @param spellDict reference to the spell-check dictionary 599 @param spellDict reference to the spell-check dictionary
592 @type emchant.Dict 600 @type emchant.Dict
593 """ 601 """
594 pass 602 pass
595 603
596 @pyqtSlot(str) 604 @pyqtSlot(str)
597 def setLanguage(self, language): 605 def setLanguage(self, language):
598 """ 606 """
599 Public slot to set the spellchecker language. 607 Public slot to set the spellchecker language.
600 608
601 @param language language to be set 609 @param language language to be set
602 @type str 610 @type str
603 """ 611 """
604 pass 612 pass
605 613
606 @pyqtSlot(str, str, str) 614 @pyqtSlot(str, str, str)
607 def setLanguageWithPWL(self, language, pwl, pel): 615 def setLanguageWithPWL(self, language, pwl, pel):
608 """ 616 """
609 Public slot to set the spellchecker language and associated user 617 Public slot to set the spellchecker language and associated user
610 word lists. 618 word lists.
611 619
612 @param language language to be set 620 @param language language to be set
613 @type str 621 @type str
614 @param pwl file name of the personal word list 622 @param pwl file name of the personal word list
615 @type str 623 @type str
616 @param pel file name of the personal exclude list 624 @param pel file name of the personal exclude list
617 @type str 625 @type str
618 """ 626 """
619 pass 627 pass
620 628
621 @classmethod 629 @classmethod
622 def setDefaultLanguage(cls, language, pwl=None, pel=None): 630 def setDefaultLanguage(cls, language, pwl=None, pel=None):
623 """ 631 """
624 Class method to set the default spell-check language. 632 Class method to set the default spell-check language.
625 633
626 @param language language to be set as default 634 @param language language to be set as default
627 @type str 635 @type str
628 @param pwl file name of the personal word list 636 @param pwl file name of the personal word list
629 @type str 637 @type str
630 @param pel file name of the personal exclude list 638 @param pel file name of the personal exclude list
635 643
636 class EricSpellCheckedPlainTextEdit(QPlainTextEdit, SpellCheckMixin): 644 class EricSpellCheckedPlainTextEdit(QPlainTextEdit, SpellCheckMixin):
637 """ 645 """
638 Class implementing a QPlainTextEdit with built-in spell checker. 646 Class implementing a QPlainTextEdit with built-in spell checker.
639 """ 647 """
648
640 def __init__(self, *args): 649 def __init__(self, *args):
641 """ 650 """
642 Constructor 651 Constructor
643 652
644 @param *args list of arguments for the QPlainTextEdit constructor. 653 @param *args list of arguments for the QPlainTextEdit constructor.
645 @type list 654 @type list
646 """ 655 """
647 QPlainTextEdit.__init__(self, *args) 656 QPlainTextEdit.__init__(self, *args)
648 SpellCheckMixin.__init__(self) 657 SpellCheckMixin.__init__(self)
650 659
651 class EricSpellCheckedTextEdit(QTextEdit, SpellCheckMixin): 660 class EricSpellCheckedTextEdit(QTextEdit, SpellCheckMixin):
652 """ 661 """
653 Class implementing a QTextEdit with built-in spell checker. 662 Class implementing a QTextEdit with built-in spell checker.
654 """ 663 """
664
655 def __init__(self, *args): 665 def __init__(self, *args):
656 """ 666 """
657 Constructor 667 Constructor
658 668
659 @param *args list of arguments for the QPlainTextEdit constructor. 669 @param *args list of arguments for the QPlainTextEdit constructor.
660 @type list 670 @type list
661 """ 671 """
662 QTextEdit.__init__(self, *args) 672 QTextEdit.__init__(self, *args)
663 SpellCheckMixin.__init__(self) 673 SpellCheckMixin.__init__(self)
664 674
665 self.setFormat("html") 675 self.setFormat("html")
666 676
667 def setAcceptRichText(self, accept): 677 def setAcceptRichText(self, accept):
668 """ 678 """
669 Public method to set the text edit mode. 679 Public method to set the text edit mode.
670 680
671 @param accept flag indicating to accept rich text 681 @param accept flag indicating to accept rich text
672 @type bool 682 @type bool
673 """ 683 """
674 QTextEdit.setAcceptRichText(self, accept) 684 QTextEdit.setAcceptRichText(self, accept)
675 self.setFormat("html" if accept else "text") 685 self.setFormat("html" if accept else "text")
676 686
677 if __name__ == '__main__': 687
688 if __name__ == "__main__":
678 import sys 689 import sys
679 import os 690 import os
680 from PyQt6.QtWidgets import QApplication 691 from PyQt6.QtWidgets import QApplication
681 692
682 if ENCHANT_AVAILABLE: 693 if ENCHANT_AVAILABLE:
683 dictPath = os.path.expanduser(os.path.join("~", ".eric7", "spelling")) 694 dictPath = os.path.expanduser(os.path.join("~", ".eric7", "spelling"))
684 SpellCheckMixin.setDefaultLanguage( 695 SpellCheckMixin.setDefaultLanguage(
685 "en_US", 696 "en_US",
686 os.path.join(dictPath, "pwl.dic"), 697 os.path.join(dictPath, "pwl.dic"),
687 os.path.join(dictPath, "pel.dic") 698 os.path.join(dictPath, "pel.dic"),
688 ) 699 )
689 700
690 app = QApplication(sys.argv) 701 app = QApplication(sys.argv)
691 spellEdit = EricSpellCheckedPlainTextEdit() 702 spellEdit = EricSpellCheckedPlainTextEdit()
692 spellEdit.show() 703 spellEdit.show()
693 704
694 sys.exit(app.exec()) 705 sys.exit(app.exec())

eric ide

mercurial