Merge branch 'blyxxyz-clip'
This commit is contained in:
commit
3d4fe5dde1
@ -30,6 +30,8 @@ Added
|
||||
(to report bugs which are difficult to reproduce).
|
||||
- New `hide-unmatched-rapid-hints` option to not hide hint unmatched hint labels
|
||||
in rapid mode.
|
||||
- New `{clipboard}` and `{primary}` replacements for the commandline which
|
||||
replace the `:paste` command.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
@ -65,6 +67,12 @@ Changed
|
||||
- The `:buffer` completion now also filters using the first column (id).
|
||||
- `:undo` has been improved to reopen tabs at the position they were closed.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- The `:paste` command got deprecated as `:open` with `{clipboard}` and
|
||||
`{primary}` can be used instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
|
@ -145,12 +145,12 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Antoni Boucher
|
||||
* Lamar Pavel
|
||||
* Bruno Oliveira
|
||||
* Jan Verbeek
|
||||
* Alexander Cogneau
|
||||
* Marshall Lochbaum
|
||||
* Jakub Klinkovský
|
||||
* Felix Van der Jeugt
|
||||
* Martin Tournoij
|
||||
* Jan Verbeek
|
||||
* Raphael Pierzina
|
||||
* Joel Torstensson
|
||||
* Patric Schmitz
|
||||
|
@ -38,7 +38,6 @@
|
||||
|<<messages,messages>>|Show a log of past messages.
|
||||
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|
||||
|<<open,open>>|Open a URL in the current/[count]th tab.
|
||||
|<<paste,paste>>|Open a page from the clipboard.
|
||||
|<<print,print>>|Print the current/[count]th tab.
|
||||
|<<quickmark-add,quickmark-add>>|Add a new quickmark.
|
||||
|<<quickmark-del,quickmark-del>>|Delete a quickmark.
|
||||
@ -487,6 +486,8 @@ Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] ['url']+
|
||||
|
||||
Open a URL in the current/[count]th tab.
|
||||
|
||||
If the URL contains newlines, each line gets opened in its own tab.
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: The URL to open.
|
||||
|
||||
@ -503,20 +504,6 @@ The tab index to open the URL in.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[paste]]
|
||||
=== paste
|
||||
Syntax: +:paste [*--sel*] [*--tab*] [*--bg*] [*--window*]+
|
||||
|
||||
Open a page from the clipboard.
|
||||
|
||||
If the pasted text contains newlines, each line gets opened in its own tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in new window.
|
||||
|
||||
[[print]]
|
||||
=== print
|
||||
Syntax: +:print [*--preview*] [*--pdf* 'file']+
|
||||
|
@ -236,6 +236,8 @@ class CommandDispatcher:
|
||||
bg=False, tab=False, window=False, count=None):
|
||||
"""Open a URL in the current/[count]th tab.
|
||||
|
||||
If the URL contains newlines, each line gets opened in its own tab.
|
||||
|
||||
Args:
|
||||
url: The URL to open.
|
||||
bg: Open in a new background tab.
|
||||
@ -247,35 +249,73 @@ class CommandDispatcher:
|
||||
"""
|
||||
if url is None:
|
||||
if tab or bg or window:
|
||||
url = config.get('general', 'default-page')
|
||||
urls = [config.get('general', 'default-page')]
|
||||
else:
|
||||
raise cmdexc.CommandError("No URL given, but -t/-b/-w is not "
|
||||
"set!")
|
||||
else:
|
||||
try:
|
||||
url = objreg.get('quickmark-manager').get(url)
|
||||
except urlmarks.Error:
|
||||
try:
|
||||
url = urlutils.fuzzy_url(url)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
# We don't use cmdexc.CommandError here as this can be
|
||||
# called async from edit_url
|
||||
message.error(self._win_id, str(e))
|
||||
return
|
||||
if tab or bg or window:
|
||||
self._open(url, tab, bg, window, not implicit)
|
||||
else:
|
||||
curtab = self._cntwidget(count)
|
||||
if curtab is None:
|
||||
if count is None:
|
||||
# We want to open a URL in the current tab, but none exists
|
||||
# yet.
|
||||
self._tabbed_browser.tabopen(url)
|
||||
else:
|
||||
# Explicit count with a tab that doesn't exist.
|
||||
return
|
||||
urls = self._parse_url_input(url)
|
||||
for i, cur_url in enumerate(urls):
|
||||
if not window and i > 0:
|
||||
tab = False
|
||||
bg = True
|
||||
if tab or bg or window:
|
||||
self._open(cur_url, tab, bg, window, not implicit)
|
||||
else:
|
||||
curtab.openurl(url)
|
||||
curtab = self._cntwidget(count)
|
||||
if curtab is None:
|
||||
if count is None:
|
||||
# We want to open a URL in the current tab, but none
|
||||
# exists yet.
|
||||
self._tabbed_browser.tabopen(cur_url)
|
||||
else:
|
||||
# Explicit count with a tab that doesn't exist.
|
||||
return
|
||||
else:
|
||||
curtab.openurl(cur_url)
|
||||
|
||||
def _parse_url(self, url, *, force_search=False):
|
||||
"""Parse a URL or quickmark or search query.
|
||||
|
||||
Args:
|
||||
url: The URL to parse.
|
||||
force_search: Whether to force a search even if the content can be
|
||||
interpreted as a URL or a path.
|
||||
|
||||
Return:
|
||||
A URL that can be opened.
|
||||
"""
|
||||
try:
|
||||
return objreg.get('quickmark-manager').get(url)
|
||||
except urlmarks.Error:
|
||||
try:
|
||||
return urlutils.fuzzy_url(url, force_search=force_search)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
# We don't use cmdexc.CommandError here as this can be
|
||||
# called async from edit_url
|
||||
message.error(self._win_id, str(e))
|
||||
return None
|
||||
|
||||
def _parse_url_input(self, url):
|
||||
"""Parse a URL or newline-separated list of URLs.
|
||||
|
||||
Args:
|
||||
url: The URL or list to parse.
|
||||
|
||||
Return:
|
||||
A list of URLs that can be opened.
|
||||
"""
|
||||
force_search = False
|
||||
urllist = [u for u in url.split('\n') if u.strip()]
|
||||
if (len(urllist) > 1 and not urlutils.is_url(urllist[0]) and
|
||||
urlutils.get_path_if_valid(urllist[0], check_exists=True)
|
||||
is None):
|
||||
urllist = [url]
|
||||
force_search = True
|
||||
for cur_url in urllist:
|
||||
parsed = self._parse_url(cur_url, force_search=force_search)
|
||||
if parsed is not None:
|
||||
yield parsed
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='reload',
|
||||
scope='window')
|
||||
@ -796,7 +836,8 @@ class CommandDispatcher:
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated="Use :open {clipboard}")
|
||||
def paste(self, sel=False, tab=False, bg=False, window=False):
|
||||
"""Open a page from the clipboard.
|
||||
|
||||
@ -810,15 +851,12 @@ class CommandDispatcher:
|
||||
window: Open in new window.
|
||||
"""
|
||||
force_search = False
|
||||
if sel and utils.supports_selection():
|
||||
target = "Primary selection"
|
||||
else:
|
||||
if not utils.supports_selection():
|
||||
sel = False
|
||||
target = "Clipboard"
|
||||
text = utils.get_clipboard(selection=sel)
|
||||
if not text.strip():
|
||||
raise cmdexc.CommandError("{} is empty.".format(target))
|
||||
log.misc.debug("{} contained: {!r}".format(target, text))
|
||||
try:
|
||||
text = utils.get_clipboard(selection=sel)
|
||||
except utils.ClipboardError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
text_urls = [u for u in text.split('\n') if u.strip()]
|
||||
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
|
||||
urlutils.get_path_if_valid(
|
||||
@ -1463,9 +1501,12 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("Focused element is not editable!")
|
||||
|
||||
try:
|
||||
sel = utils.get_clipboard(selection=True)
|
||||
except utils.SelectionUnsupportedError:
|
||||
sel = utils.get_clipboard()
|
||||
try:
|
||||
sel = utils.get_clipboard(selection=True)
|
||||
except utils.SelectionUnsupportedError:
|
||||
sel = utils.get_clipboard()
|
||||
except utils.ClipboardEmptyError:
|
||||
return
|
||||
|
||||
log.misc.debug("Pasting primary selection into element {}".format(
|
||||
elem.debug_text()))
|
||||
|
@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, objreg, qtutils
|
||||
from qutebrowser.utils import message, objreg, qtutils, utils
|
||||
from qutebrowser.misc import split
|
||||
|
||||
|
||||
@ -49,21 +49,29 @@ def _current_url(tabbed_browser):
|
||||
|
||||
def replace_variables(win_id, arglist):
|
||||
"""Utility function to replace variables like {url} in a list of args."""
|
||||
variables = {
|
||||
'{url}': lambda: _current_url(tabbed_browser).toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword),
|
||||
'{url:pretty}': lambda: _current_url(tabbed_browser).toString(
|
||||
QUrl.RemovePassword),
|
||||
'{clipboard}': utils.get_clipboard,
|
||||
'{primary}': lambda: utils.get_clipboard(selection=True),
|
||||
}
|
||||
values = {}
|
||||
args = []
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
if any('{url}' in arg for arg in arglist):
|
||||
url = _current_url(tabbed_browser).toString(QUrl.FullyEncoded |
|
||||
QUrl.RemovePassword)
|
||||
if any('{url:pretty}' in arg for arg in arglist):
|
||||
pretty_url = _current_url(tabbed_browser).toString(QUrl.RemovePassword)
|
||||
for arg in arglist:
|
||||
if '{url}' in arg:
|
||||
args.append(arg.replace('{url}', url))
|
||||
elif '{url:pretty}' in arg:
|
||||
args.append(arg.replace('{url:pretty}', pretty_url))
|
||||
else:
|
||||
|
||||
try:
|
||||
for arg in arglist:
|
||||
for var, func in variables.items():
|
||||
if var in arg:
|
||||
if var not in values:
|
||||
values[var] = func()
|
||||
arg = arg.replace(var, values[var])
|
||||
args.append(arg)
|
||||
except utils.ClipboardError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
return args
|
||||
|
||||
|
||||
|
@ -1523,12 +1523,12 @@ KEY_DATA = collections.OrderedDict([
|
||||
('yank domain -s', ['yD']),
|
||||
('yank pretty-url', ['yp']),
|
||||
('yank pretty-url -s', ['yP']),
|
||||
('paste', ['pp']),
|
||||
('paste -s', ['pP']),
|
||||
('paste -t', ['Pp']),
|
||||
('paste -ts', ['PP']),
|
||||
('paste -w', ['wp']),
|
||||
('paste -ws', ['wP']),
|
||||
('open {clipboard}', ['pp']),
|
||||
('open {primary}', ['pP']),
|
||||
('open -t {clipboard}', ['Pp']),
|
||||
('open -t {primary}', ['PP']),
|
||||
('open -w {clipboard}', ['wp']),
|
||||
('open -w {primary}', ['wP']),
|
||||
('quickmark-save', ['m']),
|
||||
('set-cmd-text -s :quickmark-load', ['b']),
|
||||
('set-cmd-text -s :quickmark-load -t', ['B']),
|
||||
@ -1698,6 +1698,11 @@ CHANGED_KEY_COMMANDS = [
|
||||
(re.compile(r'^yank-selected -p'), r'yank selection -s'),
|
||||
(re.compile(r'^yank-selected'), r'yank selection'),
|
||||
|
||||
(re.compile(r'^paste$'), r'open {clipboard}'),
|
||||
(re.compile(r'^paste -([twb])$'), r'open -\1 {clipboard}'),
|
||||
(re.compile(r'^paste -([twb])s$'), r'open -\1 {primary}'),
|
||||
(re.compile(r'^paste -s([twb])$'), r'open -\1 {primary}'),
|
||||
|
||||
(re.compile(r'^completion-item-next'), r'completion-item-focus next'),
|
||||
(re.compile(r'^completion-item-prev'), r'completion-item-focus prev'),
|
||||
]
|
||||
|
@ -47,7 +47,7 @@ class MinimalLineEditMixin:
|
||||
if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier:
|
||||
try:
|
||||
text = utils.get_clipboard(selection=True)
|
||||
except utils.SelectionUnsupportedError:
|
||||
except utils.ClipboardError:
|
||||
pass
|
||||
else:
|
||||
e.accept()
|
||||
|
@ -43,11 +43,21 @@ fake_clipboard = None
|
||||
log_clipboard = False
|
||||
|
||||
|
||||
class SelectionUnsupportedError(Exception):
|
||||
class ClipboardError(Exception):
|
||||
|
||||
"""Raised if the clipboard contents are unavailable for some reason."""
|
||||
|
||||
|
||||
class SelectionUnsupportedError(ClipboardError):
|
||||
|
||||
"""Raised if [gs]et_clipboard is used and selection=True is unsupported."""
|
||||
|
||||
|
||||
class ClipboardEmptyError(ClipboardError):
|
||||
|
||||
"""Raised if get_clipboard is used and the clipboard is empty."""
|
||||
|
||||
|
||||
def elide(text, length):
|
||||
"""Elide text so it uses a maximum of length chars."""
|
||||
if length < 1:
|
||||
@ -810,6 +820,11 @@ def get_clipboard(selection=False):
|
||||
mode = QClipboard.Selection if selection else QClipboard.Clipboard
|
||||
data = QApplication.clipboard().text(mode=mode)
|
||||
|
||||
target = "Primary selection" if selection else "Clipboard"
|
||||
if not data.strip():
|
||||
raise ClipboardEmptyError("{} is empty.".format(target))
|
||||
log.misc.debug("{} contained: {!r}".format(target, data))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
@ -524,3 +524,16 @@ Feature: Various utility commands.
|
||||
Then the following tabs should be open:
|
||||
- data/hints/link_blank.html
|
||||
- data/hello.txt (active)
|
||||
|
||||
## Variables
|
||||
|
||||
Scenario: {url} as part of an argument
|
||||
When I open data/hello.txt
|
||||
And I run :message-info foo{url}
|
||||
Then the message "foohttp://localhost:*/hello.txt" should be shown
|
||||
|
||||
Scenario: Multiple variables in an argument
|
||||
When I open data/hello.txt
|
||||
And I put "foo" into the clipboard
|
||||
And I run :message-info {clipboard}bar{url}
|
||||
Then the message "foobarhttp://localhost:*/hello.txt" should be shown
|
||||
|
@ -1,6 +1,6 @@
|
||||
Feature: Yanking and pasting.
|
||||
:yank and :paste can be used to copy/paste the URL or title from/to the
|
||||
clipboard and primary selection.
|
||||
:yank, {clipboard} and {primary} can be used to copy/paste the URL or title
|
||||
from/to the clipboard and primary selection.
|
||||
|
||||
Background:
|
||||
Given I run :tab-only
|
||||
@ -45,11 +45,11 @@ Feature: Yanking and pasting.
|
||||
Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title with spaces.html" should be shown
|
||||
And the clipboard should contain "http://localhost:(port)/data/title with spaces.html"
|
||||
|
||||
#### :paste
|
||||
#### {clipboard} and {primary}
|
||||
|
||||
Scenario: Pasting a URL
|
||||
When I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the requests should be:
|
||||
data/hello.txt
|
||||
@ -57,32 +57,32 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting a URL from primary selection
|
||||
When selection is supported
|
||||
And I put "http://localhost:(port)/data/hello2.txt" into the primary selection
|
||||
And I run :paste --sel
|
||||
And I run :open {primary}
|
||||
And I wait until data/hello2.txt is loaded
|
||||
Then the requests should be:
|
||||
data/hello2.txt
|
||||
|
||||
Scenario: Pasting with empty clipboard
|
||||
When I put "" into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard} (invalid command)
|
||||
Then the error "Clipboard is empty." should be shown
|
||||
|
||||
Scenario: Pasting with empty selection
|
||||
When selection is supported
|
||||
And I put "" into the primary selection
|
||||
And I run :paste --sel
|
||||
And I run :open {primary} (invalid command)
|
||||
Then the error "Primary selection is empty." should be shown
|
||||
|
||||
Scenario: Pasting with a space in clipboard
|
||||
When I put " " into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard} (invalid command)
|
||||
Then the error "Clipboard is empty." should be shown
|
||||
|
||||
Scenario: Pasting in a new tab
|
||||
Given I open about:blank
|
||||
When I run :tab-only
|
||||
And I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
@ -92,7 +92,7 @@ Feature: Yanking and pasting.
|
||||
Given I open about:blank
|
||||
When I run :tab-only
|
||||
And I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste -b
|
||||
And I run :open -b {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank (active)
|
||||
@ -101,7 +101,7 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting in a new window
|
||||
Given I have a fresh instance
|
||||
When I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste -w
|
||||
And I run :open -w {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the session should look like:
|
||||
windows:
|
||||
@ -119,7 +119,7 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting an invalid URL
|
||||
When I set general -> auto-search to false
|
||||
And I put "foo bar" into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard}
|
||||
Then the error "Invalid URL" should be shown
|
||||
|
||||
Scenario: Pasting multiple urls in a new tab
|
||||
@ -128,7 +128,7 @@ Feature: Yanking and pasting.
|
||||
http://localhost:(port)/data/hello.txt
|
||||
http://localhost:(port)/data/hello2.txt
|
||||
http://localhost:(port)/data/hello3.txt
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I wait until data/hello2.txt is loaded
|
||||
And I wait until data/hello3.txt is loaded
|
||||
@ -145,7 +145,7 @@ Feature: Yanking and pasting.
|
||||
this url:
|
||||
http://qutebrowser.org
|
||||
should not open
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt?q=this%20url%3A%0Ahttp%3A//qutebrowser.org%0Ashould%20not%20open is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
@ -159,7 +159,7 @@ Feature: Yanking and pasting.
|
||||
text:
|
||||
should open
|
||||
as search
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt?q=text%3A%0Ashould%20open%0Aas%20search is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
@ -172,7 +172,7 @@ Feature: Yanking and pasting.
|
||||
http://localhost:(port)/data/hello.txt
|
||||
http://localhost:(port)/data/hello2.txt
|
||||
http://localhost:(port)/data/hello3.txt
|
||||
And I run :paste -b
|
||||
And I run :open -b {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I wait until data/hello2.txt is loaded
|
||||
And I wait until data/hello3.txt is loaded
|
||||
@ -188,7 +188,7 @@ Feature: Yanking and pasting.
|
||||
http://localhost:(port)/data/hello.txt
|
||||
http://localhost:(port)/data/hello2.txt
|
||||
http://localhost:(port)/data/hello3.txt
|
||||
And I run :paste -w
|
||||
And I run :open -w {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I wait until data/hello2.txt is loaded
|
||||
And I wait until data/hello3.txt is loaded
|
||||
@ -218,13 +218,13 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting multiple urls with an empty one
|
||||
When I open about:blank
|
||||
And I put "http://localhost:(port)/data/hello.txt\n\nhttp://localhost:(port)/data/hello2.txt" into the clipboard
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
Then no crash should happen
|
||||
|
||||
Scenario: Pasting multiple urls with an almost empty one
|
||||
When I open about:blank
|
||||
And I put "http://localhost:(port)/data/hello.txt\n \nhttp://localhost:(port)/data/hello2.txt" into the clipboard
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
Then no crash should happen
|
||||
|
||||
#### :paste-primary
|
||||
|
@ -298,6 +298,10 @@ class TestKeyConfigParser:
|
||||
('yank -ds', 'yank domain -s'),
|
||||
('yank -p', 'yank pretty-url'),
|
||||
('yank -ps', 'yank pretty-url -s'),
|
||||
|
||||
('paste', 'open {clipboard}'),
|
||||
('paste -t', 'open -t {clipboard}'),
|
||||
('paste -ws', 'open -w {primary}'),
|
||||
]
|
||||
)
|
||||
def test_migrations(self, old, new_expected):
|
||||
|
Loading…
Reference in New Issue
Block a user