Start using (broken) decorators

This commit is contained in:
Florian Bruhin 2014-03-03 06:09:23 +01:00
parent 2df9b57981
commit b22b19d881
7 changed files with 127 additions and 433 deletions

View File

@ -180,13 +180,7 @@ class QuteBrowser(QApplication):
Registers all commands, connects its signals, and sets up keyparser. Registers all commands, connects its signals, and sets up keyparser.
""" """
cmdutils.register_all()
for cmd in cmdutils.cmd_dict.values():
cmd.signal.connect(self.cmd_handler)
try:
self.keyparser.from_config_sect(config.config['keybind']) self.keyparser.from_config_sect(config.config['keybind'])
except KeyError:
pass
def _process_init_args(self): def _process_init_args(self):
"""Process initial positional args. """Process initial positional args.
@ -328,67 +322,7 @@ class QuteBrowser(QApplication):
logging.debug("maybe_quit quitting.") logging.debug("maybe_quit quitting.")
self.quit() self.quit()
@pyqtSlot(tuple) @cmdutils.register(split_args=False)
def cmd_handler(self, tpl):
"""Handle commands and delegate the specific actions.
This gets called as a slot from all commands, and then calls the
appropriate command handler.
All handlers supporting a count should have a keyword argument count.
Args:
tpl: A tuple in the form (count, argv) where argv is
[cmd, arg, ...]
Return:
The handlers return value.
"""
(count, argv) = tpl
cmd = argv[0]
args = argv[1:]
browser = self.mainwindow.tabs
handlers = {
'open': browser.cur.openurl,
'opencur': browser.opencur,
'tabopen': browser.tabopen,
'tabopencur': browser.tabopencur,
'quit': self.shutdown,
'tabclose': browser.tabclose,
'tabprev': browser.switch_prev,
'tabnext': browser.switch_next,
'reload': browser.cur.reloadpage,
'stop': browser.cur.stop,
'back': browser.cur.back,
'forward': browser.cur.forward,
'print': browser.cur.printpage,
'scroll': browser.cur.scroll,
'scroll_page': browser.cur.scroll_page,
'scroll_perc_x': browser.cur.scroll_percent_x,
'scroll_perc_y': browser.cur.scroll_percent_y,
'undo': browser.undo_close,
'pyeval': self.pyeval,
'settrace': set_trace,
'nextsearch': self.searchparser.nextsearch,
'yank': browser.cur.yank,
'yanktitle': browser.cur.yank_title,
'paste': browser.paste,
'tabpaste': browser.tabpaste,
'crash': self.crash,
'version': lambda: browser.cur.openurl('qute:version'),
'zoomin': browser.cur.zoom_in,
'zoomout': browser.cur.zoom_out,
}
handler = handlers[cmd]
if count is not None and self.sender().count:
return handler(*args, count=count)
else:
return handler(*args)
def pyeval(self, s): def pyeval(self, s):
"""Evaluate a python string and display the results as a webpage. """Evaluate a python string and display the results as a webpage.
@ -406,6 +340,7 @@ class QuteBrowser(QApplication):
qutescheme.pyeval_output = out qutescheme.pyeval_output = out
self.mainwindow.tabs.cur.openurl('qute:pyeval') self.mainwindow.tabs.cur.openurl('qute:pyeval')
@cmdutils.register(hide=True)
def crash(self): def crash(self):
"""Crash for debugging purposes. """Crash for debugging purposes.
@ -418,6 +353,7 @@ class QuteBrowser(QApplication):
raise Exception("Forced crash") raise Exception("Forced crash")
@pyqtSlot() @pyqtSlot()
@cmdutils.register(name=['q', 'quit'], nargs=0)
def shutdown(self, do_quit=True): def shutdown(self, do_quit=True):
"""Try to shutdown everything cleanly. """Try to shutdown everything cleanly.

View File

@ -30,290 +30,3 @@ A command class can set the following properties:
hide -- If the command should be hidden in tab completion. Default: False hide -- If the command should be hidden in tab completion. Default: False
""" """
from qutebrowser.commands.template import Command
class Open(Command):
"""Open a page in the current or [count]th tab.
arg: The URL to open.
"""
nargs = 1
split_args = False
count = True
class OpenCur(Command):
"""Fill the statusbar with :open and the current url."""
nargs = 0
hide = True
class TabOpen(Command):
"""Open a page in a new tab.
arg: The URL to open.
"""
nargs = 1
split_args = False
class TabOpenCur(Command):
"""Fill the statusbar with :tabopen and the current url."""
nargs = 0
hide = True
class TabClose(Command):
"""Close the current tab, or tab [count]."""
nargs = 0
count = True
class TabNext(Command):
"""Switch to the next tab, or skip [count] tabs."""
nargs = 0
count = True
class TabPrev(Command):
"""Switch to the previous tab, or skip [count] tabs."""
nargs = 0
count = True
class Quit(Command):
"""Quit qutebrowser."""
name = ['quit', 'q']
nargs = 0
class Reload(Command):
"""Reload the current page, or the page in tab [count]."""
nargs = 0
count = True
class Stop(Command):
"""Stop loading the current page, or the page in tab [count]."""
nargs = 0
count = True
class Back(Command):
"""Go back one/[count] page(s) in the history."""
nargs = 0
count = True
class Forward(Command):
"""Go forward one/[count] page(s) in the history."""
nargs = 0
count = True
class Print(Command):
"""Print the current page, or the page in tab [count]."""
nargs = 0
count = True
class Scroll(Command):
"""Scroll in x/y direction by a number of pixels.
arg 1: delta x
arg 2: delta y
count: multiplicator
"""
nargs = 2
count = True
hide = True
class ScrollPage(Command):
"""Scroll N pages up/down.
arg 1: pages in x-direction
arg 2: pages in y-direction
count: multiplicator
"""
nargs = 2
count = True
hide = True
name = 'scroll_page'
class Undo(Command):
"""Undo closing a tab."""
nargs = 0
class ScrollPercX(Command):
"""Scroll N percent horizontally.
optional arg: How many percent to scroll.
count: How many percent to scroll.
"""
nargs = '?'
count = True
hide = True
name = 'scroll_perc_x'
class ScrollPercY(Command):
"""Scroll N percent vertically.
optional arg: How many percent to scroll.
count: How many percent to scroll.
"""
nargs = '?'
count = True
hide = True
name = 'scroll_perc_y'
class PyEval(Command):
"""Evaluate python code.
arg: The python code to evaluate.
"""
nargs = 1
split_args = False
class NextSearch(Command):
"""Jump to the next or [count]th next search term."""
nargs = 0
hide = True
count = True
class Yank(Command):
"""Yank the URL of the current tab to the clipboard.
optional arg: If 'sel', yank to the primary selection.
"""
nargs = '?'
class YankTitle(Command):
"""Yank the title of the current tab to the clipboard.
optional arg: If 'sel', yank to the primary selection.
"""
nargs = '?'
class Paste(Command):
"""Open an URL from the clipboard.
optional arg: If 'sel', paste from the primary selection.
"""
nargs = '?'
class TabPaste(Command):
"""Open an URL from the clipboard in a new tab.
optional arg: If 'sel', paste from the primary selection.
"""
nargs = '?'
class ZoomIn(Command):
"""Zoom in in the current tab."""
nargs = 0
count = True
class ZoomOut(Command):
"""Zoom out in the current tab."""
nargs = 0
count = True
class Crash(Command):
"""Simply raise an exception for debugging."""
nargs = 0
hide = True
class Version(Command):
"""Show version information."""
nargs = 0
class SetTrace(Command):
"""Set pdb trace."""
nargs = 0
hide = True

View File

@ -19,20 +19,15 @@
import logging import logging
from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.commands.exceptions import ArgumentCountError from qutebrowser.commands.exceptions import ArgumentCountError
class Command(QObject): class Command:
"""Base skeleton for a command. """Base skeleton for a command.
See the module documentation for qutebrowser.commands.commands for details. Attributes:
FIXME ...
Signals:
signal: Emitted when the command was executed.
arg: A tuple (command, [args])
""" """
@ -40,13 +35,14 @@ class Command(QObject):
# we should probably have some kind of typing / argument casting for args # we should probably have some kind of typing / argument casting for args
# this might be combined with help texts or so as well # this might be combined with help texts or so as well
def __init__(self, name, split_args, hide, nargs, count, handler): def __init__(self, name, split_args, hide, nargs, count, desc, handler):
super().__init__() super().__init__()
self.name = name self.name = name
self.split_args = split_args self.split_args = split_args
self.hide = hide self.hide = hide
self.nargs = nargs self.nargs = nargs
self.count = count self.count = count
self.desc = desc
self.handler = handler self.handler = handler
def check(self, args): def check(self, args):
@ -61,12 +57,11 @@ class Command(QObject):
ArgumentCountError if the argument count is wrong. ArgumentCountError if the argument count is wrong.
""" """
if ((isinstance(self.nargs, int) and len(args) != self.nargs) or if self.nargs[0] <= len(args) <= self.nargs[1]:
(self.nargs == '?' and len(args) > 1) or pass
(self.nargs == '+' and len(args) < 1)): else:
# for nargs == '*', anything is okay raise ArgumentCountError("{}-{} args expected, but got {}".format(
raise ArgumentCountError("{} args expected, but got {}".format( self.nargs[0], self.nargs[1], len(args)))
self.nargs, len(args)))
def run(self, args=None, count=None): def run(self, args=None, count=None):
"""Run the command. """Run the command.
@ -75,18 +70,15 @@ class Command(QObject):
args: Arguments to the command. args: Arguments to the command.
count: Command repetition count. count: Command repetition count.
Emit:
The command's signal.
""" """
dbgout = ["command called:", self.mainname] dbgout = ["command called:", self.name]
if args: if args:
dbgout += args dbgout += args
if count is not None: if count is not None:
dbgout.append("(count={})".format(count)) dbgout.append("(count={})".format(count))
logging.debug(' '.join(dbgout)) logging.debug(' '.join(dbgout))
argv = [self.mainname] if count is not None and self.count:
if args is not None: return self.handler(*args, count=count)
argv += args else:
self.signal.emit((count, argv)) return self.handler(*args)

View File

@ -23,8 +23,8 @@ import inspect
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
from PyQt5.QtWebKitWidgets import QWebPage from PyQt5.QtWebKitWidgets import QWebPage
import qutebrowser.commands.commands
import qutebrowser.config.config as config import qutebrowser.config.config as config
from qutebrowser.commands.template import Command
from qutebrowser.commands.exceptions import (ArgumentCountError, from qutebrowser.commands.exceptions import (ArgumentCountError,
NoSuchCommandError) NoSuchCommandError)
@ -32,28 +32,41 @@ from qutebrowser.commands.exceptions import (ArgumentCountError,
cmd_dict = {} cmd_dict = {}
def register_cmd(func, name=None, split_args=True, hide=False): class register:
"""Decorator to register a new command handler.""" """Decorator to register a new command handler.
This could also be a function, but as a class (with a "wrong" name) it's
much cleaner to implement.
"""
def __init__(self, name=None, nargs=None, split_args=True, hide=False):
self.name = name
self.split_args = split_args
self.hide = hide
self.nargs = nargs
def __call__(self, func):
global cmd_dict global cmd_dict
names = [] names = []
names += newnames name = func.__name__.lower() if self.name is None else self.name
if name is None:
name = func.__name__.lower()
if isinstance(name, str): if isinstance(name, str):
mainname = name mainname = name
names.append(name) names.append(name)
else: else:
mainname = name[0] mainname = name[0]
names += name names += name
count, nargs = _get_nargs_count(func) count, nargs = self._get_nargs_count(func)
cmd = Command(mainname, split_args, hide, nargs, count, handler=func) desc = func.__doc__.splitlines()[0].strip().rstrip('.')
cmd = Command(mainname, self.split_args, self.hide, nargs, count, desc,
handler=func)
for name in names: for name in names:
cmd_dict[name] = cmd cmd_dict[name] = cmd
return func return func
def _get_nargs_count(self, func): def _get_nargs_count(self, func):
"""Get the number of command-arguments and count-support for a function. """Get the number of command-arguments and count-support for a func.
Args: Args:
func: The function to get the argcount for. func: The function to get the argcount for.
@ -74,7 +87,21 @@ def _get_nargs_count(self, func):
spec = inspect.getfullargspec(func) spec = inspect.getfullargspec(func)
count = 'count' in spec.args count = 'count' in spec.args
# we assume count always has a default (and it should!) # we assume count always has a default (and it should!)
minargs = len(spec.args) - len(spec.defaults) if self.nargs is not None:
# If nargs is overriden, use that.
if isinstance(self.nargs, int):
# Single int
minargs, maxargs = self.nargs, self.nargs
else:
# Tuple (min, max)
minargs, maxargs = self.nargs
else:
defaultcount = (len(spec.defaults) if spec.defaults is not None
else 0)
argcount = len(spec.args)
if 'self' in spec.args:
argcount -= 1
minargs = argcount - defaultcount
if spec.varargs is not None: if spec.varargs is not None:
maxargs = None maxargs = None
else: else:
@ -147,6 +174,7 @@ class SearchParser(QObject):
""" """
self._search(text, rev=True) self._search(text, rev=True)
@register(hide=True)
def nextsearch(self, count=1): def nextsearch(self, count=1):
"""Continue the search to the ([count]th) next term. """Continue the search to the ([count]th) next term.

View File

@ -35,8 +35,7 @@ class CommandCompletionModel(CompletionModel):
cmdlist = [] cmdlist = []
for obj in set(cmd_dict.values()): for obj in set(cmd_dict.values()):
if not obj.hide: if not obj.hide:
doc = obj.__doc__.splitlines()[0].strip().rstrip('.') cmdlist.append([obj.name, obj.desc])
cmdlist.append([obj.mainname, doc])
data = OrderedDict() data = OrderedDict()
data['Commands'] = sorted(cmdlist) data['Commands'] = sorted(cmdlist)
self.init_data(data) self.init_data(data)

View File

@ -22,6 +22,8 @@ import os.path
from PyQt5.QtCore import pyqtRemoveInputHook from PyQt5.QtCore import pyqtRemoveInputHook
#import qutebrowser.commands.utils as cmdutils
try: try:
# pylint: disable=import-error # pylint: disable=import-error
from ipdb import set_trace as pdb_set_trace from ipdb import set_trace as pdb_set_trace
@ -30,10 +32,10 @@ except ImportError:
import qutebrowser import qutebrowser
# FIXME we can';t do this because of circular imports
#@cmdutils.register(name='settrace', hide=True)
def set_trace(): def set_trace():
""" """Set a tracepoint in the Python debugger that works with Qt.
Set a tracepoint in the Python debugger that works with Qt.
Based on http://stackoverflow.com/a/1745965/2085149 Based on http://stackoverflow.com/a/1745965/2085149

View File

@ -36,6 +36,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage
import qutebrowser.utils.url as urlutils import qutebrowser.utils.url as urlutils
import qutebrowser.config.config as config import qutebrowser.config.config as config
import qutebrowser.commands.utils as cmdutils
from qutebrowser.widgets.tabbar import TabWidget from qutebrowser.widgets.tabbar import TabWidget
from qutebrowser.network.networkmanager import NetworkManager from qutebrowser.network.networkmanager import NetworkManager
from qutebrowser.utils.signals import SignalCache, dbg_signal from qutebrowser.utils.signals import SignalCache, dbg_signal
@ -246,6 +247,7 @@ class TabbedBrowser(TabWidget):
tab.shutdown(callback=functools.partial(self._cb_tab_shutdown, tab.shutdown(callback=functools.partial(self._cb_tab_shutdown,
tab)) tab))
@cmdutils.register()
def tabclose(self, count=None): def tabclose(self, count=None):
"""Close the current/[count]th tab. """Close the current/[count]th tab.
@ -275,6 +277,7 @@ class TabbedBrowser(TabWidget):
elif last_close == 'blank': elif last_close == 'blank':
tab.openurl('about:blank') tab.openurl('about:blank')
@cmdutils.register(split_args=False)
def tabopen(self, url): def tabopen(self, url):
"""Open a new tab with a given url. """Open a new tab with a given url.
@ -308,6 +311,7 @@ class TabbedBrowser(TabWidget):
tab.open_tab.connect(self.tabopen) tab.open_tab.connect(self.tabopen)
tab.openurl(url) tab.openurl(url)
@cmdutils.register(hide=True)
def tabopencur(self): def tabopencur(self):
"""Set the statusbar to :tabopen and the current URL. """Set the statusbar to :tabopen and the current URL.
@ -318,6 +322,7 @@ class TabbedBrowser(TabWidget):
url = urlutils.urlstring(self.currentWidget().url()) url = urlutils.urlstring(self.currentWidget().url())
self.set_cmd_text.emit(':tabopen ' + url) self.set_cmd_text.emit(':tabopen ' + url)
@cmdutils.register(hide=True)
def opencur(self): def opencur(self):
"""Set the statusbar to :open and the current URL. """Set the statusbar to :open and the current URL.
@ -328,8 +333,9 @@ class TabbedBrowser(TabWidget):
url = urlutils.urlstring(self.currentWidget().url()) url = urlutils.urlstring(self.currentWidget().url())
self.set_cmd_text.emit(':open ' + url) self.set_cmd_text.emit(':open ' + url)
@cmdutils.register(name='undo')
def undo_close(self): def undo_close(self):
"""Undo closing a tab. """Switch to the previous tab, or skip [count] tabs.
Command handler for :undo. Command handler for :undo.
@ -337,6 +343,7 @@ class TabbedBrowser(TabWidget):
if self._url_stack: if self._url_stack:
self.tabopen(self._url_stack.pop()) self.tabopen(self._url_stack.pop())
@cmdutils.register(name='tabprev')
def switch_prev(self, count=1): def switch_prev(self, count=1):
"""Switch to the ([count]th) previous tab. """Switch to the ([count]th) previous tab.
@ -353,8 +360,9 @@ class TabbedBrowser(TabWidget):
# FIXME # FIXME
pass pass
@cmdutils.register('tabnext')
def switch_next(self, count=1): def switch_next(self, count=1):
"""Switch to the ([count]th) next tab. """Switch to the next tab, or skip [count] tabs.
Command handler for :tabnext. Command handler for :tabnext.
@ -369,6 +377,7 @@ class TabbedBrowser(TabWidget):
# FIXME # FIXME
pass pass
@cmdutils.register()
def paste(self, sel=False): def paste(self, sel=False):
"""Open a page from the clipboard. """Open a page from the clipboard.
@ -385,6 +394,7 @@ class TabbedBrowser(TabWidget):
logging.debug("Clipboard contained: '{}'".format(url)) logging.debug("Clipboard contained: '{}'".format(url))
self.openurl(url) self.openurl(url)
@cmdutils.register()
def tabpaste(self, sel=False): def tabpaste(self, sel=False):
"""Open a page from the clipboard in a new tab. """Open a page from the clipboard in a new tab.
@ -479,6 +489,7 @@ class CurCommandDispatcher(QObject):
return return
frame.setScrollBarValue(orientation, int(m * perc / 100)) frame.setScrollBarValue(orientation, int(m * perc / 100))
@cmdutils.register(name='open', split_args=False)
def openurl(self, url, count=None): def openurl(self, url, count=None):
"""Open an url in the current/[count]th tab. """Open an url in the current/[count]th tab.
@ -501,6 +512,7 @@ class CurCommandDispatcher(QObject):
else: else:
tab.openurl(url) tab.openurl(url)
@cmdutils.register(name='reload')
def reloadpage(self, count=None): def reloadpage(self, count=None):
"""Reload the current/[count]th tab. """Reload the current/[count]th tab.
@ -514,6 +526,7 @@ class CurCommandDispatcher(QObject):
if tab is not None: if tab is not None:
tab.reload() tab.reload()
@cmdutils.register()
def stop(self, count=None): def stop(self, count=None):
"""Stop loading in the current/[count]th tab. """Stop loading in the current/[count]th tab.
@ -527,6 +540,7 @@ class CurCommandDispatcher(QObject):
if tab is not None: if tab is not None:
tab.stop() tab.stop()
@cmdutils.register(name='print')
def printpage(self, count=None): def printpage(self, count=None):
"""Print the current/[count]th tab. """Print the current/[count]th tab.
@ -543,6 +557,7 @@ class CurCommandDispatcher(QObject):
preview.paintRequested.connect(tab.print) preview.paintRequested.connect(tab.print)
preview.exec_() preview.exec_()
@cmdutils.register()
def back(self, count=1): def back(self, count=1):
"""Go back in the history of the current tab. """Go back in the history of the current tab.
@ -556,6 +571,7 @@ class CurCommandDispatcher(QObject):
for i in range(count): # pylint: disable=unused-variable for i in range(count): # pylint: disable=unused-variable
self.tabs.currentWidget().back() self.tabs.currentWidget().back()
@cmdutils.register()
def forward(self, count=1): def forward(self, count=1):
"""Go forward in the history of the current tab. """Go forward in the history of the current tab.
@ -580,6 +596,7 @@ class CurCommandDispatcher(QObject):
""" """
self.tabs.currentWidget().findText(text, flags) self.tabs.currentWidget().findText(text, flags)
@cmdutils.register(hide=True)
def scroll(self, dx, dy, count=1): def scroll(self, dx, dy, count=1):
"""Scroll the current tab by count * dx/dy. """Scroll the current tab by count * dx/dy.
@ -595,8 +612,9 @@ class CurCommandDispatcher(QObject):
dy = int(count) * float(dy) dy = int(count) * float(dy)
self.tabs.currentWidget().page_.mainFrame().scroll(dx, dy) self.tabs.currentWidget().page_.mainFrame().scroll(dx, dy)
@cmdutils.register(name='scroll_perc_x', hide=True)
def scroll_percent_x(self, perc=None, count=None): def scroll_percent_x(self, perc=None, count=None):
"""Scroll the current tab to a specific percent of the page. """Scroll the current tab to a specific percent of the page (horiz).
Command handler for :scroll_perc_x. Command handler for :scroll_perc_x.
@ -607,8 +625,9 @@ class CurCommandDispatcher(QObject):
""" """
self._scroll_percent(perc, count, Qt.Horizontal) self._scroll_percent(perc, count, Qt.Horizontal)
@cmdutils.register(name='scroll_perc_y', hide=True)
def scroll_percent_y(self, perc=None, count=None): def scroll_percent_y(self, perc=None, count=None):
"""Scroll the current tab to a specific percent of the page. """Scroll the current tab to a specific percent of the page (vert).
Command handler for :scroll_perc_y Command handler for :scroll_perc_y
@ -619,6 +638,7 @@ class CurCommandDispatcher(QObject):
""" """
self._scroll_percent(perc, count, Qt.Vertical) self._scroll_percent(perc, count, Qt.Vertical)
@cmdutils.register(hide=True)
def scroll_page(self, mx, my, count=1): def scroll_page(self, mx, my, count=1):
"""Scroll the frame page-wise. """Scroll the frame page-wise.
@ -634,6 +654,7 @@ class CurCommandDispatcher(QObject):
page.mainFrame().scroll(int(count) * float(mx) * size.width(), page.mainFrame().scroll(int(count) * float(mx) * size.width(),
int(count) * float(my) * size.height()) int(count) * float(my) * size.height())
@cmdutils.register()
def yank(self, sel=False): def yank(self, sel=False):
"""Yank the current url to the clipboard or primary selection. """Yank the current url to the clipboard or primary selection.
@ -653,6 +674,7 @@ class CurCommandDispatcher(QObject):
self.temp_message.emit('URL yanked to {}'.format( self.temp_message.emit('URL yanked to {}'.format(
'primary selection' if sel else 'clipboard')) 'primary selection' if sel else 'clipboard'))
@cmdutils.register(name='yanktitle')
def yank_title(self, sel=False): def yank_title(self, sel=False):
"""Yank the current title to the clipboard or primary selection. """Yank the current title to the clipboard or primary selection.
@ -672,6 +694,7 @@ class CurCommandDispatcher(QObject):
self.temp_message.emit('Title yanked to {}'.format( self.temp_message.emit('Title yanked to {}'.format(
'primary selection' if sel else 'clipboard')) 'primary selection' if sel else 'clipboard'))
@cmdutils.register(name='zoomin')
def zoom_in(self, count=1): def zoom_in(self, count=1):
"""Zoom in in the current tab. """Zoom in in the current tab.
@ -682,6 +705,7 @@ class CurCommandDispatcher(QObject):
tab = self.tabs.currentWidget() tab = self.tabs.currentWidget()
tab.zoom(count) tab.zoom(count)
@cmdutils.register(name='zoomout')
def zoom_out(self, count=1): def zoom_out(self, count=1):
"""Zoom out in the current tab. """Zoom out in the current tab.