11

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
Mo Beigi
  • 1,614
  • 4
  • 29
  • 50

1 Answers1

8

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.

James Mills
  • 18,669
  • 3
  • 49
  • 62
  • I had to add `curses.KEY_BACKSPACE` to fix backspaces on my machine. Also this does not work as I want it to, while my thread is printing new lines every second (I passed it `stdscr` as argument and made lines global), it moves the cursor to the end of the printed line so what I am typing is at the end of the last line printed and not at the bottom near `>>>` – Mo Beigi May 16 '15 at 03:44
  • 1
    Okay managed to fix it and its now working as I want! I just had to change the way my thread was printing new lines so it would move the cursor back to the correct position after it was done printing. I added the change to the original post. – Mo Beigi May 16 '15 at 04:35
  • I'm glad this helped you solve your problems! Using ``curses.KEY_BACKSPACE`` doesn't work on my system :) – James Mills May 16 '15 at 08:16
  • 1
    I ended up using both which works on my system and the system the project will be deployed on. – Mo Beigi May 16 '15 at 08:44