Edit book: Insert special character: When searching by name match prefixes in addition to whole words. So you can now type "horiz" to match "horizontal".

This commit is contained in:
Kovid Goyal 2018-05-01 14:57:17 +05:30
parent b1ee6be746
commit b71584154a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 36 additions and 44 deletions

View File

@ -6,10 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import unicodedata, re, os, cPickle, textwrap import re, textwrap
from bisect import bisect from bisect import bisect
from functools import partial from functools import partial
from collections import defaultdict
from PyQt5.Qt import ( from PyQt5.Qt import (
QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QApplication, QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QApplication,
@ -17,12 +16,12 @@ from PyQt5.Qt import (
QStyledItemDelegate, QSplitter, QLabel, QSizePolicy, QIcon, QMimeData, QStyledItemDelegate, QSplitter, QLabel, QSizePolicy, QIcon, QMimeData,
QPushButton, QToolButton, QInputMethodEvent) QPushButton, QToolButton, QInputMethodEvent)
from calibre.constants import plugins, cache_dir from calibre.constants import plugins
from calibre.gui2.widgets2 import HistoryLineEdit2 from calibre.gui2.widgets2 import HistoryLineEdit2
from calibre.gui2.tweak_book import tprefs from calibre.gui2.tweak_book import tprefs
from calibre.gui2.tweak_book.widgets import Dialog, BusyCursor from calibre.gui2.tweak_book.widgets import Dialog, BusyCursor
from calibre.utils.icu import safe_chr as chr, icu_unicode_version from calibre.utils.icu import safe_chr as chr
from calibre.utils.unicode_names import character_name_from_code from calibre.utils.unicode_names import character_name_from_code, points_for_word
ROOT = QModelIndex() ROOT = QModelIndex()
@ -34,44 +33,9 @@ non_printing = {
0x206e: 'nads', 0x206f: 'nods', 0x20: 'sp', 0x7f: 'del', 0x2e3a: '2m', 0x2e3b: '3m', 0xad: 'shy', 0x206e: 'nads', 0x206f: 'nods', 0x20: 'sp', 0x7f: 'del', 0x2e3a: '2m', 0x2e3b: '3m', 0xad: 'shy',
} }
# Searching {{{ # Searching {{{
def load_search_index():
topchar = 0x10ffff
ver = (1, topchar, icu_unicode_version or unicodedata.unidata_version) # Increment this when you make any changes to the index
name_map = {}
path = os.path.join(cache_dir(), 'unicode-name-index.pickle')
if os.path.exists(path):
with open(path, 'rb') as f:
name_map = cPickle.load(f)
if name_map.pop('calibre-nm-version:', None) != ver:
name_map = {}
if not name_map:
name_map = defaultdict(set)
for x in xrange(1, topchar + 1):
for word in character_name_from_code(x).split():
name_map[word.lower()].add(x)
from calibre.ebooks.html_entities import html5_entities
for name, char in html5_entities.iteritems():
try:
name_map[name.lower()].add(ord(char))
except TypeError:
continue
name_map['nnbsp'].add(0x202F)
name_map['calibre-nm-version:'] = ver
cPickle.dump(dict(name_map), open(path, 'wb'), -1)
del name_map['calibre-nm-version:']
return name_map
_index = None
def search_for_chars(query, and_tokens=False): def search_for_chars(query, and_tokens=False):
global _index
if _index is None:
_index = load_search_index()
ans = set() ans = set()
for token in query.split(): for token in query.split():
token = token.lower() token = token.lower()
@ -79,7 +43,7 @@ def search_for_chars(query, and_tokens=False):
if m is not None: if m is not None:
chars = {int(m.group(1), 16)} chars = {int(m.group(1), 16)}
else: else:
chars = _index.get(token, None) chars = points_for_word(token)
if chars is not None: if chars is not None:
if and_tokens: if and_tokens:
ans &= chars ans &= chars

View File

@ -2,11 +2,39 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import (absolute_import, division, print_function, from __future__ import absolute_import, division, print_function, unicode_literals
unicode_literals)
from collections import defaultdict
from calibre.constants import plugins from calibre.constants import plugins
from calibre.utils.icu import ord_string
def character_name_from_code(code): def character_name_from_code(code):
return plugins['unicode_names'][0].name_for_codepoint(code) or 'U+{:X}'.format(code) return plugins['unicode_names'][0].name_for_codepoint(code) or 'U+{:X}'.format(code)
def html_entities():
ans = getattr(html_entities, 'ans', None)
if ans is None:
from calibre.ebooks.html_entities import html5_entities
ans = defaultdict(set)
for name, char in html5_entities.iteritems():
try:
ans[name.lower()].add(ord_string(char)[0])
except TypeError:
continue
ans['nnbsp'].add(0x202F)
ans = dict(ans)
html_entities.ans = ans
return ans
def points_for_word(w):
w = w.lower()
ans = points_for_word.cache.get(w)
if ans is None:
ans = plugins['unicode_names'][0].codepoints_for_word(w.encode('utf-8')) | html_entities().get(w, set())
points_for_word.cache[w] = ans
return ans
points_for_word.cache = {} # noqa