Get rid of SearchRunner.
A SearchRunner was per-mainwindow, which caused bugs when searching in a tab and in another before clearing the search. Instead we now split it between WebView/CommandDispatcher. Fixes #638.
This commit is contained in:
parent
71ffe8f656
commit
ffd1e673b3
@ -1076,3 +1076,71 @@ class CommandDispatcher:
|
||||
elem.evaluateJavaScript("this.value='{}'".format(text))
|
||||
except webelem.IsNullError:
|
||||
raise cmdexc.CommandError("Element vanished while editing!")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
def search(self, text="", reverse=False):
|
||||
"""Search for a text on the current page. With no text, clear results.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
reverse: Reverse search direction.
|
||||
"""
|
||||
view = self._current_widget()
|
||||
if view.search_text is not None and view.search_text != text:
|
||||
# We first clear the marked text, then the highlights
|
||||
view.search('', 0)
|
||||
view.search('', QWebPage.HighlightAllOccurrences)
|
||||
|
||||
flags = 0
|
||||
ignore_case = config.get('general', 'ignore-case')
|
||||
if ignore_case == 'smart':
|
||||
if not text.islower():
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
elif not ignore_case:
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
if config.get('general', 'wrap-search'):
|
||||
flags |= QWebPage.FindWrapsAroundDocument
|
||||
if reverse:
|
||||
flags |= QWebPage.FindBackward
|
||||
# We actually search *twice* - once to highlight everything, then again
|
||||
# to get a mark so we can navigate.
|
||||
view.search(text, flags)
|
||||
view.search(text, flags | QWebPage.HighlightAllOccurrences)
|
||||
view.search_text = text
|
||||
view.search_flags = flags
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
def search_next(self, count: {'special': 'count'}=1):
|
||||
"""Continue the search to the ([count]th) next term.
|
||||
|
||||
Args:
|
||||
count: How many elements to ignore.
|
||||
"""
|
||||
view = self._current_widget()
|
||||
if view.search_text is not None:
|
||||
for _ in range(count):
|
||||
view.search(view.search_text, view.search_flags)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
def search_prev(self, count: {'special': 'count'}=1):
|
||||
"""Continue the search to the ([count]th) previous term.
|
||||
|
||||
Args:
|
||||
count: How many elements to ignore.
|
||||
"""
|
||||
view = self._current_widget()
|
||||
if view.search_text is None:
|
||||
return
|
||||
# The int() here serves as a QFlags constructor to create a copy of the
|
||||
# QFlags instance rather as a reference. I don't know why it works this
|
||||
# way, but it does.
|
||||
flags = int(view.search_flags)
|
||||
if flags & QWebPage.FindBackward:
|
||||
flags &= ~QWebPage.FindBackward
|
||||
else:
|
||||
flags |= QWebPage.FindBackward
|
||||
for _ in range(count):
|
||||
view.search(view.search_text, flags)
|
||||
|
@ -23,7 +23,7 @@ import sys
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QObject
|
||||
from PyQt5.QtWidgets import QApplication, QStyleFactory
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
@ -62,6 +62,8 @@ class WebView(QWebView):
|
||||
registry: The ObjectRegistry associated with this tab.
|
||||
tab_id: The tab ID of the view.
|
||||
win_id: The window ID of the view.
|
||||
search_text: The text of the last search.
|
||||
search_flags: The search flags of the last search.
|
||||
_cur_url: The current URL (accessed via cur_url property).
|
||||
_has_ssl_errors: Whether SSL errors occurred during loading.
|
||||
_zoom: A NeighborList with the zoom levels.
|
||||
@ -102,6 +104,8 @@ class WebView(QWebView):
|
||||
self._zoom = None
|
||||
self._has_ssl_errors = False
|
||||
self.keep_icon = False
|
||||
self.search_text = None
|
||||
self.search_flags = 0
|
||||
self.init_neighborlist()
|
||||
cfg = objreg.get('config')
|
||||
cfg.changed.connect(self.init_neighborlist)
|
||||
@ -436,6 +440,35 @@ class WebView(QWebView):
|
||||
"left.".format(mode))
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
|
||||
def search(self, text, flags):
|
||||
"""Search for text in the current page.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
flags: The QWebPage::FindFlags.
|
||||
"""
|
||||
log.webview.debug("Searching with text '{}' and flags "
|
||||
"0x{:04x}.".format(text, int(flags)))
|
||||
old_scroll_pos = self.scroll_pos
|
||||
flags = QWebPage.FindFlags(flags)
|
||||
found = self.findText(text, flags)
|
||||
if not found and not flags & QWebPage.HighlightAllOccurrences and text:
|
||||
message.error(self.win_id, "Text '{}' not found on "
|
||||
"page!".format(text), immediately=True)
|
||||
else:
|
||||
backward = int(flags) & QWebPage.FindBackward
|
||||
|
||||
def check_scroll_pos():
|
||||
"""Check if the scroll position got smaller and show info."""
|
||||
if not backward and self.scroll_pos < old_scroll_pos:
|
||||
message.info(self.win_id, "Search hit BOTTOM, continuing "
|
||||
"at TOP", immediately=True)
|
||||
elif backward and self.scroll_pos > old_scroll_pos:
|
||||
message.info(self.win_id, "Search hit TOP, continuing at "
|
||||
"BOTTOM", immediately=True)
|
||||
# We first want QWebPage to refresh.
|
||||
QTimer.singleShot(0, check_scroll_pos)
|
||||
|
||||
def createWindow(self, wintype):
|
||||
"""Called by Qt when a page wants to create a new window.
|
||||
|
||||
|
@ -21,8 +21,7 @@
|
||||
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
@ -56,102 +55,6 @@ def replace_variables(win_id, arglist):
|
||||
return args
|
||||
|
||||
|
||||
class SearchRunner(QObject):
|
||||
|
||||
"""Run searches on web pages.
|
||||
|
||||
Attributes:
|
||||
_text: The text from the last search.
|
||||
_flags: The flags from the last search.
|
||||
|
||||
Signals:
|
||||
do_search: Emitted when a search should be started.
|
||||
arg 1: Search string.
|
||||
arg 2: Flags to use.
|
||||
"""
|
||||
|
||||
do_search = pyqtSignal(str, 'QWebPage::FindFlags')
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._text = None
|
||||
self._flags = 0
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, text=self._text, flags=self._flags)
|
||||
|
||||
@pyqtSlot(str)
|
||||
@cmdutils.register(instance='search-runner', scope='window', maxsplit=0)
|
||||
def search(self, text="", reverse=False):
|
||||
"""Search for a text on the current page. With no text, clear results.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
reverse: Reverse search direction.
|
||||
"""
|
||||
if self._text is not None and self._text != text:
|
||||
# We first clear the marked text, then the highlights
|
||||
self.do_search.emit('', 0)
|
||||
self.do_search.emit('', QWebPage.HighlightAllOccurrences)
|
||||
self._text = text
|
||||
self._flags = 0
|
||||
ignore_case = config.get('general', 'ignore-case')
|
||||
if ignore_case == 'smart':
|
||||
if not text.islower():
|
||||
self._flags |= QWebPage.FindCaseSensitively
|
||||
elif not ignore_case:
|
||||
self._flags |= QWebPage.FindCaseSensitively
|
||||
if config.get('general', 'wrap-search'):
|
||||
self._flags |= QWebPage.FindWrapsAroundDocument
|
||||
if reverse:
|
||||
self._flags |= QWebPage.FindBackward
|
||||
# We actually search *twice* - once to highlight everything, then again
|
||||
# to get a mark so we can navigate.
|
||||
self.do_search.emit(self._text, self._flags)
|
||||
self.do_search.emit(self._text, self._flags |
|
||||
QWebPage.HighlightAllOccurrences)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def search_rev(self, text):
|
||||
"""Search for a text on a website in reverse direction.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
"""
|
||||
self.search(text, reverse=True)
|
||||
|
||||
@cmdutils.register(instance='search-runner', hide=True, scope='window')
|
||||
def search_next(self, count: {'special': 'count'}=1):
|
||||
"""Continue the search to the ([count]th) next term.
|
||||
|
||||
Args:
|
||||
count: How many elements to ignore.
|
||||
"""
|
||||
if self._text is not None:
|
||||
for _ in range(count):
|
||||
self.do_search.emit(self._text, self._flags)
|
||||
|
||||
@cmdutils.register(instance='search-runner', hide=True, scope='window')
|
||||
def search_prev(self, count: {'special': 'count'}=1):
|
||||
"""Continue the search to the ([count]th) previous term.
|
||||
|
||||
Args:
|
||||
count: How many elements to ignore.
|
||||
"""
|
||||
if self._text is None:
|
||||
return
|
||||
# The int() here serves as a QFlags constructor to create a copy of the
|
||||
# QFlags instance rather as a reference. I don't know why it works this
|
||||
# way, but it does.
|
||||
flags = int(self._flags)
|
||||
if flags & QWebPage.FindBackward:
|
||||
flags &= ~QWebPage.FindBackward
|
||||
else:
|
||||
flags |= QWebPage.FindBackward
|
||||
for _ in range(count):
|
||||
self.do_search.emit(self._text, flags)
|
||||
|
||||
|
||||
class CommandRunner(QObject):
|
||||
|
||||
"""Parse and run qutebrowser commandline commands.
|
||||
|
@ -113,11 +113,6 @@ class MainWindow(QWidget):
|
||||
|
||||
self._commandrunner = runners.CommandRunner(self.win_id)
|
||||
|
||||
log.init.debug("Initializing search...")
|
||||
search_runner = runners.SearchRunner(self)
|
||||
objreg.register('search-runner', search_runner, scope='window',
|
||||
window=self.win_id)
|
||||
|
||||
log.init.debug("Initializing modes...")
|
||||
modeman.init(self.win_id, self)
|
||||
|
||||
@ -212,7 +207,6 @@ class MainWindow(QWidget):
|
||||
completion_obj = self._get_object('completion')
|
||||
tabs = self._get_object('tabbed-browser')
|
||||
cmd = self._get_object('status-command')
|
||||
search_runner = self._get_object('search-runner')
|
||||
message_bridge = self._get_object('message-bridge')
|
||||
mode_manager = self._get_object('mode-manager')
|
||||
prompter = self._get_object('prompter')
|
||||
@ -231,10 +225,7 @@ class MainWindow(QWidget):
|
||||
keyparsers[usertypes.KeyMode.normal].keystring_updated.connect(
|
||||
status.keystring.setText)
|
||||
cmd.got_cmd.connect(self._commandrunner.run_safely)
|
||||
cmd.got_search.connect(search_runner.search)
|
||||
cmd.got_search_rev.connect(search_runner.search_rev)
|
||||
cmd.returnPressed.connect(tabs.on_cmd_return_pressed)
|
||||
search_runner.do_search.connect(tabs.search)
|
||||
tabs.got_cmd.connect(self._commandrunner.run_safely)
|
||||
|
||||
# config
|
||||
|
@ -39,10 +39,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
Signals:
|
||||
got_cmd: Emitted when a command is triggered by the user.
|
||||
arg: The command string.
|
||||
got_search: Emitted when the user started a new search.
|
||||
arg: The search term.
|
||||
got_rev_search: Emitted when the user started a new reverse search.
|
||||
arg: The search term.
|
||||
clear_completion_selection: Emitted before the completion widget is
|
||||
hidden.
|
||||
hide_completion: Emitted when the completion widget should be hidden.
|
||||
@ -52,8 +48,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
"""
|
||||
|
||||
got_cmd = pyqtSignal(str)
|
||||
got_search = pyqtSignal(str)
|
||||
got_search_rev = pyqtSignal(str)
|
||||
clear_completion_selection = pyqtSignal()
|
||||
hide_completion = pyqtSignal()
|
||||
update_completion = pyqtSignal()
|
||||
@ -167,16 +161,15 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def command_accept(self):
|
||||
"""Execute the command currently in the commandline."""
|
||||
signals = {
|
||||
':': self.got_cmd,
|
||||
'/': self.got_search,
|
||||
'?': self.got_search_rev,
|
||||
prefixes = {
|
||||
':': '',
|
||||
'/': 'search ',
|
||||
'?': 'search -r ',
|
||||
}
|
||||
text = self.text()
|
||||
self.history.append(text)
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept')
|
||||
if text[0] in signals:
|
||||
signals[text[0]].emit(text[1:])
|
||||
self.got_cmd.emit(prefixes[text[0]] + text[1:])
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
|
@ -403,36 +403,6 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
self._tab_insert_idx_right))
|
||||
return idx
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def search(self, text, flags):
|
||||
"""Search for text in the current page.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
flags: The QWebPage::FindFlags.
|
||||
"""
|
||||
log.webview.debug("Searching with text '{}' and flags "
|
||||
"0x{:04x}.".format(text, int(flags)))
|
||||
widget = self.currentWidget()
|
||||
old_scroll_pos = widget.scroll_pos
|
||||
found = widget.findText(text, flags)
|
||||
if not found and not flags & QWebPage.HighlightAllOccurrences and text:
|
||||
message.error(self._win_id, "Text '{}' not found on "
|
||||
"page!".format(text), immediately=True)
|
||||
else:
|
||||
backward = int(flags) & QWebPage.FindBackward
|
||||
|
||||
def check_scroll_pos():
|
||||
"""Check if the scroll position got smaller and show info."""
|
||||
if not backward and widget.scroll_pos < old_scroll_pos:
|
||||
message.info(self._win_id, "Search hit BOTTOM, continuing "
|
||||
"at TOP", immediately=True)
|
||||
elif backward and widget.scroll_pos > old_scroll_pos:
|
||||
message.info(self._win_id, "Search hit TOP, continuing at "
|
||||
"BOTTOM", immediately=True)
|
||||
# We first want QWebPage to refresh.
|
||||
QTimer.singleShot(0, check_scroll_pos)
|
||||
|
||||
@config.change_filter('tabs', 'show-favicons')
|
||||
def update_favicons(self):
|
||||
"""Update favicons when config was changed."""
|
||||
|
Loading…
Reference in New Issue
Block a user