0

I'd like to do a console progress-bar in Python using \r to clear the display. To support multi-line progress-bars (for several parallel tasks) I am wondering if one can set terminal width for the current process so that long lines would wrap?

For instance if I set terminal width to 20, I could do my progress-bar in this way:

print '\r%-20s%-20s' % ('Task 1: 30%', 'Task 2: 60%'),
time.sleep(1)
print '\r%-20s%-20s' % ('Task 1: 35%', 'Task 2: 65%'),

I'm aware one can use ncurses (or progress-bar packages using ncurses) for that, I'm just wondering if there's a solution not using ncurses.

I have tried set_winsize from https://stackoverflow.com/a/6420070/445810, but it didn't work, and line still didn't wrap.

Thanks!

Community
  • 1
  • 1
Vadim Kantorov
  • 930
  • 1
  • 10
  • 28
  • Setting terminal width from within a program, even if possible, would be extremely impolite. _Getting_ the current terminal width is possible; `tput cols` will print it for you. – 9000 Feb 09 '17 at 17:10
  • 1
    Somehow I was missing it! And I could catch SIGWINCH to update current width. Would you like to write an answer (I'd mark it as accepted)? – Vadim Kantorov Apr 06 '17 at 12:32

2 Answers2

0

Changing the terminal width using the indicated approach (a feature that rxvt got from xterm which in turn was based on a feature from dtterm) may/may not work, depending on the actual terminal you are using. But there are more direct ways.

Rather than write a long line and hope that the terminal will wrap it, you could

  • write an escape sequence to move the cursor to the upper left corner,
  • write your progress bars (clearing the remainder of each line with an escape sequence), and wrapping those explicitly, and
  • write an escape sequence to clear the remainder of the screen before
  • waiting a little while before repeating the process

Here's an example:

import curses, time, sys

def go_to_top_left():
    curses.putp(curses.tparm(curses.tigetstr("cup"),0,0))

def show_bar(n):
    curses.putp(curses.tigetstr("rev"))
    sys.stdout.write('.' * n)
    sys.stdout.write('*')
    curses.putp(curses.tigetstr("sgr0"))
    curses.putp(curses.tigetstr("el"))
    sys.stdout.write('\n')
    sys.stdout.flush()

def clear_to_bottom():
    curses.putp(curses.tigetstr("ed"))

def progress_bars():
    curses.setupterm()
    foo = 1
    bar = 0
    while foo < 50:
        go_to_top_left()
        show_bar(foo)
        show_bar(bar)
        bar = foo
        foo = foo + 1
        clear_to_bottom()
        time.sleep(1)

progress_bars()

The example uses terminfo calls from the curses package. Like termcap, these calls do not optimize the movement of the cursor, and you have control over when the screen is actually cleared. If you wanted to dispense with clearing altogether, you'd have to make several assumptions about

  • the width of your progress bars,
  • what's already on the screen, and
  • where on the screen you're doing this.

(Someone may offer to provide a hardcoded equivalent to this example as an "improvement").

Further reading:

Community
  • 1
  • 1
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
0

Instead of trying to set the terminal width, you should rather adapt to the changes of the terminal size.

This small program does this. Run it and resize the terminal; watch the output resize.

I took the IOCTL code from this elaborate example.

import fcntl
import signal
import struct
import sys
import termios
import time

def ioctl_GWINSZ(fd): #### TABULATION FUNCTIONS
    return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))


def draw_line(width, left_cap, body, right_cap):
    w = sys.stdout.write
    w(left_cap)
    w(body * (width - 2))
    w(right_cap)


def draw_rect(width, height):
    draw_line(width, '/', '-', '\\')
    for n in xrange(height - 3):
        draw_line(width, '|', ' ', '|')
    draw_line(width, '\\', '-', '/')


def draw_screen():
    h, w = ioctl_GWINSZ(1)  # of stdout.
    draw_rect(w, h)
    sys.stdout.write('Rows: %3d, Cols: %3d' % (h, w))
    sys.stdout.flush()


def on_winch(*ignored):
    sys.stdout.write('\n')
    draw_screen()

if __name__ == '__main__':
    draw_screen()
    signal.signal(signal.SIGWINCH, on_winch)
    while True:
        time.sleep(0.1)
9000
  • 39,899
  • 9
  • 66
  • 104