class Text(textbase.TextBase): """Text displayed in the statusbar. Attributes: _normaltext: The "permanent" text. Never automatically cleared. _temptext: The temporary text to display. The temptext is shown from StatusBar when a temporary text or error is available. If not, the permanent text is shown. """ Text = usertypes.enum('Text', ['normal', 'temp']) def __init__(self, parent=None): super().__init__(parent) self._normaltext = '' self._temptext = '' def set_text(self, which, text): """Set a text. Args: which: Which text to set, a self.Text instance. text: The text to set. """ log.statusbar.debug("Setting {} text to '{}'.".format( which.name, text)) if which is self.Text.normal: self._normaltext = text elif which is self.Text.temp: self._temptext = text else: raise ValueError("Invalid value {} for which!".format(which)) self.update_text() @pyqtSlot(str) def maybe_reset_text(self, text): """Clear a normal text if it still matches an expected text.""" if self._normaltext == text: log.statusbar.debug("Resetting: '{}'".format(text)) self.set_text(self.Text.normal, '') else: log.statusbar.debug("Ignoring reset: '{}'".format(text)) def update_text(self): """Update QLabel text when needed.""" if self._temptext: self.setText(self._temptext) elif self._normaltext: self.setText(self._normaltext) else: self.setText('')
class ColorFlags: """Flags which change the appearance of the statusbar. Attributes: prompt: If we're currently in prompt-mode. insert: If we're currently in insert mode. command: If we're currently in command mode. mode: The current caret mode (CaretMode.off/.on/.selection). private: Whether this window is in private browsing mode. passthrough: If we're currently in passthrough-mode. """ CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection']) prompt = attr.ib(False) insert = attr.ib(False) command = attr.ib(False) caret = attr.ib(CaretMode.off) private = attr.ib(False) passthrough = attr.ib(False) def to_stringlist(self): """Get a string list of set flags used in the stylesheet. This also combines flags in ways they're used in the sheet. """ strings = [] if self.prompt: strings.append('prompt') if self.insert: strings.append('insert') if self.command: strings.append('command') if self.private: strings.append('private') if self.passthrough: strings.append('passthrough') if self.private and self.command: strings.append('private-command') if self.caret == self.CaretMode.on: strings.append('caret') elif self.caret == self.CaretMode.selection: strings.append('caret-selection') else: assert self.caret == self.CaretMode.off return strings
class MessageMock: """Helper object for message_mock. Attributes: _monkeypatch: The pytest monkeypatch fixture. MessageLevel: An enum with possible message levels. Message: A namedtuple representing a message. messages: A list of Message tuples. """ Message = collections.namedtuple('Message', ['level', 'win_id', 'text', 'immediate']) MessageLevel = usertypes.enum('Level', ('error', 'info', 'warning')) def __init__(self, monkeypatch): self._monkeypatch = monkeypatch self.messages = [] def _handle(self, level, win_id, text, immediately=False): self.messages.append(self.Message(level, win_id, text, immediately)) def _handle_error(self, *args, **kwargs): self._handle(self.MessageLevel.error, *args, **kwargs) def _handle_info(self, *args, **kwargs): self._handle(self.MessageLevel.info, *args, **kwargs) def _handle_warning(self, *args, **kwargs): self._handle(self.MessageLevel.warning, *args, **kwargs) def getmsg(self): """Get the only message in self.messages. Raises ValueError if there are multiple or no messages. """ if len(self.messages) != 1: raise ValueError("Got {} messages but expected a single " "one.".format(len(self.messages))) return self.messages[0] def patch(self, module_path): """Patch message.* in the given module (as a string).""" self._monkeypatch.setattr(module_path + '.error', self._handle_error) self._monkeypatch.setattr(module_path + '.info', self._handle_info) self._monkeypatch.setattr(module_path + '.warning', self._handle_warning)
FILTERS: A dictionary of filter functions for the modes. The filter for "links" filters javascript:-links and a-tags without "href". """ import collections.abc import functools from PyQt5.QtCore import QRect, QUrl from PyQt5.QtWebKit import QWebElement from qutebrowser.config import config from qutebrowser.utils import log, usertypes, utils Group = usertypes.enum("Group", ["all", "links", "images", "url", "prevnext", "focus"]) SELECTORS = { Group.all: ( "a, area, textarea, select, input:not([type=hidden]), button, " "frame, iframe, link, [onclick], [onmousedown], [role=link], " "[role=option], [role=button], img" ), Group.links: "a, area, link, [role=link]", Group.images: "img", Group.url: "[src], [href]", Group.prevnext: "a, area, button, link, [role=button]", Group.focus: "*:focus", }
# 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/>. """Tests for qutebrowser.commands.argparser.""" import inspect import pytest from PyQt5.QtCore import QUrl from qutebrowser.commands import argparser, cmdexc from qutebrowser.utils import usertypes Enum = usertypes.enum('Enum', ['foo', 'foo_bar']) class TestArgumentParser: @pytest.fixture def parser(self): return argparser.ArgumentParser('foo') def test_name(self, parser): assert parser.name == 'foo' def test_exit(self, parser): parser.add_argument('--help', action='help') with pytest.raises(argparser.ArgumentParserExit) as excinfo: parser.parse_args(['--help'])
def setUp(self): self.enum = usertypes.enum('Enum', ['one', 'two'])
SELECTORS: CSS selectors for different groups of elements. FILTERS: A dictionary of filter functions for the modes. The filter for "links" filters javascript:-links and a-tags without "href". """ import collections.abc from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer from PyQt5.QtGui import QMouseEvent from qutebrowser.config import config from qutebrowser.utils import log, usertypes, utils, qtutils Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext', 'inputs']) SELECTORS = { Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, ' 'frame, iframe, link, [onclick], [onmousedown], [role=link], ' '[role=option], [role=button], img'), Group.links: 'a, area, link, [role=link]', Group.images: 'img', Group.url: '[src], [href]', Group.prevnext: 'a, area, button, link, [role=button]', Group.inputs: ('input[type=text], input[type=email], input[type=url], ' 'input[type=tel], input[type=number], ' 'input[type=password], input[type=search], ' 'input:not([type]), textarea'), }
import itertools import functools from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame from qutebrowser.config import config from qutebrowser.keyinput import modeman from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg from qutebrowser.browser import webpage, hints, webelem LoadStatus = usertypes.enum('LoadStatus', ['none', 'success', 'success_https', 'error', 'warn', 'loading']) tab_id_gen = itertools.count(0) class WebView(QWebView): """One browser tab in TabbedBrowser. Our own subclass of a QWebView with some added bells and whistles. Attributes: hintmanager: The HintManager instance for this view. progress: loading progress of this page. scroll_pos: The current scroll position as (x%, y%) tuple.
def enum(): return usertypes.enum('Enum', ['one', 'two'])
Module attributes: ATTRIBUTES: A mapping from internal setting names to QWebSetting enum constants. SETTERS: A mapping from setting names to QWebSetting setter method names. settings: The global QWebSettings singleton instance. """ import os.path from PyQt5.QtWebKit import QWebSettings from PyQt5.QtCore import QStandardPaths from qutebrowser.config import config from qutebrowser.utils import usertypes, standarddir, objreg MapType = usertypes.enum('MapType', ['attribute', 'setter', 'static_setter']) MAPPINGS = { 'permissions': { 'allow-images': (MapType.attribute, QWebSettings.AutoLoadImages), 'allow-javascript': (MapType.attribute, QWebSettings.JavascriptEnabled), 'javascript-can-open-windows': (MapType.attribute, QWebSettings.JavascriptCanOpenWindows), 'javascript-can-close-windows': (MapType.attribute, QWebSettings.JavascriptCanCloseWindows), 'javascript-can-access-clipboard': (MapType.attribute, QWebSettings.JavascriptCanAccessClipboard), #'allow-java': # (MapType.attribute, QWebSettings.JavaEnabled), 'allow-plugins': (MapType.attribute, QWebSettings.PluginsEnabled),
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The main statusbar widget.""" import collections from PyQt5.QtCore import (pyqtSignal, pyqtSlot, pyqtProperty, Qt, QTime, QSize, QTimer) from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import (command, progress, keystring, percentage, url, prompt) from qutebrowser.mainwindow.statusbar import text as textwidget PreviousWidget = usertypes.enum('PreviousWidget', ['none', 'prompt', 'command']) Severity = usertypes.enum('Severity', ['normal', 'warning', 'error']) class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout. _stack: The QStackedLayout with cmd/txt widgets.
class TestRegister: def test_simple(self): @cmdutils.register() def fun(): """Blah.""" pass cmd = cmdutils.cmd_dict['fun'] assert cmd.handler is fun assert cmd.name == 'fun' assert len(cmdutils.cmd_dict) == 1 assert not cmdutils.aliases def test_underlines(self): """Make sure the function name is normalized correctly (_ -> -).""" @cmdutils.register() def eggs_bacon(): """Blah.""" pass assert cmdutils.cmd_dict['eggs-bacon'].name == 'eggs-bacon' assert 'eggs_bacon' not in cmdutils.cmd_dict def test_lowercasing(self): """Make sure the function name is normalized correctly (uppercase).""" @cmdutils.register() def Test(): # pylint: disable=invalid-name """Blah.""" pass assert cmdutils.cmd_dict['test'].name == 'test' assert 'Test' not in cmdutils.cmd_dict def test_explicit_name(self): """Test register with explicit name.""" @cmdutils.register(name='foobar') def fun(): """Blah.""" pass assert cmdutils.cmd_dict['foobar'].name == 'foobar' assert 'fun' not in cmdutils.cmd_dict assert len(cmdutils.cmd_dict) == 1 assert not cmdutils.aliases def test_multiple_names(self): """Test register with name being a list.""" @cmdutils.register(name=['foobar', 'blub']) def fun(): """Blah.""" pass assert cmdutils.cmd_dict['foobar'].name == 'foobar' assert cmdutils.cmd_dict['blub'].name == 'foobar' assert 'fun' not in cmdutils.cmd_dict assert len(cmdutils.cmd_dict) == 2 assert cmdutils.aliases == ['blub'] def test_multiple_registrations(self): """Make sure registering the same name twice raises ValueError.""" @cmdutils.register(name=['foobar', 'blub']) def fun(): """Blah.""" pass with pytest.raises(ValueError): @cmdutils.register(name=['blah', 'blub']) def fun2(): """Blah.""" pass def test_instance(self): """Make sure the instance gets passed to Command.""" @cmdutils.register(instance='foobar') def fun(self): """Blah.""" pass assert cmdutils.cmd_dict['fun']._instance == 'foobar' def test_kwargs(self): """Make sure the other keyword arguments get passed to Command.""" @cmdutils.register(hide=True) def fun(): """Blah.""" pass assert cmdutils.cmd_dict['fun'].hide def test_star_args(self): """Check handling of *args.""" @cmdutils.register() def fun(*args): """Blah.""" pass with pytest.raises(argparser.ArgumentParserError): cmdutils.cmd_dict['fun'].parser.parse_args([]) def test_star_args_optional(self): """Check handling of *args withstar_args_optional.""" @cmdutils.register(star_args_optional=True) def fun(*args): """Blah.""" assert not args cmd = cmdutils.cmd_dict['fun'] cmd.namespace = cmd.parser.parse_args([]) args, kwargs = cmd._get_call_args(win_id=0) fun(*args, **kwargs) @pytest.mark.parametrize('inp, expected', [(['--arg'], True), (['-a'], True), ([], False)]) def test_flag(self, inp, expected): @cmdutils.register() def fun(arg=False): """Blah.""" assert arg == expected cmd = cmdutils.cmd_dict['fun'] cmd.namespace = cmd.parser.parse_args(inp) assert cmd.namespace.arg == expected def test_flag_argument(self): @cmdutils.register() @cmdutils.argument('arg', flag='b') def fun(arg=False): """Blah.""" assert arg cmd = cmdutils.cmd_dict['fun'] with pytest.raises(argparser.ArgumentParserError): cmd.parser.parse_args(['-a']) cmd.namespace = cmd.parser.parse_args(['-b']) assert cmd.namespace.arg args, kwargs = cmd._get_call_args(win_id=0) fun(*args, **kwargs) def test_partial_arg(self): """Test with only some arguments decorated with @cmdutils.argument.""" @cmdutils.register() @cmdutils.argument('arg1', flag='b') def fun(arg1=False, arg2=False): """Blah.""" pass def test_win_id(self): @cmdutils.register() @cmdutils.argument('win_id', win_id=True) def fun(win_id): """Blah.""" pass assert cmdutils.cmd_dict['fun']._get_call_args(42) == ([42], {}) def test_count(self): @cmdutils.register() @cmdutils.argument('count', count=True) def fun(count=0): """Blah.""" pass assert cmdutils.cmd_dict['fun']._get_call_args(42) == ([0], {}) def test_count_without_default(self): with pytest.raises(TypeError) as excinfo: @cmdutils.register() @cmdutils.argument('count', count=True) def fun(count): """Blah.""" pass expected = "fun: handler has count parameter without default!" assert str(excinfo.value) == expected @pytest.mark.parametrize('hide', [True, False]) def test_pos_args(self, hide): @cmdutils.register() @cmdutils.argument('arg', hide=hide) def fun(arg): """Blah.""" pass pos_args = cmdutils.cmd_dict['fun'].pos_args if hide: assert pos_args == [] else: assert pos_args == [('arg', 'arg')] Enum = usertypes.enum('Test', ['x', 'y']) @pytest.mark.parametrize( 'typ, inp, choices, expected', [ (int, '42', None, 42), (int, 'x', None, cmdexc.ArgumentTypeError), (str, 'foo', None, 'foo'), (typing.Union[str, int], 'foo', None, 'foo'), (typing.Union[str, int], '42', None, 42), # Choices (str, 'foo', ['foo'], 'foo'), (str, 'bar', ['foo'], cmdexc.ArgumentTypeError), # Choices with Union: only checked when it's a str (typing.Union[str, int], 'foo', ['foo'], 'foo'), (typing.Union[str, int], 'bar', ['foo'], cmdexc.ArgumentTypeError), (typing.Union[str, int], '42', ['foo'], 42), (Enum, 'x', None, Enum.x), (Enum, 'z', None, cmdexc.ArgumentTypeError), ]) def test_typed_args(self, typ, inp, choices, expected): @cmdutils.register() @cmdutils.argument('arg', choices=choices) def fun(arg: typ): """Blah.""" assert arg == expected cmd = cmdutils.cmd_dict['fun'] cmd.namespace = cmd.parser.parse_args([inp]) if expected is cmdexc.ArgumentTypeError: with pytest.raises(cmdexc.ArgumentTypeError): cmd._get_call_args(win_id=0) else: args, kwargs = cmd._get_call_args(win_id=0) assert args == [expected] assert kwargs == {} fun(*args, **kwargs) def test_choices_no_annotation(self): # https://github.com/The-Compiler/qutebrowser/issues/1871 @cmdutils.register() @cmdutils.argument('arg', choices=['foo', 'bar']) def fun(arg): """Blah.""" pass cmd = cmdutils.cmd_dict['fun'] cmd.namespace = cmd.parser.parse_args(['fish']) with pytest.raises(cmdexc.ArgumentTypeError): cmd._get_call_args(win_id=0) def test_choices_no_annotation_kwonly(self): # https://github.com/The-Compiler/qutebrowser/issues/1871 @cmdutils.register() @cmdutils.argument('arg', choices=['foo', 'bar']) def fun(*, arg='foo'): """Blah.""" pass cmd = cmdutils.cmd_dict['fun'] cmd.namespace = cmd.parser.parse_args(['--arg=fish']) with pytest.raises(cmdexc.ArgumentTypeError): cmd._get_call_args(win_id=0) def test_pos_arg_info(self): @cmdutils.register() @cmdutils.argument('foo', choices=('a', 'b')) @cmdutils.argument('bar', choices=('x', 'y')) @cmdutils.argument('opt') def fun(foo, bar, opt=False): """Blah.""" pass cmd = cmdutils.cmd_dict['fun'] assert cmd.get_pos_arg_info(0) == command.ArgInfo(choices=('a', 'b')) assert cmd.get_pos_arg_info(1) == command.ArgInfo(choices=('x', 'y')) with pytest.raises(IndexError): cmd.get_pos_arg_info(2) def test_keyword_only_without_default(self): # https://github.com/The-Compiler/qutebrowser/issues/1872 def fun(*, target): """Blah.""" pass with pytest.raises(TypeError) as excinfo: fun = cmdutils.register()(fun) expected = ("fun: handler has keyword only argument 'target' without " "default!") assert str(excinfo.value) == expected def test_typed_keyword_only_without_default(self): # https://github.com/The-Compiler/qutebrowser/issues/1872 def fun(*, target: int): """Blah.""" pass with pytest.raises(TypeError) as excinfo: fun = cmdutils.register()(fun) expected = ("fun: handler has keyword only argument 'target' without " "default!") assert str(excinfo.value) == expected
import sys import shutil import os.path import contextlib from PyQt5.QtCore import QStandardPaths from PyQt5.QtWidgets import QApplication from qutebrowser.utils import log, debug, usertypes, message # The cached locations _locations = {} Location = usertypes.enum('Location', ['config', 'auto_config', 'data', 'system_data', 'cache', 'download', 'runtime']) APPNAME = 'qutebrowser' class EmptyValueError(Exception): """Error raised when QStandardPaths returns an empty value.""" @contextlib.contextmanager def _unset_organization(): """Temporarily unset QApplication.organizationName().
def test_unique(): """Make sure elements need to be unique.""" with pytest.raises(TypeError): usertypes.enum('Enum', ['item', 'item'])
def test_start(): """Test the start= argument.""" e = usertypes.enum('Enum', ['three', 'four'], start=3) assert e.three.value == 3 assert e.four.value == 4
"""KeyChainParser for "hint" and "normal" modes. Module attributes: STARTCHARS: Possible chars for starting a commandline input. """ from PyQt5.QtCore import pyqtSlot, Qt from qutebrowser.utils import message from qutebrowser.config import config from qutebrowser.keyinput import keyparser from qutebrowser.utils import usertypes, log, objreg, utils STARTCHARS = ":/?" LastPress = usertypes.enum('LastPress', ['none', 'filtertext', 'keystring']) class NormalKeyParser(keyparser.CommandKeyParser): """KeyParser for normal mode with added STARTCHARS detection and more. Attributes: _partial_timer: Timer to clear partial keypresses. """ def __init__(self, win_id, parent=None): super().__init__(win_id, parent, supports_count=True, supports_chains=True) self.read_config('normal') self._partial_timer = usertypes.Timer(self, 'partial-match')
Module attributes: STARTCHARS: Possible chars for starting a commandline input. """ import traceback from PyQt5.QtCore import pyqtSlot, Qt from qutebrowser.commands import cmdexc from qutebrowser.config import config from qutebrowser.keyinput import keyparser from qutebrowser.utils import usertypes, log, message, objreg, utils STARTCHARS = ":/?" LastPress = usertypes.enum('LastPress', ['none', 'filtertext', 'keystring']) class NormalKeyParser(keyparser.CommandKeyParser): """KeyParser for normal mode with added STARTCHARS detection and more. Attributes: _partial_timer: Timer to clear partial keypresses. """ def __init__(self, win_id, parent=None): super().__init__(win_id, parent, supports_count=True, supports_chains=True) self.read_config('normal') self._partial_timer = usertypes.Timer(self, 'partial-match')
"""The main statusbar widget.""" import collections from PyQt5.QtCore import (pyqtSignal, pyqtSlot, pyqtProperty, Qt, QTime, QSize, QTimer) from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import (command, progress, keystring, percentage, url, prompt, tabindex) from qutebrowser.mainwindow.statusbar import text as textwidget PreviousWidget = usertypes.enum('PreviousWidget', ['none', 'prompt', 'command']) Severity = usertypes.enum('Severity', ['normal', 'warning', 'error']) CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection']) class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout.
from PyQt5.QtWebKit import QWebElement from PyQt5.QtWebKitWidgets import QWebPage from qutebrowser.config import config from qutebrowser.keyinput import modeman, modeparsers from qutebrowser.browser import webelem from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners from qutebrowser.utils import usertypes, log, qtutils, message, objreg from qutebrowser.misc import guiprocess ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label']) Target = usertypes.enum('Target', ['normal', 'tab', 'tab_fg', 'tab_bg', 'window', 'yank', 'yank_primary', 'run', 'fill', 'hover', 'download', 'userscript', 'spawn']) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(mode, win_id): """Stop hinting when insert mode was entered.""" if mode == usertypes.KeyMode.insert: modeman.maybe_leave(win_id, usertypes.KeyMode.hint, 'insert mode') class HintContext: """Context namespace used for hinting. Attributes:
from PyQt5.QtWebEngineWidgets import QWebEngineProfile except ImportError: # pragma: no cover QWebEngineProfile = None import qutebrowser from qutebrowser.utils import log, utils, standarddir, usertypes, qtutils from qutebrowser.misc import objects, earlyinit, sql from qutebrowser.browser import pdfjs DistributionInfo = collections.namedtuple( 'DistributionInfo', ['id', 'parsed', 'version', 'pretty']) Distribution = usertypes.enum( 'Distribution', ['unknown', 'ubuntu', 'debian', 'void', 'arch', 'gentoo', 'fedora', 'opensuse', 'linuxmint', 'manjaro']) def distribution(): """Get some information about the running Linux distribution. Returns: A DistributionInfo object, or None if no info could be determined. parsed: A Distribution enum member version: A Version object, or None pretty: Always a string (might be "Unknown") """ filename = os.environ.get('QUTE_FAKE_OS_RELEASE', '/etc/os-release') info = {} try:
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The main statusbar widget.""" from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import (command, progress, keystring, percentage, url, prompt, tabindex) from qutebrowser.mainwindow.statusbar import text as textwidget CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection']) class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout. _stack: The QStackedLayout with cmd/txt widgets.
# # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The base completion model for completion in the command line. Module attributes: Role: An enum of user defined model roles. """ from PyQt5.QtCore import Qt from PyQt5.QtGui import QStandardItemModel, QStandardItem from qutebrowser.utils import usertypes Role = usertypes.enum('Role', ['sort', 'userdata'], start=Qt.UserRole, is_int=True) class BaseCompletionModel(QStandardItemModel): """A simple QStandardItemModel adopted for completions. Used for showing completions later in the CompletionView. Supports setting marks and adding new categories/items easily. Class Attributes: COLUMN_WIDTHS: The width percentages of the columns used in the completion view. DUMB_SORT: the dumb sorting used by the model """
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl from PyQt5.QtGui import QMouseEvent, QClipboard from PyQt5.QtWidgets import QApplication from qutebrowser.config import config from qutebrowser.keyinput import modeman from qutebrowser.browser import webelem from qutebrowser.commands import userscripts, cmdexc, cmdutils from qutebrowser.utils import usertypes, log, qtutils, message, objreg ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label']) Target = usertypes.enum('Target', ['normal', 'tab', 'tab_bg', 'yank', 'yank_primary', 'fill', 'rapid', 'download', 'userscript', 'spawn']) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(mode): """Stop hinting when insert mode was entered.""" if mode == usertypes.KeyMode.insert: modeman.maybe_leave(usertypes.KeyMode.hint, 'insert mode') class HintContext: """Context namespace used for hinting. Attributes:
class BaseKeyParser(QObject): """Parser for vim-like key sequences and shortcuts. Not intended to be instantiated directly. Subclasses have to override execute() to do whatever they want to. Class Attributes: Match: types of a match between a binding and the keystring. partial: No keychain matched yet, but it's still possible in the future. definitive: Keychain matches exactly. ambiguous: There are both a partial and a definitive match. none: No more matches possible. Types: type of a keybinding. chain: execute() was called via a chain-like keybinding special: execute() was called via a special keybinding do_log: Whether to log keypresses or not. Attributes: bindings: Bound keybindings special_bindings: Bound special bindings (<Foo>). _win_id: The window ID this keyparser is associated with. _warn_on_keychains: Whether a warning should be logged when binding keychains in a section which does not support them. _keystring: The currently entered key sequence _timer: Timer for delayed execution. _modename: The name of the input mode associated with this keyparser. _supports_count: Whether count is supported _supports_chains: Whether keychains are supported Signals: keystring_updated: Emitted when the keystring is updated. arg: New keystring. """ keystring_updated = pyqtSignal(str) do_log = True Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous', 'none']) Type = usertypes.enum('Type', ['chain', 'special']) def __init__(self, win_id, parent=None, supports_count=None, supports_chains=False): super().__init__(parent) self._win_id = win_id self._timer = None self._modename = None self._keystring = '' if supports_count is None: supports_count = supports_chains self._supports_count = supports_count self._supports_chains = supports_chains self._warn_on_keychains = True self.bindings = {} self.special_bindings = {} def __repr__(self): return utils.get_repr(self, supports_count=self._supports_count, supports_chains=self._supports_chains) def _debug_log(self, message): """Log a message to the debug log if logging is active. Args: message: The message to log. """ if self.do_log: log.keyboard.debug(message) def _handle_special_key(self, e): """Handle a new keypress with special keys (<Foo>). Return True if the keypress has been handled, and False if not. Args: e: the KeyPressEvent from Qt. Return: True if event has been handled, False otherwise. """ binding = utils.keyevent_to_string(e) if binding is None: self._debug_log("Ignoring only-modifier keyeevent.") return False binding = binding.lower() try: cmdstr = self.special_bindings[binding] except KeyError: self._debug_log("No binding found for {}.".format(binding)) return False self.execute(cmdstr, self.Type.special) return True def _split_count(self): """Get count and command from the current keystring. Return: A (count, command) tuple. """ if self._supports_count: (countstr, cmd_input) = re.match(r'^(\d*)(.*)', self._keystring).groups() count = int(countstr) if countstr else None else: cmd_input = self._keystring count = None return count, cmd_input def _handle_single_key(self, e): """Handle a new keypress with a single key (no modifiers). Separate the keypress into count/command, then check if it matches any possible command, and either run the command, ignore it, or display an error. Args: e: the KeyPressEvent from Qt. Return: True if event has been handled, False otherwise. """ txt = e.text() key = e.key() self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt)) if key == Qt.Key_Escape: self._debug_log("Escape pressed, discarding '{}'.".format( self._keystring)) self._keystring = '' return if (not txt) or unicodedata.category(txt) == 'Cc': # control chars self._debug_log("Ignoring, no text char") return False self._stop_delayed_exec() self._keystring += txt count, cmd_input = self._split_count() if not cmd_input: # Only a count, no command yet, but we handled it return True match, binding = self._match_key(cmd_input) if not isinstance(match, self.Match): raise TypeError("Value {} is no Match member!".format(match)) if match == self.Match.definitive: self._debug_log("Definitive match for '{}'.".format( self._keystring)) self._keystring = '' self.execute(binding, self.Type.chain, count) elif match == self.Match.ambiguous: self._debug_log("Ambigious match for '{}'.".format( self._keystring)) self._handle_ambiguous_match(binding, count) elif match == self.Match.partial: self._debug_log("No match for '{}' (added {})".format( self._keystring, txt)) elif match == self.Match.none: self._debug_log("Giving up with '{}', no matches".format( self._keystring)) self._keystring = '' return False return True def _match_key(self, cmd_input): """Try to match a given keystring with any bound keychain. Args: cmd_input: The command string to find. Return: A tuple (matchtype, binding). matchtype: Match.definitive, Match.ambiguous, Match.partial or Match.none binding: - None with Match.partial/Match.none - The found binding with Match.definitive/ Match.ambiguous """ # A (cmd_input, binding) tuple (k, v of bindings) or None. definitive_match = None partial_match = False # Check definitive match try: definitive_match = (cmd_input, self.bindings[cmd_input]) except KeyError: pass # Check partial match for binding in self.bindings: if definitive_match is not None and binding == definitive_match[0]: # We already matched that one continue elif binding.startswith(cmd_input): partial_match = True break if definitive_match is not None and partial_match: return (self.Match.ambiguous, definitive_match[1]) elif definitive_match is not None: return (self.Match.definitive, definitive_match[1]) elif partial_match: return (self.Match.partial, None) else: return (self.Match.none, None) def _stop_delayed_exec(self): """Stop a delayed execution if any is running.""" if self._timer is not None: if self.do_log: log.keyboard.debug("Stopping delayed execution.") self._timer.stop() self._timer = None def _handle_ambiguous_match(self, binding, count): """Handle an ambiguous match. Args: binding: The command-string to execute. count: The count to pass. """ self._debug_log("Ambiguous match for '{}'".format(self._keystring)) time = config.get('input', 'timeout') if time == 0: # execute immediately self._keystring = '' self.execute(binding, self.Type.chain, count) else: # execute in `time' ms self._debug_log("Scheduling execution of {} in {}ms".format( binding, time)) self._timer = usertypes.Timer(self, 'ambigious_match') self._timer.setSingleShot(True) self._timer.setInterval(time) self._timer.timeout.connect( functools.partial(self.delayed_exec, binding, count)) self._timer.start() def delayed_exec(self, command, count): """Execute a delayed command. Args: command/count: As if passed to self.execute() """ self._debug_log("Executing delayed command now!") self._timer = None self._keystring = '' self.keystring_updated.emit(self._keystring) self.execute(command, self.Type.chain, count) def handle(self, e): """Handle a new keypress and call the respective handlers. Args: e: the KeyPressEvent from Qt """ handled = self._handle_special_key(e) if handled or not self._supports_chains: return handled handled = self._handle_single_key(e) self.keystring_updated.emit(self._keystring) return handled def read_config(self, modename=None): """Read the configuration. Config format: key = command, e.g.: <Ctrl+Q> = quit Args: modename: Name of the mode to use. """ if modename is None: if self._modename is None: raise ValueError("read_config called with no mode given, but " "None defined so far!") modename = self._modename else: self._modename = modename self.bindings = {} self.special_bindings = {} keyconfparser = objreg.get('key-config') for (key, cmd) in keyconfparser.get_bindings_for(modename).items(): if not cmd: continue elif key.startswith('<') and key.endswith('>'): keystr = utils.normalize_keystr(key[1:-1]) self.special_bindings[keystr] = cmd elif self._supports_chains: self.bindings[key] = cmd elif self._warn_on_keychains: log.keyboard.warning( "Ignoring keychain '{}' in mode '{}' because " "keychains are not supported there.".format(key, modename)) def execute(self, cmdstr, keytype, count=None): """Handle a completed keychain. Args: cmdstr: The command to execute as a string. keytype: Type.chain or Type.special count: The count if given. """ raise NotImplementedError @pyqtSlot(str) def on_keyconfig_changed(self, mode): """Re-read the config if a keybinding was changed.""" if self._modename is None: raise AttributeError("on_keyconfig_changed called but no section " "defined!") if mode == self._modename: self.read_config()
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The main statusbar widget.""" import collections from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QTime, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import command, progress, keystring, percentage, url, prompt, tabindex from qutebrowser.mainwindow.statusbar import text as textwidget PreviousWidget = usertypes.enum("PreviousWidget", ["none", "prompt", "command"]) Severity = usertypes.enum("Severity", ["normal", "warning", "error"]) CaretMode = usertypes.enum("CaretMode", ["off", "on", "selection"]) class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar.
import itertools import functools from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl from PyQt5.QtWidgets import QApplication from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage from qutebrowser.config import config from qutebrowser.keyinput import modeman from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg from qutebrowser.browser import webpage, hints, webelem from qutebrowser.commands import cmdexc LoadStatus = usertypes.enum('LoadStatus', ['none', 'success', 'error', 'warn', 'loading']) tab_id_gen = itertools.count(0) class WebView(QWebView): """One browser tab in TabbedBrowser. Our own subclass of a QWebView with some added bells and whistles. Attributes: hintmanager: The HintManager instance for this view. progress: loading progress of this page. scroll_pos: The current scroll position as (x%, y%) tuple.
class BaseKeyParser(QObject): """Parser for vim-like key sequences and shortcuts. Not intended to be instantiated directly. Subclasses have to override execute() to do whatever they want to. Class Attributes: Match: types of a match between a binding and the keystring. partial: No keychain matched yet, but it's still possible in the future. definitive: Keychain matches exactly. none: No more matches possible. Types: type of a key binding. chain: execute() was called via a chain-like key binding special: execute() was called via a special key binding do_log: Whether to log keypresses or not. passthrough: Whether unbound keys should be passed through with this handler. Attributes: bindings: Bound key bindings special_bindings: Bound special bindings (<Foo>). _win_id: The window ID this keyparser is associated with. _warn_on_keychains: Whether a warning should be logged when binding keychains in a section which does not support them. _keystring: The currently entered key sequence _modename: The name of the input mode associated with this keyparser. _supports_count: Whether count is supported _supports_chains: Whether keychains are supported Signals: keystring_updated: Emitted when the keystring is updated. arg: New keystring. request_leave: Emitted to request leaving a mode. arg 0: Mode to leave. arg 1: Reason for leaving. arg 2: Ignore the request if we're not in that mode """ keystring_updated = pyqtSignal(str) request_leave = pyqtSignal(usertypes.KeyMode, str, bool) do_log = True passthrough = False Match = usertypes.enum('Match', ['partial', 'definitive', 'other', 'none']) Type = usertypes.enum('Type', ['chain', 'special']) def __init__(self, win_id, parent=None, supports_count=None, supports_chains=False): super().__init__(parent) self._win_id = win_id self._modename = None self._keystring = '' if supports_count is None: supports_count = supports_chains self._supports_count = supports_count self._supports_chains = supports_chains self._warn_on_keychains = True self.bindings = {} self.special_bindings = {} config.instance.changed.connect(self._on_config_changed) def __repr__(self): return utils.get_repr(self, supports_count=self._supports_count, supports_chains=self._supports_chains) def _debug_log(self, message): """Log a message to the debug log if logging is active. Args: message: The message to log. """ if self.do_log: log.keyboard.debug(message) def _handle_special_key(self, e): """Handle a new keypress with special keys (<Foo>). Return True if the keypress has been handled, and False if not. Args: e: the KeyPressEvent from Qt. Return: True if event has been handled, False otherwise. """ binding = utils.keyevent_to_string(e) if binding is None: self._debug_log("Ignoring only-modifier keyeevent.") return False if binding not in self.special_bindings: key_mappings = config.val.bindings.key_mappings try: binding = key_mappings['<{}>'.format(binding)][1:-1] except KeyError: pass try: cmdstr = self.special_bindings[binding] except KeyError: self._debug_log("No special binding found for {}.".format(binding)) return False count, _command = self._split_count(self._keystring) self.execute(cmdstr, self.Type.special, count) self.clear_keystring() return True def _split_count(self, keystring): """Get count and command from the current keystring. Args: keystring: The key string to split. Return: A (count, command) tuple. """ if self._supports_count: (countstr, cmd_input) = re.match(r'^(\d*)(.*)', keystring).groups() count = int(countstr) if countstr else None if count == 0 and not cmd_input: cmd_input = keystring count = None else: cmd_input = keystring count = None return count, cmd_input def _handle_single_key(self, e): """Handle a new keypress with a single key (no modifiers). Separate the keypress into count/command, then check if it matches any possible command, and either run the command, ignore it, or display an error. Args: e: the KeyPressEvent from Qt. Return: A self.Match member. """ txt = e.text() key = e.key() self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt)) if len(txt) == 1: category = unicodedata.category(txt) is_control_char = (category == 'Cc') else: is_control_char = False if (not txt) or is_control_char: self._debug_log("Ignoring, no text char") return self.Match.none count, cmd_input = self._split_count(self._keystring + txt) match, binding = self._match_key(cmd_input) if match == self.Match.none: mappings = config.val.bindings.key_mappings mapped = mappings.get(txt, None) if mapped is not None: txt = mapped count, cmd_input = self._split_count(self._keystring + txt) match, binding = self._match_key(cmd_input) self._keystring += txt if match == self.Match.definitive: self._debug_log("Definitive match for '{}'.".format( self._keystring)) self.clear_keystring() self.execute(binding, self.Type.chain, count) elif match == self.Match.partial: self._debug_log("No match for '{}' (added {})".format( self._keystring, txt)) elif match == self.Match.none: self._debug_log("Giving up with '{}', no matches".format( self._keystring)) self.clear_keystring() elif match == self.Match.other: pass else: raise AssertionError("Invalid match value {!r}".format(match)) return match def _match_key(self, cmd_input): """Try to match a given keystring with any bound keychain. Args: cmd_input: The command string to find. Return: A tuple (matchtype, binding). matchtype: Match.definitive, Match.partial or Match.none. binding: - None with Match.partial/Match.none. - The found binding with Match.definitive. """ if not cmd_input: # Only a count, no command yet, but we handled it return (self.Match.other, None) # A (cmd_input, binding) tuple (k, v of bindings) or None. definitive_match = None partial_match = False # Check definitive match try: definitive_match = (cmd_input, self.bindings[cmd_input]) except KeyError: pass # Check partial match for binding in self.bindings: if definitive_match is not None and binding == definitive_match[0]: # We already matched that one continue elif binding.startswith(cmd_input): partial_match = True break if definitive_match is not None: return (self.Match.definitive, definitive_match[1]) elif partial_match: return (self.Match.partial, None) else: return (self.Match.none, None) def handle(self, e): """Handle a new keypress and call the respective handlers. Args: e: the KeyPressEvent from Qt Return: True if the event was handled, False otherwise. """ handled = self._handle_special_key(e) if handled or not self._supports_chains: return handled match = self._handle_single_key(e) # don't emit twice if the keystring was cleared in self.clear_keystring if self._keystring: self.keystring_updated.emit(self._keystring) return match != self.Match.none @config.change_filter('bindings') def _on_config_changed(self): self._read_config() def _read_config(self, modename=None): """Read the configuration. Config format: key = command, e.g.: <Ctrl+Q> = quit Args: modename: Name of the mode to use. """ if modename is None: if self._modename is None: raise ValueError("read_config called with no mode given, but " "None defined so far!") modename = self._modename else: self._modename = modename self.bindings = {} self.special_bindings = {} for key, cmd in config.key_instance.get_bindings_for(modename).items(): assert cmd self._parse_key_command(modename, key, cmd) def _parse_key_command(self, modename, key, cmd): """Parse the keys and their command and store them in the object.""" if utils.is_special_key(key): self.special_bindings[key[1:-1]] = cmd elif self._supports_chains: self.bindings[key] = cmd elif self._warn_on_keychains: log.keyboard.warning("Ignoring keychain '{}' in mode '{}' because " "keychains are not supported there.".format( key, modename)) def execute(self, cmdstr, keytype, count=None): """Handle a completed keychain. Args: cmdstr: The command to execute as a string. keytype: Type.chain or Type.special count: The count if given. """ raise NotImplementedError def clear_keystring(self): """Clear the currently entered key sequence.""" if self._keystring: self._debug_log("discarding keystring '{}'.".format( self._keystring)) self._keystring = '' self.keystring_updated.emit(self._keystring)
# You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The main statusbar widget.""" import collections from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QTime from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.widgets.statusbar import (command, progress, keystring, percentage, url, prompt) from qutebrowser.widgets.statusbar import text as textwidget PreviousWidget = usertypes.enum('PreviousWidget', ['none', 'prompt', 'command']) class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout. _stack: The QStackedLayout with cmd/txt widgets. _text_queue: A deque of (error, text) tuples to be displayed.
class DocstringParser: """Generate documentation based on a docstring of a command handler. The docstring needs to follow the format described in CONTRIBUTING. Attributes: _state: The current state of the parser state machine. _cur_arg_name: The name of the argument we're currently handling. _short_desc_parts: The short description of the function as list. _long_desc_parts: The long description of the function as list. short_desc: The short description of the function. long_desc: The long description of the function. arg_descs: A dict of argument names to their descriptions """ State = usertypes.enum( 'State', ['short', 'desc', 'desc_hidden', 'arg_start', 'arg_inside', 'misc']) def __init__(self, func): """Constructor. Args: func: The function to parse the docstring for. """ self._state = self.State.short self._cur_arg_name = None self._short_desc_parts = [] self._long_desc_parts = [] self.arg_descs = collections.OrderedDict() doc = inspect.getdoc(func) handlers = { self.State.short: self._parse_short, self.State.desc: self._parse_desc, self.State.desc_hidden: self._skip, self.State.arg_start: self._parse_arg_start, self.State.arg_inside: self._parse_arg_inside, self.State.misc: self._skip, } if doc is None: if sys.flags.optimize < 2: log.commands.warning( "Function {}() from {} has no docstring".format( utils.qualname(func), inspect.getsourcefile(func))) self.long_desc = "" self.short_desc = "" return for line in doc.splitlines(): handler = handlers[self._state] stop = handler(line) if stop: break for k, v in self.arg_descs.items(): desc = ' '.join(v) desc = re.sub(r', or None($|\.)', r'\1', desc) desc = re.sub(r', or None', r', or not given', desc) self.arg_descs[k] = desc self.long_desc = ' '.join(self._long_desc_parts) self.short_desc = ' '.join(self._short_desc_parts) def _process_arg(self, line): """Helper method to process a line like 'fooarg: Blah blub'.""" self._cur_arg_name, argdesc = line.split(':', maxsplit=1) self._cur_arg_name = self._cur_arg_name.strip().lstrip('*') self.arg_descs[self._cur_arg_name] = [argdesc.strip()] def _skip(self, line): """Handler to ignore everything until we get 'Args:'.""" if line.startswith('Args:'): self._state = self.State.arg_start def _parse_short(self, line): """Parse the short description (first block) in the docstring.""" if not line: self._state = self.State.desc else: self._short_desc_parts.append(line.strip()) def _parse_desc(self, line): """Parse the long description in the docstring.""" if line.startswith('Args:'): self._state = self.State.arg_start elif line.strip() == '//': self._state = self.State.desc_hidden elif line.strip(): self._long_desc_parts.append(line.strip()) def _parse_arg_start(self, line): """Parse first argument line.""" self._process_arg(line) self._state = self.State.arg_inside def _parse_arg_inside(self, line): """Parse subsequent argument lines.""" argname = self._cur_arg_name if re.match(r'^[A-Z][a-z]+:$', line): if not self.arg_descs[argname][-1].strip(): self.arg_descs[argname] = self.arg_descs[argname][:-1] return True elif not line.strip(): self.arg_descs[argname].append('\n\n') elif line[4:].startswith(' '): self.arg_descs[argname].append(line.strip() + '\n') else: self._process_arg(line) return False
def test_start(self): """Test the start= argument.""" e = usertypes.enum('Enum', ['three', 'four'], start=3) self.assertEqual(e.three.value, 3) self.assertEqual(e.four.value, 4)
import collections from PyQt5.QtCore import (pyqtSignal, pyqtSlot, pyqtProperty, Qt, QTime, QSize, QTimer) from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import (command, progress, keystring, percentage, url, prompt, tabindex) from qutebrowser.mainwindow.statusbar import text as textwidget PreviousWidget = usertypes.enum('PreviousWidget', ['none', 'prompt', 'command']) Severity = usertypes.enum('Severity', ['normal', 'warning', 'error']) CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection']) class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar.
import functools import html import attr from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QApplication, QDialog, QPushButton, QHBoxLayout, QVBoxLayout, QLabel, QMessageBox) from PyQt5.QtNetwork import QSslSocket from qutebrowser.config import config from qutebrowser.utils import usertypes, objreg, version, qtutils, log from qutebrowser.misc import objects, msgbox _Result = usertypes.enum( '_Result', ['quit', 'restart', 'restart_webkit', 'restart_webengine'], is_int=True, start=QDialog.Accepted + 1) @attr.s class _Button: """A button passed to BackendProblemDialog.""" text = attr.ib() setting = attr.ib() value = attr.ib() default = attr.ib(default=False) class _Dialog(QDialog):
from qutebrowser.browser import pdfjs @attr.s class DistributionInfo: """Information about the running distribution.""" id = attr.ib() parsed = attr.ib() version = attr.ib() pretty = attr.ib() Distribution = usertypes.enum( 'Distribution', ['unknown', 'ubuntu', 'debian', 'void', 'arch', 'gentoo', 'fedora', 'opensuse', 'linuxmint', 'manjaro']) def distribution(): """Get some information about the running Linux distribution. Returns: A DistributionInfo object, or None if no info could be determined. parsed: A Distribution enum member version: A Version object, or None pretty: Always a string (might be "Unknown") """ filename = os.environ.get('QUTE_FAKE_OS_RELEASE', '/etc/os-release') info = {} try:
webkittab.init() class WebTabError(Exception): """Base class for various errors.""" class UnsupportedOperationError(WebTabError): """Raised when an operation is not supported with the given backend.""" TerminationStatus = usertypes.enum( 'TerminationStatus', [ 'normal', 'abnormal', # non-zero exit status 'crashed', # e.g. segfault 'killed', 'unknown', ]) class TabData: """A simple namespace with a fixed set of attributes. Attributes: keep_icon: Whether the (e.g. cloned) icon should not be cleared on page load. inspector: The QWebInspector used for this webview. viewing_source: Set if we're currently showing a source view. override_target: Override for open_target for fake clicks (like hints).
"""pytest helper to monkeypatch the message module.""" import logging import collections import pytest from qutebrowser.utils import usertypes Message = collections.namedtuple('Message', ['level', 'win_id', 'text', 'immediate']) Level = usertypes.enum('Level', ('error', 'info', 'warning')) class MessageMock: """Helper object for message_mock. Attributes: _monkeypatch: The pytest monkeypatch fixture. Message: A namedtuple representing a message. messages: A list of Message tuples. caplog: The pytest-capturelog fixture. Level: The Level type for easier usage as a fixture. """ Level = Level
without "href". """ import collections.abc from html.parser import HTMLParser from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer from PyQt5.QtGui import QMouseEvent from qutebrowser.config import config from qutebrowser.keyinput import modeman from qutebrowser.utils import log, usertypes, utils, qtutils, objreg Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext', 'inputs', 'headers']) SELECTORS = { Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, ' 'frame, iframe, link, [onclick], [onmousedown], [role=link], ' '[role=option], [role=button], img'), Group.links: 'a, area, link, [role=link]', Group.images: 'img', Group.url: '[src], [href]', Group.prevnext: 'a, area, button, link, [role=button]', Group.inputs: ('input[type=text], input[type=email], input[type=url], ' 'input[type=tel], input[type=number], ' 'input[type=password], input[type=search], ' 'input:not([type]), textarea'), Group.headers: ('h1, h2, h3, h4, h5, h6'),
# You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The base completion model for completion in the command line. Module attributes: Role: An enum of user defined model roles. """ from PyQt5.QtCore import Qt from PyQt5.QtGui import QStandardItemModel, QStandardItem from qutebrowser.utils import usertypes, qtutils Role = usertypes.enum('Role', ['marks', 'sort'], start=Qt.UserRole, is_int=True) class BaseCompletionModel(QStandardItemModel): """A simple QStandardItemModel adopted for completions. Used for showing completions later in the CompletionView. Supports setting marks and adding new categories/items easily. """ def __init__(self, parent=None): super().__init__(parent) self.setColumnCount(3) def _get_marks(self, needle, haystack):
import datetime import pkg_resources from PyQt5.QtCore import pyqtSlot, Qt, QSize from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton, QVBoxLayout, QHBoxLayout, QCheckBox, QDialogButtonBox, QApplication) import qutebrowser from qutebrowser.utils import version, log, utils, objreg, usertypes from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient, pastebin) from qutebrowser.config import config, configfiles Result = usertypes.enum('Result', ['restore', 'no_restore'], is_int=True, start=QDialog.Accepted + 1) def parse_fatal_stacktrace(text): """Get useful information from a fatal faulthandler stacktrace. Args: text: The text to parse. Return: A tuple with the first element being the error type, and the second element being the first stacktrace frame. """ lines = [ r'Fatal Python error: (.*)',
class WebTabError(Exception): """Base class for various errors.""" class UnsupportedOperationError(WebTabError): """Raised when an operation is not supported with the given backend.""" TerminationStatus = usertypes.enum('TerminationStatus', [ 'normal', 'abnormal', # non-zero exit status 'crashed', # e.g. segfault 'killed', 'unknown', ]) @attr.s class TabData: """A simple namespace with a fixed set of attributes. Attributes: keep_icon: Whether the (e.g. cloned) icon should not be cleared on page load. inspector: The QWebInspector used for this webview. viewing_source: Set if we're currently showing a source view.
"""The tab widget used for TabbedBrowser from browser.py.""" import collections import functools from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize, QRect, QTimer from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, QStyle, QStylePainter, QStyleOptionTab) from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes from qutebrowser.config import config from qutebrowser.browser import webview PixelMetrics = usertypes.enum('PixelMetrics', ['icon_padding'], start=QStyle.PM_CustomBase, is_int=True) class TabWidget(QTabWidget): """The tab widget used for TabbedBrowser. Signals: tab_index_changed: Emitted when the current tab was changed. arg 0: The index of the tab which is now focused. arg 1: The total count of tabs. """ tab_index_changed = pyqtSignal(int, int) def __init__(self, win_id, parent=None):
class TestRegister: # pylint: disable=unused-variable def test_simple(self): @cmdutils.register() def fun(): """Blah.""" pass cmd = cmdutils.cmd_dict['fun'] assert cmd.handler is fun assert cmd.name == 'fun' assert len(cmdutils.cmd_dict) == 1 assert not cmdutils.aliases def test_underlines(self): """Make sure the function name is normalized correctly (_ -> -).""" @cmdutils.register() def eggs_bacon(): """Blah.""" pass assert cmdutils.cmd_dict['eggs-bacon'].name == 'eggs-bacon' assert 'eggs_bacon' not in cmdutils.cmd_dict def test_lowercasing(self): """Make sure the function name is normalized correctly (uppercase).""" @cmdutils.register() def Test(): # pylint: disable=invalid-name """Blah.""" pass assert cmdutils.cmd_dict['test'].name == 'test' assert 'Test' not in cmdutils.cmd_dict def test_explicit_name(self): """Test register with explicit name.""" @cmdutils.register(name='foobar') def fun(): """Blah.""" pass assert cmdutils.cmd_dict['foobar'].name == 'foobar' assert 'fun' not in cmdutils.cmd_dict assert len(cmdutils.cmd_dict) == 1 assert not cmdutils.aliases def test_multiple_names(self): """Test register with name being a list.""" @cmdutils.register(name=['foobar', 'blub']) def fun(): """Blah.""" pass assert cmdutils.cmd_dict['foobar'].name == 'foobar' assert cmdutils.cmd_dict['blub'].name == 'foobar' assert 'fun' not in cmdutils.cmd_dict assert len(cmdutils.cmd_dict) == 2 assert cmdutils.aliases == ['blub'] def test_multiple_registrations(self): """Make sure registering the same name twice raises ValueError.""" @cmdutils.register(name=['foobar', 'blub']) def fun(): """Blah.""" pass with pytest.raises(ValueError): @cmdutils.register(name=['blah', 'blub']) def fun2(): """Blah.""" pass def test_instance(self): """Make sure the instance gets passed to Command.""" @cmdutils.register(instance='foobar') def fun(self): """Blah.""" pass assert cmdutils.cmd_dict['fun']._instance == 'foobar' def test_kwargs(self): """Make sure the other keyword arguments get passed to Command.""" @cmdutils.register(hide=True) def fun(): """Blah.""" pass assert cmdutils.cmd_dict['fun'].hide def test_star_args(self): """Check handling of *args.""" @cmdutils.register() def fun(*args): """Blah.""" pass with pytest.raises(argparser.ArgumentParserError): cmdutils.cmd_dict['fun'].parser.parse_args([]) def test_star_args_optional(self): """Check handling of *args withstar_args_optional.""" @cmdutils.register(star_args_optional=True) def fun(*args): """Blah.""" pass cmdutils.cmd_dict['fun'].parser.parse_args([]) def test_flag(self): @cmdutils.register() def fun(arg=False): """Blah.""" pass parser = cmdutils.cmd_dict['fun'].parser assert parser.parse_args(['--arg']).arg assert parser.parse_args(['-a']).arg assert not parser.parse_args([]).arg def test_flag_argument(self): @cmdutils.register() @cmdutils.argument('arg', flag='b') def fun(arg=False): """Blah.""" pass parser = cmdutils.cmd_dict['fun'].parser assert parser.parse_args(['-b']).arg with pytest.raises(argparser.ArgumentParserError): parser.parse_args(['-a']) def test_partial_arg(self): """Test with only some arguments decorated with @cmdutils.argument.""" @cmdutils.register() @cmdutils.argument('arg1', flag='b') def fun(arg1=False, arg2=False): """Blah.""" pass def test_win_id(self): @cmdutils.register() @cmdutils.argument('win_id', win_id=True) def fun(win_id): """Blah.""" pass assert cmdutils.cmd_dict['fun']._get_call_args(42) == ([42], {}) def test_count(self): @cmdutils.register() @cmdutils.argument('count', count=True) def fun(count=0): """Blah.""" pass assert cmdutils.cmd_dict['fun']._get_call_args(42) == ([0], {}) def test_count_without_default(self): with pytest.raises(TypeError) as excinfo: @cmdutils.register() @cmdutils.argument('count', count=True) def fun(count): """Blah.""" pass expected = "fun: handler has count parameter without default!" assert str(excinfo.value) == expected @pytest.mark.parametrize('hide', [True, False]) def test_pos_args(self, hide): @cmdutils.register() @cmdutils.argument('arg', hide=hide) def fun(arg): """Blah.""" pass pos_args = cmdutils.cmd_dict['fun'].pos_args if hide: assert pos_args == [] else: assert pos_args == [('arg', 'arg')] Enum = usertypes.enum('Test', ['x', 'y']) @pytest.mark.parametrize( 'typ, inp, choices, expected', [ (int, '42', None, 42), (int, 'x', None, cmdexc.ArgumentTypeError), (str, 'foo', None, 'foo'), (typing.Union[str, int], 'foo', None, 'foo'), (typing.Union[str, int], '42', None, 42), # Choices (str, 'foo', ['foo'], 'foo'), (str, 'bar', ['foo'], cmdexc.ArgumentTypeError), # Choices with Union: only checked when it's a str (typing.Union[str, int], 'foo', ['foo'], 'foo'), (typing.Union[str, int], 'bar', ['foo'], cmdexc.ArgumentTypeError), (typing.Union[str, int], '42', ['foo'], 42), (Enum, 'x', None, Enum.x), (Enum, 'z', None, cmdexc.ArgumentTypeError), ]) def test_typed_args(self, typ, inp, choices, expected): @cmdutils.register() @cmdutils.argument('arg', choices=choices) def fun(arg: typ): """Blah.""" pass cmd = cmdutils.cmd_dict['fun'] cmd.namespace = cmd.parser.parse_args([inp]) if expected is cmdexc.ArgumentTypeError: with pytest.raises(cmdexc.ArgumentTypeError): cmd._get_call_args(win_id=0) else: assert cmd._get_call_args(win_id=0) == ([expected], {})
import collections import functools from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer, QUrl) from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, QStyle, QStylePainter, QStyleOptionTab, QStyleFactory, QWidget) from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes, log from qutebrowser.config import config from qutebrowser.misc import objects PixelMetrics = usertypes.enum('PixelMetrics', ['icon_padding'], start=QStyle.PM_CustomBase, is_int=True) class TabWidget(QTabWidget): """The tab widget used for TabbedBrowser. Signals: tab_index_changed: Emitted when the current tab was changed. arg 0: The index of the tab which is now focused. arg 1: The total count of tabs. """ tab_index_changed = pyqtSignal(int, int) def __init__(self, win_id, parent=None):
Module attributes: ATTRIBUTES: A mapping from internal setting names to QWebSetting enum constants. SETTERS: A mapping from setting names to QWebSetting setter method names. settings: The global QWebSettings singleton instance. """ import os.path from PyQt5.QtWebKit import QWebSettings from PyQt5.QtCore import QStandardPaths from qutebrowser.config import config from qutebrowser.utils import usertypes, standarddir, objreg MapType = usertypes.enum('MapType', ['attribute', 'setter', 'static_setter']) MAPPINGS = { 'permissions': { 'allow-images': (MapType.attribute, QWebSettings.AutoLoadImages), 'allow-javascript': (MapType.attribute, QWebSettings.JavascriptEnabled), 'javascript-can-open-windows': (MapType.attribute, QWebSettings.JavascriptCanOpenWindows), 'javascript-can-close-windows': (MapType.attribute, QWebSettings.JavascriptCanCloseWindows), 'javascript-can-access-clipboard': (MapType.attribute, QWebSettings.JavascriptCanAccessClipboard), #'allow-java':
import os import sys import shutil import os.path import contextlib from PyQt5.QtCore import QStandardPaths from PyQt5.QtWidgets import QApplication from qutebrowser.utils import log, debug, usertypes, message, utils # The cached locations _locations = {} Location = usertypes.enum('Location', [ 'config', 'auto_config', 'data', 'system_data', 'cache', 'download', 'runtime' ]) APPNAME = 'qutebrowser' class EmptyValueError(Exception): """Error raised when QStandardPaths returns an empty value.""" @contextlib.contextmanager def _unset_organization(): """Temporarily unset QApplication.organizationName(). This is primarily needed in config.py. """
def enum(): return usertypes.enum("Enum", ["one", "two"])
SELECTORS: CSS selectors for different groups of elements. FILTERS: A dictionary of filter functions for the modes. The filter for "links" filters javascript:-links and a-tags without "href". """ import collections.abc import functools from PyQt5.QtCore import QRect, QUrl from PyQt5.QtWebKit import QWebElement from qutebrowser.config import config from qutebrowser.utils import log, usertypes, utils Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext', 'focus']) SELECTORS = { Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, ' 'frame, iframe, link, [onclick], [onmousedown], [role=link], ' '[role=option], [role=button], img'), Group.links: 'a, area, link, [role=link]', Group.images: 'img', Group.url: '[src], [href]', Group.prevnext: 'a, area, button, link, [role=button]', Group.focus: '*:focus',
def test_start(): """Test the start= argument.""" e = usertypes.enum("Enum", ["three", "four"], start=3) assert e.three.value == 3 assert e.four.value == 4
import collections import functools import pathlib import tempfile import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, QTimer, QAbstractListModel) from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config from qutebrowser.utils import (usertypes, standarddir, utils, message, log, qtutils) ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole, is_int=True) # Remember the last used directory last_used_directory = None # All REFRESH_INTERVAL milliseconds, speeds will be recalculated and downloads # redrawn. _REFRESH_INTERVAL = 500 class UnsupportedAttribute: """Class which is used to create attributes which are not supported.
def test_is_int(): """Test the is_int argument.""" int_enum = usertypes.enum("Enum", ["item"], is_int=True) no_int_enum = usertypes.enum("Enum", ["item"]) assert isinstance(int_enum.item, int) assert not isinstance(no_int_enum.item, int)
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl from PyQt5.QtGui import QMouseEvent, QClipboard from PyQt5.QtWidgets import QApplication from PyQt5.QtWebKit import QWebElement from qutebrowser.config import config from qutebrowser.keyinput import modeman, modeparsers from qutebrowser.browser import webelem from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners from qutebrowser.utils import usertypes, log, qtutils, message, objreg ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label']) Target = usertypes.enum('Target', [ 'normal', 'tab', 'tab_bg', 'window', 'yank', 'yank_primary', 'run', 'fill', 'hover', 'rapid', 'rapid_win', 'download', 'userscript', 'spawn' ]) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(mode, win_id): """Stop hinting when insert mode was entered.""" if mode == usertypes.KeyMode.insert: modeman.maybe_leave(win_id, usertypes.KeyMode.hint, 'insert mode') class HintContext: """Context namespace used for hinting. Attributes: frames: The QWebFrames to use.
def test_unique(): """Make sure elements need to be unique.""" with pytest.raises(TypeError): usertypes.enum("Enum", ["item", "item"])
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QTimer, Qt, QVariant, QAbstractListModel, QModelIndex, QUrl) from PyQt5.QtGui import QDesktopServices from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply # We need this import so PyQt can use it inside pyqtSlot from PyQt5.QtWebKitWidgets import QWebPage # pylint: disable=unused-import from qutebrowser.config import config from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import (message, usertypes, log, utils, urlutils, objreg, standarddir, qtutils) from qutebrowser.browser import http from qutebrowser.browser.network import networkmanager ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole, is_int=True) RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager']) DownloadPath = collections.namedtuple('DownloadPath', ['filename', 'question']) # Remember the last used directory last_used_directory = None # All REFRESH_INTERVAL milliseconds, speeds will be recalculated and downloads # redrawn.
# # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """URL displayed in the statusbar.""" from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt from qutebrowser.browser import webview from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.config import style from qutebrowser.utils import usertypes # Note this has entries for success/error/warn from widgets.webview:LoadStatus UrlType = usertypes.enum('UrlType', ['success', 'error', 'warn', 'hover', 'normal']) class UrlText(textbase.TextBase): """URL displayed in the statusbar. Attributes: _normal_url: The normal URL to be displayed as a UrlType instance. _normal_url_type: The type of the normal URL as a UrlType instance. _hover_url: The URL we're currently hovering over. _ssl_errors: Whether SSL errors occured while loading. Class attributes: _urltype: The URL type to show currently (normal/ok/error/warn/hover). Accessed via the urltype property.
# # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """Tests for qutebrowser.commands.argparser.""" import inspect import pytest from PyQt5.QtCore import QUrl from qutebrowser.commands import argparser, cmdexc from qutebrowser.utils import usertypes, objreg Enum = usertypes.enum("Enum", ["foo", "foo_bar"]) class FakeTabbedBrowser: def __init__(self): self.opened_url = None def tabopen(self, url): self.opened_url = url class TestArgumentParser: @pytest.fixture def parser(self): return argparser.ArgumentParser("foo")
# # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """Tests for qutebrowser.commands.argparser.""" import inspect import pytest from PyQt5.QtCore import QUrl from qutebrowser.commands import argparser, cmdexc from qutebrowser.utils import usertypes, objreg Enum = usertypes.enum('Enum', ['foo', 'foo_bar']) class FakeTabbedBrowser: def __init__(self): self.opened_url = None def tabopen(self, url): self.opened_url = url class TestArgumentParser: @pytest.fixture def parser(self):
# You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. """The base completion model for completion in the command line. Module attributes: Role: An enum of user defined model roles. """ from PyQt5.QtCore import Qt from PyQt5.QtGui import QStandardItemModel, QStandardItem from qutebrowser.utils import usertypes Role = usertypes.enum('Role', ['sort', 'userdata'], start=Qt.UserRole, is_int=True) class BaseCompletionModel(QStandardItemModel): """A simple QStandardItemModel adopted for completions. Used for showing completions later in the CompletionView. Supports setting marks and adding new categories/items easily. Class Attributes: COLUMN_WIDTHS: The width percentages of the columns used in the completion view. DUMB_SORT: the dumb sorting used by the model """
class Text(textbase.TextBase): """Text displayed in the statusbar. Attributes: _normaltext: The "permanent" text. Never automatically cleared. _temptext: The temporary text to display. _jstext: The text javascript wants to display. The temptext is shown from StatusBar when a temporary text or error is available. If not, the permanent text is shown. """ Text = usertypes.enum('Text', ['normal', 'temp', 'js']) def __init__(self, parent=None): super().__init__(parent) self._normaltext = '' self._temptext = '' self._jstext = '' objreg.get('config').changed.connect(self.update_text) def set_text(self, which, text): """Set a text. Args: which: Which text to set, a self.Text instance. text: The text to set. """ log.statusbar.debug("Setting {} text to '{}'.".format( which.name, text)) if which is self.Text.normal: self._normaltext = text elif which is self.Text.temp: self._temptext = text elif which is self.Text.js: self._jstext = text else: raise ValueError("Invalid value {} for which!".format(which)) self.update_text() @pyqtSlot(str) def maybe_reset_text(self, text): """Clear a normal text if it still matches an expected text.""" if self._normaltext == text: log.statusbar.debug("Resetting: '{}'".format(text)) self.set_text(self.Text.normal, '') else: log.statusbar.debug("Ignoring reset: '{}'".format(text)) @config.change_filter('ui', 'display-statusbar-messages') def update_text(self): """Update QLabel text when needed.""" if self._temptext: self.setText(self._temptext) elif self._jstext and config.get('ui', 'display-statusbar-messages'): self.setText(self._jstext) elif self._normaltext: self.setText(self._normaltext) else: self.setText('') @pyqtSlot(str) def on_statusbar_message(self, val): """Called when javascript tries to set a statusbar message.""" self._jstext = val @pyqtSlot() def on_load_started(self): """Clear jstext when page loading started.""" self._jstext = '' @pyqtSlot(int) def on_tab_changed(self, tab): """Set the correct jstext when the current tab changed.""" self._jstext = tab.statusbar_message