Here's another approach using ANSI/VT100 terminal control escape sequences.
We tell the terminal to only scroll the upper region of the terminal, where the output data is printed, so that the lower region, where the input prompt is printed, stays fixed in place. When we exit the program (using Ctrl C) we need to restore the default scrolling settings.
This program first clears the screen and then sits in a loop prompting the user for a number n
and then prints the numbers in range(n)
, one per line, with a small time delay to make it easier to see what's happening. The output for each range follows on from the previous range. If the user enters a non-integer the prompt is re-printed. The readline
module is imported so that editing and history are available at the input prompt; this module may not be available on all platforms.
First, a Python 2 version.
''' Print text in a scrolling region of the terminal above a fixed line for input
Written by PM 2Ring 2016.05.29
Python 2 version
'''
from __future__ import print_function
from time import sleep
import readline
# Some ANSI/VT100 Terminal Control Escape Sequences
CSI = '\x1b['
CLEAR = CSI + '2J'
CLEAR_LINE = CSI + '2K'
SAVE_CURSOR = CSI + 's'
UNSAVE_CURSOR = CSI + 'u'
def emit(*args):
print(*args, sep='', end='')
def set_scroll(n):
return CSI + '0;%dr' % n
# Height of scrolling region
height = 40
GOTO_INPUT = CSI + '%d;0H' % (height + 1)
emit(CLEAR, set_scroll(height))
try:
while True:
#Get input
emit(SAVE_CURSOR, GOTO_INPUT, CLEAR_LINE)
try:
n = int(raw_input('Number: '))
except ValueError:
continue
finally:
emit(UNSAVE_CURSOR)
#Display some output
for i in range(n):
print(i)
sleep(0.1)
except KeyboardInterrupt:
#Disable scrolling, but leave cursor below the input row
emit(set_scroll(0), GOTO_INPUT, '\n')
And here's a version that runs on Python 2 and Python 3. When run on Python 3 this script calls shutil.get_terminal_size()
to set the height of the scrolling region. It is possible to get the terminal size in Python 2, but the code is rather messy, so I opted for a fixed height.
I should mention that both versions of this script don't cope well if the terminal size is changed while they are running; handling that properly is left as an exercise for the reader. :)
''' Print text in a scrolling region of the terminal above a fixed line for input
Written by PM 2Ring 2016.05.29
Python 2 / 3 version
'''
from __future__ import print_function
import sys
import readline
from time import sleep
if sys.version_info > (3,):
# Get the (current) number of lines in the terminal
import shutil
height = shutil.get_terminal_size().lines - 1
stdout_write_bytes = sys.stdout.buffer.write
else:
height = 40
input = raw_input
stdout_write_bytes = sys.stdout.write
# Some ANSI/VT100 Terminal Control Escape Sequences
CSI = b'\x1b['
CLEAR = CSI + b'2J'
CLEAR_LINE = CSI + b'2K'
SAVE_CURSOR = CSI + b's'
UNSAVE_CURSOR = CSI + b'u'
GOTO_INPUT = CSI + b'%d;0H' % (height + 1)
def emit(*args):
stdout_write_bytes(b''.join(args))
def set_scroll(n):
return CSI + b'0;%dr' % n
emit(CLEAR, set_scroll(height))
try:
while True:
#Get input
emit(SAVE_CURSOR, GOTO_INPUT, CLEAR_LINE)
try:
n = int(input('Number: '))
except ValueError:
continue
finally:
emit(UNSAVE_CURSOR)
#Display some output
for i in range(n):
print(i)
sleep(0.1)
except KeyboardInterrupt:
#Disable scrolling, but leave cursor below the input row
emit(set_scroll(0), GOTO_INPUT, b'\n')