Implement find over multiple files and in marked text regions

This commit is contained in:
Kovid Goyal 2013-11-14 20:34:17 +05:30
parent 5291283265
commit 91a42ff6cf
3 changed files with 110 additions and 33 deletions

View File

@ -7,10 +7,11 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import tempfile, shutil, sys, os
from collections import OrderedDict
from functools import partial
from PyQt4.Qt import (
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt,
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt, QCursor,
QDialogButtonBox, QIcon, QTimer, QPixmap, QTextBrowser, QVBoxLayout)
from calibre import prints
@ -272,7 +273,7 @@ class Boss(QObject):
# Ensure the search panel is visible
sp.setVisible(True)
ed = self.gui.central.current_editor
name = None
name = editor = None
for n, x in editors.iteritems():
if x is ed:
name = n
@ -295,47 +296,74 @@ class Boss(QObject):
if err:
return error_dialog(self.gui, _('Cannot search'), err, show=True)
del err
files = OrderedDict()
do_all = state['wrap'] or action in {'replace-all', 'count'}
marked = False
if where == 'current':
files = [name]
editor = ed
elif where in {'styles', 'text', 'selected'}:
files = searchable_names[where]
if name in files:
# Start searching in the current editor
editor = ed
else:
common = set(editors).intersection(set(files))
if common:
name = next(x for x in files if x in common)
editor = editors[name]
self.gui.central.show_editor(editor)
# Re-order the list of other files so that we search in the same
# order every time. Depending on direction, search the files
# that come after the current file, or before the current file,
# first.
lfiles = list(files)
idx = lfiles.index(name)
before, after = lfiles[:idx], lfiles[idx+1:]
if state['direction'] == 'up':
lfiles = list(reversed(before))
if do_all:
lfiles += list(reversed(after)) + [name]
else:
pass # TODO: Find the first name with a match and open its editor
lfiles = after
if do_all:
lfiles += before + [name]
files = OrderedDict((m, files[m]) for m in lfiles)
else:
files = [name]
pass # marked text TODO: Implement this
editor = ed
marked = True
def no_match():
msg = '<p>' + _('No matches were found for %s.') % state['find']
if not state['wrap']:
msg += '<p>' + _('You have turned off search wrapping, so all text might not have been searched.'
' Try the search again, with wrapping enabled. Wrapping is enabled via the'
' "Wrap" checkbox at the bottom of the search panel.')
return error_dialog(
self.gui, _('Not found'), _(
'No matches were found for %s') % state['find'], show=True)
self.gui, _('Not found'), msg, show=True)
pat = sp.get_regex(state)
def do_find():
found = editor.find(pat)
if found:
return
if len(files) == 1:
if not state['wrap']:
return no_match()
found = editor.find(pat, wrap=True)
if not found:
return no_match()
else:
pass # TODO: handle multiple file search
if editor is not None:
if editor.find(pat, marked=marked):
return
if not files:
if not state['wrap']:
return no_match()
return editor.find(pat, wrap=True, marked=marked) or no_match()
for fname, syntax in files.iteritems():
if fname in editors:
if not editors[fname].find(pat, complete=True):
continue
return self.show_editor(fname)
raw = current_container().raw_data(fname)
if pat.search(raw) is not None:
self.edit_file(fname, syntax)
if editors[fname].find(pat, complete=True):
return
return no_match()
if action == 'find':
return do_find()
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
try:
if action == 'find':
return do_find()
finally:
QApplication.restoreOverrideCursor()
def save_book(self):
c = current_container()

View File

@ -154,12 +154,59 @@ class TextEdit(QPlainTextEdit):
self.current_search_mark = None
self.update_extra_selections()
def find(self, pat, wrap=False):
def find_in_marked(self, pat, wrap=False):
if self.current_search_mark is None:
return False
csm = self.current_search_mark.cursor
reverse = pat.flags & regex.REVERSE
c = self.textCursor()
c.clearSelection()
pos = c.Start if reverse else c.End
m_start = min(csm.position(), csm.anchor())
m_end = max(csm.position(), csm.anchor())
if c.position() < m_start:
c.setPosition(m_start)
if c.position() > m_end:
c.setPosition(m_end)
pos = m_start if reverse else m_end
if wrap:
pos = m_end if reverse else m_start
c.setPosition(pos, c.KeepAnchor)
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
m = pat.search(raw)
if m is None:
return False
start, end = m.span()
if start == end:
return False
if wrap:
if reverse:
textpos = c.anchor()
start, end = textpos + end, textpos + start
else:
start, end = m_start + start, m_start + end
else:
if reverse:
start, end = m_start + end, m_start + start
else:
start, end = c.anchor() + start, c.anchor() + end
c.clearSelection()
c.setPosition(start)
c.setPosition(end, c.KeepAnchor)
self.setTextCursor(c)
return True
def find(self, pat, wrap=False, marked=False, complete=False):
if marked:
return self.find_in_marked(pat, wrap=wrap)
reverse = pat.flags & regex.REVERSE
c = self.textCursor()
c.clearSelection()
if complete:
# Search the entire text
c.movePosition(c.End if reverse else c.Start)
pos = c.Start if reverse else c.End
if wrap and not complete:
pos = c.End if reverse else c.Start
c.movePosition(pos, c.KeepAnchor)
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
@ -169,7 +216,7 @@ class TextEdit(QPlainTextEdit):
start, end = m.span()
if start == end:
return False
if wrap:
if wrap and not complete:
if reverse:
textpos = c.anchor()
start, end = textpos + end, textpos + start

View File

@ -7,6 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from binascii import hexlify
from collections import OrderedDict
from PyQt4.Qt import (
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal)
@ -17,6 +18,7 @@ from calibre.ebooks.oeb.polish.container import guess_type
from calibre.ebooks.oeb.polish.cover import get_cover_page_name, get_raster_cover_name
from calibre.gui2 import error_dialog
from calibre.gui2.tweak_book import current_container
from calibre.gui2.tweak_book.editor import syntax_from_mime
from calibre.utils.icu import sort_key
TOP_ICON_SIZE = 24
@ -344,18 +346,18 @@ class FileList(QTreeWidget):
@property
def searchable_names(self):
ans = {'text':[], 'styles':[], 'selected':[]}
ans = {'text':OrderedDict(), 'styles':OrderedDict(), 'selected':OrderedDict()}
for item in self.all_files:
category = unicode(item.data(0, CATEGORY_ROLE).toString())
mime = unicode(item.data(0, MIME_ROLE).toString())
name = unicode(item.data(0, NAME_ROLE).toString())
ok = category in {'text', 'styles'}
if ok:
ans[category].append(name)
ans[category][name] = syntax_from_mime(mime)
if not ok and category == 'misc':
ok = mime in {guess_type('a.'+x) for x in ('opf', 'ncx', 'txt', 'xml')}
if ok and item.isSelected():
ans['selected'].append(name)
ans['selected'][name] = syntax_from_mime(mime)
return ans
class FileListWidget(QWidget):