Python Readline on macOS behaves differently than on Linux/Windows

Viewed 91

A project I'm working on uses a custom CLI handler instead of Python's cmd.Cmd class. Without getting too much in detail, the handler features TAB-key completion to assist the operator with command usage. The feature works as expected on Windows (using pyreadline) and Linux (using GNU's readline).

Here is an example of the expected behavior (assume "cmd > " is the prompt and that [TAB] is a push of the TAB key):

cmd > [TAB]
cd exit load save                        # all the available commands
cmd > c[TAB]                             # autocompletes to 'cd'
cmd > cd [TAB]
cd ./folder1 cd ./folder2 cd ./folder3   # folders in the cwd
cmd > cd C:\[TAB]
cd C:\Users cd C:\Windows...             # enumerates folders in C:\ (on windows)
cmd > cd /[TAB]
cd /bin cd /opt cd /usr...               # enumerates folders from root (on linux)

The custom class defines the following tab completion method, which is set using readline.set_completer():

def tab_completer(self, text, state):
    # rl delims set to "" so we get the whole line as a single string
    words = re.split(r'[\s\t\n]+', text)
    # find_subcompleter populates a list of possible matches or next words
    # each command implements its own completer_stub depending on the function (ex: cd will complete directory names)
    retval = self.find_subcompleter(words.pop())
    try:
        return retval[state]
    except IndexError:
        return None

The function works as expected on Windows (10, Python 3.6.6) and Linux (CentOS 7, Python 3.6.8), but something strange happens on macOS (10.15.7, Python 3.8.2 via xcode on zsh terminal):

cmd > [TAB]
cd exit load save    # this is good
cmd > c[TAB]         # still autocompletes to 'cd', good
cmd > cd [TAB]
cd exit load save    # as if I've typed nothing!

For those of you wondering, this behavior happens with ANY command, not just with cd.

I'm aware that the underlying readline implementation on macOS uses libedit due to GNU licensing. I just haven't seen anyone else (to date) mention this difference on any other forums. A possible solution that comes to mind is to add a conditional for libedit implementations to use get_line_buffer() and redisplay() to mimic the correct behavior. Any pointers in the right direction are appreciated!

Thank you

0 Answers
Related