Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep stdin line at top or bottom of terminal screen

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
like image 908
Mo Beigi Avatar asked May 15 '15 11:05

Mo Beigi


People also ask

How do I signal end of STDIN?

Ctrl-D is the canonical way to terminate keyboard stdin in any shell command.

How do you go up a line in terminal?

And to scroll down in the terminal, use Shift + PageDown. To go up or down in the terminal by line, use Ctrl + Shift + Up or Ctrl + Shift + Down respectively.

How do you go to next line in terminal without pressing enter?

The most used newline character If you don't want to use echo repeatedly to create new lines in your shell script, then you can use the \n character. The \n is a newline character for Unix-based systems; it helps to push the commands that come after it onto a new line.

How do you get the first line of a shell?

There are many other ways to capture the first line too, including sed 1q (quit after first line), sed -n 1p (only print first line, but read everything), awk 'FNR == 1' (only print first line, but again, read everything) etc. Save this answer.


1 Answers

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.

like image 55
James Mills Avatar answered Sep 19 '22 11:09

James Mills