Python : correct use of set_completion_display_matches_hook - python

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 ?

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()

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)

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)

Related

How can I edit what has already been printed?

For example, let's say one of the printed things is "Hello World" and the second is "Hello". How would I print "Hello" on the same line as the one that says "Hello World"? This is just an example. Realistically I have no idea how long the printed text will be.
Here is an example script:
x = open("file.txt", "r+").read().split("\n")
for i in x:
print(something)
where something = I don't know. I want the output to be what the first line of the text file says, then what the second line says, and so on except print the second/third/fourth... line over the first line and each line is an unknown length, some shorter than others. Lets say the file.txt says:
Overflow
Stack
I would want it to print "Overflow" then "Stack", except each word gets printed on the first line and once you print "Stack", every part of "Overflow" can't be seen
Keep in mind that print("Hello World", end="\r") won't work because of the length.
You could work around the \r solution by padding each line with spaces according to the previous line:
prev_size = 0
with open("file.txt", "r+") as f:
for line in f:
print(f"{line.strip()}{' '*prev_size}", end='\r')
prev_size = len(line)
You would probably want to add a sleep between prints to be able to actually see the text changing...
When you use the print function in python, you're just pushing characters onto a pipe somewhere (the pipe might be connected to the standard out, but your program doesn't know that). Once its, pushed, there is nothing you can do to un-push it. Afterall, the pipe might not even be connected to a screen. It might be directly connected to the input of another program, or it might be using a physical printer for display. What would un-pushing even mean in those cases?
There are, however, special control characters (such as the "backspace" character) that you push to the pipe to signal that you want to erase characters. However, The terminal you are connected to is free to do what it wants with these characters. It can respect your wishes and erase characters, or it can print the literal '\b' characters to indicate a backspace, or it can completely ignore you and continue to print after the previous letters. That's completely out of your control.
Assuming the terminal that print the characters supports overwriting the characters, you can use the ANSI control sequences. The sequence for moving cursor to beginning of line is '\033[1G' and for erasing the everything from current cursor position to end of line is '\033[0K'. So,
import time
print('hello world', end='', flush=True) # prints "hello world" with no newline at the end
time.sleep(2) # wait 2 seconds
print('\033[1G\033[0K', end='') # moves cursor to beginning of line and erases the line
print('hi') # prints "hi" with newline at the end
flush=True is needed because the print function is buffered by default and doesn't actually print anything until it hits a newline. This tells the function you want to flush the buffer immediately.
Take a look at ANSI escape codes, section on "CSI sequences" to see what other codes are available.

How can i print on a new line without skipping?

I have a bit of code from a class which prints a line, and every line is followed by an empty line.
Is there a way to adjust the following code so that I don't have to have those empty lines?
def bfield(self):
self.n=0
for i in self.whole:
for j in i:
print("{:>4}".format(j), end='')
self.n=self.n+1
if self.n==len(i):
print('\n')
self.n=0
I'll agree with Rahul Chowdhury, remove the \n. Pythons print command, by default, will always start a new line after each print call. Hence your addition of '\n' will always result in an empty line.
If you wanted to look into how to get around the whole newline thing python does (every call in its own line), I found this link for you. It is fairly simple to do!
EDIT: It just occurred to me I should maybe list a few of the options just in case the link goes down. Here is one example:
print("Hello ", end = '')
print("World!")
With this, you overwrite the usual python lineend with your end = '' argument.
Another option would be to use the sys library
import sys
and then call the stdout.write() function, like so:
sys.stdout.write("Hello ")
sys.stdout.write("World!")

What does this mean in Python '\x1b[2K'?

I've just learnt that to clear a line that you printed in Python, do this:
sys.stdout.write('\x1b[2K')
Why is it so complicated? what does that weird code mean? and is there any alternative in print command?
Print does offer "end" option that allows to go back and forth in lines, but no way to clear what you printed. Overwriting via \r doesn't always work especially if the new line is shorter than the old one. You will get traces from the old line, so I need clearing first.
Thanks.
\x1b[2K is what's known as an ANSI terminal control sequence. They are a legacy of the 1970s and still used today (but vastly extended) to control terminal emulators.
\x1b is the ASCII for ESCAPE (literally the ESC key on your keyboard). [2K is the command "erase the current line".
There are many libraries in Python for working with the terminal, such as Urwid. These libraries will hide the inner workings of the terminal from you and give you higher-level constructs to create TUIs.
However, there is a much more efficient way of doing this:
You can use the print() command as usual, and delete the screen using
os.system("cls") # For Windows
or
os.system("clear") # For Linux
Alternative to print on a single line
I have a script that prints the x, y coordinates of the mouse as such:
import pyautogui
import time
while True:
x, y = pyautogui.position()
position_string = "X: {} Y: {}".format(str(x).rjust(4), str(y).rjust(4))
print(position_string, end='')
print('\b' * len(position_string), end='', flush=True)
time.sleep(1)
Where I will point out that you can print the backspace character ('\b') the amount of times that there are characters on the screen (len(position_string)), and when used with the end='' and flush=True options this will constantly print on a single line within your console. I should also note that this does not work in IDLE, but only on an actual command line! In IDLE the backspace characters are actually printed as some weird square shape...
This is called ANSI escape code . 2K is the name for Erase in Line. Quote from the link:
Erases part of the line. If n is 0 (or missing), clear from cursor to the end of the line. If n is 1, clear from cursor to beginning of the line. If n is 2, clear entire line. Cursor position does not change.
You can also try echo -e '\x1b[2k' in the terminal for better understanding.

Incorrect syntax near GO in SQL

I am concatenating many sql statements and am running into the following error.
"Incorrect syntax near GO" and "Incorrect syntax near "-
It seems that when i delete the trailing space and the go and the space after the go, and then CTRL+Z to put back the GO this makes the error go away? its pretty weird
why??
How could I code it in Python, thanks
')
END TRY
BEGIN CATCH
print ERROR_MESSAGE()
END CATCH
GO
As already mentioned in comments, GO is not part of the SQL syntax, rather a batch delimiter in Management Studio.
You can go around it in two ways, use Subprocess to call SqlCmd, or cut the scripts within Python. The Subprocess + SqlCmd will only really work for you if you don't care about query results as you would need to parse console output to get those.
I needed to build a database from SSMS generated scripts in past and created the below function as a result (updating, as I now have a better version that leaves comments in):
def partition_script(sql_script: str) -> list:
""" Function will take the string provided as parameter and cut it on every line that contains only a "GO" string.
Contents of the script are also checked for commented GO's, these are removed from the comment if found.
If a GO was left in a multi-line comment,
the cutting step would generate invalid code missing a multi-line comment marker in each part.
:param sql_script: str
:return: list
"""
# Regex for finding GO's that are the only entry in a line
find_go = re.compile(r'^\s*GO\s*$', re.IGNORECASE | re.MULTILINE)
# Regex to find multi-line comments
find_comments = re.compile(r'/\*.*?\*/', flags=re.DOTALL)
# Get a list of multi-line comments that also contain lines with only GO
go_check = [comment for comment in find_comments.findall(sql_script) if find_go.search(comment)]
for comment in go_check:
# Change the 'GO' entry to '-- GO', making it invisible for the cutting step
sql_script = sql_script.replace(comment, re.sub(find_go, '-- GO', comment))
# Removing single line comments, uncomment if needed
# file_content = re.sub(r'--.*$', '', file_content, flags=re.MULTILINE)
# Returning everything besides empty strings
return [part for part in find_go.split(sql_script) if part != '']
Using this function, you can run scripts containing GO like this:
import pymssql
conn = pymssql.connect(server, user, password, "tempdb")
cursor = conn.cursor()
for part in partition_script(your_script):
cursor.execute(part)
conn.close()
I hope this helps.

Shift + Return to insert linebreak in python

I'm trying to get the behaviour of typical IM clients that use Return to send a text and Shift + Return to insert a linebreak. Is there a way to achieve that with minimal effort in Python, using e.g. readline and raw_input?
Ok, I heard it can be accomplished also with the readline, in a way.
You can import readline and set in configuration your desired key (Shift+Enter) to a macro that put some special char to the end of the line and newline. Then you can call raw_input in a loop.
Like this:
import readline
# I am using Ctrl+K to insert line break
# (dont know what symbol is for shift+enter)
readline.parse_and_bind('C-k: "#\n"')
text = []
line = "#"
while line and line[-1]=='#':
line = raw_input("> ")
if line.endswith("#"):
text.append(line[:-1])
else:
text.append(line)
# all lines are in "text" list variable
print "\n".join(text)
I doubt you'd be able to do that just using the readline module as it will not capture the individual keys pressed and rather just processes the character responses from your input driver.
You could do it with PyHook though and if the Shift key is pressed along with the Enter key to inject a new-line into your readline stream.
I think that with minimal effort you can use urwid library for Python. Unfortunately, this does not satisfy your requirement to use readline/raw_input.
Update: Please see also this answer for other solution.
import readline
# I am using Ctrl+x to insert line break
# (dont know the symbols and bindings for meta-key or shift-key,
# let alone 4 shift+enter)
def startup_hook():
readline.insert_text('» ') # \033[32m»\033[0m
def prmpt():
try:
readline.parse_and_bind('tab: complete')
readline.parse_and_bind('set editing-mode vi')
readline.parse_and_bind('C-x: "\x16\n"') # \x16 is C-v which writes
readline.set_startup_hook(startup_hook) # the \n without returning
except Exception as e: # thus no need 4 appending
print (e) # '#' 2 write multilines
return # simply use ctrl-x or other some other bind
while True: # instead of shift + enter
try:
line = raw_input()
print '%s' % line
except EOFError:
print 'EOF signaled, exiting...'
break
# It can probably be improved more to use meta+key or maybe even shift enter
# Anyways sry 4 any errors I probably may have made.. first time answering

Categories