mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Implement searching in the diff view
This commit is contained in:
parent
d0cfb55682
commit
66e4fc9ae1
@ -142,11 +142,13 @@ class Diff(Dialog):
|
|||||||
b.setIcon(QIcon(I('arrow-down.png')))
|
b.setIcon(QIcon(I('arrow-down.png')))
|
||||||
b.clicked.connect(partial(self.do_search, False))
|
b.clicked.connect(partial(self.do_search, False))
|
||||||
b.setToolTip(_('Find next match'))
|
b.setToolTip(_('Find next match'))
|
||||||
|
b.setText(_('&Next')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||||
l.addWidget(b, l.rowCount() - 1, l.columnCount(), 1, 1)
|
l.addWidget(b, l.rowCount() - 1, l.columnCount(), 1, 1)
|
||||||
self.sbp = b = QToolButton(self)
|
self.sbp = b = QToolButton(self)
|
||||||
b.setIcon(QIcon(I('arrow-up.png')))
|
b.setIcon(QIcon(I('arrow-up.png')))
|
||||||
b.clicked.connect(partial(self.do_search, True))
|
b.clicked.connect(partial(self.do_search, True))
|
||||||
b.setToolTip(_('Find previous match'))
|
b.setToolTip(_('Find previous match'))
|
||||||
|
b.setText(_('&Previous')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||||
l.addWidget(b, l.rowCount() - 1, l.columnCount(), 1, 1)
|
l.addWidget(b, l.rowCount() - 1, l.columnCount(), 1, 1)
|
||||||
self.lb = b = QRadioButton(_('Left panel'), self)
|
self.lb = b = QRadioButton(_('Left panel'), self)
|
||||||
b.setToolTip(_('Perform search in the left panel'))
|
b.setToolTip(_('Perform search in the left panel'))
|
||||||
@ -157,6 +159,7 @@ class Diff(Dialog):
|
|||||||
b.setChecked(True)
|
b.setChecked(True)
|
||||||
self.pb = b = QToolButton(self)
|
self.pb = b = QToolButton(self)
|
||||||
b.setIcon(QIcon(I('config.png')))
|
b.setIcon(QIcon(I('config.png')))
|
||||||
|
b.setText(_('&Context')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||||
b.setToolTip(_('Change the amount of context shown around the changes'))
|
b.setToolTip(_('Change the amount of context shown around the changes'))
|
||||||
b.setPopupMode(b.InstantPopup)
|
b.setPopupMode(b.InstantPopup)
|
||||||
m = QMenu(b)
|
m = QMenu(b)
|
||||||
@ -172,7 +175,11 @@ class Diff(Dialog):
|
|||||||
self.view.setFocus(Qt.OtherFocusReason)
|
self.view.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
def do_search(self, reverse):
|
def do_search(self, reverse):
|
||||||
pass
|
text = unicode(self.search.text())
|
||||||
|
if not text.strip():
|
||||||
|
return
|
||||||
|
v = self.view.view.left if self.lb.isChecked() else self.view.view.right
|
||||||
|
v.search(text, reverse=reverse)
|
||||||
|
|
||||||
def change_context(self, context):
|
def change_context(self, context):
|
||||||
if context == self.context:
|
if context == self.context:
|
||||||
@ -233,6 +240,10 @@ class Diff(Dialog):
|
|||||||
|
|
||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
if not self.view.handle_key(ev):
|
if not self.view.handle_key(ev):
|
||||||
|
if ev.key() in (Qt.Key_Enter, Qt.Key_Return):
|
||||||
|
return # The enter key is used by the search box, so prevent it closing the dialog
|
||||||
|
if ev.key() == Qt.Key_Slash:
|
||||||
|
return self.search.setFocus(Qt.OtherFocusReason)
|
||||||
return Dialog.keyPressEvent(self, ev)
|
return Dialog.keyPressEvent(self, ev)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -7,12 +7,14 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import re, unicodedata
|
import re, unicodedata
|
||||||
|
from itertools import chain
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from collections import namedtuple, OrderedDict
|
from collections import namedtuple, OrderedDict
|
||||||
from difflib import SequenceMatcher
|
from difflib import SequenceMatcher
|
||||||
from future_builtins import zip
|
from future_builtins import zip
|
||||||
|
|
||||||
|
import regex
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QSplitter, QApplication, QPlainTextDocumentLayout, QTextDocument, QTimer,
|
QSplitter, QApplication, QPlainTextDocumentLayout, QTextDocument, QTimer,
|
||||||
QTextCursor, QTextCharFormat, Qt, QRect, QPainter, QPalette, QPen, QBrush,
|
QTextCursor, QTextCharFormat, Qt, QRect, QPainter, QPalette, QPen, QBrush,
|
||||||
@ -21,6 +23,7 @@ from PyQt4.Qt import (
|
|||||||
QMenu, QIcon)
|
QMenu, QIcon)
|
||||||
|
|
||||||
from calibre import human_readable, fit_image
|
from calibre import human_readable, fit_image
|
||||||
|
from calibre.gui2 import info_dialog
|
||||||
from calibre.gui2.tweak_book import tprefs
|
from calibre.gui2.tweak_book import tprefs
|
||||||
from calibre.gui2.tweak_book.editor.text import PlainTextEdit, get_highlighter, default_font_family, LineNumbers
|
from calibre.gui2.tweak_book.editor.text import PlainTextEdit, get_highlighter, default_font_family, LineNumbers
|
||||||
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
|
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
|
||||||
@ -47,6 +50,7 @@ class TextBrowser(PlainTextEdit): # {{{
|
|||||||
resized = pyqtSignal()
|
resized = pyqtSignal()
|
||||||
wheel_event = pyqtSignal(object)
|
wheel_event = pyqtSignal(object)
|
||||||
goto_change = pyqtSignal(object)
|
goto_change = pyqtSignal(object)
|
||||||
|
scrolled = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, right=False, parent=None):
|
def __init__(self, right=False, parent=None):
|
||||||
PlainTextEdit.__init__(self, parent)
|
PlainTextEdit.__init__(self, parent)
|
||||||
@ -89,6 +93,7 @@ class TextBrowser(PlainTextEdit): # {{{
|
|||||||
pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
|
pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
|
||||||
pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
|
pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
|
||||||
self.line_number_map = {}
|
self.line_number_map = {}
|
||||||
|
self.search_header_pos = 0
|
||||||
self.changes, self.headers, self.images = [], [], OrderedDict()
|
self.changes, self.headers, self.images = [], [], OrderedDict()
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
self.diff_backgrounds = {
|
self.diff_backgrounds = {
|
||||||
@ -142,12 +147,60 @@ class TextBrowser(PlainTextEdit): # {{{
|
|||||||
if len(m.actions()) > 0:
|
if len(m.actions()) > 0:
|
||||||
m.exec_(self.mapToGlobal(pos))
|
m.exec_(self.mapToGlobal(pos))
|
||||||
|
|
||||||
|
def search(self, query, reverse=False):
|
||||||
|
if not query.strip():
|
||||||
|
return
|
||||||
|
c = self.textCursor()
|
||||||
|
lnum = c.block().blockNumber()
|
||||||
|
cpos = c.positionInBlock()
|
||||||
|
headers = dict(self.headers)
|
||||||
|
if lnum in headers:
|
||||||
|
cpos = self.search_header_pos
|
||||||
|
lines = unicode(self.toPlainText()).splitlines()
|
||||||
|
for hn, text in self.headers:
|
||||||
|
lines[hn] = text
|
||||||
|
prefix, postfix = lines[lnum][:cpos], lines[lnum][cpos:]
|
||||||
|
before, after = enumerate(lines[0:lnum]), ((lnum+1+i, x) for i, x in enumerate(lines[lnum+1:]))
|
||||||
|
if reverse:
|
||||||
|
sl = chain([(lnum, prefix)], reversed(tuple(before)), reversed(tuple(after)), [(lnum, postfix)])
|
||||||
|
else:
|
||||||
|
sl = chain([(lnum, postfix)], after, before, [(lnum, prefix)])
|
||||||
|
flags = regex.REVERSE if reverse else 0
|
||||||
|
pat = regex.compile(regex.escape(query, special_only=True), flags=regex.UNICODE|regex.IGNORECASE|flags)
|
||||||
|
for num, text in sl:
|
||||||
|
try:
|
||||||
|
m = next(pat.finditer(text))
|
||||||
|
except StopIteration:
|
||||||
|
continue
|
||||||
|
start, end = m.span()
|
||||||
|
length = end - start
|
||||||
|
if text is postfix:
|
||||||
|
start += cpos
|
||||||
|
c = QTextCursor(self.document().findBlockByNumber(num))
|
||||||
|
c.setPosition(c.position() + start)
|
||||||
|
if num in headers:
|
||||||
|
self.search_header_pos = start + length
|
||||||
|
else:
|
||||||
|
c.setPosition(c.position() + length, c.KeepAnchor)
|
||||||
|
self.search_header_pos = 0
|
||||||
|
if reverse:
|
||||||
|
pos, anchor = c.position(), c.anchor()
|
||||||
|
c.setPosition(pos), c.setPosition(anchor, c.KeepAnchor)
|
||||||
|
self.setTextCursor(c)
|
||||||
|
self.centerCursor()
|
||||||
|
self.scrolled.emit()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
info_dialog(self, _('No matches found'), _(
|
||||||
|
'No matches found for query: %s' % query), show=True)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
PlainTextEdit.clear(self)
|
PlainTextEdit.clear(self)
|
||||||
self.line_number_map.clear()
|
self.line_number_map.clear()
|
||||||
del self.changes[:]
|
del self.changes[:]
|
||||||
del self.headers[:]
|
del self.headers[:]
|
||||||
self.images.clear()
|
self.images.clear()
|
||||||
|
self.search_header_pos = 0
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
|
|
||||||
def update_line_number_area_width(self, block_count=0):
|
def update_line_number_area_width(self, block_count=0):
|
||||||
@ -799,10 +852,11 @@ class DiffView(QWidget): # {{{
|
|||||||
self.bars.append(bar)
|
self.bars.append(bar)
|
||||||
bar.valueChanged[int].connect(partial(self.scrolled, i))
|
bar.valueChanged[int].connect(partial(self.scrolled, i))
|
||||||
self.view.left.resized.connect(self.resized)
|
self.view.left.resized.connect(self.resized)
|
||||||
for v in self.view.left, self.view.right, self.view.handle(1):
|
for i, v in enumerate((self.view.left, self.view.right, self.view.handle(1))):
|
||||||
v.wheel_event.connect(self.scrollbar.wheelEvent)
|
v.wheel_event.connect(self.scrollbar.wheelEvent)
|
||||||
if hasattr(v, 'goto_change'):
|
if i < 2:
|
||||||
v.goto_change.connect(self.goto_change)
|
v.goto_change.connect(self.goto_change)
|
||||||
|
v.scrolled.connect(partial(self.scrolled, i + 1))
|
||||||
|
|
||||||
def goto_change(self, change):
|
def goto_change(self, change):
|
||||||
for v in (self.view.left, self.view.right):
|
for v in (self.view.left, self.view.right):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user