parent
5e7ef5201c
commit
d71147898b
@ -19,10 +19,10 @@
|
||||
|
||||
"""Completer attached to a CompletionView."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QTimer
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.commands import cmdutils, runners
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
from qutebrowser.models import completion as models
|
||||
from qutebrowser.models.completionfilter import CompletionFilterModel as CFM
|
||||
@ -33,29 +33,22 @@ class Completer(QObject):
|
||||
"""Completer which manages completions in a CompletionView.
|
||||
|
||||
Attributes:
|
||||
_ignore_change: Whether to ignore the next completion update.
|
||||
models: dict of available completion models.
|
||||
_cmd: The statusbar Command object this completer belongs to.
|
||||
_ignore_change: Whether to ignore the next completion update.
|
||||
_win_id: The window ID this completer is in.
|
||||
_timer: The timer used to trigger the completion update.
|
||||
_prefix: The prefix to be used for the next completion update.
|
||||
_parts: The parts to be used for the next completion update.
|
||||
_cursor_part: The cursor part index for the next completion update.
|
||||
|
||||
Signals:
|
||||
change_completed_part: Text which should be substituted for the word
|
||||
we're currently completing.
|
||||
arg 0: The text to change to.
|
||||
arg 1: True if the text should be set
|
||||
immediately, without continuing
|
||||
completing the current field.
|
||||
"""
|
||||
|
||||
change_completed_part = pyqtSignal(str, bool)
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
def __init__(self, cmd, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._cmd = cmd
|
||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||
self._cmd.textEdited.connect(self.on_text_edited)
|
||||
self._ignore_change = False
|
||||
self._empty_item_idx = None
|
||||
|
||||
self._models = {
|
||||
usertypes.Completion.option: {},
|
||||
@ -68,8 +61,6 @@ class Completer(QObject):
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(0)
|
||||
self._timer.timeout.connect(self.update_completion)
|
||||
self._prefix = None
|
||||
self._parts = None
|
||||
self._cursor_part = None
|
||||
|
||||
def __repr__(self):
|
||||
@ -224,7 +215,7 @@ class Completer(QObject):
|
||||
return s
|
||||
|
||||
def selection_changed(self, selected, _deselected):
|
||||
"""Emit change_completed_part if a new item was selected.
|
||||
"""Change the completed part if a new item was selected.
|
||||
|
||||
Called from the views selectionChanged method.
|
||||
|
||||
@ -243,39 +234,30 @@ class Completer(QObject):
|
||||
if model.count() == 1 and config.get('completion', 'quick-complete'):
|
||||
# If we only have one item, we want to apply it immediately
|
||||
# and go on to the next part.
|
||||
self.change_completed_part.emit(data, True)
|
||||
self.change_completed_part(data, immediate=True)
|
||||
else:
|
||||
self._ignore_change = True
|
||||
self.change_completed_part.emit(data, False)
|
||||
self.change_completed_part(data)
|
||||
|
||||
@pyqtSlot(str, list, int)
|
||||
def on_update_completion(self, prefix, parts, cursor_part):
|
||||
@pyqtSlot()
|
||||
def schedule_completion_update(self):
|
||||
"""Schedule updating/enabling completion.
|
||||
|
||||
Slot for the textChanged signal of the statusbar command widget.
|
||||
|
||||
For performance reasons we don't want to block here, instead we do this
|
||||
in the background.
|
||||
"""
|
||||
log.completion.debug("Scheduling completion update.")
|
||||
self._timer.start()
|
||||
log.completion.debug("Scheduling completion update. prefix {}, parts "
|
||||
"{}, cursor_part {}".format(prefix, parts,
|
||||
cursor_part))
|
||||
self._prefix = prefix
|
||||
self._parts = parts
|
||||
self._cursor_part = cursor_part
|
||||
|
||||
@pyqtSlot()
|
||||
def update_completion(self):
|
||||
"""Check if completions are available and activate them."""
|
||||
self.update_cursor_part()
|
||||
parts = self.split()
|
||||
|
||||
assert self._prefix is not None
|
||||
assert self._parts is not None
|
||||
assert self._cursor_part is not None
|
||||
|
||||
log.completion.debug("Updating completion - prefix {}, parts {}, "
|
||||
"cursor_part {}".format(self._prefix, self._parts,
|
||||
self._cursor_part))
|
||||
log.completion.debug(
|
||||
"Updating completion - prefix {}, parts {}, cursor_part {}".format(
|
||||
self._cmd.prefix(), parts, self._cursor_part))
|
||||
if self._ignore_change:
|
||||
self._ignore_change = False
|
||||
log.completion.debug("Ignoring completion update")
|
||||
@ -284,7 +266,7 @@ class Completer(QObject):
|
||||
completion = objreg.get('completion', scope='window',
|
||||
window=self._win_id)
|
||||
|
||||
if self._prefix != ':':
|
||||
if self._cmd.prefix() != ':':
|
||||
# This is a search or gibberish, so we don't need to complete
|
||||
# anything (yet)
|
||||
# FIXME complete searchs
|
||||
@ -292,7 +274,7 @@ class Completer(QObject):
|
||||
completion.hide()
|
||||
return
|
||||
|
||||
model = self._get_new_completion(self._parts, self._cursor_part)
|
||||
model = self._get_new_completion(parts, self._cursor_part)
|
||||
|
||||
if model != self._model():
|
||||
if model is None:
|
||||
@ -301,19 +283,18 @@ class Completer(QObject):
|
||||
completion.set_model(model)
|
||||
|
||||
if model is None:
|
||||
log.completion.debug("No completion model for {}.".format(
|
||||
self._parts))
|
||||
log.completion.debug("No completion model for {}.".format(parts))
|
||||
return
|
||||
|
||||
try:
|
||||
pattern = self._parts[self._cursor_part].strip()
|
||||
pattern = parts[self._cursor_part].strip()
|
||||
except IndexError:
|
||||
pattern = ''
|
||||
self._model().set_pattern(pattern)
|
||||
|
||||
log.completion.debug(
|
||||
"New completion for {}: {}, with pattern '{}'".format(
|
||||
self._parts, model.srcmodel.__class__.__name__, pattern))
|
||||
parts, model.srcmodel.__class__.__name__, pattern))
|
||||
|
||||
if self._model().count() == 0:
|
||||
completion.hide()
|
||||
@ -321,3 +302,114 @@ class Completer(QObject):
|
||||
|
||||
if completion.enabled:
|
||||
completion.show()
|
||||
|
||||
def split(self, keep=False):
|
||||
"""Get the text split up in parts.
|
||||
|
||||
Args:
|
||||
keep: Whether to keep special chars and whitespace.
|
||||
"""
|
||||
text = self._cmd.text()[len(self._cmd.prefix()):]
|
||||
if not text:
|
||||
# When only ":" is entered, we already have one imaginary part,
|
||||
# which just is empty at the moment.
|
||||
return ['']
|
||||
if not text.strip():
|
||||
# Text is only whitespace so we treat this as a single element with
|
||||
# the whitespace.
|
||||
return [text]
|
||||
runner = runners.CommandRunner(self._win_id)
|
||||
parts = runner.parse(text, fallback=True, alias_no_args=False,
|
||||
keep=keep)
|
||||
if self._empty_item_idx is not None:
|
||||
log.completion.debug("Empty element queued at {}, "
|
||||
"inserting.".format(self._empty_item_idx))
|
||||
parts.insert(self._empty_item_idx, '')
|
||||
#log.completion.debug("Splitting '{}' -> {}".format(text, parts))
|
||||
return parts
|
||||
|
||||
@pyqtSlot()
|
||||
def update_cursor_part(self):
|
||||
"""Get the part index of the commandline where the cursor is over."""
|
||||
cursor_pos = self._cmd.cursorPosition()
|
||||
snippet = slice(cursor_pos - 1, cursor_pos + 1)
|
||||
if self._cmd.text()[snippet] == ' ':
|
||||
spaces = True
|
||||
else:
|
||||
spaces = False
|
||||
cursor_pos -= len(self._cmd.prefix())
|
||||
parts = self.split(keep=True)
|
||||
log.completion.vdebug(
|
||||
"text: {}, parts: {}, cursor_pos after removing prefix '{}': "
|
||||
"{}".format(self._cmd.text(), parts, self._cmd.prefix(),
|
||||
cursor_pos))
|
||||
for i, part in enumerate(parts):
|
||||
log.completion.vdebug("Checking part {}: {}".format(i, parts[i]))
|
||||
if cursor_pos <= len(part):
|
||||
# foo| bar
|
||||
self._cursor_part = i
|
||||
if spaces:
|
||||
self._empty_item_idx = i
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.vdebug("cursor_pos {} <= len(part) {}, "
|
||||
"setting cursor_part {}, empty_item_idx "
|
||||
"{}".format(cursor_pos, len(part), i,
|
||||
self._empty_item_idx))
|
||||
break
|
||||
cursor_pos -= len(part)
|
||||
log.completion.vdebug(
|
||||
"Removing len({!r}) -> {} from cursor_pos -> {}".format(
|
||||
part, len(part), cursor_pos))
|
||||
else:
|
||||
self._cursor_part = i
|
||||
if spaces:
|
||||
self._empty_item_idx = i
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.debug("cursor_part {}, spaces {}".format(
|
||||
self._cursor_part, spaces))
|
||||
return
|
||||
|
||||
def change_completed_part(self, newtext, immediate=False):
|
||||
"""Change the part we're currently completing in the commandline.
|
||||
|
||||
Args:
|
||||
text: The text to set (string).
|
||||
immediate: True if the text should be completed immediately
|
||||
including a trailing space and we shouldn't continue
|
||||
completing the current item.
|
||||
"""
|
||||
parts = self.split()
|
||||
log.completion.debug("changing part {} to '{}'".format(
|
||||
self._cursor_part, newtext))
|
||||
try:
|
||||
parts[self._cursor_part] = newtext
|
||||
except IndexError:
|
||||
parts.append(newtext)
|
||||
# We want to place the cursor directly after the part we just changed.
|
||||
cursor_str = self._cmd.prefix() + ' '.join(
|
||||
parts[:self._cursor_part + 1])
|
||||
if immediate:
|
||||
# If we should complete immediately, we want to move the cursor by
|
||||
# one more char, to get to the next field.
|
||||
cursor_str += ' '
|
||||
text = self._cmd.prefix() + ' '.join(parts)
|
||||
if immediate and self._cursor_part == len(parts) - 1:
|
||||
# If we should complete immediately and we're completing the last
|
||||
# part in the commandline, we automatically add a space.
|
||||
text += ' '
|
||||
self._cmd.setText(text)
|
||||
log.completion.debug("Placing cursor after '{}'".format(cursor_str))
|
||||
log.modes.debug("Completion triggered, focusing {!r}".format(self))
|
||||
self._cmd.setCursorPosition(len(cursor_str))
|
||||
self._cmd.setFocus()
|
||||
self._cmd.show_cmd.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_text_edited(self):
|
||||
"""Reset _empty_item_idx if text was edited."""
|
||||
self._empty_item_idx = None
|
||||
# We also want to update the cursor part and emit update_completion
|
||||
# here, but that's already done for us by cursorPositionChanged
|
||||
# anyways, so we don't need to do it twice.
|
||||
|
@ -93,7 +93,8 @@ class CompletionView(QTreeView):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
objreg.register('completion', self, scope='window', window=win_id)
|
||||
completer_obj = completer.Completer(win_id, self)
|
||||
cmd = objreg.get('status-command', scope='window', window=win_id)
|
||||
completer_obj = completer.Completer(cmd, win_id, self)
|
||||
objreg.register('completer', completer_obj, scope='window',
|
||||
window=win_id)
|
||||
self.enabled = config.get('completion', 'show')
|
||||
|
@ -91,11 +91,11 @@ class MainWindow(QWidget):
|
||||
window=win_id)
|
||||
self._vbox.addWidget(self._tabbed_browser)
|
||||
|
||||
self._completion = completion.CompletionView(win_id, self)
|
||||
|
||||
self.status = bar.StatusBar(win_id)
|
||||
self._vbox.addWidget(self.status)
|
||||
|
||||
self._completion = completion.CompletionView(win_id, self)
|
||||
|
||||
self._commandrunner = runners.CommandRunner(win_id)
|
||||
|
||||
log.init.debug("Initializing search...")
|
||||
@ -256,8 +256,6 @@ class MainWindow(QWidget):
|
||||
cmd.clear_completion_selection.connect(
|
||||
completion_obj.on_clear_completion_selection)
|
||||
cmd.hide_completion.connect(completion_obj.hide)
|
||||
cmd.update_completion.connect(completer.on_update_completion)
|
||||
completer.change_completed_part.connect(cmd.on_change_completed_part)
|
||||
|
||||
# downloads
|
||||
tabs.start_download.connect(download_manager.fetch)
|
||||
|
@ -23,7 +23,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
|
||||
from PyQt5.QtWidgets import QSizePolicy
|
||||
|
||||
from qutebrowser.keyinput import modeman, modeparsers
|
||||
from qutebrowser.commands import runners, cmdexc, cmdutils
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.widgets import misc
|
||||
from qutebrowser.models import cmdhistory
|
||||
from qutebrowser.utils import usertypes, log, objreg
|
||||
@ -34,7 +34,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
"""The commandline part of the statusbar.
|
||||
|
||||
Attributes:
|
||||
_cursor_part: The part the cursor is currently over.
|
||||
_win_id: The window ID this widget is associated with.
|
||||
|
||||
Signals:
|
||||
@ -48,10 +47,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
hidden.
|
||||
hide_completion: Emitted when the completion widget should be hidden.
|
||||
update_completion: Emitted when the completion should be shown/updated.
|
||||
arg 0: The prefix used.
|
||||
arg 1: A list of strings (commandline separated into
|
||||
parts)
|
||||
arg 2: The part the cursor is currently in.
|
||||
show_cmd: Emitted when command input should be shown.
|
||||
hide_cmd: Emitted when command input can be hidden.
|
||||
"""
|
||||
@ -61,7 +56,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
got_search_rev = pyqtSignal(str)
|
||||
clear_completion_selection = pyqtSignal()
|
||||
hide_completion = pyqtSignal()
|
||||
update_completion = pyqtSignal(str, list, int)
|
||||
update_completion = pyqtSignal()
|
||||
show_cmd = pyqtSignal()
|
||||
hide_cmd = pyqtSignal()
|
||||
|
||||
@ -69,13 +64,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
misc.CommandLineEdit.__init__(self, parent)
|
||||
misc.MinimalLineEditMixin.__init__(self)
|
||||
self._win_id = win_id
|
||||
self._cursor_part = 0
|
||||
self.history.history = objreg.get('command-history').data
|
||||
self._empty_item_idx = None
|
||||
self.textEdited.connect(self.on_text_edited)
|
||||
self.cursorPositionChanged.connect(self._update_cursor_part)
|
||||
self.cursorPositionChanged.connect(self.on_cursor_position_changed)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
|
||||
self.cursorPositionChanged.connect(self.update_completion)
|
||||
|
||||
def prefix(self):
|
||||
"""Get the currently entered command prefix."""
|
||||
@ -87,79 +78,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
else:
|
||||
return ''
|
||||
|
||||
def split(self, keep=False):
|
||||
"""Get the text split up in parts.
|
||||
|
||||
Args:
|
||||
keep: Whether to keep special chars and whitespace.
|
||||
"""
|
||||
text = self.text()[len(self.prefix()):]
|
||||
if not text:
|
||||
# When only ":" is entered, we already have one imaginary part,
|
||||
# which just is empty at the moment.
|
||||
return ['']
|
||||
if not text.strip():
|
||||
# Text is only whitespace so we treat this as a single element with
|
||||
# the whitespace.
|
||||
return [text]
|
||||
runner = runners.CommandRunner(self._win_id)
|
||||
parts = runner.parse(text, fallback=True, alias_no_args=False,
|
||||
keep=keep)
|
||||
if self._empty_item_idx is not None:
|
||||
log.completion.debug("Empty element queued at {}, "
|
||||
"inserting.".format(self._empty_item_idx))
|
||||
parts.insert(self._empty_item_idx, '')
|
||||
#log.completion.debug("Splitting '{}' -> {}".format(text, parts))
|
||||
return parts
|
||||
|
||||
@pyqtSlot()
|
||||
def _update_cursor_part(self):
|
||||
"""Get the part index of the commandline where the cursor is over."""
|
||||
cursor_pos = self.cursorPosition()
|
||||
snippet = slice(cursor_pos - 1, cursor_pos + 1)
|
||||
if self.text()[snippet] == ' ':
|
||||
spaces = True
|
||||
else:
|
||||
spaces = False
|
||||
cursor_pos -= len(self.prefix())
|
||||
parts = self.split(keep=True)
|
||||
log.completion.vdebug(
|
||||
"text: {}, parts: {}, cursor_pos after removing prefix '{}': "
|
||||
"{}".format(self.text(), parts, self.prefix(), cursor_pos))
|
||||
for i, part in enumerate(parts):
|
||||
log.completion.vdebug("Checking part {}: {}".format(i, parts[i]))
|
||||
if cursor_pos <= len(part):
|
||||
# foo| bar
|
||||
self._cursor_part = i
|
||||
if spaces:
|
||||
self._empty_item_idx = i
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.vdebug("cursor_pos {} <= len(part) {}, "
|
||||
"setting cursor_part {}, empty_item_idx "
|
||||
"{}".format(cursor_pos, len(part), i,
|
||||
self._empty_item_idx))
|
||||
break
|
||||
cursor_pos -= len(part)
|
||||
log.completion.vdebug(
|
||||
"Removing len({!r}) -> {} from cursor_pos -> {}".format(
|
||||
part, len(part), cursor_pos))
|
||||
else:
|
||||
self._cursor_part = i
|
||||
if spaces:
|
||||
self._empty_item_idx = i
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.debug("cursor_part {}, spaces {}".format(
|
||||
self._cursor_part, spaces))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cursor_position_changed(self):
|
||||
"""Update completion when the cursor position changed."""
|
||||
self.update_completion.emit(self.prefix(), self.split(),
|
||||
self._cursor_part)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_cmd_text(self, text):
|
||||
"""Preset the statusbar to some text.
|
||||
@ -172,8 +90,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
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(self.prefix(), self.split(),
|
||||
self._cursor_part)
|
||||
self.update_completion.emit()
|
||||
log.modes.debug("Setting command text, focusing {!r}".format(self))
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
@ -207,41 +124,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
"Invalid command text '{}'.".format(text))
|
||||
self.set_cmd_text(text)
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def on_change_completed_part(self, newtext, immediate):
|
||||
"""Change the part we're currently completing in the commandline.
|
||||
|
||||
Args:
|
||||
text: The text to set (string).
|
||||
immediate: True if the text should be completed immediately
|
||||
including a trailing space and we shouldn't continue
|
||||
completing the current item.
|
||||
"""
|
||||
parts = self.split()
|
||||
log.completion.debug("changing part {} to '{}'".format(
|
||||
self._cursor_part, newtext))
|
||||
try:
|
||||
parts[self._cursor_part] = newtext
|
||||
except IndexError:
|
||||
parts.append(newtext)
|
||||
# We want to place the cursor directly after the part we just changed.
|
||||
cursor_str = self.prefix() + ' '.join(parts[:self._cursor_part + 1])
|
||||
if immediate:
|
||||
# If we should complete immediately, we want to move the cursor by
|
||||
# one more char, to get to the next field.
|
||||
cursor_str += ' '
|
||||
text = self.prefix() + ' '.join(parts)
|
||||
if immediate and self._cursor_part == len(parts) - 1:
|
||||
# If we should complete immediately and we're completing the last
|
||||
# part in the commandline, we automatically add a space.
|
||||
text += ' '
|
||||
self.setText(text)
|
||||
log.completion.debug("Placing cursor after '{}'".format(cursor_str))
|
||||
log.modes.debug("Completion triggered, focusing {!r}".format(self))
|
||||
self.setCursorPosition(len(cursor_str))
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@cmdutils.register(instance='status-command', hide=True,
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def command_history_prev(self):
|
||||
@ -285,15 +167,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
if text[0] in signals:
|
||||
signals[text[0]].emit(text.lstrip(text[0]))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_text_edited(self, _text):
|
||||
"""Slot for textEdited. Stop history and update completion."""
|
||||
self.history.stop()
|
||||
self._empty_item_idx = None
|
||||
# We also want to update the cursor part and emit update_completion
|
||||
# here, but that's already done for us by cursorPositionChanged
|
||||
# anyways, so we don't need to do it twice.
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear up when ommand mode was left.
|
||||
|
Loading…
Reference in New Issue
Block a user