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.
|
_ignore_change: Whether to ignore the next completion update.
|
||||||
_win_id: The window ID this completer is in.
|
_win_id: The window ID this completer is in.
|
||||||
_timer: The timer used to trigger the completion update.
|
_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
|
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||||
updates.
|
updates.
|
||||||
_last_text: The old command text 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.setSingleShot(True)
|
||||||
self._timer.setInterval(0)
|
self._timer.setInterval(0)
|
||||||
self._timer.timeout.connect(self._update_completion)
|
self._timer.timeout.connect(self._update_completion)
|
||||||
self._cursor_part = None
|
|
||||||
self._last_cursor_pos = None
|
self._last_cursor_pos = None
|
||||||
self._last_text = None
|
self._last_text = None
|
||||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||||
self._cmd.textChanged.connect(self._on_text_edited)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_repr(self)
|
return utils.get_repr(self)
|
||||||
@ -66,23 +63,22 @@ class Completer(QObject):
|
|||||||
completion = self.parent()
|
completion = self.parent()
|
||||||
return completion.model()
|
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.
|
"""Get a completion model based on an enum member.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
completion: A usertypes.Completion member.
|
completion: A usertypes.Completion member.
|
||||||
parts: The parts currently in the commandline.
|
pos_args: The positional args entered before the cursor.
|
||||||
cursor_part: The part the cursor is in.
|
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
A completion model or None.
|
A completion model or None.
|
||||||
"""
|
"""
|
||||||
if completion == usertypes.Completion.option:
|
if completion == usertypes.Completion.option:
|
||||||
section = parts[cursor_part - 1]
|
section = pos_args[0]
|
||||||
model = instances.get(completion).get(section)
|
model = instances.get(completion).get(section)
|
||||||
elif completion == usertypes.Completion.value:
|
elif completion == usertypes.Completion.value:
|
||||||
section = parts[cursor_part - 2]
|
section = pos_args[0]
|
||||||
option = parts[cursor_part - 1]
|
option = pos_args[1]
|
||||||
try:
|
try:
|
||||||
model = instances.get(completion)[section][option]
|
model = instances.get(completion)[section][option]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -96,72 +92,46 @@ class Completer(QObject):
|
|||||||
else:
|
else:
|
||||||
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
||||||
|
|
||||||
def _filter_cmdline_parts(self, parts, cursor_part):
|
def _get_new_completion(self, before_cursor, under_cursor):
|
||||||
"""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):
|
|
||||||
"""Get a new completion.
|
"""Get a new completion.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parts: The command chunks to get a completion for.
|
before_cursor: The command chunks before the cursor.
|
||||||
cursor_part: The part the cursor is over currently.
|
under_cursor: The command chunk under the cursor.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
A completion model.
|
A completion model.
|
||||||
"""
|
"""
|
||||||
|
if '--' in before_cursor:
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
if parts[cursor_part].startswith('-'):
|
if under_cursor.startswith('-'):
|
||||||
# cursor on a flag
|
# cursor on a flag
|
||||||
return
|
return None
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
log.completion.debug("Before filtering flags: parts {}, cursor_part "
|
log.completion.debug("Before removing flags: {}".format(before_cursor))
|
||||||
"{}".format(parts, cursor_part))
|
before_cursor = [x for x in before_cursor if not x.startswith('-')]
|
||||||
parts, cursor_part = self._filter_cmdline_parts(parts, cursor_part)
|
log.completion.debug("After removing flags: {}".format(before_cursor))
|
||||||
log.completion.debug("After filtering flags: parts {}, cursor_part "
|
if len(before_cursor) == 0:
|
||||||
"{}".format(parts, cursor_part))
|
|
||||||
if not parts:
|
|
||||||
return None
|
|
||||||
if cursor_part == 0:
|
|
||||||
# '|' or 'set|'
|
# '|' or 'set|'
|
||||||
model = instances.get(usertypes.Completion.command)
|
model = instances.get(usertypes.Completion.command)
|
||||||
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
return sortfilter.CompletionFilterModel(source=model, parent=self)
|
||||||
# delegate completion to command
|
|
||||||
try:
|
try:
|
||||||
cmd = cmdutils.cmd_dict[parts[0]]
|
cmd = cmdutils.cmd_dict[before_cursor[0]]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# entering an unknown command
|
log.completion.debug("No completion for unknown command: {}"
|
||||||
|
.format(before_cursor[0]))
|
||||||
return None
|
return None
|
||||||
|
argpos = len(before_cursor) - 1
|
||||||
try:
|
try:
|
||||||
idx = cursor_part - 1
|
completion = cmd.get_pos_arg_info(argpos).completion
|
||||||
completion = cmd.get_pos_arg_info(idx).completion
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# user provided more positional arguments than the command takes
|
log.completion.debug("No completion in position {}".format(argpos))
|
||||||
return None
|
return None
|
||||||
if completion is None:
|
if completion is None:
|
||||||
return None
|
return None
|
||||||
model = self._get_completion_model(completion, parts, cursor_part)
|
model = self._get_completion_model(completion, before_cursor[1:])
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def _quote(self, s):
|
def _quote(self, s):
|
||||||
@ -253,12 +223,10 @@ class Completer(QObject):
|
|||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _update_completion(self):
|
def _update_completion(self):
|
||||||
"""Check if completions are available and activate them."""
|
"""Check if completions are available and activate them."""
|
||||||
self._update_cursor_part()
|
before_cursor, pattern, after_cursor = self._partition()
|
||||||
parts = self._split()
|
|
||||||
|
|
||||||
log.completion.debug(
|
log.completion.debug("Updating completion: {} {} {}".format(
|
||||||
"Updating completion - prefix {}, parts {}, cursor_part {}".format(
|
before_cursor, pattern, after_cursor))
|
||||||
self._cmd.prefix(), parts, self._cursor_part))
|
|
||||||
|
|
||||||
if self._ignore_change:
|
if self._ignore_change:
|
||||||
log.completion.debug("Ignoring completion update because "
|
log.completion.debug("Ignoring completion update because "
|
||||||
@ -276,19 +244,11 @@ class Completer(QObject):
|
|||||||
completion.set_model(None)
|
completion.set_model(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
model = self._get_new_completion(parts, self._cursor_part)
|
model = self._get_new_completion(before_cursor, pattern)
|
||||||
|
|
||||||
try:
|
log.completion.debug("Setting completion model to {} with pattern '{}'"
|
||||||
pattern = parts[self._cursor_part].strip()
|
.format(model.srcmodel.__class__.__name__ if model else 'None',
|
||||||
except IndexError:
|
pattern))
|
||||||
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))
|
|
||||||
|
|
||||||
completion.set_model(model, pattern)
|
completion.set_model(model, pattern)
|
||||||
|
|
||||||
@ -318,55 +278,6 @@ class Completer(QObject):
|
|||||||
#log.completion.debug("Splitting '{}' -> {}".format(text, parts))
|
#log.completion.debug("Splitting '{}' -> {}".format(text, parts))
|
||||||
return 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):
|
def _change_completed_part(self, newtext, immediate=False):
|
||||||
"""Change the part we're currently completing in the commandline.
|
"""Change the part we're currently completing in the commandline.
|
||||||
|
|
||||||
@ -390,11 +301,3 @@ class Completer(QObject):
|
|||||||
self._cmd.setCursorPosition(pos)
|
self._cmd.setCursorPosition(pos)
|
||||||
self._cmd.setFocus()
|
self._cmd.setFocus()
|
||||||
self._cmd.show_cmd.emit()
|
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 -t |', usertypes.Completion.url),
|
||||||
(':open --tab |', usertypes.Completion.url),
|
(':open --tab |', usertypes.Completion.url),
|
||||||
(':open | -t', usertypes.Completion.url),
|
(':open | -t', usertypes.Completion.url),
|
||||||
(':--foo --bar |', None),
|
|
||||||
(':tab-detach |', None),
|
(':tab-detach |', None),
|
||||||
(':bind --mode=caret <c-x> |', usertypes.Completion.command),
|
(':bind --mode=caret <c-x> |', usertypes.Completion.command),
|
||||||
pytest.mark.xfail(reason='issue #74')((':bind --mode caret <c-x> |',
|
pytest.mark.xfail(reason='issue #74')((':bind --mode caret <c-x> |',
|
||||||
|
Loading…
Reference in New Issue
Block a user