mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Make jumping to character locations robust against file edits
This commit is contained in:
parent
b05ee27ffe
commit
a937ccdcdf
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import posixpath, os
|
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.container import OEB_DOCS, OEB_STYLES, OEB_FONTS
|
||||||
from calibre.ebooks.oeb.polish.spell import get_all_words
|
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)
|
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())))
|
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):
|
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:
|
for name, is_linear in container.spine_names:
|
||||||
if container.mime_map.get(name) not in OEB_DOCS:
|
if container.mime_map.get(name) not in OEB_DOCS:
|
||||||
continue
|
continue
|
||||||
raw = container.raw_data(name)
|
raw = container.raw_data(name)
|
||||||
for i, codepoint in enumerate(ord_string(raw)):
|
counts = Counter(ord_string(raw))
|
||||||
chars[codepoint].append(Location(name, character_offset=i))
|
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()):
|
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):
|
def gather_data(container, book_locale):
|
||||||
data = {'files':tuple(file_data(container))}
|
data = {'files':tuple(file_data(container))}
|
||||||
|
@ -1244,10 +1244,11 @@ class Boss(QObject):
|
|||||||
self.gui.central.show_editor(editors[name])
|
self.gui.central.show_editor(editors[name])
|
||||||
editors[name].set_focus()
|
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:
|
if name in editors:
|
||||||
self.gui.central.show_editor(editors[name])
|
self.gui.central.show_editor(editors[name])
|
||||||
return 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)
|
syntax = syntax or syntax_from_mime(name, mime)
|
||||||
if not syntax:
|
if not syntax:
|
||||||
return error_dialog(
|
return error_dialog(
|
||||||
|
@ -20,7 +20,6 @@ from PyQt5.Qt import (
|
|||||||
QStyledItemDelegate, QModelIndex, QRect, QStyle, QPalette, QTimer, QMenu)
|
QStyledItemDelegate, QModelIndex, QRect, QStyle, QPalette, QTimer, QMenu)
|
||||||
|
|
||||||
from calibre import human_readable, fit_image
|
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.ebooks.oeb.polish.report import gather_data
|
||||||
from calibre.gui2 import error_dialog, question_dialog
|
from calibre.gui2 import error_dialog, question_dialog
|
||||||
from calibre.gui2.tweak_book import current_container, tprefs, dictionaries
|
from calibre.gui2.tweak_book import current_container, tprefs, dictionaries
|
||||||
@ -276,8 +275,7 @@ class Jump(object): # {{{
|
|||||||
if boss is None:
|
if boss is None:
|
||||||
return
|
return
|
||||||
name = loc.name
|
name = loc.name
|
||||||
mt = current_container().mime_map.get(name, guess_type(name))
|
editor = boss.edit_file_requested(name)
|
||||||
editor = boss.edit_file_requested(name, None, mt)
|
|
||||||
if editor is None:
|
if editor is None:
|
||||||
return
|
return
|
||||||
editor = editor.editor
|
editor = editor.editor
|
||||||
@ -290,10 +288,6 @@ class Jump(object): # {{{
|
|||||||
editor.setTextCursor(c)
|
editor.setTextCursor(c)
|
||||||
if loc.text_on_line is not None:
|
if loc.text_on_line is not None:
|
||||||
editor.find(regex.compile(regex.escape(loc.text_on_line)))
|
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() # }}}
|
jump = Jump() # }}}
|
||||||
|
|
||||||
@ -557,7 +551,7 @@ class CharsModel(FileCollection):
|
|||||||
if col == 2:
|
if col == 2:
|
||||||
return ('U+%04X' if entry.codepoint < 0x10000 else 'U+%06X') % entry.codepoint
|
return ('U+%04X' if entry.codepoint < 0x10000 else 'U+%06X') % entry.codepoint
|
||||||
if col == 3:
|
if col == 3:
|
||||||
return type('')(len(entry.usage))
|
return type('')(entry.count)
|
||||||
if role == Qt.UserRole:
|
if role == Qt.UserRole:
|
||||||
try:
|
try:
|
||||||
return self.files[index.row()]
|
return self.files[index.row()]
|
||||||
@ -595,10 +589,37 @@ class CharsWidget(QWidget):
|
|||||||
def double_clicked(self, index):
|
def double_clicked(self, index):
|
||||||
entry = index.data(Qt.UserRole)
|
entry = index.data(Qt.UserRole)
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
jump((id(self), entry.id), entry.usage)
|
self.find_next_location(entry)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
save_state('chars-table', bytearray(self.chars.horizontalHeader().saveState()))
|
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 {{{
|
# Wrapper UI {{{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user