Question

I've been working on a command line tool for which I'm now making a PyQT GUI. I'd like to take my current autocomplete implementation using the readline module, and put that in a QLineEdit text box. Is this possible? Do you have any recommendations?

Here's an example of the stuff I'm doing with the readline module:

import readline

values = ['these','are','my','autocomplete','words']
completions = {}

def completer(text,state):
    try:
        matches = completions[text]
    except KeyError:
        matches = [value for value in values if text.upper() in value.upper()]
        completions[text] = matches
    try:
        return matches[state]
    except IndexError:
        return None

readline.set_completer(completer)
readline.parse_and_bind('tab: menu-complete')

whie 1:
    text = raw_input('> ')
    text.dostuff()

Ultimately, if I can't get the readline module to work in a QLineEdit widget, what I'd ultimately like to do is complete on a list of words, with the ability to have multiple words separated by symbols like +-*/() etc...

Thanks!

Was it helpful?

Solution

I can tell you that first off, it is a huge pain to try and wrap a QCompleter around new functionality. You have to be able to satisfy all of the QCompleter's interface, and bridge it around that realine code.

You have to manually update a QStringListModel set on the QCompleter, and provide the implementation of getting the current completion, and the total number of completions, for the given search prefix.

Here is a working example that is compatible with the PopupCompletion mode:

import re

class ReadlineCompleter(QtGui.QCompleter):

    def __init__(self, completeFn, *args, **kwargs):
        super(ReadlineCompleter, self).__init__(*args, **kwargs)
        self._completer = completeFn
        self.setModel(QtGui.QStringListModel())
        self.update()

    def setCompletionPrefix(self, val):
        super(ReadlineCompleter, self).setCompletionPrefix(val)
        self.update()

    def currentCompletion(self):
        state = self.currentRow()
        return self._completionAt(state)

    def completionCount(self):
        state = 0
        while True:
            result = self._completionAt(state)
            if not result:
                break
            state += 1
        return state

    def update(self):
        matches = [self._completionAt(i) for i in xrange(self.completionCount())]
        self.model().setStringList(matches)

    def _completionAt(self, state):
        text = str(self.completionPrefix())

        # regex to split on any whitespace, or the char set +*/^()-
        match = re.match(r'^(.*)([\s+*/^()-]+)(.*)$', text)
        if match:
            prefix, sep, text = match.groups()

        result = self._completer(str(text), state)

        if result and match:
            result = sep.join([prefix, result])

        return '' if result is None else result     

Notice in the _completionAt() method, I added the extra functionality you wanted, for detecting a separator pattern. You can adjust this obviously. But it will split off the last part and use that value to check the completion, then rejoin the result with the prefix again.

Usage

Important. You need to connect the textChanged signal from the QLineEdit to the completer to force an update. Otherwise none of the functionality will be used in the completer.

line = QtGui.QLineEdit()
comp = ReadlineCompleter(completer)
comp.setCompletionMode(comp.PopupCompletion)
line.setCompleter(comp)
# important
line.textChanged.connect(comp.setCompletionPrefix)

There are examples here showing how other people have had to fill in functionality in a custom line edit, where they completely go around the standard signaling of the completer and trigger it themselves. You can see its a little bit of effort.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top