From a937ccdcdfda386b8a3496d52336d2cd0a57dedc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2015 10:25:30 +0530 Subject: [PATCH] Make jumping to character locations robust against file edits --- src/calibre/ebooks/oeb/polish/report.py | 22 ++++++++++---- src/calibre/gui2/tweak_book/boss.py | 3 +- src/calibre/gui2/tweak_book/reports.py | 39 +++++++++++++++++++------ 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/report.py b/src/calibre/ebooks/oeb/polish/report.py index 98c3912ae4..582270f632 100644 --- a/src/calibre/ebooks/oeb/polish/report.py +++ b/src/calibre/ebooks/oeb/polish/report.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' import posixpath, os -from collections import namedtuple, defaultdict +from collections import namedtuple, defaultdict, Counter from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, OEB_FONTS from calibre.ebooks.oeb.polish.spell import get_all_words @@ -93,19 +93,29 @@ def word_data(container, book_locale): count, words = get_all_words(container, book_locale, get_word_count=True) return (count, tuple(Word(i, word, locale, v) for i, ((word, locale), v) in enumerate(words.iteritems()))) -Char = namedtuple('Char', 'id char codepoint usage') +Char = namedtuple('Char', 'id char codepoint usage count') def char_data(container): - chars = defaultdict(list) + chars = defaultdict(set) + counter = Counter() + def count(codepoint): + counter[codepoint] += 1 + for name, is_linear in container.spine_names: if container.mime_map.get(name) not in OEB_DOCS: continue raw = container.raw_data(name) - for i, codepoint in enumerate(ord_string(raw)): - chars[codepoint].append(Location(name, character_offset=i)) + counts = Counter(ord_string(raw)) + counter.update(counts) + for codepoint in counts: + chars[codepoint].add(name) + + nmap = {n:i for i, (n, l) in enumerate(container.spine_names)} + def sort_key(name): + return nmap.get(name, len(nmap)), numeric_sort_key(name) for i, (codepoint, usage) in enumerate(chars.iteritems()): - yield Char(i, safe_chr(codepoint), codepoint, sort_locations(container, usage)) + yield Char(i, safe_chr(codepoint), codepoint, sorted(usage, key=sort_key), counter[codepoint]) def gather_data(container, book_locale): data = {'files':tuple(file_data(container))} diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 8db250dea2..7a75eebf9f 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -1244,10 +1244,11 @@ class Boss(QObject): self.gui.central.show_editor(editors[name]) editors[name].set_focus() - def edit_file_requested(self, name, syntax, mime): + def edit_file_requested(self, name, syntax=None, mime=None): if name in editors: self.gui.central.show_editor(editors[name]) return editors[name] + mime = mime or current_container().mime_map.get(name, guess_type(name)) syntax = syntax or syntax_from_mime(name, mime) if not syntax: return error_dialog( diff --git a/src/calibre/gui2/tweak_book/reports.py b/src/calibre/gui2/tweak_book/reports.py index 948361eb9e..47b46591e8 100644 --- a/src/calibre/gui2/tweak_book/reports.py +++ b/src/calibre/gui2/tweak_book/reports.py @@ -20,7 +20,6 @@ from PyQt5.Qt import ( QStyledItemDelegate, QModelIndex, QRect, QStyle, QPalette, QTimer, QMenu) from calibre import human_readable, fit_image -from calibre.ebooks.oeb.polish.container import guess_type from calibre.ebooks.oeb.polish.report import gather_data from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.tweak_book import current_container, tprefs, dictionaries @@ -276,8 +275,7 @@ class Jump(object): # {{{ if boss is None: return name = loc.name - mt = current_container().mime_map.get(name, guess_type(name)) - editor = boss.edit_file_requested(name, None, mt) + editor = boss.edit_file_requested(name) if editor is None: return editor = editor.editor @@ -290,10 +288,6 @@ class Jump(object): # {{{ editor.setTextCursor(c) if loc.text_on_line is not None: editor.find(regex.compile(regex.escape(loc.text_on_line))) - elif loc.character_offset is not None: - c = editor.textCursor() - c.setPosition(loc.character_offset + 1) # put cursor after the character - editor.setTextCursor(c) jump = Jump() # }}} @@ -557,7 +551,7 @@ class CharsModel(FileCollection): if col == 2: return ('U+%04X' if entry.codepoint < 0x10000 else 'U+%06X') % entry.codepoint if col == 3: - return type('')(len(entry.usage)) + return type('')(entry.count) if role == Qt.UserRole: try: return self.files[index.row()] @@ -595,10 +589,37 @@ class CharsWidget(QWidget): def double_clicked(self, index): entry = index.data(Qt.UserRole) if entry is not None: - jump((id(self), entry.id), entry.usage) + self.find_next_location(entry) def save(self): save_state('chars-table', bytearray(self.chars.horizontalHeader().saveState())) + + def find_next_location(self, entry): + from calibre.gui2.tweak_book.boss import get_boss + boss = get_boss() + if boss is None: + return + files = entry.usage + current_editor_name = boss.currently_editing + 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] + + pat = regex.compile(regex.escape(entry.char)) + for file_name in files: + from_cursor = False + if file_name == current_editor_name: + from_cursor = True + current_editor_name = None + ed = boss.edit_file_requested(file_name) + if ed.editor.find(pat, complete=not from_cursor): + boss.show_editor(file_name) + return True + return False + # }}} # Wrapper UI {{{