Here is my code
print("1")
print("2")
My desired output
2
1
I tried using ansi characters but the second line is overighting the first line
print("1")
print("\033[3A2")
output
2
How can I achieve my desired output using other means apart from loop to reverse the order of the print statement
I prefer ansi characters, terminal(), but I will also use other methods
To achieve the visual effect of printing a new line at the top while moving previous lines downwards, you can keep the previous lines in a list and print them in reverse order when printing a new line.
Before printing a new line though, you need to move the cursor up for however many previous lines there are by using the <ESC>[{count}A
control code. Use the <ESC>[K
control code to clear from the current cursor position to the end of the current line to erase any leftover characters from a longer previous line:
def reverse_print_factory(print=print):
def _print(line):
if lines:
print(f'\033[{len(lines)}A', end='')
lines.append(line)
for line in reversed(lines):
print(f'{line}\033[K')
lines = []
return _print
print = reverse_print_factory()
so that:
from time import sleep
for text in 'first', 'second', 'third':
print(text)
sleep(1)
would produce the following output on a VT100/ANSI-compatible terminal:
first
and then after a second:
second
first
and then after another second:
third
second
first
EDIT: A more efficient (but slightly less portable because of the need to control the standard input) approach would be to scroll up the region of the previous lines so you don't have to store previous lines in a list.
This can be done by using the <ESC>[{start row};{end row}r
control code to set the scrollable region, move the cursor to the top of the region with the <ESC>[{row};{column}f
control code, and then using the <ESC>M
control code to scroll up, effectively moving the lines in the region downwards.
To obtain the starting row, you can use the <ESC>[6n
control code to query the cursor position and then read the response from standard input in the <ESC>[{row};{column}R
format. This has been helpfully written into the cursorPos
function in this answer, which I'm using in the following example:
def reverse_print_factory(print=print):
def _print(text):
nonlocal count
if count:
print(f'\033[{start_row};{start_row + count}r\033[{start_row};1f\033M', end='')
count += 1
print(f'{text}\033[K\033[{start_row + count};1f', flush=True, end='')
start_row = int(cursorPos()[1])
count = 0
return _print
print = reverse_print_factory()
To further account for lines that reach the height of the terminal (obtainable with shutil.get_terminal_size
), you can move the lines before the starting row upwards (with the <ESC>D
control code) to make way for the new line instead of moving the previous lines downwards, until the entire screen is filled with output, at which point move the entire screen downwards again to make way for the new line at the top.
Also use atexist.register
to register an exit handler that resets the scrollable region to the entire screen and outputs an additional newline if the lines have reached the terminal height to prevent the command prompt from unnecessarily overwriting the last line of output:
import shutil
import atexit
def reverse_print_factory(print=print):
def _print(text):
nonlocal start_row, count
if count:
if 1 < start_row > height - count:
print(f'\033[1;{start_row - 1}r\033[{start_row - 1};1f\033D\033[{start_row - 1};1f', end='')
start_row -= 1
else:
print(f'\033[{start_row};{start_row + count}r\033[{start_row};1f\033M', end='')
count += 1
print(f'{text}\033[K\033[{start_row + count};1f', flush=True, end='')
def _exit():
print(f'\033[r\033[{start_row + count};1f', end='')
if 1 < start_row > height - count:
print()
height = shutil.get_terminal_size()[1]
start_row = int(cursorPos()[1])
count = 0
atexit.register(_exit)
return _print
print = reverse_print_factory()
Note that a complete set of VT100 control sequences can be found at: https://vt100.net/docs/vt100-ug/chapter3.html