mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit Book: Add support for spell checking int he code view. Now spelling errors are highlighted in the code view for convenient correction as you type. This can be turned off via Preferences->Editor.
This commit is contained in:
parent
d723bbc253
commit
5b24a497cd
@ -157,12 +157,16 @@ def group_sort(locations):
|
||||
order[loc.file_name] = len(order)
|
||||
return sorted(locations, key=lambda l:(order[l.file_name], l.sourceline))
|
||||
|
||||
def get_all_words(container, book_locale):
|
||||
words = defaultdict(list)
|
||||
def get_checkable_file_names(container):
|
||||
file_names = [name for name, linear in container.spine_names] + [container.opf_name]
|
||||
toc = find_existing_toc(container)
|
||||
if toc is not None and container.exists(toc):
|
||||
file_names.append(toc)
|
||||
return file_names, toc
|
||||
|
||||
def get_all_words(container, book_locale):
|
||||
words = defaultdict(list)
|
||||
file_names, toc = get_checkable_file_names(container)
|
||||
for file_name in file_names:
|
||||
if not container.exists(file_name):
|
||||
continue
|
||||
|
@ -46,6 +46,7 @@ d['disable_completion_popup_for_search'] = False
|
||||
d['saved_searches'] = []
|
||||
d['insert_tag_mru'] = ['p', 'div', 'li', 'h1', 'h2', 'h3', 'h4', 'em', 'strong', 'td', 'tr']
|
||||
d['spell_check_case_sensitive_sort'] = False
|
||||
d['inline_spell_check'] = True
|
||||
|
||||
del d
|
||||
|
||||
|
@ -39,7 +39,7 @@ from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
|
||||
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
|
||||
from calibre.gui2.tweak_book.preferences import Preferences
|
||||
from calibre.gui2.tweak_book.search import validate_search_request, run_search
|
||||
from calibre.gui2.tweak_book.spell import find_next as find_next_word
|
||||
from calibre.gui2.tweak_book.spell import find_next as find_next_word, find_next_error
|
||||
from calibre.gui2.tweak_book.widgets import (
|
||||
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
|
||||
InsertSemantics, BusyCursor, InsertTag, FilterCSS)
|
||||
@ -116,10 +116,12 @@ class Boss(QObject):
|
||||
self.gui.spell_check.find_word.connect(self.find_word)
|
||||
self.gui.spell_check.refresh_requested.connect(self.commit_all_editors_to_container)
|
||||
self.gui.spell_check.word_replaced.connect(self.word_replaced)
|
||||
self.gui.spell_check.word_ignored.connect(self.word_ignored)
|
||||
|
||||
def preferences(self):
|
||||
p = Preferences(self.gui)
|
||||
ret = p.exec_()
|
||||
orig_spell = tprefs['inline_spell_check']
|
||||
if p.dictionaries_changed:
|
||||
dictionaries.clear_caches()
|
||||
dictionaries.initialize(force=True) # Reread user dictionaries
|
||||
@ -129,6 +131,12 @@ class Boss(QObject):
|
||||
if ret == p.Accepted or p.dictionaries_changed:
|
||||
for ed in editors.itervalues():
|
||||
ed.apply_settings(dictionaries_changed=p.dictionaries_changed)
|
||||
if orig_spell != tprefs['inline_spell_check']:
|
||||
for ed in editors.itervalues():
|
||||
try:
|
||||
ed.editor.highlighter.rehighlight()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def mark_requested(self, name, action):
|
||||
self.commit_dirty_opf()
|
||||
@ -740,10 +748,28 @@ class Boss(QObject):
|
||||
break
|
||||
find_next_word(word, locations, ed, name, self.gui, self.show_editor, self.edit_file)
|
||||
|
||||
def next_spell_error(self):
|
||||
' Go to the next spelling error '
|
||||
ed = self.gui.central.current_editor
|
||||
name = None
|
||||
for n, x in editors.iteritems():
|
||||
if x is ed:
|
||||
name = n
|
||||
break
|
||||
find_next_error(ed, name, self.gui, self.show_editor, self.edit_file)
|
||||
|
||||
def word_replaced(self, changed_names):
|
||||
self.set_modified()
|
||||
self.update_editors_from_container(names=set(changed_names))
|
||||
|
||||
def word_ignored(self, word, locale):
|
||||
if tprefs['inline_spell_check']:
|
||||
for ed in editors.itervalues():
|
||||
try:
|
||||
ed.editor.recheck_word(word, locale)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def saved_searches(self):
|
||||
self.gui.saved_searches.show(), self.gui.saved_searches.raise_()
|
||||
|
||||
@ -1042,6 +1068,8 @@ class Boss(QObject):
|
||||
editor.copy_available_state_changed.connect(self.editor_copy_available_state_changed)
|
||||
editor.cursor_position_changed.connect(self.sync_preview_to_editor)
|
||||
editor.cursor_position_changed.connect(self.update_cursor_position)
|
||||
if hasattr(editor, 'word_ignored'):
|
||||
editor.word_ignored.connect(self.word_ignored)
|
||||
if data is not None:
|
||||
if use_template:
|
||||
editor.init_from_template(data)
|
||||
|
@ -34,6 +34,7 @@ def editor_from_syntax(syntax, parent=None):
|
||||
|
||||
|
||||
SYNTAX_PROPERTY = QTextCharFormat.UserProperty
|
||||
SPELL_PROPERTY = SYNTAX_PROPERTY + 1
|
||||
|
||||
class SyntaxTextCharFormat(QTextCharFormat):
|
||||
|
||||
|
@ -151,6 +151,10 @@ class SyntaxHighlighter(object):
|
||||
finally:
|
||||
doc.contentsChange.connect(self.reformat_blocks)
|
||||
|
||||
def reformat_block(self, block):
|
||||
if block.isValid():
|
||||
self.reformat_blocks(block.position(), 0, 1)
|
||||
|
||||
def apply_format_changes(self, doc, block, formats):
|
||||
layout = block.layout()
|
||||
preedit_start = layout.preeditAreaPosition()
|
||||
|
@ -14,7 +14,9 @@ from PyQt4.Qt import QFont, QTextBlockUserData
|
||||
|
||||
from calibre.ebooks.oeb.polish.spell import html_spell_tags, xml_spell_tags
|
||||
from calibre.spell.dictionary import parse_lang_code
|
||||
from calibre.gui2.tweak_book.editor import SyntaxTextCharFormat
|
||||
from calibre.spell.break_iterator import split_into_words_and_positions
|
||||
from calibre.gui2.tweak_book import dictionaries, tprefs
|
||||
from calibre.gui2.tweak_book.editor import SyntaxTextCharFormat, SPELL_PROPERTY
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter, run_loop
|
||||
from calibre.gui2.tweak_book.editor.syntax.css import (
|
||||
create_formats as create_css_formats, state_map as css_state_map, CSSState, CSSUserData)
|
||||
@ -170,6 +172,16 @@ class HTMLUserData(QTextBlockUserData):
|
||||
self.tags, self.attributes = [], []
|
||||
self.state = State() if state is None else state
|
||||
|
||||
@classmethod
|
||||
def tag_ok_for_spell(cls, name):
|
||||
return name not in html_spell_tags
|
||||
|
||||
class XMLUserData(HTMLUserData):
|
||||
|
||||
@classmethod
|
||||
def tag_ok_for_spell(cls, name):
|
||||
return name in xml_spell_tags
|
||||
|
||||
def add_tag_data(user_data, tag):
|
||||
user_data.tags.append(tag)
|
||||
|
||||
@ -211,7 +223,7 @@ def cdata(state, text, i, formats, user_data):
|
||||
add_tag_data(user_data, TagStart(m.start(), '', name, True, True))
|
||||
return [(num, fmt), (2, formats['end_tag']), (len(m.group()) - 2, formats['tag_name'])]
|
||||
|
||||
def mark_nbsp(state, text, nbsp_format):
|
||||
def process_text(state, text, nbsp_format, spell_format, user_data):
|
||||
ans = []
|
||||
fmt = None
|
||||
if state.is_bold or state.is_italic:
|
||||
@ -226,6 +238,36 @@ def mark_nbsp(state, text, nbsp_format):
|
||||
last = m.end()
|
||||
if not ans:
|
||||
ans = [(len(text), fmt)]
|
||||
|
||||
if tprefs['inline_spell_check'] and state.tags and user_data.tag_ok_for_spell(state.tags[-1].name):
|
||||
split_ans = []
|
||||
locale = state.current_lang or dictionaries.default_locale
|
||||
sfmt = SyntaxTextCharFormat(spell_format)
|
||||
if fmt is not None:
|
||||
sfmt.merge(fmt)
|
||||
|
||||
tpos = 0
|
||||
for tlen, fmt in ans:
|
||||
if fmt is nbsp_format:
|
||||
split_ans.append((tlen, fmt))
|
||||
else:
|
||||
ctext = text[tpos:tpos+tlen]
|
||||
ppos = 0
|
||||
for start, length in split_into_words_and_positions(ctext, lang=locale.langcode):
|
||||
if start > ppos:
|
||||
split_ans.append((start - ppos, fmt))
|
||||
ppos = start + length
|
||||
recognized = dictionaries.recognized(ctext[start:ppos], locale)
|
||||
if not recognized:
|
||||
wsfmt = SyntaxTextCharFormat(sfmt)
|
||||
wsfmt.setProperty(SPELL_PROPERTY, (ctext[start:ppos], locale))
|
||||
split_ans.append((length, fmt if recognized else wsfmt))
|
||||
if ppos == 0:
|
||||
split_ans.append((tlen, fmt))
|
||||
|
||||
tpos += tlen
|
||||
ans = split_ans
|
||||
|
||||
return ans
|
||||
|
||||
def normal(state, text, i, formats, user_data):
|
||||
@ -277,7 +319,7 @@ def normal(state, text, i, formats, user_data):
|
||||
return [(1, formats['>'])]
|
||||
|
||||
t = normal_pat.search(text, i).group()
|
||||
return mark_nbsp(state, t, formats['nbsp'])
|
||||
return process_text(state, t, formats['nbsp'], formats['spell'], user_data)
|
||||
|
||||
def opening_tag(cdata_tags, state, text, i, formats, user_data):
|
||||
'An opening tag, like <a>'
|
||||
@ -417,6 +459,7 @@ def create_formats(highlighter, add_css=True):
|
||||
'nsprefix': t['Constant'],
|
||||
'preproc': t['PreProc'],
|
||||
'nbsp': t['SpecialCharacter'],
|
||||
'spell': t['SpellError'],
|
||||
}
|
||||
for name, msg in {
|
||||
'<': _('An unescaped < is not allowed. Replace it with <'),
|
||||
@ -445,18 +488,19 @@ class HTMLHighlighter(SyntaxHighlighter):
|
||||
user_data_factory = HTMLUserData
|
||||
|
||||
def tag_ok_for_spell(self, name):
|
||||
return name not in html_spell_tags
|
||||
return HTMLUserData.tag_ok_for_spell(name)
|
||||
|
||||
class XMLHighlighter(HTMLHighlighter):
|
||||
|
||||
state_map = xml_state_map
|
||||
spell_attributes = ('opf:file-as',)
|
||||
user_data_factory = XMLUserData
|
||||
|
||||
def create_formats_func(self):
|
||||
return create_formats(self, add_css=False)
|
||||
|
||||
def tag_ok_for_spell(self, name):
|
||||
return name in xml_spell_tags
|
||||
return XMLUserData.tag_ok_for_spell(name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2.tweak_book.editor.widget import launch_editor
|
||||
|
@ -18,7 +18,7 @@ from PyQt4.Qt import (
|
||||
|
||||
from calibre import prepare_string_for_xml, xml_entity_to_unicode
|
||||
from calibre.gui2.tweak_book import tprefs, TOP
|
||||
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY
|
||||
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY, SPELL_PROPERTY
|
||||
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color, theme_format
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter, XMLHighlighter
|
||||
@ -231,6 +231,11 @@ class TextEdit(PlainTextEdit):
|
||||
self.setTextCursor(c)
|
||||
self.ensureCursorVisible()
|
||||
|
||||
def simple_replace(self, text):
|
||||
c = self.textCursor()
|
||||
c.insertText(unicodedata.normalize('NFC', text))
|
||||
self.setTextCursor(c)
|
||||
|
||||
def go_to_line(self, lnum, col=None):
|
||||
lnum = max(1, min(self.blockCount(), lnum))
|
||||
c = self.textCursor()
|
||||
@ -383,7 +388,7 @@ class TextEdit(PlainTextEdit):
|
||||
self.saved_matches[save_match] = (pat, m)
|
||||
return True
|
||||
|
||||
def find_spell_word(self, original_words, lang, from_cursor=True):
|
||||
def find_spell_word(self, original_words, lang, from_cursor=True, center_on_cursor=True):
|
||||
c = self.textCursor()
|
||||
c.setPosition(c.position())
|
||||
if not from_cursor:
|
||||
@ -407,13 +412,30 @@ class TextEdit(PlainTextEdit):
|
||||
c.setPosition(c.position() + string_length(word), c.KeepAnchor)
|
||||
if self.smarts.verify_for_spellcheck(c, self.highlighter):
|
||||
self.setTextCursor(c)
|
||||
self.centerCursor()
|
||||
if center_on_cursor:
|
||||
self.centerCursor()
|
||||
return True
|
||||
c.setPosition(c.position())
|
||||
c.movePosition(c.End, c.KeepAnchor)
|
||||
|
||||
return False
|
||||
|
||||
def find_next_spell_error(self, from_cursor=True):
|
||||
c = self.textCursor()
|
||||
if not from_cursor:
|
||||
c.movePosition(c.Start)
|
||||
block = c.block()
|
||||
while block.isValid():
|
||||
for r in block.layout().additionalFormats():
|
||||
if r.format.property(SPELL_PROPERTY).toPyObject() is not None:
|
||||
if not from_cursor or block.position() + r.start + r.length > c.position():
|
||||
c.setPosition(block.position() + r.start)
|
||||
c.setPosition(c.position() + r.length, c.KeepAnchor)
|
||||
self.setTextCursor(c)
|
||||
return True
|
||||
block = block.next()
|
||||
return False
|
||||
|
||||
def replace(self, pat, template, saved_match='gui'):
|
||||
c = self.textCursor()
|
||||
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
|
||||
@ -546,6 +568,18 @@ class TextEdit(PlainTextEdit):
|
||||
return False
|
||||
return QPlainTextEdit.event(self, ev)
|
||||
|
||||
def recheck_word(self, word, locale):
|
||||
c = self.textCursor()
|
||||
c.movePosition(c.Start)
|
||||
block = c.block()
|
||||
while block.isValid():
|
||||
for r in block.layout().additionalFormats():
|
||||
x = r.format.property(SPELL_PROPERTY).toPyObject()
|
||||
if x is not None and word == x[0]:
|
||||
self.highlighter.reformat_block(block)
|
||||
break
|
||||
block = block.next()
|
||||
|
||||
# Tooltips {{{
|
||||
def syntax_format_for_cursor(self, cursor):
|
||||
if cursor.isNull():
|
||||
|
@ -60,7 +60,7 @@ SOLARIZED = \
|
||||
SpecialCharacter bg={base02}
|
||||
|
||||
Error us=wave uc={red}
|
||||
SpellError us=wave uc={orange}
|
||||
SpellError us=spell uc={orange}
|
||||
Tooltip fg=black bg=ffffed
|
||||
|
||||
DiffDelete bg={base02} fg={red}
|
||||
@ -101,7 +101,7 @@ THEMES = {
|
||||
Keyword fg={keyword}
|
||||
Special fg=e7f6da
|
||||
Error us=wave uc=red
|
||||
SpellError us=wave uc=orange
|
||||
SpellError us=spell uc=orange
|
||||
SpecialCharacter bg={cursor_loc}
|
||||
|
||||
DiffDelete bg=341414 fg=642424
|
||||
@ -148,7 +148,7 @@ THEMES = {
|
||||
Special fg=70a0d0 italic
|
||||
SpecialCharacter bg={cursor_loc}
|
||||
Error us=wave uc=red
|
||||
SpellError us=wave uc=orange
|
||||
SpellError us=spell uc=magenta
|
||||
|
||||
DiffDelete bg=rgb(255,180,200) fg=rgb(200,80,110)
|
||||
DiffInsert bg=rgb(180,255,180) fg=rgb(80,210,80)
|
||||
|
@ -14,8 +14,10 @@ from PyQt4.Qt import (
|
||||
QImage, QColor, QIcon, QPixmap, QToolButton)
|
||||
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.tweak_book import actions, current_container, tprefs
|
||||
from calibre.gui2.tweak_book import actions, current_container, tprefs, dictionaries
|
||||
from calibre.gui2.tweak_book.editor import SPELL_PROPERTY
|
||||
from calibre.gui2.tweak_book.editor.text import TextEdit
|
||||
from calibre.utils.icu import utf16_length
|
||||
|
||||
def create_icon(text, palette=None, sz=32, divider=2):
|
||||
if palette is None:
|
||||
@ -79,6 +81,7 @@ class Editor(QMainWindow):
|
||||
copy_available_state_changed = pyqtSignal(object)
|
||||
data_changed = pyqtSignal(object)
|
||||
cursor_position_changed = pyqtSignal()
|
||||
word_ignored = pyqtSignal(object, object)
|
||||
|
||||
def __init__(self, syntax, parent=None):
|
||||
QMainWindow.__init__(self, parent)
|
||||
@ -258,6 +261,10 @@ class Editor(QMainWindow):
|
||||
self.modification_state_changed.disconnect()
|
||||
except TypeError:
|
||||
pass # in case this signal was never connected
|
||||
try:
|
||||
self.word_ignored.disconnect()
|
||||
except TypeError:
|
||||
pass # in case this signal was never connected
|
||||
self.undo_redo_state_changed.disconnect()
|
||||
self.copy_available_state_changed.disconnect()
|
||||
self.cursor_position_changed.disconnect()
|
||||
@ -344,6 +351,43 @@ class Editor(QMainWindow):
|
||||
def show_context_menu(self, pos):
|
||||
m = QMenu(self)
|
||||
a = m.addAction
|
||||
c = self.editor.cursorForPosition(pos)
|
||||
fmt = self.editor.syntax_format_for_cursor(c)
|
||||
spell = fmt.property(SPELL_PROPERTY).toPyObject() if fmt is not None else None
|
||||
if spell is not None:
|
||||
word, locale = spell
|
||||
orig_pos = c.position()
|
||||
c.setPosition(orig_pos - utf16_length(word))
|
||||
found = False
|
||||
self.editor.setTextCursor(c)
|
||||
if self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False):
|
||||
found = True
|
||||
fc = self.editor.textCursor()
|
||||
if fc.position() < c.position():
|
||||
self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False)
|
||||
if found:
|
||||
suggestions = dictionaries.suggestions(word, locale)[:7]
|
||||
if suggestions:
|
||||
for suggestion in suggestions:
|
||||
ac = m.addAction(suggestion, partial(self.editor.simple_replace, suggestion))
|
||||
f = ac.font()
|
||||
f.setBold(True), ac.setFont(f)
|
||||
m.addSeparator()
|
||||
m.addAction(actions['spell-next'])
|
||||
m.addAction(_('Ignore this word'), partial(self._nuke_word, None, word, locale))
|
||||
dics = dictionaries.active_user_dictionaries
|
||||
if len(dics) > 0:
|
||||
if len(dics) == 1:
|
||||
m.addAction(_('Add this word to the dictionary: {0}').format(dics[0].name), partial(
|
||||
self._nuke_word, dics[0].name, word, locale))
|
||||
else:
|
||||
ac = m.addAction(_('Add this word to the dictionary'))
|
||||
dmenu = QMenu(m)
|
||||
ac.setMenu(dmenu)
|
||||
for dic in dics:
|
||||
dmenu.addAction(dic.name, partial(self._nuke_word, dic.name, word, locale))
|
||||
m.addSeparator()
|
||||
|
||||
for x in ('undo', 'redo'):
|
||||
a(actions['editor-%s' % x])
|
||||
m.addSeparator()
|
||||
@ -356,6 +400,12 @@ class Editor(QMainWindow):
|
||||
m.addAction(actions['multisplit'])
|
||||
m.exec_(self.editor.mapToGlobal(pos))
|
||||
|
||||
def _nuke_word(self, dic, word, locale):
|
||||
if dic is None:
|
||||
dictionaries.ignore_word(word, locale)
|
||||
else:
|
||||
dictionaries.add_to_user_dictionary(dic, word, locale)
|
||||
self.word_ignored.emit(word, locale)
|
||||
|
||||
def launch_editor(path_to_edit, path_is_raw=False, syntax='html'):
|
||||
from calibre.gui2.tweak_book.main import option_parser
|
||||
|
@ -194,6 +194,13 @@ class EditorSettings(BasicSettings):
|
||||
' time you open a HTML/CSS/etc. file for editing.'))
|
||||
l.addRow(lw)
|
||||
|
||||
lw = self('inline_spell_check')
|
||||
lw.setText(_('Show misspelled words underlined in the code view'))
|
||||
lw.setToolTip('<p>' + _(
|
||||
'This will cause spelling errors to be highlighted in the code view'
|
||||
' for easy correction as you type.'))
|
||||
l.addRow(lw)
|
||||
|
||||
self.dictionaries = d = QPushButton(_('Manage &spelling dictionaries'), self)
|
||||
d.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
d.clicked.connect(self.manage_dictionaries)
|
||||
|
@ -19,7 +19,7 @@ from PyQt4.Qt import (
|
||||
QComboBox, QListWidget, QListWidgetItem, QInputDialog, QPlainTextEdit, QKeySequence)
|
||||
|
||||
from calibre.constants import __appname__, plugins
|
||||
from calibre.ebooks.oeb.polish.spell import replace_word, get_all_words, merge_locations
|
||||
from calibre.ebooks.oeb.polish.spell import replace_word, get_all_words, merge_locations, get_checkable_file_names
|
||||
from calibre.gui2 import choose_files, error_dialog
|
||||
from calibre.gui2.complete2 import LineEdit
|
||||
from calibre.gui2.languages import LanguagesEdit
|
||||
@ -585,6 +585,8 @@ class ManageDictionaries(Dialog): # {{{
|
||||
# Spell Check Dialog {{{
|
||||
class WordsModel(QAbstractTableModel):
|
||||
|
||||
word_ignored = pyqtSignal(object, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
self.counts = (0, 0)
|
||||
@ -705,6 +707,7 @@ class WordsModel(QAbstractTableModel):
|
||||
(dictionaries.unignore_word if ignored else dictionaries.ignore_word)(*w)
|
||||
self.spell_map[w] = dictionaries.recognized(*w)
|
||||
self.update_word(w)
|
||||
self.word_ignored.emit(*w)
|
||||
|
||||
def ignore_words(self, rows):
|
||||
words = {self.word_for_row(r) for r in rows}
|
||||
@ -714,6 +717,7 @@ class WordsModel(QAbstractTableModel):
|
||||
(dictionaries.unignore_word if ignored else dictionaries.ignore_word)(*w)
|
||||
self.spell_map[w] = dictionaries.recognized(*w)
|
||||
self.update_word(w)
|
||||
self.word_ignored.emit(*w)
|
||||
|
||||
def add_word(self, row, udname):
|
||||
w = self.word_for_row(row)
|
||||
@ -721,6 +725,7 @@ class WordsModel(QAbstractTableModel):
|
||||
if dictionaries.add_to_user_dictionary(udname, *w):
|
||||
self.spell_map[w] = dictionaries.recognized(*w)
|
||||
self.update_word(w)
|
||||
self.word_ignored.emit(*w)
|
||||
|
||||
def add_words(self, dicname, rows):
|
||||
words = {self.word_for_row(r) for r in rows}
|
||||
@ -730,6 +735,7 @@ class WordsModel(QAbstractTableModel):
|
||||
dictionaries.remove_from_user_dictionary(dicname, [w])
|
||||
self.spell_map[w] = dictionaries.recognized(*w)
|
||||
self.update_word(w)
|
||||
self.word_ignored.emit(*w)
|
||||
|
||||
def remove_word(self, row):
|
||||
w = self.word_for_row(row)
|
||||
@ -855,6 +861,7 @@ class SpellCheck(Dialog):
|
||||
find_word = pyqtSignal(object, object)
|
||||
refresh_requested = pyqtSignal()
|
||||
word_replaced = pyqtSignal(object)
|
||||
word_ignored = pyqtSignal(object, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.__current_word = None
|
||||
@ -922,6 +929,7 @@ class SpellCheck(Dialog):
|
||||
w.setModel(m)
|
||||
m.dataChanged.connect(self.current_word_changed)
|
||||
m.modelReset.connect(self.current_word_changed)
|
||||
m.word_ignored.connect(self.word_ignored)
|
||||
if state is not None:
|
||||
hh.restoreState(state)
|
||||
# Sort by the restored state, if any
|
||||
@ -1249,6 +1257,30 @@ def find_next(word, locations, current_editor, current_editor_name,
|
||||
show_editor(file_name)
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_next_error(current_editor, current_editor_name, gui_parent, show_editor, edit_file):
|
||||
files = get_checkable_file_names(current_container())[0]
|
||||
if current_editor_name not in files:
|
||||
current_editor_name = None
|
||||
else:
|
||||
idx = files.index(current_editor_name)
|
||||
before, after = files[:idx], files[idx+1:]
|
||||
files = [current_editor_name] + after + before + [current_editor_name]
|
||||
|
||||
for file_name in files:
|
||||
from_cursor = False
|
||||
if file_name == current_editor_name:
|
||||
from_cursor = True
|
||||
current_editor_name = None
|
||||
ed = editors.get(file_name, None)
|
||||
if ed is None:
|
||||
edit_file(file_name)
|
||||
ed = editors[file_name]
|
||||
if ed.editor.find_next_spell_error(from_cursor=from_cursor):
|
||||
show_editor(file_name)
|
||||
return True
|
||||
return False
|
||||
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -410,6 +410,8 @@ class Main(MainWindow):
|
||||
self.check_book.next_error, delta=1), 'check-book-next', ('Ctrl+F7'), _('Show next error'))
|
||||
self.action_check_book_previous = reg('back.png', _('&Previous error'), partial(
|
||||
self.check_book.next_error, delta=-1), 'check-book-previous', ('Ctrl+Shift+F7'), _('Show previous error'))
|
||||
self.action_spell_check_next = reg('forward.png', _('&Next spelling mistake'),
|
||||
self.boss.next_spell_error, 'spell-next', ('F8'), _('Go to next spelling mistake'))
|
||||
|
||||
# Miscellaneous actions
|
||||
group = _('Miscellaneous')
|
||||
|
Loading…
x
Reference in New Issue
Block a user