mirror of
git://github.com/kovidgoyal/calibre.git
synced 2026-04-25 18:54:12 +02: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
12 changed files with 223 additions and 16 deletions
|
|
@ -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['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.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 @@ def __call__(self, gui):
|
|||
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 @@ def preferences(self):
|
|||
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 @@ def find_word(self, word, locations):
|
|||
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 @@ def init_editor(self, name, editor, data=None, use_template=False):
|
|||
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 @@ def reformat_blocks(self, position, removed, added):
|
|||
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 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 @@ def clear(self, state=None):
|
|||
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 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 @@ def replace_text(self, text):
|
|||
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 @@ def find(self, pat, wrap=False, marked=False, complete=False, save_match=None):
|
|||
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 @@ def find_first_word(haystack):
|
|||
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 @@ def event(self, ev):
|
|||
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 @@ def default_theme():
|
|||
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 @@ def default_theme():
|
|||
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 @@ def default_theme():
|
|||
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 @@
|
|||
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 @@ def break_cycles(self):
|
|||
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 @@ def pretty_print(self, name):
|
|||
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 @@ def show_context_menu(self, pos):
|
|||
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 @@ def __init__(self, parent=None):
|
|||
' 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 @@
|
|||
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 @@ def test(cls):
|
|||
# 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 @@ def toggle_ignored(self, row):
|
|||
(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 @@ def ignore_words(self, rows):
|
|||
(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 @@ def add_word(self, row, udname):
|
|||
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 @@ def add_words(self, dicname, rows):
|
|||
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 @@ def setup_ui(self):
|
|||
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 @@ def sreg(name, text, action, overrides={}, keys=(), description=None, icon=None)
|
|||
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…
Reference in a new issue