2 # -*- coding: utf-8 -*-
5 2012, Peter Norvig (http://norvig.com/spell-correct.html)
6 2013, David Aguilar <davvid@gmail.com>
13 from PyQt4
.Qt
import QAction
14 from PyQt4
.Qt
import QApplication
15 from PyQt4
.Qt
import QEvent
16 from PyQt4
.Qt
import QMenu
17 from PyQt4
.Qt
import QMouseEvent
18 from PyQt4
.Qt
import QSyntaxHighlighter
19 from PyQt4
.Qt
import QTextCharFormat
20 from PyQt4
.Qt
import QTextCursor
21 from PyQt4
.Qt
import Qt
22 from PyQt4
.QtCore
import SIGNAL
24 from cola
.compat
import set
25 from cola
.i18n
import N_
26 from cola
.widgets
.text
import HintedTextEdit
29 alphabet
= 'abcdefghijklmnopqrstuvwxyz'
32 def train(features
, model
):
39 splits
= [(word
[:i
], word
[i
:]) for i
in range(len(word
) + 1)]
40 deletes
= [a
+ b
[1:] for a
, b
in splits
if b
]
41 transposes
= [a
+ b
[1] + b
[0] + b
[2:] for a
, b
in splits
if len(b
)>1]
42 replaces
= [a
+ c
+ b
[1:] for a
, b
in splits
for c
in alphabet
if b
]
43 inserts
= [a
+ c
+ b
for a
, b
in splits
for c
in alphabet
]
44 return set(deletes
+ transposes
+ replaces
+ inserts
)
47 def known_edits2(word
, words
):
48 return set(e2
for e1
in edits1(word
)
49 for e2
in edits1(e1
) if e2
in words
)
51 def known(word
, words
):
52 return set(w
for w
in word
if w
in words
)
55 def suggest(word
, words
):
56 candidates
= (known([word
], words
) or
57 known(edits1(word
), words
) or
58 known_edits2(word
, words
) or [word
])
62 def correct(word
, words
):
63 candidates
= suggest(word
, words
)
64 return max(candidates
, key
=words
.get
)
67 class NorvigSpellCheck(object):
69 self
.words
= collections
.defaultdict(lambda: 1)
70 self
.extra_words
= set()
71 self
.initialized
= False
76 self
.initialized
= True
77 train(self
.read(), self
.words
)
78 train(self
.extra_words
, self
.words
)
80 def add_word(self
, word
):
81 self
.extra_words
.add(word
)
83 def suggest(self
, word
):
85 return suggest(word
, self
.words
)
87 def check(self
, word
):
89 return word
.replace('.', '') in self
.words
92 for (path
, title
) in (('/usr/share/dict/words', True),
93 ('/usr/share/dict/propernames', False)):
95 with
open(path
, 'r') as f
:
99 yield word
.rstrip().title()
105 class SpellCheckTextEdit(HintedTextEdit
):
107 def __init__(self
, hint
, parent
=None):
108 HintedTextEdit
.__init
__(self
, hint
, parent
)
110 # Default dictionary based on the current locale.
111 self
.spellcheck
= NorvigSpellCheck()
112 self
.highlighter
= Highlighter(self
.document(), self
.spellcheck
)
114 def mousePressEvent(self
, event
):
115 if event
.button() == Qt
.RightButton
:
116 # Rewrite the mouse event to a left button event so the cursor is
117 # moved to the location of the pointer.
118 event
= QMouseEvent(QEvent
.MouseButtonPress
,
123 HintedTextEdit
.mousePressEvent(self
, event
)
125 def context_menu(self
):
126 popup_menu
= HintedTextEdit
.createStandardContextMenu(self
)
128 # Select the word under the cursor.
129 cursor
= self
.textCursor()
130 cursor
.select(QTextCursor
.WordUnderCursor
)
131 self
.setTextCursor(cursor
)
133 # Check if the selected word is misspelled and offer spelling
134 # suggestions if it is.
136 if self
.textCursor().hasSelection():
137 text
= unicode(self
.textCursor().selectedText())
138 if not self
.spellcheck
.check(text
):
139 spell_menu
= QMenu(N_('Spelling Suggestions'))
140 for word
in self
.spellcheck
.suggest(text
):
141 action
= SpellAction(word
, spell_menu
)
142 self
.connect(action
, SIGNAL('correct'), self
.correct
)
143 spell_menu
.addAction(action
)
144 # Only add the spelling suggests to the menu if there are
146 if len(spell_menu
.actions()) > 0:
147 popup_menu
.addSeparator()
148 popup_menu
.addMenu(spell_menu
)
150 return popup_menu
, spell_menu
152 def contextMenuEvent(self
, event
):
153 popup_menu
= self
.context_menu()
154 popup_menu
.exec_(self
.mapToGlobal(event
.pos()))
156 def correct(self
, word
):
157 """Replaces the selected text with word."""
158 cursor
= self
.textCursor()
159 cursor
.beginEditBlock()
161 cursor
.removeSelectedText()
162 cursor
.insertText(word
)
164 cursor
.endEditBlock()
167 class Highlighter(QSyntaxHighlighter
):
169 WORDS
= "(?iu)[\w']+"
171 def __init__(self
, doc
, spellcheck
):
172 QSyntaxHighlighter
.__init
__(self
, doc
)
173 self
.spellcheck
= spellcheck
176 def enable(self
, enabled
):
177 self
.enabled
= enabled
180 def highlightBlock(self
, text
):
184 fmt
= QTextCharFormat()
185 fmt
.setUnderlineColor(Qt
.red
)
186 fmt
.setUnderlineStyle(QTextCharFormat
.SpellCheckUnderline
)
188 for word_object
in re
.finditer(self
.WORDS
, text
):
189 if not self
.spellcheck
.check(word_object
.group()):
190 self
.setFormat(word_object
.start(),
191 word_object
.end() - word_object
.start(), fmt
)
194 class SpellAction(QAction
):
195 """QAction that returns the text in a signal.
198 def __init__(self
, *args
):
199 QAction
.__init
__(self
, *args
)
200 self
.connect(self
, SIGNAL('triggered()'), self
.correct
)
203 self
.emit(SIGNAL('correct'), unicode(self
.text()))
206 def main(args
=sys
.argv
):
207 app
= QApplication(args
)
209 widget
= SpellCheckTextEdit('Type here')
216 if __name__
== '__main__':