Merge remote-tracking branch 'origin/pr/3525'

This commit is contained in:
Florian Bruhin 2018-02-11 10:44:27 +01:00
commit d306f81130
13 changed files with 73 additions and 16 deletions

View File

@ -1420,6 +1420,7 @@ How many steps to zoom out.
|<<prompt-accept,prompt-accept>>|Accept the current prompt. |<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item. |<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|<<prompt-open-download,prompt-open-download>>|Immediately open a download. |<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|<<prompt-yank,prompt-yank>>|Yank URL to clipboard or primary selection.
|<<rl-backward-char,rl-backward-char>>|Move back a character. |<<rl-backward-char,rl-backward-char>>|Move back a character.
|<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor. |<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor.
|<<rl-backward-kill-word,rl-backward-kill-word>>|Remove chars from the cursor to the beginning of the word. |<<rl-backward-kill-word,rl-backward-kill-word>>|Remove chars from the cursor to the beginning of the word.
@ -1625,6 +1626,15 @@ If no specific command is given, this will use the system's default application
==== note ==== note
* 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.
[[prompt-yank]]
=== prompt-yank
Syntax: +:prompt-yank [*--sel*]+
Yank URL to clipboard or primary selection.
==== optional arguments
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
[[rl-backward-char]] [[rl-backward-char]]
=== rl-backward-char === rl-backward-char
Move back a character. Move back a character.

View File

@ -617,6 +617,8 @@ Default:
* +pass:[&lt;Alt-Backspace&gt;]+: +pass:[rl-backward-kill-word]+ * +pass:[&lt;Alt-Backspace&gt;]+: +pass:[rl-backward-kill-word]+
* +pass:[&lt;Alt-D&gt;]+: +pass:[rl-kill-word]+ * +pass:[&lt;Alt-D&gt;]+: +pass:[rl-kill-word]+
* +pass:[&lt;Alt-F&gt;]+: +pass:[rl-forward-word]+ * +pass:[&lt;Alt-F&gt;]+: +pass:[rl-forward-word]+
* +pass:[&lt;Alt-Shift-Y&gt;]+: +pass:[prompt-yank --sel]+
* +pass:[&lt;Alt-Y&gt;]+: +pass:[prompt-yank]+
* +pass:[&lt;Ctrl-?&gt;]+: +pass:[rl-delete-char]+ * +pass:[&lt;Ctrl-?&gt;]+: +pass:[rl-delete-char]+
* +pass:[&lt;Ctrl-A&gt;]+: +pass:[rl-beginning-of-line]+ * +pass:[&lt;Ctrl-A&gt;]+: +pass:[rl-beginning-of-line]+
* +pass:[&lt;Ctrl-B&gt;]+: +pass:[rl-backward-char]+ * +pass:[&lt;Ctrl-B&gt;]+: +pass:[rl-backward-char]+

View File

@ -31,7 +31,7 @@ import enum
import sip import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel) QTimer, QAbstractListModel, QUrl)
from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import config from qutebrowser.config import config
@ -166,6 +166,7 @@ def get_filename_question(*, suggested_filename, url, parent=None):
q.title = "Save file to:" q.title = "Save file to:"
q.text = "Please enter a location for <b>{}</b>".format( q.text = "Please enter a location for <b>{}</b>".format(
html.escape(url.toDisplayString())) html.escape(url.toDisplayString()))
q.url = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
q.mode = usertypes.PromptMode.download q.mode = usertypes.PromptMode.download
q.completed.connect(q.deleteLater) q.completed.connect(q.deleteLater)
q.default = _path_suggestion(suggested_filename) q.default = _path_suggestion(suggested_filename)

View File

@ -20,6 +20,7 @@
"""Download manager.""" """Download manager."""
import io import io
import os.path
import shutil import shutil
import functools import functools
@ -198,21 +199,23 @@ class DownloadItem(downloads.AbstractDownloadItem):
def _ask_confirm_question(self, title, msg): def _ask_confirm_question(self, title, msg):
no_action = functools.partial(self.cancel, remove_data=False) no_action = functools.partial(self.cancel, remove_data=False)
url = 'file://{}'.format(self._filename)
message.confirm_async(title=title, text=msg, message.confirm_async(title=title, text=msg,
yes_action=self._after_set_filename, yes_action=self._after_set_filename,
no_action=no_action, cancel_action=no_action, no_action=no_action, cancel_action=no_action,
abort_on=[self.cancelled, self.error]) abort_on=[self.cancelled, self.error], url=url)
def _ask_create_parent_question(self, title, msg, def _ask_create_parent_question(self, title, msg,
force_overwrite, remember_directory): force_overwrite, remember_directory):
no_action = functools.partial(self.cancel, remove_data=False) no_action = functools.partial(self.cancel, remove_data=False)
url = 'file://{}'.format(os.path.dirname(self._filename))
message.confirm_async(title=title, text=msg, message.confirm_async(title=title, text=msg,
yes_action=(lambda: yes_action=(lambda:
self._after_create_parent_question( self._after_create_parent_question(
force_overwrite, force_overwrite,
remember_directory)), remember_directory)),
no_action=no_action, cancel_action=no_action, no_action=no_action, cancel_action=no_action,
abort_on=[self.cancelled, self.error]) abort_on=[self.cancelled, self.error], url=url)
def _set_fileobj(self, fileobj, *, autoclose=True): def _set_fileobj(self, fileobj, *, autoclose=True):
"""Set the file object to write the download to. """Set the file object to write the download to.

View File

@ -23,13 +23,14 @@ import os
import html import html
import netrc import netrc
from PyQt5.QtCore import QUrl
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
class CallSuper(Exception): class CallSuper(Exception):
"""Raised when the caller should call the superclass instead.""" """Raised when the caller should call the superclass instead."""
@ -63,9 +64,10 @@ def authentication_required(url, authenticator, abort_on):
else: else:
msg = '<b>{}</b> needs authentication'.format( msg = '<b>{}</b> needs authentication'.format(
html.escape(url.toDisplayString())) html.escape(url.toDisplayString()))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask(title="Authentication required", text=msg, answer = message.ask(title="Authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd, mode=usertypes.PromptMode.user_pwd,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
if answer is not None: if answer is not None:
authenticator.setUser(answer.user) authenticator.setUser(answer.user)
authenticator.setPassword(answer.password) authenticator.setPassword(answer.password)
@ -80,9 +82,10 @@ def javascript_confirm(url, js_msg, abort_on):
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()), msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg)) html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
ans = message.ask('Javascript confirm', msg, ans = message.ask('Javascript confirm', msg,
mode=usertypes.PromptMode.yesno, mode=usertypes.PromptMode.yesno,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
return bool(ans) return bool(ans)
@ -96,10 +99,11 @@ def javascript_prompt(url, js_msg, default, abort_on):
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()), msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg)) html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask('Javascript prompt', msg, answer = message.ask('Javascript prompt', msg,
mode=usertypes.PromptMode.text, mode=usertypes.PromptMode.text,
default=default, default=default,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
if answer is None: if answer is None:
return (False, "") return (False, "")
@ -118,8 +122,9 @@ def javascript_alert(url, js_msg, abort_on):
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()), msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg)) html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert, message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
def javascript_log_message(level, source, line, msg): def javascript_log_message(level, source, line, msg):
@ -166,9 +171,10 @@ def ignore_certificate_errors(url, errors, abort_on):
""".strip()) """.strip())
msg = err_template.render(url=url, errors=errors) msg = err_template.render(url=url, errors=errors)
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
ignore = message.ask(title="Certificate errors - continue?", text=msg, ignore = message.ask(title="Certificate errors - continue?", text=msg,
mode=usertypes.PromptMode.yesno, default=False, mode=usertypes.PromptMode.yesno, default=False,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
if ignore is None: if ignore is None:
# prompt aborted # prompt aborted
ignore = False ignore = False
@ -204,15 +210,17 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
config_val = config.instance.get(option) config_val = config.instance.get(option)
if config_val == 'ask': if config_val == 'ask':
if url.isValid(): if url.isValid():
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
text = "Allow the website at <b>{}</b> to {}?".format( text = "Allow the website at <b>{}</b> to {}?".format(
html.escape(url.toDisplayString()), msg) html.escape(url.toDisplayString()), msg)
else: else:
urlstr = None
text = "Allow the website to {}?".format(msg) text = "Allow the website to {}?".format(msg)
return message.confirm_async( return message.confirm_async(
yes_action=yes_action, no_action=no_action, yes_action=yes_action, no_action=no_action,
cancel_action=no_action, abort_on=abort_on, cancel_action=no_action, abort_on=abort_on,
title='Permission request', text=text) title='Permission request', text=text, url=urlstr)
elif config_val: elif config_val:
yes_action() yes_action()
return None return None

View File

@ -161,7 +161,7 @@ class QuickmarkManager(UrlMarkManager):
"Add quickmark:", usertypes.PromptMode.text, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(self.quickmark_add, urlstr), functools.partial(self.quickmark_add, urlstr),
text="Please enter a quickmark name for<br/><b>{}</b>".format( text="Please enter a quickmark name for<br/><b>{}</b>".format(
html.escape(url.toDisplayString()))) html.escape(url.toDisplayString())), url=urlstr)
@cmdutils.register(instance='quickmark-manager') @cmdutils.register(instance='quickmark-manager')
def quickmark_add(self, url, name): def quickmark_add(self, url, name):
@ -192,7 +192,7 @@ class QuickmarkManager(UrlMarkManager):
if name in self.marks: if name in self.marks:
message.confirm_async( message.confirm_async(
title="Override existing quickmark?", title="Override existing quickmark?",
yes_action=set_mark, default=True) yes_action=set_mark, default=True, url=url)
else: else:
set_mark() set_mark()

View File

@ -135,6 +135,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
question = usertypes.Question() question = usertypes.Question()
question.title = title question.title = title
question.text = msg question.text = msg
question.url = 'file://{}'.format(self._filename)
question.mode = usertypes.PromptMode.yesno question.mode = usertypes.PromptMode.yesno
question.answered_yes.connect(self._after_set_filename) question.answered_yes.connect(self._after_set_filename)
question.answered_no.connect(no_action) question.answered_no.connect(no_action)
@ -149,6 +150,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
question = usertypes.Question() question = usertypes.Question()
question.title = title question.title = title
question.text = msg question.text = msg
question.url = 'file://{}'.format(os.path.dirname(self._filename))
question.mode = usertypes.PromptMode.yesno question.mode = usertypes.PromptMode.yesno
question.answered_yes.connect(lambda: question.answered_yes.connect(lambda:
self._after_create_parent_question( self._after_create_parent_question(

View File

@ -757,10 +757,11 @@ class WebEngineTab(browsertab.AbstractTab):
"""Called when a proxy needs authentication.""" """Called when a proxy needs authentication."""
msg = "<b>{}</b> requires a username and password.".format( msg = "<b>{}</b> requires a username and password.".format(
html_utils.escape(proxy_host)) html_utils.escape(proxy_host))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask( answer = message.ask(
title="Proxy authentication required", text=msg, title="Proxy authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd, mode=usertypes.PromptMode.user_pwd,
abort_on=[self.shutting_down, self.load_started]) abort_on=[self.shutting_down, self.load_started], url=urlstr)
if answer is not None: if answer is not None:
authenticator.setUser(answer.user) authenticator.setUser(answer.user)
authenticator.setPassword(answer.password) authenticator.setPassword(answer.password)

View File

@ -147,7 +147,8 @@ class BrowserPage(QWebPage):
title="Open external application for {}-link?".format(scheme), title="Open external application for {}-link?".format(scheme),
text="URL: <b>{}</b>".format( text="URL: <b>{}</b>".format(
html.escape(url.toDisplayString())), html.escape(url.toDisplayString())),
yes_action=functools.partial(QDesktopServices.openUrl, url)) yes_action=functools.partial(QDesktopServices.openUrl, url),
url=info.url.toString(QUrl.FullyEncoded))
return True return True
elif (info.domain, info.error) in ignored_errors: elif (info.domain, info.error) in ignored_errors:
log.webview.debug("Ignored error on {}: {} (error domain: {}, " log.webview.debug("Ignored error on {}: {} (error domain: {}, "

View File

@ -2360,6 +2360,8 @@ bindings.default:
<Up>: prompt-item-focus prev <Up>: prompt-item-focus prev
<Tab>: prompt-item-focus next <Tab>: prompt-item-focus next
<Down>: prompt-item-focus next <Down>: prompt-item-focus next
<Alt-Y>: prompt-yank
<Alt-Shift-Y>: prompt-yank --sel
<Ctrl-B>: rl-backward-char <Ctrl-B>: rl-backward-char
<Ctrl-F>: rl-forward-char <Ctrl-F>: rl-forward-char
<Alt-B>: rl-backward-word <Alt-B>: rl-backward-word
@ -2371,9 +2373,9 @@ bindings.default:
<Alt-D>: rl-kill-word <Alt-D>: rl-kill-word
<Ctrl-W>: rl-unix-word-rubout <Ctrl-W>: rl-unix-word-rubout
<Alt-Backspace>: rl-backward-kill-word <Alt-Backspace>: rl-backward-kill-word
<Ctrl-Y>: rl-yank
<Ctrl-?>: rl-delete-char <Ctrl-?>: rl-delete-char
<Ctrl-H>: rl-backward-delete-char <Ctrl-H>: rl-backward-delete-char
<Ctrl-Y>: rl-yank
<Escape>: leave-mode <Escape>: leave-mode
caret: caret:
v: toggle-selection v: toggle-selection

View File

@ -422,6 +422,27 @@ class PromptContainer(QWidget):
except UnsupportedOperationError: except UnsupportedOperationError:
pass pass
@cmdutils.register(
instance='prompt-container', scope='window',
modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno])
def prompt_yank(self, sel=False):
"""Yank URL to clipboard or primary selection.
Args:
sel: Use the primary selection instead of the clipboard.
"""
question = self._prompt.question
if question.url is None:
message.error('No URL found.')
return
if sel and utils.supports_selection():
target = 'primary selection'
else:
sel = False
target = 'clipboard'
utils.set_clipboard(question.url, sel)
message.info("Yanked to {}: {}".format(target, question.url))
class LineEdit(QLineEdit): class LineEdit(QLineEdit):
@ -721,6 +742,7 @@ class DownloadFilenamePrompt(FilenamePrompt):
('prompt-accept', 'Accept'), ('prompt-accept', 'Accept'),
('leave-mode', 'Abort'), ('leave-mode', 'Abort'),
('prompt-open-download', "Open download"), ('prompt-open-download', "Open download"),
('prompt-yank', "Yank URL"),
] ]
return cmds return cmds
@ -811,6 +833,7 @@ class YesNoPrompt(_BasePrompt):
cmds = [ cmds = [
('prompt-accept yes', "Yes"), ('prompt-accept yes', "Yes"),
('prompt-accept no', "No"), ('prompt-accept no', "No"),
('prompt-yank', "Yank URL"),
] ]
if self.question.default is not None: if self.question.default is not None:

View File

@ -87,7 +87,8 @@ def info(message, *, replace=False):
global_bridge.show(usertypes.MessageLevel.info, message, replace) global_bridge.show(usertypes.MessageLevel.info, message, replace)
def _build_question(title, text=None, *, mode, default=None, abort_on=()): def _build_question(title, text=None, *, mode, default=None, abort_on=(),
url=None):
"""Common function for ask/ask_async.""" """Common function for ask/ask_async."""
if not isinstance(mode, usertypes.PromptMode): if not isinstance(mode, usertypes.PromptMode):
raise TypeError("Mode {} is no PromptMode member!".format(mode)) raise TypeError("Mode {} is no PromptMode member!".format(mode))
@ -96,6 +97,7 @@ def _build_question(title, text=None, *, mode, default=None, abort_on=()):
question.text = text question.text = text
question.mode = mode question.mode = mode
question.default = default question.default = default
question.url = url
for sig in abort_on: for sig in abort_on:
sig.connect(question.abort) sig.connect(question.abort)
return question return question

View File

@ -266,6 +266,7 @@ class Question(QObject):
For user_pwd, a default username as string. For user_pwd, a default username as string.
title: The question title to show. title: The question title to show.
text: The prompt text to display to the user. text: The prompt text to display to the user.
url: Any URL referenced in prompts.
answer: The value the user entered (as password for user_pwd). answer: The value the user entered (as password for user_pwd).
is_aborted: Whether the question was aborted. is_aborted: Whether the question was aborted.
interrupted: Whether the question was interrupted by another one. interrupted: Whether the question was interrupted by another one.
@ -296,6 +297,7 @@ class Question(QObject):
self.default = None self.default = None
self.title = None self.title = None
self.text = None self.text = None
self.url = None
self.answer = None self.answer = None
self.is_aborted = False self.is_aborted = False
self.interrupted = False self.interrupted = False