# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qutebrowser is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # 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 . """The commandline in the statusbar.""" from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl from PyQt5.QtWidgets import QSizePolicy from qutebrowser.keyinput import modeman, modeparsers from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.widgets import misc from qutebrowser.models import cmdhistory from qutebrowser.utils import usertypes, log, objreg class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): """The commandline part of the statusbar. Attributes: _win_id: The window ID this widget is associated with. Signals: got_cmd: Emitted when a command is triggered by the user. arg: The command string. got_search: Emitted when the user started a new search. arg: The search term. got_rev_search: Emitted when the user started a new reverse search. arg: The search term. clear_completion_selection: Emitted before the completion widget is hidden. hide_completion: Emitted when the completion widget should be hidden. update_completion: Emitted when the completion should be shown/updated. show_cmd: Emitted when command input should be shown. hide_cmd: Emitted when command input can be hidden. """ got_cmd = pyqtSignal(str) got_search = pyqtSignal(str) got_search_rev = pyqtSignal(str) clear_completion_selection = pyqtSignal() hide_completion = pyqtSignal() update_completion = pyqtSignal() show_cmd = pyqtSignal() hide_cmd = pyqtSignal() def __init__(self, win_id, parent=None): misc.CommandLineEdit.__init__(self, parent) misc.MinimalLineEditMixin.__init__(self) self._win_id = win_id self.history.handle_private_mode = True self.history.history = objreg.get('command-history').data self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) self.cursorPositionChanged.connect(self.update_completion) def prefix(self): """Get the currently entered command prefix.""" text = self.text() if not text: return '' elif text[0] in modeparsers.STARTCHARS: return text[0] else: return '' @pyqtSlot(str) def set_cmd_text(self, text): """Preset the statusbar to some text. Args: text: The text to set as string. """ old_text = self.text() self.setText(text) if old_text != text and len(old_text) == len(text): # We want the completion to pop out here, but the cursor position # won't change, so we make sure we emit update_completion. self.update_completion.emit() log.modes.debug("Setting command text, focusing {!r}".format(self)) self.setFocus() self.show_cmd.emit() @cmdutils.register(instance='status-command', name='set-cmd-text', scope='window') def set_cmd_text_command(self, text): """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. """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) if '{url}' in text: url = tabbed_browser.current_url().toString( QUrl.FullyEncoded | QUrl.RemovePassword) # 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 not text[0] in modeparsers.STARTCHARS: raise cmdexc.CommandError( "Invalid command text '{}'.".format(text)) self.set_cmd_text(text) @cmdutils.register(instance='status-command', hide=True, modes=[usertypes.KeyMode.command], scope='window') def command_history_prev(self): """Go back in the commandline history.""" try: if not self.history.is_browsing(): item = self.history.start(self.text().strip()) else: item = self.history.previtem() except (cmdhistory.HistoryEmptyError, cmdhistory.HistoryEndReachedError): return if item: self.set_cmd_text(item) @cmdutils.register(instance='status-command', hide=True, modes=[usertypes.KeyMode.command], scope='window') def command_history_next(self): """Go forward in the commandline history.""" if not self.history.is_browsing(): return try: item = self.history.nextitem() except cmdhistory.HistoryEndReachedError: return if item: self.set_cmd_text(item) @cmdutils.register(instance='status-command', hide=True, modes=[usertypes.KeyMode.command], scope='window') def command_accept(self): """Execute the command currently in the commandline.""" signals = { ':': self.got_cmd, '/': self.got_search, '?': self.got_search_rev, } text = self.text() self.history.append(text) modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept') if text[0] in signals: signals[text[0]].emit(text.lstrip(text[0])) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Clear up when command mode was left. - Clear the statusbar text if it's explicitely unfocused. - Clear completion selection - Hide completion Args: mode: The mode which was left. """ if mode == usertypes.KeyMode.command: self.setText('') self.history.stop() self.hide_cmd.emit() self.clear_completion_selection.emit() self.hide_completion.emit() def focusInEvent(self, e): """Extend focusInEvent to enter command mode.""" modeman.maybe_enter(self._win_id, usertypes.KeyMode.command, 'cmd focus') super().focusInEvent(e) def setText(self, text): """Extend setText to set prefix and make sure the prompt is ok.""" if not text: pass elif text[0] in modeparsers.STARTCHARS: super().set_prompt(text[0]) else: raise AssertionError("setText got called with invalid text " "'{}'!".format(text)) super().setText(text) def keyPressEvent(self, e): """Override keyPressEvent to ignore Return key presses. If this widget is focused, we are in passthrough key mode, and Enter/Shift+Enter/etc. will cause QLineEdit to think it's finished without command_accept to be called. """ if e.key() == Qt.Key_Return: e.ignore() return else: super().keyPressEvent(e)