Simplify update_completion.
Remove the class variables _cursor_part and _empty_item_index. Instead, split up the commandline around the cursor whenever that information is needed. Using locals instead of class variables makes the logic easier to follow and ends up requiring much less code.
This commit is contained in:
parent
581d7659ba
commit
127412d91c
@ -36,7 +36,6 @@ class Completer(QObject):
|
||||
_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.
|
||||
_cursor_part: The cursor part index for the next completion update.
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
_last_text: The old command text so we avoid double completion updates.
|
||||
@ -52,11 +51,9 @@ class Completer(QObject):
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(0)
|
||||
self._timer.timeout.connect(self._update_completion)
|
||||
self._cursor_part = None
|
||||
self._last_cursor_pos = None
|
||||
self._last_text = None
|
||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||
self._cmd.textChanged.connect(self._on_text_edited)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
@ -66,23 +63,22 @@ class Completer(QObject):
|
||||
completion = self.parent()
|
||||
return completion.model()
|
||||
|
||||
def _get_completion_model(self, completion, parts, cursor_part):
|
||||
def _get_completion_model(self, completion, pos_args):
|
||||
"""Get a completion model based on an enum member.
|
||||
|
||||
Args:
|
||||
completion: A usertypes.Completion member.
|
||||
parts: The parts currently in the commandline.
|
||||
cursor_part: The part the cursor is in.
|
||||
pos_args: The positional args entered before the cursor.
|
||||
|
||||
Return:
|
||||
A completion model or None.
|
||||
"""
|
||||
if completion == usertypes.Completion.option:
|
||||
section = parts[cursor_part - 1]
|
||||
section = pos_args[0]
|
||||
model = instances.get(completion).get(section)
|
||||
elif completion == usertypes.Completion.value:
|
||||
section = parts[cursor_part - 2]
|
||||
option = parts[cursor_part - 1]
|
||||
section = pos_args[0]
|
||||
option = pos_args[1]
|
||||
try:
|
||||
model = instances.get(completion)[section][option]
|
||||
except KeyError:
|
||||
@ -96,72 +92,46 @@ class Completer(QObject):
|
||||
else:
|
||||
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
||||
|
||||
def _filter_cmdline_parts(self, parts, cursor_part):
|
||||
"""Filter a list of commandline parts to exclude flags.
|
||||
|
||||
Args:
|
||||
parts: A list of parts.
|
||||
cursor_part: The index of the part the cursor is over.
|
||||
|
||||
Return:
|
||||
A (parts, cursor_part) tuple with the modified values.
|
||||
"""
|
||||
if parts == ['']:
|
||||
# Empty commandline, i.e. only :.
|
||||
return [''], 0
|
||||
filtered_parts = []
|
||||
for i, part in enumerate(parts):
|
||||
if part == '--':
|
||||
break
|
||||
elif part.startswith('-'):
|
||||
if cursor_part >= i:
|
||||
cursor_part -= 1
|
||||
else:
|
||||
filtered_parts.append(part)
|
||||
return filtered_parts, cursor_part
|
||||
|
||||
def _get_new_completion(self, parts, cursor_part):
|
||||
def _get_new_completion(self, before_cursor, under_cursor):
|
||||
"""Get a new completion.
|
||||
|
||||
Args:
|
||||
parts: The command chunks to get a completion for.
|
||||
cursor_part: The part the cursor is over currently.
|
||||
before_cursor: The command chunks before the cursor.
|
||||
under_cursor: The command chunk under the cursor.
|
||||
|
||||
Return:
|
||||
A completion model.
|
||||
"""
|
||||
if '--' in before_cursor:
|
||||
return None
|
||||
try:
|
||||
if parts[cursor_part].startswith('-'):
|
||||
if under_cursor.startswith('-'):
|
||||
# cursor on a flag
|
||||
return
|
||||
return None
|
||||
except IndexError:
|
||||
pass
|
||||
log.completion.debug("Before filtering flags: parts {}, cursor_part "
|
||||
"{}".format(parts, cursor_part))
|
||||
parts, cursor_part = self._filter_cmdline_parts(parts, cursor_part)
|
||||
log.completion.debug("After filtering flags: parts {}, cursor_part "
|
||||
"{}".format(parts, cursor_part))
|
||||
if not parts:
|
||||
return None
|
||||
if cursor_part == 0:
|
||||
log.completion.debug("Before removing flags: {}".format(before_cursor))
|
||||
before_cursor = [x for x in before_cursor if not x.startswith('-')]
|
||||
log.completion.debug("After removing flags: {}".format(before_cursor))
|
||||
if len(before_cursor) == 0:
|
||||
# '|' or 'set|'
|
||||
model = instances.get(usertypes.Completion.command)
|
||||
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
||||
# delegate completion to command
|
||||
try:
|
||||
cmd = cmdutils.cmd_dict[parts[0]]
|
||||
cmd = cmdutils.cmd_dict[before_cursor[0]]
|
||||
except KeyError:
|
||||
# entering an unknown command
|
||||
log.completion.debug("No completion for unknown command: {}"
|
||||
.format(before_cursor[0]))
|
||||
return None
|
||||
argpos = len(before_cursor) - 1
|
||||
try:
|
||||
idx = cursor_part - 1
|
||||
completion = cmd.get_pos_arg_info(idx).completion
|
||||
completion = cmd.get_pos_arg_info(argpos).completion
|
||||
except IndexError:
|
||||
# user provided more positional arguments than the command takes
|
||||
log.completion.debug("No completion in position {}".format(argpos))
|
||||
return None
|
||||
if completion is None:
|
||||
return None
|
||||
model = self._get_completion_model(completion, parts, cursor_part)
|
||||
model = self._get_completion_model(completion, before_cursor[1:])
|
||||
return model
|
||||
|
||||
def _quote(self, s):
|
||||
@ -253,12 +223,10 @@ class Completer(QObject):
|
||||
@pyqtSlot()
|
||||
def _update_completion(self):
|
||||
"""Check if completions are available and activate them."""
|
||||
self._update_cursor_part()
|
||||
parts = self._split()
|
||||
before_cursor, pattern, after_cursor = self._partition()
|
||||
|
||||
log.completion.debug(
|
||||
"Updating completion - prefix {}, parts {}, cursor_part {}".format(
|
||||
self._cmd.prefix(), parts, self._cursor_part))
|
||||
log.completion.debug("Updating completion: {} {} {}".format(
|
||||
before_cursor, pattern, after_cursor))
|
||||
|
||||
if self._ignore_change:
|
||||
log.completion.debug("Ignoring completion update because "
|
||||
@ -276,19 +244,11 @@ class Completer(QObject):
|
||||
completion.set_model(None)
|
||||
return
|
||||
|
||||
model = self._get_new_completion(parts, self._cursor_part)
|
||||
model = self._get_new_completion(before_cursor, pattern)
|
||||
|
||||
try:
|
||||
pattern = parts[self._cursor_part].strip()
|
||||
except IndexError:
|
||||
pattern = ''
|
||||
|
||||
if model is None:
|
||||
log.completion.debug("No completion model for {}.".format(parts))
|
||||
else:
|
||||
log.completion.debug(
|
||||
"New completion for {}: {}, with pattern '{}'".format(
|
||||
parts, model.srcmodel.__class__.__name__, pattern))
|
||||
log.completion.debug("Setting completion model to {} with pattern '{}'"
|
||||
.format(model.srcmodel.__class__.__name__ if model else 'None',
|
||||
pattern))
|
||||
|
||||
completion.set_model(model, pattern)
|
||||
|
||||
@ -318,55 +278,6 @@ class Completer(QObject):
|
||||
#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)
|
||||
spaces = self._cmd.text()[snippet] == ' '
|
||||
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))
|
||||
skip = 0
|
||||
for i, part in enumerate(parts):
|
||||
log.completion.vdebug("Checking part {}: {!r}".format(i, parts[i]))
|
||||
if not part:
|
||||
skip += 1
|
||||
continue
|
||||
if cursor_pos <= len(part):
|
||||
# foo| bar
|
||||
self._cursor_part = i - skip
|
||||
if spaces:
|
||||
self._empty_item_idx = i - skip
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.vdebug("cursor_pos {} <= len(part) {}, "
|
||||
"setting cursor_part {} - {} (skip), "
|
||||
"empty_item_idx {}".format(
|
||||
cursor_pos, len(part), i, skip,
|
||||
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:
|
||||
if i == 0:
|
||||
# Initial `:` press without any text.
|
||||
self._cursor_part = 0
|
||||
else:
|
||||
self._cursor_part = i - skip
|
||||
if spaces:
|
||||
self._empty_item_idx = i - skip
|
||||
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.
|
||||
|
||||
@ -390,11 +301,3 @@ class Completer(QObject):
|
||||
self._cmd.setCursorPosition(pos)
|
||||
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.
|
||||
|
@ -174,7 +174,6 @@ def _set_cmd_prompt(cmd, txt):
|
||||
(':open -t |', usertypes.Completion.url),
|
||||
(':open --tab |', usertypes.Completion.url),
|
||||
(':open | -t', usertypes.Completion.url),
|
||||
(':--foo --bar |', None),
|
||||
(':tab-detach |', None),
|
||||
(':bind --mode=caret <c-x> |', usertypes.Completion.command),
|
||||
pytest.mark.xfail(reason='issue #74')((':bind --mode caret <c-x> |',
|
||||
|
Loading…
Reference in New Issue
Block a user