diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py
index bb744db81..a0027aa94 100644
--- a/qutebrowser/browser/curcommand.py
+++ b/qutebrowser/browser/curcommand.py
@@ -222,6 +222,26 @@ class CurCommandDispatcher(QObject):
"""Fire a completed hint."""
self._tabs.currentWidget().hintmanager.fire(keystr)
+ @cmdutils.register(instance='mainwindow.tabs.cur')
+ def prev_page(self):
+ """Click on a "previous" link."""
+ widget = self._tabs.currentWidget()
+ frame = widget.page_.currentFrame()
+ if frame is None:
+ message.error("No frame focused!")
+ return
+ widget.hintmanager.click_prevnext(frame, prev=True)
+
+ @cmdutils.register(instance='mainwindow.tabs.cur')
+ def next_page(self):
+ """Click on a "next" link."""
+ widget = self._tabs.currentWidget()
+ frame = widget.page_.currentFrame()
+ if frame is None:
+ message.error("No frame focused!")
+ return
+ widget.hintmanager.click_prevnext(frame, prev=False)
+
@pyqtSlot(str, int)
def search(self, text, flags):
"""Search for text in the current page.
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py
index 37319bdf0..716d9d565 100644
--- a/qutebrowser/browser/hints.py
+++ b/qutebrowser/browser/hints.py
@@ -207,19 +207,24 @@ class HintManager(QObject):
css, string))
return doc.lastChild()
- def _click(self, elem):
+ def _click(self, elem, target=None, frame=None):
"""Click an element.
Args:
elem: The QWebElement to click.
+ target_override: Target which overrides self._target
"""
- if self._target == 'rapid':
+ if target is not None:
+ pass
+ elif self._target == 'rapid':
target = 'bgtab'
else:
target = self._target
+ if frame is None:
+ frame = self._frame
self.set_open_target.emit(target)
point = elem.geometry().topLeft()
- scrollpos = self._frame.scrollPosition()
+ scrollpos = frame.scrollPosition()
logging.debug("Clicking on \"{}\" at {}/{} - {}/{}".format(
elem.toPlainText(), point.x(), point.y(), scrollpos.x(),
scrollpos.y()))
@@ -281,22 +286,23 @@ class HintManager(QObject):
def click_prevnext(self, frame, prev=False):
"""Click a "previous"/"next" element on the page."""
# First check for
- self.target='normal'
elems = frame.findAllElements(webelem.SELECTORS['prevnext_rel'])
rel_values = ['prev', 'previous'] if prev else ['next']
for e in elems:
if e.attribute('rel') in rel_values:
- self.click(e)
+ self._click(e, 'normal', frame)
return
# Then check for regular links
elems = frame.findAllElements(webelem.SELECTORS['prevnext'])
option = 'prev-regexes' if prev else 'next-regexes'
- for regex in config.get('hints', option):
- for e in elems:
- if regex.match(e.toPlainText()):
- self.click(e)
- return
- message.error("No prev/forward links found!")
+ if elems:
+ for regex in config.get('hints', option):
+ for e in elems:
+ if regex.match(e.toPlainText()):
+ self._click(e, 'normal', frame)
+ return
+ message.error("No {} links found!".format("prev" if prev
+ else "forward"))
def start(self, frame, baseurl, mode='all', target='normal'):
diff --git a/qutebrowser/config/_conftypes.py b/qutebrowser/config/_conftypes.py
index d4c458591..cc6c36b00 100644
--- a/qutebrowser/config/_conftypes.py
+++ b/qutebrowser/config/_conftypes.py
@@ -17,7 +17,9 @@
"""Setting options used for qutebrowser."""
+import re
import shlex
+from sre_constants import error as RegexError
from PyQt5.QtGui import QColor
@@ -511,6 +513,42 @@ class KeyBindingName(BaseType):
pass
+class Regex(BaseType):
+
+ """A regular expression."""
+
+ def __init__(self, flags=0):
+ self.flags = flags
+
+ def validate(self, value):
+ try:
+ re.compile(value, self.flags)
+ except RegexError as e:
+ raise ValidationError(value, "must be a valid regex - " + str(e))
+
+ def transform(self, value):
+ return re.compile(value, self.flags)
+
+
+class RegexList(List):
+
+ """A list of regexes."""
+
+ def __init__(self, flags=0):
+ self.flags=flags
+
+ def transform(self, value):
+ vals = super().transform(value)
+ return [re.compile(pattern, self.flags) for pattern in vals]
+
+ def validate(self, value):
+ try:
+ self.transform(value)
+ except RegexError as e:
+ raise ValidationError(value, "must be a list valid regexes - " +
+ str(e))
+
+
class AutoSearch(BaseType):
"""Whether to start a search when something else than an URL is entered."""
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index b5af4337f..bbcee95c5 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -24,6 +24,7 @@ SECTION_DESC: A dictionary with descriptions for sections.
DATA: The config defaults, an OrderedDict of sections.
"""
+import re
from collections import OrderedDict
from qutebrowser.config._value import SettingValue
@@ -397,6 +398,18 @@ DATA = OrderedDict([
('auto-follow',
SettingValue(types.Bool(), 'true'),
"Whether to auto-follow a hint if there's only one left."),
+
+ ('next-regexes',
+ SettingValue(types.RegexList(flags=re.IGNORECASE),
+ r'\bnext\b,\bmore\b,\bnewer\b,^>$$,^(>>|»|→|≫)$$,'
+ r'^(>|»|→|≫),(>|»|→|≫)$$'),
+ "A comma-separated list of regexes to use for 'next' links."),
+
+ ('prev-regexes',
+ SettingValue(types.RegexList(flags=re.IGNORECASE),
+ r'\bprev(ious)\b,\bback\b,\bolder\b,^<$$,^(<<|«|←|≪)$$,'
+ r'^(<|«|←|≪),(<|«|←|≪)$$'),
+ "A comma-separated list of regexes to use for 'prev' links."),
)),
('searchengines', sect.ValueList(
@@ -456,6 +469,8 @@ DATA = OrderedDict([
('PP', 'tabpaste sel'),
('-', 'zoomout'),
('+', 'zoomin'),
+ ('[[', 'prev_page'),
+ (']]', 'next_page'),
('', 'enter_mode passthrough'),
('', 'quit'),
('', 'undo'),
diff --git a/qutebrowser/utils/webelem.py b/qutebrowser/utils/webelem.py
index 63faf8d5a..5a0bad7cc 100644
--- a/qutebrowser/utils/webelem.py
+++ b/qutebrowser/utils/webelem.py
@@ -36,8 +36,8 @@ SELECTORS = {
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], textarea'),
'url': '[src], [href]',
- 'prevnext_rel': 'link [role=link]',
- 'prevnext': 'a button [role=button]',
+ 'prevnext_rel': 'link, [role=link]',
+ 'prevnext': 'a, button, [role=button]',
}
SELECTORS['editable_focused'] = ', '.join(