def repeat(times: {'type': int}, *command, win_id: {'special': 'win_id'}): """Repeat a given command. Args: times: How many times to repeat. *command: The command to run, with optional args. """ if times < 0: raise cmdexc.CommandError("A negative count doesn't make sense.") cmdline = ' '.join(command) commandrunner = runners.CommandRunner(win_id) for _ in range(times): commandrunner.run_safely(cmdline)
def check_overflow(arg, ctype): """Check if the given argument is in bounds for the given type. Args: arg: The argument to check ctype: The C/Qt type to check as a string. """ try: qtutils.check_overflow(arg, ctype) except OverflowError: raise cmdexc.CommandError( "Numeric argument is too large for internal {} " "representation.".format(ctype))
def tab_next(self, count: {'special': 'count'} = 1): """Switch to the next tab, or switch [count] tabs forward. Args: count: How many tabs to switch forward. """ newidx = self._current_index() + count if newidx < self._count(): self._set_current_index(newidx) elif config.get('tabs', 'wrap'): self._set_current_index(newidx % self._count()) else: raise cmdexc.CommandError("Last tab")
def tab_move(self, direction: {'type': ('+', '-')} = None, count: {'special': 'count'} = None): """Move the current tab. Args: direction: `+` or `-` for relative moving, not given for absolute moving. count: If moving absolutely: New position (default: 0) If moving relatively: Offset. """ if direction is None: new_idx = self._tab_move_absolute(count) elif direction in '+-': try: new_idx = self._tab_move_relative(direction, count) except ValueError: raise cmdexc.CommandError("Count must be given for relative " "moving!") else: raise cmdexc.CommandError( "Invalid direction '{}'!".format(direction)) if not 0 <= new_idx < self._count(): raise cmdexc.CommandError( "Can't move tab to position {}!".format(new_idx)) tabbed_browser = self._tabbed_browser() tab = self._current_widget() cur_idx = self._current_index() icon = tabbed_browser.tabIcon(cur_idx) label = tabbed_browser.tabText(cur_idx) cmdutils.check_overflow(cur_idx, 'int') cmdutils.check_overflow(new_idx, 'int') tabbed_browser.setUpdatesEnabled(False) try: tabbed_browser.removeTab(cur_idx) tabbed_browser.insertTab(new_idx, tab, icon, label) self._set_current_index(new_idx) finally: tabbed_browser.setUpdatesEnabled(True)
def check_exclusive(flags, names): """Check if only one flag is set with exclusive flags. Raise a CommandError if not. Args: flags: An iterable of booleans to check. names: An iterable of flag names for the error message. """ if sum(1 for e in flags if e) > 1: argstr = '/'.join('-' + e for e in names) raise cmdexc.CommandError("Only one of {} can be given!".format( argstr))
def tab_prev(self, count: {'special': 'count'} = 1): """Switch to the previous tab, or switch [count] tabs back. Args: count: How many tabs to switch back. """ newidx = self._current_index() - count if newidx >= 0: self._set_current_index(newidx) elif config.get('tabs', 'wrap'): self._set_current_index(newidx % self._count()) else: raise cmdexc.CommandError("First tab")
def cancel_download(self, count: {'special': 'count'} = 1): """Cancel the first/[count]th download. Args: count: The index of the download to cancel. """ if count == 0: return try: download = self.downloads[count - 1] except IndexError: raise cmdexc.CommandError("There's no download {}!".format(count)) download.cancel()
def zoom_perc(self, perc, fuzzyval=True): """Zoom to a given zoom percentage. Args: perc: The zoom percentage as int. fuzzyval: Whether to set the NeighborLists fuzzyval. """ if fuzzyval: self._zoom.fuzzyval = int(perc) if perc < 0: raise cmdexc.CommandError("Can't zoom {}%!".format(perc)) self.setZoomFactor(float(perc) / 100) message.info(self._win_id, "Zoom level: {}%".format(perc))
def config_write_py(self, filename=None, force=False, defaults=False): """Write the current configuration to a config.py file. Args: filename: The file to write to, or None for the default config.py. force: Force overwriting existing files. defaults: Write the defaults instead of values configured via :set. """ if filename is None: filename = os.path.join(standarddir.config(), 'config.py') else: if not os.path.isabs(filename): filename = os.path.join(standarddir.config(), filename) filename = os.path.expanduser(filename) if os.path.exists(filename) and not force: raise cmdexc.CommandError("{} already exists - use --force to " "overwrite!".format(filename)) if defaults: options = [(None, opt, opt.default) for _name, opt in sorted(configdata.DATA.items())] bindings = dict(configdata.DATA['bindings.default'].default) commented = True else: options = [] for values in self._config: for scoped in values: options.append((scoped.pattern, values.opt, scoped.value)) bindings = dict(self._config.get_mutable_obj('bindings.commands')) commented = False writer = configfiles.ConfigPyWriter(options, bindings, commented=commented) try: writer.write(filename) except OSError as e: raise cmdexc.CommandError(str(e))
def completion_item_focus(self, which, history=False): """Shift the focus of the completion menu to another item. Args: which: 'next', 'prev', 'next-category', or 'prev-category'. history: Navigate through command history if no text was typed. """ if history: status = objreg.get('status-command', scope='window', window=self._win_id) if (status.text() == ':' or status.history.is_browsing() or not self._active): if which == 'next': status.command_history_next() return elif which == 'prev': status.command_history_prev() return else: raise cmdexc.CommandError("Can't combine --history with " "{}!".format(which)) if not self._active: return selmodel = self.selectionModel() indices = { 'next': self._next_idx(upwards=False), 'prev': self._next_idx(upwards=True), 'next-category': self._next_category_idx(upwards=False), 'prev-category': self._next_category_idx(upwards=True), } idx = indices[which] if not idx.isValid(): return selmodel.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) # if the last item is focused, try to fetch more if idx.row() == self.model().rowCount(idx.parent()) - 1: self.expandAll() count = self.model().count() if count == 0: self.hide() elif count == 1 and config.val.completion.quick: self.hide() elif config.val.completion.show == 'auto': self.show()
def set(self, win_id, option=None, value=None, temp=False, print_=False, *, pattern=None): """Set an option. If the option name ends with '?' or no value is provided, the value of the option is shown instead. Using :set without any arguments opens a page where settings can be changed interactively. Args: option: The name of the option. value: The value to set. pattern: The URL pattern to use. temp: Set value temporarily until qutebrowser is closed. print_: Print the value after setting. """ if option is None: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) tabbed_browser.openurl(QUrl('qute://settings'), newtab=False) return if option.endswith('!'): raise cmdexc.CommandError("Toggling values was moved to the " ":config-cycle command") pattern = self._parse_pattern(pattern) if option.endswith('?') and option != '?': self._print_value(option[:-1], pattern=pattern) return with self._handle_config_error(): if value is None: self._print_value(option, pattern=pattern) else: self._config.set_str(option, value, pattern=pattern, save_yaml=not temp) if print_: self._print_value(option, pattern=pattern)
def session_save(self, name: str = default, current=False, quiet=False, force=False, only_active_window=False, with_private=False, win_id=None): """Save a session. Args: name: The name of the session. If not given, the session configured in session_default_name is saved. current: Save the current session instead of the default. quiet: Don't show confirmation message. force: Force saving internal sessions (starting with an underline). only_active_window: Saves only tabs of the currently active window. with_private: Include private windows. """ if name is not default and name.startswith('_') and not force: raise cmdexc.CommandError("{} is an internal session, use --force " "to save anyways.".format(name)) if current: if self._current is None: raise cmdexc.CommandError("No session loaded currently!") name = self._current assert not name.startswith('_') try: if only_active_window: name = self.save(name, only_window=win_id, with_private=True) else: name = self.save(name, with_private=with_private) except SessionError as e: raise cmdexc.CommandError( "Error while saving session: {}".format(e)) else: if not quiet: message.info("Saved session {}.".format(name))
def session_load(self, name, clear=False, temp=False, force=False, delete=False): """Load a session. Args: name: The name of the session. clear: Close all existing windows. temp: Don't set the current session for :session-save. force: Force loading internal sessions (starting with an underline). delete: Delete the saved session once it has loaded. """ if name.startswith('_') and not force: raise cmdexc.CommandError("{} is an internal session, use --force " "to load anyways.".format(name)) old_windows = list(objreg.window_registry.values()) try: self.load(name, temp=temp) except SessionNotFoundError: raise cmdexc.CommandError("Session {} not found!".format(name)) except SessionError as e: raise cmdexc.CommandError("Error while loading session: {}" .format(e)) else: if clear: for win in old_windows: win.close() if delete: try: self.delete(name) except SessionError as e: log.sessions.exception("Error while deleting session!") raise cmdexc.CommandError( "Error while deleting session: {}" .format(e)) else: log.sessions.debug( "Loaded & deleted session {}.".format(name))
def show_help(self, tab=False, bg=False, window=False, topic=None): r"""Show help about a command or setting. Args: tab: Open in a new tab. bg: Open in a background tab. window: Open in a new window. topic: The topic to show help for. - :__command__ for commands. - __section__\->__option__ for settings. """ if topic is None: path = 'index.html' elif topic.startswith(':'): command = topic[1:] if command not in cmdutils.cmd_dict: raise cmdexc.CommandError( "Invalid command {}!".format(command)) path = 'commands.html#{}'.format(command) elif '->' in topic: parts = topic.split('->') if len(parts) != 2: raise cmdexc.CommandError( "Invalid help topic {}!".format(topic)) try: config.get(*parts) except config.NoSectionError: raise cmdexc.CommandError("Invalid section {}!".format( parts[0])) except config.NoOptionError: raise cmdexc.CommandError("Invalid option {}!".format( parts[1])) path = 'settings.html#{}'.format(topic.replace('->', '-')) else: raise cmdexc.CommandError("Invalid help topic {}!".format(topic)) url = QUrl('qute://help/{}'.format(path)) self._open(url, tab, bg, window)
def download_retry(self, count=0): """Retry the first failed/[count]th download. Args: count: The index of the download to cancel. """ if count: try: download = self.downloads[count - 1] except IndexError: self.raise_no_download(count) if download.successful or not download.done: raise cmdexc.CommandError( "Download {} did not fail!".format(count)) else: to_retry = [ d for d in self.downloads if d.done and not d.successful ] if not to_retry: raise cmdexc.CommandError("No failed downloads!") else: download = to_retry[0] download.retry()
def config_dict_remove(self, option, key, temp=False): """Remove a key from a dict. Args: option: The name of the option. key: The key to remove from the dict. temp: Remove value temporarily until qutebrowser is closed. """ opt = self._config.get_opt(option) if not isinstance(opt.typ, configtypes.Dict): raise cmdexc.CommandError(":config-dict-remove can only be used " "for dicts") with self._handle_config_error(): option_value = self._config.get_mutable_obj(option) if key not in option_value: raise cmdexc.CommandError("{} is not in {}!".format( key, option)) del option_value[key] self._config.update_mutables(save_yaml=not temp)
def enter_mode(self, mode): """Enter a key mode. Args: mode: The mode to enter. """ try: m = usertypes.KeyMode[mode] except KeyError: raise cmdexc.CommandError("Mode {} does not exist!".format(mode)) if m in [ usertypes.KeyMode.hint, usertypes.KeyMode.command, usertypes.KeyMode.yesno, usertypes.KeyMode.prompt ]: raise cmdexc.CommandError( "Mode {} can't be entered manually!".format(mode)) elif (m == usertypes.KeyMode.caret and objects.backend == usertypes.Backend.QtWebEngine): raise cmdexc.CommandError("Caret mode is not supported with " "QtWebEngine yet.") self.enter(m, 'command')
def tab_focus(self, index: {'type': (int, 'last')}=None, count: {'special': 'count'}=None): """Select the tab given as argument/[count]. Args: index: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab. count: The tab index to focus, starting with 1. """ if index == 'last': self._tab_focus_last() return try: idx = cmdutils.arg_or_count(index, count, default=1, countzero=self._count()) except ValueError as e: raise cmdexc.CommandError(e) cmdutils.check_overflow(idx + 1, 'int') if 1 <= idx <= self._count(): self._set_current_index(idx - 1) else: raise cmdexc.CommandError("There's no tab with index {}!".format( idx))
def set_command(self, win_id: {'special': 'win_id'}, sectname: {'name': 'section'}, optname: {'name': 'option'}, value=None, temp=False): """Set an option. If the option name ends with '?', the value of the option is shown instead. // Wrapper for self.set() to output exceptions in the status bar. Args: sectname: The section where the option is in. optname: The name of the option. value: The value to set. temp: Set value temporarily. """ try: if optname.endswith('?'): val = self.get(sectname, optname[:-1], transformed=False) message.info(win_id, "{} {} = {}".format(sectname, optname[:-1], val), immediately=True) else: if value is None: raise cmdexc.CommandError("set: The following arguments " "are required: value") layer = 'temp' if temp else 'conf' self.set(layer, sectname, optname, value) except (NoOptionError, NoSectionError, configtypes.ValidationError, ValueError) as e: raise cmdexc.CommandError("set: {} - {}".format( e.__class__.__name__, e))
def set_cmd_text_command(self, text, space=False): """Preset the statusbar to some text. // Wrapper for set_cmd_text to check the arguments and allow multiple strings which will get joined. Args: text: The commandline to set. space: If given, a space is added to the end. """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) if '{url}' in text: try: url = tabbed_browser.current_url().toString( QUrl.FullyEncoded | QUrl.RemovePassword) except qtutils.QtValueError as e: msg = "Current URL is invalid" if e.reason: msg += " ({})".format(e.reason) msg += "!" raise cmdexc.CommandError(msg) # FIXME we currently replace the URL in any place in the arguments, # rather than just replacing it if it is a dedicated argument. We # could split the args, but then trailing spaces would be lost, so # I'm not sure what's the best thing to do here # https://github.com/The-Compiler/qutebrowser/issues/123 text = text.replace('{url}', url) if space: text += ' ' if not text or text[0] not in modeparsers.STARTCHARS: raise cmdexc.CommandError( "Invalid command text '{}'.".format(text)) self.set_cmd_text(text)
def config_cycle(self, option, *values, pattern=None, temp=False, print_=False): """Cycle an option between multiple values. Args: option: The name of the option. values: The values to cycle through. pattern: The URL pattern to use. temp: Set value temporarily until qutebrowser is closed. print_: Print the value after setting. """ pattern = self._parse_pattern(pattern) with self._handle_config_error(): opt = self._config.get_opt(option) old_value = self._config.get_obj_for_pattern(option, pattern=pattern) if not values and isinstance(opt.typ, configtypes.Bool): values = ['true', 'false'] if len(values) < 2: raise cmdexc.CommandError("Need at least two values for " "non-boolean settings.") # Use the next valid value from values, or the first if the current # value does not appear in the list with self._handle_config_error(): values = [opt.typ.from_str(val) for val in values] try: idx = values.index(old_value) idx = (idx + 1) % len(values) value = values[idx] except ValueError: value = values[0] with self._handle_config_error(): self._config.set_obj(option, value, pattern=pattern, save_yaml=not temp) if print_: self._print_value(option, pattern=pattern)
def _navigate_up(self, url, tab, background, window): """Helper method for :navigate when `where' is up. Args: url: The current url. tab: Whether to open the link in a new tab. background: Open the link in a new background tab. window: Open the link in a new window. """ path = url.path() if not path or path == '/': raise cmdexc.CommandError("Can't go up!") new_path = posixpath.join(path, posixpath.pardir) url.setPath(new_path) self._open(url, tab, background, window)
def download_open(self, count=0): """Open the last/[count]th download. Args: count: The index of the download to cancel. """ try: download = self.downloads[count - 1] except IndexError: self.raise_no_download(count) if not download.successful: if not count: count = len(self.downloads) raise cmdexc.CommandError("Download {} is not done!".format(count)) download.open_file()
def unbind(self, key, mode='normal'): """Unbind a keychain. Args: key: The keychain or special key (inside <...>) to unbind. mode: A comma-separated list of modes to unbind the key in (default: `normal`). """ if utils.is_special_key(key): # <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent key = key.lower() mode = self._normalize_sectname(mode) for m in mode.split(','): if m not in configdata.KEY_DATA: raise cmdexc.CommandError("Invalid mode {}!".format(m)) try: sect = self.keybindings[mode] except KeyError: raise cmdexc.CommandError( "Can't find mode section '{}'!".format(mode)) try: del sect[key] except KeyError: raise cmdexc.CommandError("Can't find binding '{}' in section " "'{}'!".format(key, mode)) else: if key in itertools.chain.from_iterable( configdata.KEY_DATA[mode].values()): try: self._add_binding(mode, key, self.UNBOUND_COMMAND) except DuplicateKeychainError: pass for m in mode.split(','): self.changed.emit(m) self._mark_config_dirty()
def follow_prevnext(self, frame, baseurl, prev=False, tab=False, background=False, window=False): """Click a "previous"/"next" element on the page. Args: frame: The frame where the element is in. baseurl: The base URL of the current tab. prev: True to open a "previous" link, False to open a "next" link. tab: True to open in a new tab, False for the current tab. background: True to open in a background tab. window: True to open in a new window, False for the current one. """ from qutebrowser.mainwindow import mainwindow elem = self._find_prevnext(frame, prev) if elem is None: raise cmdexc.CommandError("No {} links found!".format( "prev" if prev else "forward")) url = self._resolve_url(elem, baseurl) if url is None: raise cmdexc.CommandError("No {} links found!".format( "prev" if prev else "forward")) qtutils.ensure_valid(url) if window: new_window = mainwindow.MainWindow() new_window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) tabbed_browser.tabopen(url, background=background) else: webview = objreg.get('webview', scope='tab', window=self._win_id, tab=self._tab_id) webview.openurl(url)
def bind(self, key, command=None, *, mode='normal', force=False): """Bind a key to a command. Args: key: The keychain or special key (inside `<...>`) to bind. command: The command to execute, with optional args, or None to print the current binding. mode: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the available modes. force: Rebind the key if it is already bound. """ if command is None: if utils.is_special_key(key): # self._keyconfig.get_command does this, but we also need it # normalized for the output below key = utils.normalize_keystr(key) cmd = self._keyconfig.get_command(key, mode) if cmd is None: message.info("{} is unbound in {} mode".format(key, mode)) else: message.info("{} is bound to '{}' in {} mode".format( key, cmd, mode)) return try: self._keyconfig.bind(key, command, mode=mode, force=force, save_yaml=True) except configexc.DuplicateKeyError as e: raise cmdexc.CommandError( "bind: {} - use --force to override!".format(e)) except configexc.KeybindingError as e: raise cmdexc.CommandError("bind: {}".format(e))
def set_cmd_text_command(self, text, space=False, append=False): """Preset the statusbar to some text. // Wrapper for set_cmd_text to check the arguments and allow multiple strings which will get joined. Args: text: The commandline to set. space: If given, a space is added to the end. append: If given, the text is appended to the current text. """ if space: text += ' ' if append: if not self.text(): raise cmdexc.CommandError("No current text!") text = self.text() + text if not text or text[0] not in modeparsers.STARTCHARS: raise cmdexc.CommandError( "Invalid command text '{}'.".format(text)) self.set_cmd_text(text)
def quit(self, save=False, session=None): """Quit qutebrowser. Args: save: When given, save the open windows even if auto_save.session is turned off. session: The name of the session to save. """ if session is not None and not save: raise cmdexc.CommandError("Session name given without --save!") if save: if session is None: session = sessions.default self.shutdown(session=session) else: self.shutdown()
def set(self, win_id, option=None, value=None, temp=False, print_=False): """Set an option. If the option name ends with '?', the value of the option is shown instead. Args: option: The name of the option. value: The value to set. temp: Set value temporarily until qutebrowser is closed. print_: Print the value after setting. """ if option is None: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) tabbed_browser.openurl(QUrl('qute://settings'), newtab=False) return if option.endswith('!'): raise cmdexc.CommandError("Toggling values was moved to the " ":config-cycle command") if option.endswith('?') and option != '?': self._print_value(option[:-1]) return with self._handle_config_error(): if value is None: raise cmdexc.CommandError("set: The following arguments " "are required: value") else: self._config.set_str(option, value, save_yaml=not temp) if print_: self._print_value(option)
def config_list_remove(self, option, value, temp=False): """Remove a value from a list. Args: option: The name of the option. value: The value to remove from the list. temp: Remove value temporarily until qutebrowser is closed. """ opt = self._config.get_opt(option) valid_list_types = (configtypes.List, configtypes.ListOrValue) if not isinstance(opt.typ, valid_list_types): raise cmdexc.CommandError(":config-list-remove can only be used " "for lists") with self._handle_config_error(): option_value = self._config.get_mutable_obj(option) if value not in option_value: raise cmdexc.CommandError("{} is not in {}!".format( value, option)) option_value.remove(value) self._config.update_mutables(save_yaml=not temp)