Add initial filename completion

This commit is contained in:
Florian Bruhin 2016-10-26 22:53:14 +02:00
parent 6ab51e0b7b
commit 9bdbb257ba
3 changed files with 72 additions and 8 deletions

View File

@ -977,6 +977,7 @@ How many steps to zoom out.
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|<<repeat-command,repeat-command>>|Repeat the last executed command.
|<<rl-backward-char,rl-backward-char>>|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']+

View File

@ -1680,6 +1680,8 @@ KEY_DATA = collections.OrderedDict([
('prompt-accept yes', ['y']),
('prompt-accept no', ['n']),
('prompt-open-download', ['<Ctrl-X>']),
('prompt-item-focus prev', ['<Shift-Tab>', '<Up>']),
('prompt-item-focus next', ['<Tab>', '<Down>']),
])),
('command,prompt', collections.OrderedDict([

View File

@ -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')]