From 6f34f377e565a246c0b5825b13ab632d88888b30 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 29 Aug 2020 18:38:39 +0200 Subject: [PATCH] Autocomplete: return only the common prefix + one word. --- plugins/Autocomplete/plugin.py | 53 +++++++++++++++++++++++++--------- plugins/Autocomplete/test.py | 32 +++++++++++++++++--- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/plugins/Autocomplete/plugin.py b/plugins/Autocomplete/plugin.py index 7a734c0d3..4ab8ce7b7 100644 --- a/plugins/Autocomplete/plugin.py +++ b/plugins/Autocomplete/plugin.py @@ -45,30 +45,55 @@ REQUEST_TAG = "+draft/autocomplete-request" RESPONSE_TAG = "+draft/autocomplete-response" +def _commonPrefix(L): + """Takes a list of lists, and returns their longest common prefix.""" + assert L + if len(L) == 1: + return L[0] + + for n in range(1, max(map(len, L)) + 1): + prefix = L[0][:n] + for item in L[1:]: + if prefix != item[:n]: + return prefix[0:-1] + + assert False + + def _getAutocompleteResponse(irc, msg, payload): """Returns the value of the +draft/autocomplete-response tag for the given +draft/autocomplete-request payload.""" tokens = callbacks.tokenize(payload, channel=msg.channel, network=irc.network) normalized_payload = " ".join(tokens) - candidates = _getCandidates(irc, normalized_payload) + candidate_commands = _getCandidates(irc, normalized_payload) - if not candidates: - return "" + if len(candidate_commands) == 0: + # No result + return None + elif len(candidate_commands) == 1: + # One result, return it directly + commands = candidate_commands + else: + # Multiple results, return only the longest common prefix + one word + + tokenized_candidates = [ + callbacks.tokenize(c, channel=msg.channel, network=irc.network) + for c in candidate_commands + ] + + common_prefix = _commonPrefix(tokenized_candidates) + + words_after_prefix = { + candidate[len(common_prefix)] for candidate in tokenized_candidates + } + + commands = [" ".join(common_prefix + [word]) for word in words_after_prefix] # strip what the user already typed - assert all(candidate.startswith(normalized_payload) for candidate in candidates) + assert all(command.startswith(normalized_payload) for command in commands) normalized_payload_length = len(normalized_payload) - candidate_commands = [ - candidate[normalized_payload_length:] for candidate in candidates - ] - - tokenized_candidates = [ - callbacks.tokenize(c, channel=msg.channel, network=irc.network) - for c in candidate_commands - ] - - response_items = {candidate[0] for candidate in tokenized_candidates} + response_items = [command[normalized_payload_length:] for command in commands] return "\t".join(sorted(response_items)) diff --git a/plugins/Autocomplete/test.py b/plugins/Autocomplete/test.py index 68355c409..ff44233eb 100644 --- a/plugins/Autocomplete/test.py +++ b/plugins/Autocomplete/test.py @@ -63,17 +63,41 @@ class AutocompleteTestCase(PluginTestCase): ) def testResponse(self): + with conf.supybot.protocols.irc.experimentalExtensions.context(True): + with conf.supybot.plugins.Autocomplete.enabled.context(True): + self._assertAutocompleteResponse("apro", "pos") + + def testSingleCommandName(self): with conf.supybot.protocols.irc.experimentalExtensions.context(True): with conf.supybot.plugins.Autocomplete.enabled.context(True): self._assertAutocompleteResponse("apro", "pos") self._assertAutocompleteResponse("apr", "opos") - self._assertAutocompleteResponse("te", "ll\tstplugin") self._assertAutocompleteResponse("tel", "l") - self._assertAutocompleteResponse("mi", "sc") + + def testTwoResults(self): + with conf.supybot.protocols.irc.experimentalExtensions.context(True): + with conf.supybot.plugins.Autocomplete.enabled.context(True): + self._assertAutocompleteResponse("te", "ll\tstplugin") + + def testCommandNameAndPluginName(self): + with conf.supybot.protocols.irc.experimentalExtensions.context(True): + with conf.supybot.plugins.Autocomplete.enabled.context(True): self._assertAutocompleteResponse("misc t", "ell") self._assertAutocompleteResponse("misc c", "learmores\tompletenick") - self._assertAutocompleteResponse("lat", "er") - self._assertAutocompleteResponse("later", "notes\tremove\ttell\tundo") + + def testSinglePluginName(self): + with conf.supybot.protocols.irc.experimentalExtensions.context(True): + with conf.supybot.plugins.Autocomplete.enabled.context(True): + self._assertAutocompleteResponse( + "lat", "er notes\ter remove\ter tell\ter undo" + ) + + def testNextWord(self): + with conf.supybot.protocols.irc.experimentalExtensions.context(True): + with conf.supybot.plugins.Autocomplete.enabled.context(True): + self._assertAutocompleteResponse( + "later", " notes\t remove\t tell\t undo" + ) def testNoResponse(self): with conf.supybot.protocols.irc.experimentalExtensions.context(True):