Merge branch 'marks' of https://github.com/rcorre/qutebrowser into rcorre-marks
This commit is contained in:
commit
ef91fa3821
@ -160,6 +160,7 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* Claude
|
* Claude
|
||||||
* meles5
|
* meles5
|
||||||
* Tarcisio Fedrizzi
|
* Tarcisio Fedrizzi
|
||||||
|
* Ryan Roden-Corrent
|
||||||
* Artur Shaik
|
* Artur Shaik
|
||||||
* Nathan Isom
|
* Nathan Isom
|
||||||
* Thorsten Wißmann
|
* Thorsten Wißmann
|
||||||
@ -183,7 +184,6 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* Zach-Button
|
* Zach-Button
|
||||||
* Halfwit
|
* Halfwit
|
||||||
* rikn00
|
* rikn00
|
||||||
* Ryan Roden-Corrent
|
|
||||||
* Michael Ilsaas
|
* Michael Ilsaas
|
||||||
* Martin Zimmermann
|
* Martin Zimmermann
|
||||||
* Brian Jackson
|
* Brian Jackson
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
|<<home,home>>|Open main startpage in current tab.
|
|<<home,home>>|Open main startpage in current tab.
|
||||||
|<<inspector,inspector>>|Toggle the web inspector.
|
|<<inspector,inspector>>|Toggle the web inspector.
|
||||||
|<<jseval,jseval>>|Evaluate a JavaScript string.
|
|<<jseval,jseval>>|Evaluate a JavaScript string.
|
||||||
|
|<<jump-mark,jump-mark>>|Jump to the mark named by `key`.
|
||||||
|<<later,later>>|Execute a command after some time.
|
|<<later,later>>|Execute a command after some time.
|
||||||
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|
||||||
|<<open,open>>|Open a URL in the current/[count]th tab.
|
|<<open,open>>|Open a URL in the current/[count]th tab.
|
||||||
@ -50,6 +51,7 @@
|
|||||||
|<<session-save,session-save>>|Save a session.
|
|<<session-save,session-save>>|Save a session.
|
||||||
|<<set,set>>|Set an option.
|
|<<set,set>>|Set an option.
|
||||||
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|
||||||
|
|<<set-mark,set-mark>>|Set a mark at the current scroll position in the current tab.
|
||||||
|<<spawn,spawn>>|Spawn a command in a shell.
|
|<<spawn,spawn>>|Spawn a command in a shell.
|
||||||
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|
||||||
|<<tab-clone,tab-clone>>|Duplicate the current tab.
|
|<<tab-clone,tab-clone>>|Duplicate the current tab.
|
||||||
@ -377,6 +379,15 @@ Evaluate a JavaScript string.
|
|||||||
* This command does not split arguments after the last argument and handles quotes literally.
|
* This command does not split arguments after the last argument and handles quotes literally.
|
||||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||||
|
|
||||||
|
[[jump-mark]]
|
||||||
|
=== jump-mark
|
||||||
|
Syntax: +:jump-mark 'key'+
|
||||||
|
|
||||||
|
Jump to the mark named by `key`.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'key'+: mark identifier; capital indicates a global mark
|
||||||
|
|
||||||
[[later]]
|
[[later]]
|
||||||
=== later
|
=== later
|
||||||
Syntax: +:later 'ms' 'command'+
|
Syntax: +:later 'ms' 'command'+
|
||||||
@ -649,6 +660,15 @@ Preset the statusbar to some text.
|
|||||||
* This command does not split arguments after the last argument and handles quotes literally.
|
* This command does not split arguments after the last argument and handles quotes literally.
|
||||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||||
|
|
||||||
|
[[set-mark]]
|
||||||
|
=== set-mark
|
||||||
|
Syntax: +:set-mark 'key'+
|
||||||
|
|
||||||
|
Set a mark at the current scroll position in the current tab.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'key'+: mark identifier; capital indicates a global mark
|
||||||
|
|
||||||
[[spawn]]
|
[[spawn]]
|
||||||
=== spawn
|
=== spawn
|
||||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
|
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
|
||||||
@ -1244,7 +1264,7 @@ Scroll the current tab by 'count * dx/dy' pixels.
|
|||||||
|
|
||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'dx'+: How much to scroll in x-direction.
|
* +'dx'+: How much to scroll in x-direction.
|
||||||
* +'dy'+: How much to scroll in x-direction.
|
* +'dy'+: How much to scroll in y-direction.
|
||||||
|
|
||||||
==== count
|
==== count
|
||||||
multiplier
|
multiplier
|
||||||
|
@ -472,6 +472,9 @@ class CommandDispatcher:
|
|||||||
bg: Open in a background tab.
|
bg: Open in a background tab.
|
||||||
window: Open in a new window.
|
window: Open in a new window.
|
||||||
"""
|
"""
|
||||||
|
# save the pre-jump position in the special ' mark
|
||||||
|
self.set_mark("'")
|
||||||
|
|
||||||
cmdutils.check_exclusive((tab, bg, window), 'tbw')
|
cmdutils.check_exclusive((tab, bg, window), 'tbw')
|
||||||
widget = self._current_widget()
|
widget = self._current_widget()
|
||||||
frame = widget.page().currentFrame()
|
frame = widget.page().currentFrame()
|
||||||
@ -500,7 +503,7 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
dx: How much to scroll in x-direction.
|
dx: How much to scroll in x-direction.
|
||||||
dy: How much to scroll in x-direction.
|
dy: How much to scroll in y-direction.
|
||||||
count: multiplier
|
count: multiplier
|
||||||
"""
|
"""
|
||||||
dx *= count
|
dx *= count
|
||||||
@ -582,6 +585,9 @@ class CommandDispatcher:
|
|||||||
horizontal: Scroll horizontally instead of vertically.
|
horizontal: Scroll horizontally instead of vertically.
|
||||||
count: Percentage to scroll.
|
count: Percentage to scroll.
|
||||||
"""
|
"""
|
||||||
|
# save the pre-jump position in the special ' mark
|
||||||
|
self.set_mark("'")
|
||||||
|
|
||||||
if perc is None and count is None:
|
if perc is None and count is None:
|
||||||
perc = 100
|
perc = 100
|
||||||
elif perc is None:
|
elif perc is None:
|
||||||
@ -1414,6 +1420,7 @@ class CommandDispatcher:
|
|||||||
text: The text to search for.
|
text: The text to search for.
|
||||||
reverse: Reverse search direction.
|
reverse: Reverse search direction.
|
||||||
"""
|
"""
|
||||||
|
self.set_mark("'")
|
||||||
view = self._current_widget()
|
view = self._current_widget()
|
||||||
self._clear_search(view, text)
|
self._clear_search(view, text)
|
||||||
flags = 0
|
flags = 0
|
||||||
@ -1444,6 +1451,7 @@ class CommandDispatcher:
|
|||||||
Args:
|
Args:
|
||||||
count: How many elements to ignore.
|
count: How many elements to ignore.
|
||||||
"""
|
"""
|
||||||
|
self.set_mark("'")
|
||||||
view = self._current_widget()
|
view = self._current_widget()
|
||||||
|
|
||||||
self._clear_search(view, self._tabbed_browser.search_text)
|
self._clear_search(view, self._tabbed_browser.search_text)
|
||||||
@ -1464,6 +1472,7 @@ class CommandDispatcher:
|
|||||||
Args:
|
Args:
|
||||||
count: How many elements to ignore.
|
count: How many elements to ignore.
|
||||||
"""
|
"""
|
||||||
|
self.set_mark("'")
|
||||||
view = self._current_widget()
|
view = self._current_widget()
|
||||||
self._clear_search(view, self._tabbed_browser.search_text)
|
self._clear_search(view, self._tabbed_browser.search_text)
|
||||||
|
|
||||||
@ -1882,3 +1891,21 @@ class CommandDispatcher:
|
|||||||
self.openurl, bg=bg, tab=tab, window=window, count=count))
|
self.openurl, bg=bg, tab=tab, window=window, count=count))
|
||||||
|
|
||||||
ed.edit(url or self._current_url().toString())
|
ed.edit(url or self._current_url().toString())
|
||||||
|
|
||||||
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
|
def set_mark(self, key):
|
||||||
|
"""Set a mark at the current scroll position in the current tab.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: mark identifier; capital indicates a global mark
|
||||||
|
"""
|
||||||
|
self._tabbed_browser.set_mark(key)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
|
def jump_mark(self, key):
|
||||||
|
"""Jump to the mark named by `key`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: mark identifier; capital indicates a global mark
|
||||||
|
"""
|
||||||
|
self._tabbed_browser.jump_mark(key)
|
||||||
|
@ -466,6 +466,13 @@ class HintManager(QObject):
|
|||||||
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
|
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
|
||||||
Qt.NoButton, modifiers),
|
Qt.NoButton, modifiers),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if context.target in [Target.normal, Target.current]:
|
||||||
|
# Set the pre-jump mark ', so we can jump back here after following
|
||||||
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
|
window=self._win_id)
|
||||||
|
tabbed_browser.set_mark("'")
|
||||||
|
|
||||||
if context.target == Target.current:
|
if context.target == Target.current:
|
||||||
elem.remove_blank_target()
|
elem.remove_blank_target()
|
||||||
for evt in events:
|
for evt in events:
|
||||||
|
@ -1442,6 +1442,8 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('search-prev', ['N']),
|
('search-prev', ['N']),
|
||||||
('enter-mode insert', ['i']),
|
('enter-mode insert', ['i']),
|
||||||
('enter-mode caret', ['v']),
|
('enter-mode caret', ['v']),
|
||||||
|
('enter-mode set_mark', ['`']),
|
||||||
|
('enter-mode jump_mark', ["'"]),
|
||||||
('yank', ['yy']),
|
('yank', ['yy']),
|
||||||
('yank -s', ['yY']),
|
('yank -s', ['yY']),
|
||||||
('yank -t', ['yt']),
|
('yank -t', ['yt']),
|
||||||
|
@ -67,9 +67,13 @@ class BaseKeyParser(QObject):
|
|||||||
Signals:
|
Signals:
|
||||||
keystring_updated: Emitted when the keystring is updated.
|
keystring_updated: Emitted when the keystring is updated.
|
||||||
arg: New keystring.
|
arg: New keystring.
|
||||||
|
request_leave: Emitted to request leaving a mode.
|
||||||
|
arg 0: Mode to leave.
|
||||||
|
arg 1: Reason for leaving.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
keystring_updated = pyqtSignal(str)
|
keystring_updated = pyqtSignal(str)
|
||||||
|
request_leave = pyqtSignal(usertypes.KeyMode, str)
|
||||||
do_log = True
|
do_log = True
|
||||||
passthrough = False
|
passthrough = False
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QObject, QEvent
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from qutebrowser.keyinput import modeparsers, keyparser
|
from qutebrowser.keyinput import modeparsers, keyparser
|
||||||
@ -78,6 +78,8 @@ def init(win_id, parent):
|
|||||||
warn=False),
|
warn=False),
|
||||||
KM.yesno: modeparsers.PromptKeyParser(win_id, modeman),
|
KM.yesno: modeparsers.PromptKeyParser(win_id, modeman),
|
||||||
KM.caret: modeparsers.CaretKeyParser(win_id, modeman),
|
KM.caret: modeparsers.CaretKeyParser(win_id, modeman),
|
||||||
|
KM.set_mark: modeparsers.MarkKeyParser(win_id, KM.set_mark, modeman),
|
||||||
|
KM.jump_mark: modeparsers.MarkKeyParser(win_id, KM.jump_mark, modeman),
|
||||||
}
|
}
|
||||||
objreg.register('keyparsers', keyparsers, scope='window', window=win_id)
|
objreg.register('keyparsers', keyparsers, scope='window', window=win_id)
|
||||||
modeman.destroyed.connect(
|
modeman.destroyed.connect(
|
||||||
@ -223,6 +225,7 @@ class ModeManager(QObject):
|
|||||||
assert isinstance(mode, usertypes.KeyMode)
|
assert isinstance(mode, usertypes.KeyMode)
|
||||||
assert parser is not None
|
assert parser is not None
|
||||||
self._parsers[mode] = parser
|
self._parsers[mode] = parser
|
||||||
|
parser.request_leave.connect(self.leave)
|
||||||
|
|
||||||
def enter(self, mode, reason=None, only_if_normal=False):
|
def enter(self, mode, reason=None, only_if_normal=False):
|
||||||
"""Enter a new mode.
|
"""Enter a new mode.
|
||||||
@ -268,6 +271,7 @@ class ModeManager(QObject):
|
|||||||
raise cmdexc.CommandError("Mode {} does not exist!".format(mode))
|
raise cmdexc.CommandError("Mode {} does not exist!".format(mode))
|
||||||
self.enter(m, 'command')
|
self.enter(m, 'command')
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
def leave(self, mode, reason=None):
|
def leave(self, mode, reason=None):
|
||||||
"""Leave a key mode.
|
"""Leave a key mode.
|
||||||
|
|
||||||
|
@ -230,3 +230,55 @@ class CaretKeyParser(keyparser.CommandKeyParser):
|
|||||||
super().__init__(win_id, parent, supports_count=True,
|
super().__init__(win_id, parent, supports_count=True,
|
||||||
supports_chains=True)
|
supports_chains=True)
|
||||||
self.read_config('caret')
|
self.read_config('caret')
|
||||||
|
|
||||||
|
|
||||||
|
class MarkKeyParser(keyparser.BaseKeyParser):
|
||||||
|
|
||||||
|
"""KeyParser for set_mark and jump_mark mode.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_mode: Either KeyMode.set_mark or KeyMode.jump_mark.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, win_id, mode, parent=None):
|
||||||
|
super().__init__(win_id, parent, supports_count=False,
|
||||||
|
supports_chains=False)
|
||||||
|
self._mode = mode
|
||||||
|
|
||||||
|
def handle(self, e):
|
||||||
|
"""Override handle to always match the next key and create a mark.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
e: the KeyPressEvent from Qt.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if event has been handled, False otherwise.
|
||||||
|
"""
|
||||||
|
if utils.keyevent_to_string(e) is None:
|
||||||
|
# this is a modifier key, let it pass and keep going
|
||||||
|
return False
|
||||||
|
|
||||||
|
key = e.text()
|
||||||
|
|
||||||
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
|
window=self._win_id)
|
||||||
|
|
||||||
|
if self._mode == usertypes.KeyMode.set_mark:
|
||||||
|
tabbed_browser.set_mark(key)
|
||||||
|
elif self._mode == usertypes.KeyMode.jump_mark:
|
||||||
|
tabbed_browser.jump_mark(key)
|
||||||
|
else:
|
||||||
|
raise ValueError("{} is not a valid mark mode".format(self._mode))
|
||||||
|
|
||||||
|
self.request_leave.emit(self._mode, "valid mark key")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def on_keyconfig_changed(self, mode):
|
||||||
|
"""MarkKeyParser has no config section (no bindable keys)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute(self, cmdstr, _keytype, count=None):
|
||||||
|
"""Should never be called on MarkKeyParser."""
|
||||||
|
assert False
|
||||||
|
@ -30,7 +30,8 @@ from qutebrowser.config import config
|
|||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
from qutebrowser.mainwindow import tabwidget
|
from qutebrowser.mainwindow import tabwidget
|
||||||
from qutebrowser.browser import signalfilter, webview
|
from qutebrowser.browser import signalfilter, webview
|
||||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg, urlutils
|
from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
|
||||||
|
urlutils, message)
|
||||||
|
|
||||||
|
|
||||||
UndoEntry = collections.namedtuple('UndoEntry', ['url', 'history'])
|
UndoEntry = collections.namedtuple('UndoEntry', ['url', 'history'])
|
||||||
@ -64,6 +65,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
_tab_insert_idx_right: Same as above, for 'right'.
|
_tab_insert_idx_right: Same as above, for 'right'.
|
||||||
_undo_stack: List of UndoEntry namedtuples of closed tabs.
|
_undo_stack: List of UndoEntry namedtuples of closed tabs.
|
||||||
shutting_down: Whether we're currently shutting down.
|
shutting_down: Whether we're currently shutting down.
|
||||||
|
_local_marks: Jump markers local to each page
|
||||||
|
_global_marks: Jump markers used across all pages
|
||||||
|
|
||||||
Signals:
|
Signals:
|
||||||
cur_progress: Progress of the current tab changed (loadProgress).
|
cur_progress: Progress of the current tab changed (loadProgress).
|
||||||
@ -114,6 +117,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
self._now_focused = None
|
self._now_focused = None
|
||||||
self.search_text = None
|
self.search_text = None
|
||||||
self.search_flags = 0
|
self.search_flags = 0
|
||||||
|
self._local_marks = {}
|
||||||
|
self._global_marks = {}
|
||||||
objreg.get('config').changed.connect(self.update_favicons)
|
objreg.get('config').changed.connect(self.update_favicons)
|
||||||
objreg.get('config').changed.connect(self.update_window_title)
|
objreg.get('config').changed.connect(self.update_window_title)
|
||||||
objreg.get('config').changed.connect(self.update_tab_titles)
|
objreg.get('config').changed.connect(self.update_tab_titles)
|
||||||
@ -637,3 +642,53 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
self._now_focused.wheelEvent(e)
|
self._now_focused.wheelEvent(e)
|
||||||
else:
|
else:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
|
|
||||||
|
def set_mark(self, key):
|
||||||
|
"""Set a mark at the current scroll position in the current tab.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: mark identifier; capital indicates a global mark
|
||||||
|
"""
|
||||||
|
# strip the fragment as it may interfere with scrolling
|
||||||
|
url = self.current_url().adjusted(QUrl.RemoveFragment)
|
||||||
|
point = self.currentWidget().page().currentFrame().scrollPosition()
|
||||||
|
|
||||||
|
if key.isupper():
|
||||||
|
self._global_marks[key] = point, url
|
||||||
|
else:
|
||||||
|
if url not in self._local_marks:
|
||||||
|
self._local_marks[url] = {}
|
||||||
|
self._local_marks[url][key] = point
|
||||||
|
|
||||||
|
def jump_mark(self, key):
|
||||||
|
"""Jump to the mark named by `key`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: mark identifier; capital indicates a global mark
|
||||||
|
"""
|
||||||
|
# consider urls that differ only in fragment to be identical
|
||||||
|
urlkey = self.current_url().adjusted(QUrl.RemoveFragment)
|
||||||
|
frame = self.currentWidget().page().currentFrame()
|
||||||
|
|
||||||
|
if key.isupper() and key in self._global_marks:
|
||||||
|
point, url = self._global_marks[key]
|
||||||
|
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def callback(ok):
|
||||||
|
if ok:
|
||||||
|
self.cur_load_finished.disconnect(callback)
|
||||||
|
frame.setScrollPosition(point)
|
||||||
|
|
||||||
|
self.openurl(url, newtab=False)
|
||||||
|
self.cur_load_finished.connect(callback)
|
||||||
|
elif urlkey in self._local_marks and key in self._local_marks[urlkey]:
|
||||||
|
point = self._local_marks[urlkey][key]
|
||||||
|
|
||||||
|
# save the pre-jump position in the special ' mark
|
||||||
|
# this has to happen after we read the mark, otherwise jump_mark
|
||||||
|
# "'" would just jump to the current position every time
|
||||||
|
self.set_mark("'")
|
||||||
|
|
||||||
|
frame.setScrollPosition(point)
|
||||||
|
else:
|
||||||
|
message.error(self._win_id, "Mark {} is not set".format(key))
|
||||||
|
@ -231,7 +231,8 @@ ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window'])
|
|||||||
|
|
||||||
# Key input modes
|
# Key input modes
|
||||||
KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
|
KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
|
||||||
'insert', 'passthrough', 'caret'])
|
'insert', 'passthrough', 'caret', 'set_mark',
|
||||||
|
'jump_mark'])
|
||||||
|
|
||||||
|
|
||||||
# Available command completions
|
# Available command completions
|
||||||
|
16
tests/integration/data/marks.html
Normal file
16
tests/integration/data/marks.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Marks I</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="top">Top</h1>
|
||||||
|
<a href="#top">Top</a>
|
||||||
|
<a href="#bottom">Bottom</a>
|
||||||
|
<div style="height: 3000px; width: 3000px;">Holy Grail</div>
|
||||||
|
<div style="height: 3000px; width: 3000px;">Waldo</div>
|
||||||
|
<div style="height: 3000px; width: 3000px;">Holy Grail</div>
|
||||||
|
<h1 id="bottom">Bottom</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
91
tests/integration/features/marks.feature
Normal file
91
tests/integration/features/marks.feature
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
Feature: Setting positional marks
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given I open data/marks.html
|
||||||
|
And I run :tab-only
|
||||||
|
|
||||||
|
## :set-mark, :jump-mark
|
||||||
|
|
||||||
|
Scenario: Setting and jumping to a local mark
|
||||||
|
When I run :scroll-px 5 10
|
||||||
|
And I run :set-mark a
|
||||||
|
And I run :scroll-px 0 20
|
||||||
|
And I run :jump-mark a
|
||||||
|
Then the page should be scrolled to 5 10
|
||||||
|
|
||||||
|
Scenario: Jumping back after jumping to a particular percentage
|
||||||
|
When I run :scroll-px 10 20
|
||||||
|
And I run :scroll-perc 100
|
||||||
|
And I run :jump-mark "'"
|
||||||
|
Then the page should be scrolled to 10 20
|
||||||
|
|
||||||
|
Scenario: Setting the same local mark on another page
|
||||||
|
When I run :scroll-px 5 10
|
||||||
|
And I run :set-mark a
|
||||||
|
And I open data/marks.html
|
||||||
|
And I run :scroll-px 0 20
|
||||||
|
And I run :set-mark a
|
||||||
|
And I run :jump-mark a
|
||||||
|
Then the page should be scrolled to 0 20
|
||||||
|
|
||||||
|
Scenario: Jumping to a local mark after returning to a page
|
||||||
|
When I run :scroll-px 5 10
|
||||||
|
And I run :set-mark a
|
||||||
|
And I open data/numbers/1.txt
|
||||||
|
And I run :set-mark a
|
||||||
|
And I open data/marks.html
|
||||||
|
And I run :jump-mark a
|
||||||
|
Then the page should be scrolled to 5 10
|
||||||
|
|
||||||
|
Scenario: Setting and jumping to a global mark
|
||||||
|
When I run :scroll-px 5 20
|
||||||
|
And I run :set-mark A
|
||||||
|
And I open data/numbers/1.txt
|
||||||
|
And I run :jump-mark A
|
||||||
|
Then data/marks.html should be loaded
|
||||||
|
And the page should be scrolled to 5 20
|
||||||
|
|
||||||
|
Scenario: Jumping to an unset mark
|
||||||
|
When I run :jump-mark b
|
||||||
|
Then the error "Mark b is not set" should be shown
|
||||||
|
|
||||||
|
Scenario: Jumping to a local mark that was set on another page
|
||||||
|
When I run :set-mark b
|
||||||
|
And I open data/numbers/1.txt
|
||||||
|
And I run :jump-mark b
|
||||||
|
Then the error "Mark b is not set" should be shown
|
||||||
|
|
||||||
|
Scenario: Jumping to a local mark after changing fragments
|
||||||
|
When I open data/marks.html#top
|
||||||
|
And I run :scroll 'top'
|
||||||
|
And I run :scroll-px 10 10
|
||||||
|
And I run :set-mark a
|
||||||
|
When I open data/marks.html#bottom
|
||||||
|
And I run :jump-mark a
|
||||||
|
Then the page should be scrolled to 10 10
|
||||||
|
|
||||||
|
Scenario: Jumping back after following a link
|
||||||
|
When I run :hint links normal
|
||||||
|
And I run :follow-hint s
|
||||||
|
And I run :jump-mark "'"
|
||||||
|
Then the page should be scrolled to 0 0
|
||||||
|
|
||||||
|
Scenario: Jumping back after searching
|
||||||
|
When I run :scroll-px 20 15
|
||||||
|
And I run :search Waldo
|
||||||
|
And I run :jump-mark "'"
|
||||||
|
Then the page should be scrolled to 20 15
|
||||||
|
|
||||||
|
Scenario: Jumping back after search-next
|
||||||
|
When I run :search Grail
|
||||||
|
And I run :search-next
|
||||||
|
And I run :jump-mark "'"
|
||||||
|
Then the page should be scrolled to 0 0
|
||||||
|
|
||||||
|
Scenario: Hovering a hint does not set the ' mark
|
||||||
|
When I run :scroll-px 30 20
|
||||||
|
And I run :scroll-perc 0
|
||||||
|
And I run :hint links hover
|
||||||
|
And I run :follow-hint s
|
||||||
|
And I run :jump-mark "'"
|
||||||
|
Then the page should be scrolled to 30 20
|
29
tests/integration/features/test_marks.py
Normal file
29
tests/integration/features/test_marks.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2016 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
|
||||||
|
#
|
||||||
|
# This file is part of qutebrowser.
|
||||||
|
#
|
||||||
|
# qutebrowser is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# qutebrowser is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import pytest_bdd as bdd
|
||||||
|
bdd.scenarios('marks.feature')
|
||||||
|
|
||||||
|
|
||||||
|
@bdd.then(bdd.parsers.parse("the page should be scrolled to {x} {y}"))
|
||||||
|
def check_y(quteproc, x, y):
|
||||||
|
data = quteproc.get_session()
|
||||||
|
pos = data['windows'][0]['tabs'][0]['history'][-1]['scroll-pos']
|
||||||
|
assert int(x) == pos['x']
|
||||||
|
assert int(y) == pos['y']
|
@ -22,14 +22,17 @@ import pytest
|
|||||||
from qutebrowser.keyinput import modeman as modeman_module
|
from qutebrowser.keyinput import modeman as modeman_module
|
||||||
from qutebrowser.utils import usertypes
|
from qutebrowser.utils import usertypes
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt, QObject, pyqtSignal
|
||||||
|
|
||||||
|
|
||||||
class FakeKeyparser:
|
class FakeKeyparser(QObject):
|
||||||
|
|
||||||
"""A fake BaseKeyParser which doesn't handle anything."""
|
"""A fake BaseKeyParser which doesn't handle anything."""
|
||||||
|
|
||||||
|
request_leave = pyqtSignal(usertypes.KeyMode, str)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self.passthrough = False
|
self.passthrough = False
|
||||||
|
|
||||||
def handle(self, evt):
|
def handle(self, evt):
|
||||||
|
Loading…
Reference in New Issue
Block a user