Autocomplete: return only the common prefix + one word.

This commit is contained in:
Valentin Lorentz 2020-08-29 18:38:39 +02:00
parent efb4d476a5
commit 6f34f377e5
2 changed files with 67 additions and 18 deletions

View File

@ -45,30 +45,55 @@ REQUEST_TAG = "+draft/autocomplete-request"
RESPONSE_TAG = "+draft/autocomplete-response" 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): def _getAutocompleteResponse(irc, msg, payload):
"""Returns the value of the +draft/autocomplete-response tag for the given """Returns the value of the +draft/autocomplete-response tag for the given
+draft/autocomplete-request payload.""" +draft/autocomplete-request payload."""
tokens = callbacks.tokenize(payload, channel=msg.channel, network=irc.network) tokens = callbacks.tokenize(payload, channel=msg.channel, network=irc.network)
normalized_payload = " ".join(tokens) normalized_payload = " ".join(tokens)
candidates = _getCandidates(irc, normalized_payload) candidate_commands = _getCandidates(irc, normalized_payload)
if not candidates: if len(candidate_commands) == 0:
return "" # 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 # 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) normalized_payload_length = len(normalized_payload)
candidate_commands = [ response_items = [command[normalized_payload_length:] for command in 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}
return "\t".join(sorted(response_items)) return "\t".join(sorted(response_items))

View File

@ -63,17 +63,41 @@ class AutocompleteTestCase(PluginTestCase):
) )
def testResponse(self): 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.protocols.irc.experimentalExtensions.context(True):
with conf.supybot.plugins.Autocomplete.enabled.context(True): with conf.supybot.plugins.Autocomplete.enabled.context(True):
self._assertAutocompleteResponse("apro", "pos") self._assertAutocompleteResponse("apro", "pos")
self._assertAutocompleteResponse("apr", "opos") self._assertAutocompleteResponse("apr", "opos")
self._assertAutocompleteResponse("te", "ll\tstplugin")
self._assertAutocompleteResponse("tel", "l") 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 t", "ell")
self._assertAutocompleteResponse("misc c", "learmores\tompletenick") 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): def testNoResponse(self):
with conf.supybot.protocols.irc.experimentalExtensions.context(True): with conf.supybot.protocols.irc.experimentalExtensions.context(True):