ncrocfer
ncrocfer

Reputation: 2570

Python : correct use of set_completion_display_matches_hook

I'm trying to write a function to display a custom view when users press the tab button. Apparently "set_completion_display_matches_hook" function is what I need, I can display a custom view, but the problem is that I have to press Enter to get a prompt again.

The solution in Python2 seems to be that (solution here):

def match_display_hook(self, substitution, matches, longest_match_length):
    print ''
    for match in matches:
        print match
    print self.prompt.rstrip(),
    print readline.get_line_buffer(),
    readline.redisplay()

But it doesn't work with Python3. I made these syntax changes :

def match_display_hook(self, substitution, matches, longest_match_length):
        print('\n----------------------------------------------\n')
        for match in matches:
            print(match)
        print(self.prompt.rstrip() + readline.get_line_buffer())
        readline.redisplay()

Any ideas please ?

Upvotes: 4

Views: 999

Answers (3)

rawan qadri
rawan qadri

Reputation: 26

this one worked for me for redisplaying substitution and the end of matches display for python3:

    def match_display_hook(self, substitution, matches, longest_match_length):
        print("")
        for match in matches:
            print(match)
        print("")
        sys.stdout.write(substitution)
        sys.stdout.flush()
        return None

while previous ones using print prompt didn't. (didn't get to the bottom of the problem)

Upvotes: 0

user3342816
user3342816

Reputation: 1263

The redisplay() function

voidrl_redisplay (void)
Change what's displayed on the screen to reflect the current contents of rl_line_buffer.

In your example you have written to stdout, but not changed that buffer.

Print and flush as described by in other answer should work.

One issue you will have, however, is cursor position. Say you have this scenario:

$ cmd   some_file
      ^
      +---- User has back-tracked here and want to insert an option.
            <TAB> completion with print and flush will put cursor
            at end of `some_file' and the line will get an extra 15
            spaces after that ...

To remedy this one way is to first get cursor position, then use ANSI sequences to re-position the cursor.

buf = readline.get_line_buffer()
x = readline.get_endidx()

print(self.prompt + buf, end = '')

if x < len(buf):
    """ Set cursor at old column position """
    print("\r\033[%dC" % (x + len(self.prompt)), end = '')

sys.stdout.flush()

Now, of course, you get another issue if prompt has ANSI sequences in-iteself. Typically color or the like. Then you can not use len(prompt) but have to find printed / visible length.

One has to use open and close bytes elsewhere, typically \0x01 and \0x02 respectively.

So one typically get:

prompt = '\001\033[31;1m\002VISIBLE_TEXT\001\033[0m\002 '

instead of:

prompt = '\033[31;1mVISIBLE_TEXT\033[0m '

With those guards it should be easy enough to strip out the visible text.

Typically something like:

clean_prompt = re.sub(r'\001[^\002]*\002', '', prompt))

Cache the length of that and use when printing the readline manually. Note that you also have to remove the guards when using it manually - as in the hook function. (But it is needed in input(prompt)

Upvotes: 1

rbrich
rbrich

Reputation: 135

First, the Python 2 code uses commas to leave the line unfinished. In Python 3, it's done using end keyword:

print(self.prompt.rstrip(), readline.get_line_buffer(), sep='', end='')

Then, a flush is required to actually display the unfinished line (due to line buffering):

sys.stdout.flush()

The redisplay() call does not seem to be needed.

The final code:

def match_display_hook(self, substitution, matches, longest_match_length):
    print()
    for match in matches:
        print(match)
    print(self.prompt.rstrip(), readline.get_line_buffer(), sep='', end='')
    sys.stdout.flush()

Upvotes: 1

Related Questions