From 9bdbb257ba2156078475e6e93d6b3f5b45abaae8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 26 Oct 2016 22:53:14 +0200 Subject: [PATCH] Add initial filename completion --- doc/help/commands.asciidoc | 10 +++++ qutebrowser/config/configdata.py | 2 + qutebrowser/mainwindow/prompt.py | 68 ++++++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 093b6f2c6..8c5471ab1 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -977,6 +977,7 @@ How many steps to zoom out. |<>|Move the cursor or selection to the start of previous block. |<>|Open an external editor with the currently selected form field. |<>|Accept the current prompt. +|<>|Shift the focus of the prompt file completion menu to another item. |<>|Immediately open a download. |<>|Repeat the last executed command. |<>|Move back a character. @@ -1229,6 +1230,15 @@ Accept the current prompt. * +'value'+: If given, uses this value instead of the entered one. For boolean prompts, "yes"/"no" are accepted as value. +[[prompt-item-focus]] +=== prompt-item-focus +Syntax: +:prompt-item-focus 'which'+ + +Shift the focus of the prompt file completion menu to another item. + +==== positional arguments +* +'which'+: 'next', 'prev' + [[prompt-open-download]] === prompt-open-download Syntax: +:prompt-open-download ['cmdline']+ diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index b97a8316a..2056cca01 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1680,6 +1680,8 @@ KEY_DATA = collections.OrderedDict([ ('prompt-accept yes', ['y']), ('prompt-accept no', ['n']), ('prompt-open-download', ['']), + ('prompt-item-focus prev', ['', '']), + ('prompt-item-focus next', ['', '']), ])), ('command,prompt', collections.OrderedDict([ diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 8544f44f7..b0fd35d59 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -24,7 +24,8 @@ import html import collections import sip -from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex +from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, + QItemSelectionModel) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QWidgetItem, QFileSystemModel, QTreeView, QSizePolicy) @@ -264,6 +265,20 @@ class PromptContainer(QWidget): except UnsupportedOperationError: pass + @cmdutils.register(instance='prompt-container', hide=True, scope='window', + modes=[usertypes.KeyMode.prompt]) + @cmdutils.argument('which', choices=['next', 'prev']) + def prompt_item_focus(self, which): + """Shift the focus of the prompt file completion menu to another item. + + Args: + which: 'next', 'prev' + """ + try: + self._prompt.item_focus(which) + except UnsupportedOperationError: + pass + @pyqtSlot(usertypes.Question, bool) def ask_question(self, question, blocking): """Display a prompt for a given question. @@ -425,6 +440,10 @@ class _BasePrompt(QWidget): """Open the download directly if this is a download prompt.""" raise UnsupportedOperationError + def item_focus(self, _which): + """Switch to next file item if this is a filename prompt..""" + raise UnsupportedOperationError + def _allowed_commands(self): """Get the commands we could run as response to this message.""" raise NotImplementedError @@ -477,7 +496,7 @@ class FilenamePrompt(_BasePrompt): @pyqtSlot(str) def _set_fileview_root(self, path): """Set the root path for the file display.""" - if not path.endswith('/'): + if not path.endswith('/') or path == '/': return path.rstrip('/') @@ -498,25 +517,33 @@ class FilenamePrompt(_BasePrompt): self._file_view.setRootIndex(root) @pyqtSlot(QModelIndex) - def _on_clicked(self, index): - """Handle a click on an element.""" + def _insert_path(self, index, *, clicked=True): + """Handle an element selection. + + Args: + index: The QModelIndex of the selected element. + clicked: Whether the element was clicked. + """ parts = [] cur = index while cur.isValid(): parts.append(cur.data()) cur = cur.parent() - path = os.path.normpath(os.path.join(*reversed(parts))) + os.sep + path = os.path.normpath(os.path.join(*reversed(parts))) + if clicked: + path += os.sep log.prompt.debug('Clicked {!r} -> {}'.format(parts, path)) self._lineedit.setText(path) self._lineedit.setFocus() - # Avoid having a ..-subtree highlighted - self._file_view.setCurrentIndex(QModelIndex()) + if clicked: + # Avoid having a ..-subtree highlighted + self._file_view.setCurrentIndex(QModelIndex()) def _init_fileview(self): self._file_view = QTreeView(self) self._file_model = QFileSystemModel(self) self._file_view.setModel(self._file_model) - self._file_view.clicked.connect(self._on_clicked) + self._file_view.clicked.connect(self._insert_path) self._vbox.addWidget(self._file_view) # Only show name self._file_view.setHeaderHidden(True) @@ -528,6 +555,31 @@ class FilenamePrompt(_BasePrompt): self.question.answer = text return True + def item_focus(self, which): + # This duplicates some completion code, but I don't see a nicer way... + assert which in ['prev', 'next'], which + selmodel = self._file_view.selectionModel() + + first_index = self._file_model.index(0, 0) + last_index = self._file_model.index(self._file_model.rowCount() - 1, 0) + + idx = selmodel.currentIndex() + if not idx.isValid(): + # No item selected yet + idx = last_index if which == 'prev' else first_index + + if which == 'prev': + idx = self._file_view.indexAbove(idx) + else: + idx = self._file_view.indexBelow(idx) + # wrap around if we arrived at beginning/end + if not idx.isValid(): + idx = last_index if which == 'prev' else first_index + + selmodel.setCurrentIndex( + idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + self._insert_path(idx, clicked=False) + def _allowed_commands(self): """Get the commands we could run as response to this message.""" return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')]