So I am writing a project where I run a program that constantly receives/sends messages to other computers running the same program.
The receiver/sender of data is running on a thread and prints to stdout. I get stuff like this:
[INFO] User 'blah' wants to send message to you.
[INFO] some other info
[MSG REC] Message 'hello' received from blah.
Now the issue is that sometimes I wish to input commands into the terminal, the problem is when I try to enter a command and a new info message or MSG REC
is printed to stdout. I have commands such as quit
and status
etc.
>> indicates the input line.
Something like this may happen:
[INFO] User 'blah' wants to send message to you.
[INFO] some other info
[MSG REC] Message 'hello' received from blah.
>> stat[MSG REC] Message 'sup' received from Bob.
us
Then I would press enter and the command status
gets executed but looks so poor in the terminal. A message appears every 2-4 seconds so this is an issue. Is there a good way to solve this? I tried using ANSI cursor commands to try and insert a new line before the last line so the last line would always remain as the input line and I could type in "stat", wait for a while and finish it with "us" without any issues.
I also saw people recommend curses
but attempting to integrate that with my program completely messed up the formatting of my output among other things (and I think its overkill perhaps).
So is there an easy way to make the thread insert new MSG REC
lines 1 line above the last line so the last line would always remain as the input line with >> and whatever else I have typed in.
Using Python2.7 on Linux.
EDIT: Change that made James Mills answer work: I had to use this whenever my thread was printing a new line.
myY, myX = stdscr.getyx();
str = "blah blah"; #my message I want to print
stdscr.addstr(len(lines), 0, str)
lines.append(str)
stdscr.move(myY, myX) #move cursor back to proper position
Here is a basic example:
Code:
#!/usr/bin/env python
from string import printable
from curses import erasechar, wrapper
PRINTABLE = map(ord, printable)
def input(stdscr):
ERASE = input.ERASE = getattr(input, "ERASE", ord(erasechar()))
Y, X = stdscr.getyx()
s = []
while True:
c = stdscr.getch()
if c in (13, 10):
break
elif c == ERASE:
y, x = stdscr.getyx()
if x > X:
del s[-1]
stdscr.move(y, (x - 1))
stdscr.clrtoeol()
stdscr.refresh()
elif c in PRINTABLE:
s.append(chr(c))
stdscr.addch(c)
return "".join(s)
def prompt(stdscr, y, x, prompt=">>> "):
stdscr.move(y, x)
stdscr.clrtoeol()
stdscr.addstr(y, x, prompt)
return input(stdscr)
def main(stdscr):
Y, X = stdscr.getmaxyx()
lines = []
max_lines = (Y - 3)
stdscr.clear()
while True:
s = prompt(stdscr, (Y - 1), 0) # noqa
if s == ":q":
break
# scroll
if len(lines) > max_lines:
lines = lines[1:]
stdscr.clear()
for i, line in enumerate(lines):
stdscr.addstr(i, 0, line)
stdscr.addstr(len(lines), 0, s)
lines.append(s)
stdscr.refresh()
wrapper(main)
This basically sets up a demo curses app which prompts the user for input and displays the prompt at (24, 0)
. The demo terminates on the user entering :q
. For any other input it appends the input to the top of the screen. Enjoy! (<BACKSAPCE>
also works!) :)
See: curses; all of the API I used in this example is straight from this standard library. Whilst using curses may or may not be "overkill" IHMO I would recommend the use of urwid especially if the complexity of your application starts to outgrow plain 'ol curses.