From 5b24a497cd89df4b952a52cdf9d47af6bfd6d96e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 May 2014 21:23:22 +0530 Subject: [PATCH] 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. --- src/calibre/ebooks/oeb/polish/spell.py | 8 ++- src/calibre/gui2/tweak_book/__init__.py | 1 + src/calibre/gui2/tweak_book/boss.py | 30 ++++++++++- .../gui2/tweak_book/editor/__init__.py | 1 + .../gui2/tweak_book/editor/syntax/base.py | 4 ++ .../gui2/tweak_book/editor/syntax/html.py | 54 +++++++++++++++++-- src/calibre/gui2/tweak_book/editor/text.py | 40 ++++++++++++-- src/calibre/gui2/tweak_book/editor/themes.py | 6 +-- src/calibre/gui2/tweak_book/editor/widget.py | 52 +++++++++++++++++- src/calibre/gui2/tweak_book/preferences.py | 7 +++ src/calibre/gui2/tweak_book/spell.py | 34 +++++++++++- src/calibre/gui2/tweak_book/ui.py | 2 + 12 files changed, 223 insertions(+), 16 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/spell.py b/src/calibre/ebooks/oeb/polish/spell.py index 7fc7542ba7..db06be6fa3 100644 --- a/src/calibre/ebooks/oeb/polish/spell.py +++ b/src/calibre/ebooks/oeb/polish/spell.py @@ -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 diff --git a/src/calibre/gui2/tweak_book/__init__.py b/src/calibre/gui2/tweak_book/__init__.py index c07d2baecb..0fe95e3c18 100644 --- a/src/calibre/gui2/tweak_book/__init__.py +++ b/src/calibre/gui2/tweak_book/__init__.py @@ -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 diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 578ec79e0b..648fb2cb08 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -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) diff --git a/src/calibre/gui2/tweak_book/editor/__init__.py b/src/calibre/gui2/tweak_book/editor/__init__.py index a16cc7fae6..0f4f09b9bd 100644 --- a/src/calibre/gui2/tweak_book/editor/__init__.py +++ b/src/calibre/gui2/tweak_book/editor/__init__.py @@ -34,6 +34,7 @@ def editor_from_syntax(syntax, parent=None): SYNTAX_PROPERTY = QTextCharFormat.UserProperty +SPELL_PROPERTY = SYNTAX_PROPERTY + 1 class SyntaxTextCharFormat(QTextCharFormat): diff --git a/src/calibre/gui2/tweak_book/editor/syntax/base.py b/src/calibre/gui2/tweak_book/editor/syntax/base.py index 191eb06eb2..f8075bcbf0 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/base.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/base.py @@ -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() diff --git a/src/calibre/gui2/tweak_book/editor/syntax/html.py b/src/calibre/gui2/tweak_book/editor/syntax/html.py index 9d92394e70..e117943e88 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/html.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/html.py @@ -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 ' @@ -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 diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index da0ea747f8..96cf4d5b1b 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -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(): diff --git a/src/calibre/gui2/tweak_book/editor/themes.py b/src/calibre/gui2/tweak_book/editor/themes.py index eac7d29a8d..cd04d30a5d 100644 --- a/src/calibre/gui2/tweak_book/editor/themes.py +++ b/src/calibre/gui2/tweak_book/editor/themes.py @@ -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) diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py index 837f88b6ac..4e9cdfd53a 100644 --- a/src/calibre/gui2/tweak_book/editor/widget.py +++ b/src/calibre/gui2/tweak_book/editor/widget.py @@ -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 diff --git a/src/calibre/gui2/tweak_book/preferences.py b/src/calibre/gui2/tweak_book/preferences.py index 059103258d..0e5bd2c84f 100644 --- a/src/calibre/gui2/tweak_book/preferences.py +++ b/src/calibre/gui2/tweak_book/preferences.py @@ -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('

' + _( + '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) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 43939513cf..ddd2169aa0 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -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__': diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 9570bbf95c..c43084f54c 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -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')